mirror of
https://github.com/luanti-org/luanti.git
synced 2026-01-13 12:45:29 +01:00
Outline focused element when Tab is pressed (#16798)
This commit is contained in:
@@ -323,6 +323,34 @@ local scroll_fs =
|
||||
--style_type[label;border=;bgcolor=]
|
||||
--label[0.75,2;Reset]
|
||||
|
||||
local autoscroll_fs =
|
||||
"label[0.5,0.5;Auto-Scroll Test - Tab through buttons to test auto-scroll centering]" ..
|
||||
"label[0.5,1;Vertical scroll container:]" ..
|
||||
"scroll_container[0.5,1.5;5.5,5;autoscroll_v;vertical]" ..
|
||||
"button[0,0;5,1;asv_btn1;Button 1]" ..
|
||||
"button[0,1;5,1;asv_btn2;Button 2]" ..
|
||||
"button[0,2;5,1;asv_btn3;Button 3]" ..
|
||||
"button[0,3;5,1;asv_btn4;Button 4]" ..
|
||||
"button[0,4;5,1;asv_btn5;Button 5]" ..
|
||||
"button[0,5;5,1;asv_btn6;Button 6]" ..
|
||||
"button[0,6;5,1;asv_btn7;Button 7]" ..
|
||||
"button[0,7;5,1;asv_btn8;Button 8]" ..
|
||||
"button[0,8;5,1;asv_btn9;Button 9]" ..
|
||||
"button[0,9;5,1;asv_btn10;Button 10]" ..
|
||||
"scroll_container_end[]" ..
|
||||
"scrollbaroptions[max=50]" ..
|
||||
"scrollbar[5.8,1.5;0.3,5;vertical;autoscroll_v;0]" ..
|
||||
"label[7,1;Horizontal scroll container:]" ..
|
||||
"scroll_container[7,1.5;4.5,2;autoscroll_h;horizontal]" ..
|
||||
"button[0,0;3,1;ash_btn1;Btn1]" ..
|
||||
"button[3,0;3,1;ash_btn2;Btn2]" ..
|
||||
"button[6,0;3,1;ash_btn3;Btn3]" ..
|
||||
"button[9,0;3,1;ash_btn4;Btn4]" ..
|
||||
"button[12,0;3,1;ash_btn5;Btn5]" ..
|
||||
"scroll_container_end[]" ..
|
||||
"scrollbaroptions[max=105]" ..
|
||||
"scrollbar[7,2.7;4.5,0.3;horizontal;autoscroll_h;0]"
|
||||
|
||||
local window = {
|
||||
sizex = 12,
|
||||
sizey = 13,
|
||||
@@ -477,6 +505,10 @@ mouse control = true]
|
||||
"formspec_version[7]size[12,13]" ..
|
||||
scroll_fs,
|
||||
|
||||
-- Autoscroll
|
||||
"formspec_version[7]size[12,13]" ..
|
||||
autoscroll_fs,
|
||||
|
||||
-- Sound
|
||||
[[
|
||||
formspec_version[3]
|
||||
@@ -528,7 +560,7 @@ local function show_test_formspec(pname)
|
||||
page = page()
|
||||
end
|
||||
|
||||
local fs = page .. "tabheader[0,0;11,0.65;maintabs;Real Coord,Styles,Noclip,Table,Hypertext,Tabs,Invs,Window,Anim,Model,ScrollC,Sound,Background,Unsized;" .. page_id .. ";false;false]"
|
||||
local fs = page .. "tabheader[0,0;11,0.65;maintabs;Real Coord,Styles,Noclip,Table,Hypertext,Tabs,Invs,Window,Anim,Model,ScrollC,Autoscroll,Sound,Background,Unsized;" .. page_id .. ";false;false]"
|
||||
|
||||
core.show_formspec(pname, "testformspec:formspec", fs)
|
||||
end
|
||||
|
||||
@@ -3482,6 +3482,9 @@ void GUIFormSpecMenu::drawMenu()
|
||||
|
||||
updateSelectedItem();
|
||||
|
||||
// Auto-scroll to center focused element when Tab enables focus tracking
|
||||
autoScroll();
|
||||
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
|
||||
/*
|
||||
@@ -3613,6 +3616,27 @@ void GUIFormSpecMenu::drawMenu()
|
||||
cursor_control->setActiveIcon(ECI_NORMAL);
|
||||
}
|
||||
|
||||
// Draw white outline around keyboard-focused form elements.
|
||||
const gui::IGUIElement *focused = Environment->getFocus();
|
||||
if (focused && m_show_focus) {
|
||||
core::rect<s32> rect = focused->getAbsoluteClippingRect();
|
||||
const video::SColor white(255, 255, 255, 255);
|
||||
const s32 border = 2;
|
||||
|
||||
driver->draw2DRectangle(white,
|
||||
core::rect<s32>(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y,
|
||||
rect.LowerRightCorner.X, rect.UpperLeftCorner.Y + border), nullptr);
|
||||
driver->draw2DRectangle(white,
|
||||
core::rect<s32>(rect.UpperLeftCorner.X, rect.LowerRightCorner.Y - border,
|
||||
rect.LowerRightCorner.X, rect.LowerRightCorner.Y), nullptr);
|
||||
driver->draw2DRectangle(white,
|
||||
core::rect<s32>(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y,
|
||||
rect.UpperLeftCorner.X + border, rect.LowerRightCorner.Y), nullptr);
|
||||
driver->draw2DRectangle(white,
|
||||
core::rect<s32>(rect.LowerRightCorner.X - border, rect.UpperLeftCorner.Y,
|
||||
rect.LowerRightCorner.X, rect.LowerRightCorner.Y), nullptr);
|
||||
}
|
||||
|
||||
m_tooltip_element->draw();
|
||||
|
||||
/*
|
||||
@@ -3684,6 +3708,89 @@ void GUIFormSpecMenu::showTooltip(const std::wstring &text,
|
||||
bringToFront(m_tooltip_element);
|
||||
}
|
||||
|
||||
void GUIFormSpecMenu::autoScroll()
|
||||
{
|
||||
gui::IGUIElement *focus = Environment->getFocus();
|
||||
if (!m_show_focus || !focus)
|
||||
return;
|
||||
|
||||
// Only process if focus changed or this is the first focus
|
||||
if (focus == m_last_focused && m_last_focused != nullptr)
|
||||
return;
|
||||
|
||||
bool first_focus = (m_last_focused == nullptr);
|
||||
m_last_focused = focus;
|
||||
|
||||
// Find the scroll container that contains the focused element
|
||||
for (const auto &cont : m_scroll_containers) {
|
||||
if (!cont.second->isMyChild(focus))
|
||||
continue;
|
||||
|
||||
gui::IGUIElement *clipper = cont.second->getParent();
|
||||
if (!clipper)
|
||||
break;
|
||||
|
||||
// Find scrollbars for this container
|
||||
GUIScrollBar *scrollbar_v = nullptr;
|
||||
GUIScrollBar *scrollbar_h = nullptr;
|
||||
for (const auto &sb : m_scrollbars) {
|
||||
if (sb.first.fname == cont.first) {
|
||||
if (sb.second->isHorizontal())
|
||||
scrollbar_h = sb.second;
|
||||
else
|
||||
scrollbar_v = sb.second;
|
||||
}
|
||||
}
|
||||
|
||||
core::rect<s32> clip = clipper->getAbsoluteClippingRect();
|
||||
core::rect<s32> elem = focus->getAbsolutePosition();
|
||||
f32 scrollfactor = cont.second->getScrollFactor();
|
||||
|
||||
if (scrollfactor == 0)
|
||||
break;
|
||||
|
||||
// Handle vertical scrolling
|
||||
if (scrollbar_v) {
|
||||
bool visible = elem.UpperLeftCorner.Y >= clip.UpperLeftCorner.Y &&
|
||||
elem.LowerRightCorner.Y <= clip.LowerRightCorner.Y;
|
||||
if (first_focus || !visible) {
|
||||
s32 target_y = elem.UpperLeftCorner.Y;
|
||||
if (elem.getHeight() < clip.getHeight())
|
||||
target_y = clip.UpperLeftCorner.Y + (clip.getHeight() - elem.getHeight()) / 2;
|
||||
|
||||
s32 new_pos = scrollbar_v->getPos() + (s32)std::round((target_y - elem.UpperLeftCorner.Y) / scrollfactor);
|
||||
new_pos = rangelim(new_pos, scrollbar_v->getMin(), scrollbar_v->getMax());
|
||||
|
||||
if (new_pos != scrollbar_v->getPos()) {
|
||||
scrollbar_v->setPos(new_pos);
|
||||
cont.second->updateScrolling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle horizontal scrolling
|
||||
if (scrollbar_h) {
|
||||
bool visible = elem.UpperLeftCorner.X >= clip.UpperLeftCorner.X &&
|
||||
elem.LowerRightCorner.X <= clip.LowerRightCorner.X;
|
||||
if (first_focus || !visible) {
|
||||
s32 target_x = elem.UpperLeftCorner.X;
|
||||
if (elem.getWidth() < clip.getWidth())
|
||||
target_x = clip.UpperLeftCorner.X + (clip.getWidth() - elem.getWidth()) / 2;
|
||||
|
||||
s32 new_pos = scrollbar_h->getPos() + (s32)std::round((target_x - elem.UpperLeftCorner.X) / scrollfactor);
|
||||
new_pos = rangelim(new_pos, scrollbar_h->getMin(), scrollbar_h->getMax());
|
||||
|
||||
if (new_pos != scrollbar_h->getPos()) {
|
||||
scrollbar_h->setPos(new_pos);
|
||||
cont.second->updateScrolling();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GUIFormSpecMenu::updateSelectedItem()
|
||||
{
|
||||
// Don't update when dragging an item
|
||||
@@ -3948,6 +4055,37 @@ bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
|
||||
if (GUIModalMenu::preprocessEvent(event))
|
||||
return true;
|
||||
|
||||
// Handle keyboard and touch input to show/hide focus outline
|
||||
switch (event.EventType) {
|
||||
case EET_KEY_INPUT_EVENT:
|
||||
if (event.KeyInput.PressedDown && event.KeyInput.Key == KEY_TAB &&
|
||||
!event.KeyInput.Control) {
|
||||
m_show_focus = true;
|
||||
m_last_focused = nullptr;
|
||||
}
|
||||
break;
|
||||
case EET_MOUSE_INPUT_EVENT:
|
||||
switch (event.MouseInput.Event) {
|
||||
case EMIE_LMOUSE_PRESSED_DOWN:
|
||||
case EMIE_RMOUSE_PRESSED_DOWN:
|
||||
case EMIE_MMOUSE_PRESSED_DOWN:
|
||||
m_show_focus = false;
|
||||
m_last_focused = nullptr;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case EET_TOUCH_INPUT_EVENT:
|
||||
if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
|
||||
m_show_focus = false;
|
||||
m_last_focused = nullptr;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// The IGUITabControl renders visually using the skin's selected
|
||||
// font, which we override for the duration of form drawing,
|
||||
// but computes tab hotspots based on how it would have rendered
|
||||
|
||||
@@ -385,6 +385,8 @@ private:
|
||||
std::optional<std::string> m_focused_element = std::nullopt;
|
||||
JoystickController *m_joystick;
|
||||
bool m_show_debug = false;
|
||||
bool m_show_focus = false;
|
||||
gui::IGUIElement *m_last_focused = nullptr;
|
||||
|
||||
struct parserData {
|
||||
bool explicit_size;
|
||||
@@ -496,6 +498,12 @@ private:
|
||||
void showTooltip(const std::wstring &text, const video::SColor &color,
|
||||
const video::SColor &bgcolor);
|
||||
|
||||
/**
|
||||
* Auto-scrolls a scroll container to center the focused element.
|
||||
* Handles both vertical and horizontal scrolling.
|
||||
*/
|
||||
void autoScroll();
|
||||
|
||||
/**
|
||||
* In formspec version < 2 the elements were not ordered properly. Some element
|
||||
* types were drawn before others.
|
||||
|
||||
@@ -31,6 +31,11 @@ public:
|
||||
void setScrollBar(GUIScrollBar *scrollbar);
|
||||
void updateScrolling();
|
||||
|
||||
inline f32 getScrollFactor() const
|
||||
{
|
||||
return m_scrollfactor;
|
||||
}
|
||||
|
||||
private:
|
||||
enum OrientationEnum
|
||||
{
|
||||
|
||||
@@ -828,8 +828,8 @@ bool GUITable::OnEvent(const SEvent &event)
|
||||
}
|
||||
else if (event.KeyInput.Key == KEY_ESCAPE ||
|
||||
event.KeyInput.Key == KEY_SPACE ||
|
||||
(event.KeyInput.Key == KEY_TAB && event.KeyInput.Control)) {
|
||||
// pass to parent
|
||||
event.KeyInput.Key == KEY_TAB) {
|
||||
// pass to parent for focus cycling (both plain Tab and Ctrl+Tab)
|
||||
return IGUIElement::OnEvent(event);
|
||||
}
|
||||
else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
|
||||
|
||||
Reference in New Issue
Block a user