summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-05-15 02:39:58 +0200
committerAndreas Kling <awesomekling@gmail.com>2019-05-15 02:39:58 +0200
commitad731cc08f2cf84ec5fca5b1cf303fa0ac3cd390 (patch)
tree2a8a7e7bcbd269da0d3b16683189652e74a924a7
parent01ffcdfa31ba560fcceee82ba2158f867ad114ec (diff)
downloadserenity-ad731cc08f2cf84ec5fca5b1cf303fa0ac3cd390.zip
LibGUI: Support cycling through focusable widgets with Tab and Shift-Tab.
-rw-r--r--Applications/FontEditor/GlyphEditorWidget.h1
-rw-r--r--Applications/FontEditor/GlyphMapWidget.h1
-rw-r--r--LibGUI/GButton.cpp10
-rw-r--r--LibGUI/GButton.h1
-rw-r--r--LibGUI/GCheckBox.cpp10
-rw-r--r--LibGUI/GTextEditor.cpp5
-rw-r--r--LibGUI/GWidget.cpp34
-rw-r--r--LibGUI/GWidget.h2
-rw-r--r--LibGUI/GWindow.cpp24
-rw-r--r--LibGUI/GWindow.h2
10 files changed, 82 insertions, 8 deletions
diff --git a/Applications/FontEditor/GlyphEditorWidget.h b/Applications/FontEditor/GlyphEditorWidget.h
index 40fdef7e9a..2e9c311ccd 100644
--- a/Applications/FontEditor/GlyphEditorWidget.h
+++ b/Applications/FontEditor/GlyphEditorWidget.h
@@ -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&);
diff --git a/Applications/FontEditor/GlyphMapWidget.h b/Applications/FontEditor/GlyphMapWidget.h
index eb963e3df7..07aafbe2e1 100644
--- a/Applications/FontEditor/GlyphMapWidget.h
+++ b/Applications/FontEditor/GlyphMapWidget.h
@@ -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;
diff --git a/LibGUI/GButton.cpp b/LibGUI/GButton.cpp
index 7a957a1f5f..c7e5202872 100644
--- a/LibGUI/GButton.cpp
+++ b/LibGUI/GButton.cpp
@@ -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);
}
diff --git a/LibGUI/GButton.h b/LibGUI/GButton.h
index 7a163bc537..1175885edb 100644
--- a/LibGUI/GButton.h
+++ b/LibGUI/GButton.h
@@ -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;
diff --git a/LibGUI/GCheckBox.cpp b/LibGUI/GCheckBox.cpp
index cb14d587f7..913c5ecfa0 100644
--- a/LibGUI/GCheckBox.cpp
+++ b/LibGUI/GCheckBox.cpp
@@ -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)
diff --git a/LibGUI/GTextEditor.cpp b/LibGUI/GTextEditor.cpp
index 2af1593994..0b11e1ded0 100644
--- a/LibGUI/GTextEditor.cpp
+++ b/LibGUI/GTextEditor.cpp
@@ -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()
diff --git a/LibGUI/GWidget.cpp b/LibGUI/GWidget.cpp
index 1d98c4d2e1..ba8ce9bccb 100644
--- a/LibGUI/GWidget.cpp
+++ b/LibGUI/GWidget.cpp
@@ -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);
+ }
+}
diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h
index daee5cbf8a..61e896fba1 100644
--- a/LibGUI/GWidget.h
+++ b/LibGUI/GWidget.h
@@ -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);
diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp
index 02c8c84a5c..3f1ca52c07 100644
--- a/LibGUI/GWindow.cpp
+++ b/LibGUI/GWindow.cpp
@@ -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;
+}
diff --git a/LibGUI/GWindow.h b/LibGUI/GWindow.h
index 4adf2f7f9d..51c2981618 100644
--- a/LibGUI/GWindow.h
+++ b/LibGUI/GWindow.h
@@ -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: