LibGUI: Support cycling through focusable widgets with Tab and Shift-Tab.

This commit is contained in:
Andreas Kling 2019-05-15 02:39:58 +02:00
parent 01ffcdfa31
commit ad731cc08f
Notes: sideshowbarker 2024-07-19 14:08:48 +09:00
10 changed files with 82 additions and 8 deletions

View File

@ -21,7 +21,6 @@ private:
virtual void paint_event(GPaintEvent&) override;
virtual void mousedown_event(GMouseEvent&) override;
virtual void mousemove_event(GMouseEvent&) override;
virtual bool accepts_focus() const override { return true; }
void draw_at_mouse(const GMouseEvent&);

View File

@ -27,7 +27,6 @@ public:
private:
virtual void paint_event(GPaintEvent&) override;
virtual void mousedown_event(GMouseEvent&) override;
virtual bool accepts_focus() const override { return true; }
Rect get_outer_rect(byte glyph) const;

View File

@ -60,9 +60,15 @@ void GButton::paint_event(GPaintEvent& event)
content_rect.move_by(m_icon->width() + 4, 0);
content_rect.set_width(content_rect.width() - m_icon->width() - 4);
}
if (is_enabled())
if (is_enabled()) {
painter.draw_text(content_rect, m_caption, font, text_alignment(), foreground_color(), TextElision::Right);
else {
if (is_focused()) {
Rect focus_rect = { 0, 0, font.width(m_caption), font.glyph_height() };
focus_rect.inflate(6, 4);
focus_rect.center_within(content_rect);
painter.draw_rect(focus_rect, Color(140, 140, 140));
}
} else {
painter.draw_text(content_rect.translated(1, 1), m_caption, font, text_alignment(), Color::White, TextElision::Right);
painter.draw_text(content_rect, m_caption, font, text_alignment(), Color::from_rgb(0x808080), TextElision::Right);
}

View File

@ -40,6 +40,7 @@ public:
void set_action(GAction&);
virtual const char* class_name() const override { return "GButton"; }
virtual bool accepts_focus() const override { return true; }
protected:
virtual void paint_event(GPaintEvent&) override;

View File

@ -60,7 +60,9 @@ void GCheckBox::paint_event(GPaintEvent& event)
auto text_rect = rect();
text_rect.set_left(s_box_width + 4);
text_rect.set_width(font().width(m_caption));
text_rect.set_top(height() / 2 - font().glyph_height() / 2);
text_rect.set_height(font().glyph_height());
if (fill_with_background_color())
painter.fill_rect(rect(), background_color());
@ -78,8 +80,14 @@ void GCheckBox::paint_event(GPaintEvent& event)
if (m_checked)
painter.draw_bitmap(box_rect.shrunken(4, 4).location(), *s_checked_bitmap, foreground_color());
if (!caption().is_empty())
if (!caption().is_empty()) {
painter.draw_text(text_rect, caption(), TextAlignment::TopLeft, foreground_color());
if (is_focused()) {
Rect focus_rect = text_rect;
focus_rect.inflate(6, 4);
painter.draw_rect(focus_rect, Color(140, 140, 140));
}
}
}
void GCheckBox::mousemove_event(GMouseEvent& event)

View File

@ -315,6 +315,9 @@ void GTextEditor::toggle_selection_if_needed_for_event(const GKeyEvent& event)
void GTextEditor::keydown_event(GKeyEvent& event)
{
if (is_single_line() && event.key() == KeyCode::Key_Tab)
return GWidget::keydown_event(event);
if (event.key() == KeyCode::Key_Escape) {
if (on_escape_pressed)
on_escape_pressed();
@ -505,8 +508,6 @@ void GTextEditor::keydown_event(GKeyEvent& event)
if (!is_readonly() && !event.ctrl() && !event.alt() && !event.text().is_empty())
insert_at_cursor_or_replace_selection(event.text());
return GWidget::keydown_event(event);
}
void GTextEditor::delete_current_line()

View File

@ -243,8 +243,14 @@ void GWidget::hide_event(GHideEvent&)
{
}
void GWidget::keydown_event(GKeyEvent&)
void GWidget::keydown_event(GKeyEvent& event)
{
if (!event.alt() && !event.ctrl() && !event.logo() && event.key() == KeyCode::Key_Tab) {
if (event.shift())
focus_previous_widget();
else
focus_next_widget();
}
}
void GWidget::keyup_event(GKeyEvent&)
@ -543,3 +549,29 @@ void GWidget::set_updates_enabled(bool enabled)
if (enabled)
update();
}
void GWidget::focus_previous_widget()
{
auto focusable_widgets = window()->focusable_widgets();
for (int i = focusable_widgets.size() - 1; i >= 0; --i) {
if (focusable_widgets[i] != this)
continue;
if (i > 0)
focusable_widgets[i - 1]->set_focus(true);
else
focusable_widgets.last()->set_focus(true);
}
}
void GWidget::focus_next_widget()
{
auto focusable_widgets = window()->focusable_widgets();
for (int i = 0; i < focusable_widgets.size(); ++i) {
if (focusable_widgets[i] != this)
continue;
if (i < focusable_widgets.size() - 1)
focusable_widgets[i + 1]->set_focus(true);
else
focusable_widgets.first()->set_focus(true);
}
}

View File

@ -194,6 +194,8 @@ private:
void handle_enter_event(CEvent&);
void handle_leave_event(CEvent&);
void do_layout();
void focus_previous_widget();
void focus_next_widget();
CElapsedTimer& click_clock(GMouseButton);

View File

@ -513,3 +513,27 @@ void GWindow::start_wm_resize()
message.wm.window_id = m_window_id;
GEventLoop::post_message_to_server(message);
}
Vector<GWidget*> GWindow::focusable_widgets() const
{
if (!m_main_widget)
return { };
Vector<GWidget*> collected_widgets;
Function<void(GWidget&)> collect_focusable_widgets = [&] (GWidget& widget) {
if (widget.accepts_focus())
collected_widgets.append(&widget);
for (auto& child : widget.children()) {
if (!child->is_widget())
continue;
auto& child_widget = *static_cast<GWidget*>(child);
if (!child_widget.is_visible())
continue;
collect_focusable_widgets(child_widget);
}
};
collect_focusable_widgets(*m_main_widget);
return collected_widgets;
}

View File

@ -113,6 +113,8 @@ public:
String icon_path() const { return m_icon_path; }
void set_icon_path(const String&);
Vector<GWidget*> focusable_widgets() const;
virtual const char* class_name() const override { return "GWindow"; }
protected: