summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthankyouverycool <66646555+thankyouverycool@users.noreply.github.com>2020-07-14 17:18:12 -0400
committerAndreas Kling <kling@serenityos.org>2020-07-15 13:19:44 +0200
commit6a78db07f11e101901333e00cb0bac76a32874ff (patch)
tree6c15bdbe7e028b1700f1292fbcc62e80e047eb48
parentb2783a234aa14e5c382d9bf225b4ec54d9b98852 (diff)
downloadserenity-6a78db07f11e101901333e00cb0bac76a32874ff.zip
LibGUI: Add hover highlighting and keyboard controls to ComboBox
Adds a new highlighting effect to the actively selected row in ComboBox ListView. ComboBoxEditor can now be controlled with page up, page down, and the up and down arrow keys. ESC and loss of focus now cause comboboxes to close. Now activates on mouseup as well as return.
-rw-r--r--Libraries/LibGUI/AbstractView.cpp13
-rw-r--r--Libraries/LibGUI/AbstractView.h3
-rw-r--r--Libraries/LibGUI/ComboBox.cpp53
-rw-r--r--Libraries/LibGUI/ListView.cpp61
-rw-r--r--Libraries/LibGUI/ListView.h6
-rw-r--r--Libraries/LibGUI/TextEditor.cpp16
-rw-r--r--Libraries/LibGUI/TextEditor.h5
7 files changed, 142 insertions, 15 deletions
diff --git a/Libraries/LibGUI/AbstractView.cpp b/Libraries/LibGUI/AbstractView.cpp
index ab6199d55d..6635f2d0ea 100644
--- a/Libraries/LibGUI/AbstractView.cpp
+++ b/Libraries/LibGUI/AbstractView.cpp
@@ -225,6 +225,16 @@ void AbstractView::set_hovered_index(const ModelIndex& index)
if (m_hovered_index == index)
return;
m_hovered_index = index;
+ if (m_hovered_index.is_valid())
+ m_last_valid_hovered_index = m_hovered_index;
+ update();
+}
+
+void AbstractView::set_last_valid_hovered_index(const ModelIndex& index)
+{
+ if (m_last_valid_hovered_index == index)
+ return;
+ m_last_valid_hovered_index = index;
update();
}
@@ -325,6 +335,9 @@ void AbstractView::mouseup_event(MouseEvent& event)
m_might_drag = false;
update();
}
+
+ if (activates_on_selection())
+ activate_selected();
}
void AbstractView::doubleclick_event(MouseEvent& event)
diff --git a/Libraries/LibGUI/AbstractView.h b/Libraries/LibGUI/AbstractView.h
index 295efa546e..7910871952 100644
--- a/Libraries/LibGUI/AbstractView.h
+++ b/Libraries/LibGUI/AbstractView.h
@@ -74,6 +74,8 @@ public:
NonnullRefPtr<Gfx::Font> font_for_index(const ModelIndex&) const;
+ void set_last_valid_hovered_index(const ModelIndex&);
+
protected:
AbstractView();
virtual ~AbstractView() override;
@@ -107,6 +109,7 @@ protected:
bool m_might_drag { false };
ModelIndex m_hovered_index;
+ ModelIndex m_last_valid_hovered_index;
private:
RefPtr<Model> m_model;
diff --git a/Libraries/LibGUI/ComboBox.cpp b/Libraries/LibGUI/ComboBox.cpp
index 42a0bdf61e..ae6a236edb 100644
--- a/Libraries/LibGUI/ComboBox.cpp
+++ b/Libraries/LibGUI/ComboBox.cpp
@@ -49,6 +49,8 @@ private:
virtual void mousewheel_event(MouseEvent& event) override
{
+ if (!is_focused())
+ set_focus(true);
if (on_mousewheel)
on_mousewheel(event.wheel_delta());
}
@@ -57,6 +59,7 @@ private:
ComboBox::ComboBox()
{
m_editor = add<ComboBoxEditor>();
+ m_editor->set_has_open_button(true);
m_editor->on_change = [this] {
if (on_change)
on_change(m_editor->text(), m_list_view->selection().first());
@@ -65,6 +68,27 @@ ComboBox::ComboBox()
if (on_return_pressed)
on_return_pressed();
};
+ m_editor->on_up_pressed = [this] {
+ m_list_view->move_selection(-1);
+ };
+ m_editor->on_down_pressed = [this] {
+ m_list_view->move_selection(1);
+ };
+ m_editor->on_pageup_pressed = [this] {
+ m_list_view->move_selection(-m_list_view->selection().first().row());
+ };
+ m_editor->on_pagedown_pressed = [this] {
+ if (model())
+ m_list_view->move_selection((model()->row_count() - 1) - m_list_view->selection().first().row());
+ };
+ m_editor->on_mousewheel = [this](int delta) {
+ m_list_view->move_selection(delta);
+ };
+ m_editor->on_mousedown = [this] {
+ if (only_allow_values_from_model())
+ m_open_button->click();
+ };
+
m_open_button = add<Button>();
m_open_button->set_focusable(false);
m_open_button->set_text("\xE2\xAC\x87"); // DOWNWARDS BLACK ARROW
@@ -77,25 +101,38 @@ ComboBox::ComboBox()
m_list_window = add<Window>();
m_list_window->set_frameless(true);
+ m_list_window->on_activity_change = [this](const bool is_active) {
+ if (!is_active) {
+ m_open_button->set_enabled(false);
+ close();
+ }
+ m_open_button->set_enabled(true);
+ };
m_list_view = m_list_window->set_main_widget<ListView>();
m_list_view->horizontal_scrollbar().set_visible(false);
-
+ m_list_view->set_alternating_row_colors(false);
+ m_list_view->set_hover_highlighting(true);
+ m_list_view->set_frame_thickness(1);
+ m_list_view->set_frame_shadow(Gfx::FrameShadow::Plain);
m_list_view->on_selection = [this](auto& index) {
ASSERT(model());
+ m_list_view->set_activates_on_selection(true);
auto new_value = model()->data(index).to_string();
m_editor->set_text(new_value);
if (!m_only_allow_values_from_model)
m_editor->select_all();
- close();
deferred_invoke([this, index](auto&) {
if (on_change)
on_change(m_editor->text(), index);
});
};
-
- m_editor->on_mousewheel = [this](int delta) {
- m_list_view->move_selection(delta);
+ m_list_view->on_activation = [this](auto&) {
+ m_list_view->set_activates_on_selection(false);
+ close();
+ };
+ m_list_view->on_escape_pressed = [this] {
+ close();
};
}
@@ -152,6 +189,11 @@ void ComboBox::open()
Gfx::IntRect list_window_rect { my_screen_rect.bottom_left(), size };
list_window_rect.intersect(Desktop::the().rect().shrunken(0, 128));
+ if (m_list_view->hover_highlighting())
+ m_list_view->set_last_valid_hovered_index({});
+
+ m_editor->set_has_visible_list(true);
+ m_editor->set_focus(true);
m_list_window->set_rect(list_window_rect);
m_list_window->show();
}
@@ -159,6 +201,7 @@ void ComboBox::open()
void ComboBox::close()
{
m_list_window->hide();
+ m_editor->set_has_visible_list(false);
m_editor->set_focus(true);
}
diff --git a/Libraries/LibGUI/ListView.cpp b/Libraries/LibGUI/ListView.cpp
index 8318ec33d5..2b3d1785a2 100644
--- a/Libraries/LibGUI/ListView.cpp
+++ b/Libraries/LibGUI/ListView.cpp
@@ -127,7 +127,12 @@ void ListView::paint_event(PaintEvent& event)
int painted_item_index = 0;
for (int row_index = 0; row_index < model()->row_count(); ++row_index) {
- bool is_selected_row = selection().contains_row(row_index);
+ bool is_selected_row;
+ if (hover_highlighting() && m_last_valid_hovered_index.is_valid())
+ is_selected_row = row_index == m_last_valid_hovered_index.row();
+ else
+ is_selected_row = selection().contains_row(row_index);
+
int y = painted_item_index * item_height();
Color background_color;
@@ -187,15 +192,29 @@ void ListView::move_selection(int steps)
auto& model = *this->model();
ModelIndex new_index;
if (!selection().is_empty()) {
- auto old_index = selection().first();
- new_index = model.index(old_index.row() + steps, old_index.column());
+ if (hover_highlighting() && m_last_valid_hovered_index.is_valid()) {
+ new_index = model.index(m_last_valid_hovered_index.row() + steps, m_last_valid_hovered_index.column());
+ } else {
+ auto old_index = selection().first();
+ new_index = model.index(old_index.row() + steps, old_index.column());
+ }
} else {
- new_index = model.index(0, 0);
+ if (hover_highlighting() && m_last_valid_hovered_index.is_valid()) {
+ new_index = model.index(m_last_valid_hovered_index.row() + steps, m_last_valid_hovered_index.column());
+ } else {
+ new_index = model.index(0, 0);
+ }
}
if (model.is_valid(new_index)) {
+ set_last_valid_hovered_index({});
selection().set(new_index);
scroll_into_view(new_index, Orientation::Vertical);
update();
+ } else {
+ if (hover_highlighting() && m_last_valid_hovered_index.is_valid()) {
+ new_index = model.index(m_last_valid_hovered_index.row(), m_last_valid_hovered_index.column());
+ selection().set(new_index);
+ }
}
}
@@ -204,7 +223,12 @@ void ListView::keydown_event(KeyEvent& event)
if (!model())
return;
auto& model = *this->model();
+ ModelIndex new_index;
if (event.key() == KeyCode::Key_Return) {
+ if (hover_highlighting() && m_last_valid_hovered_index.is_valid()) {
+ auto new_index = model.index(m_last_valid_hovered_index.row(), m_last_valid_hovered_index.column());
+ selection().set(new_index);
+ }
activate_selected();
return;
}
@@ -217,9 +241,15 @@ void ListView::keydown_event(KeyEvent& event)
return;
}
if (event.key() == KeyCode::Key_PageUp) {
- int items_per_page = visible_content_rect().height() / item_height();
- auto old_index = selection().first();
- auto new_index = model.index(max(0, old_index.row() - items_per_page), old_index.column());
+ if (hover_highlighting())
+ set_last_valid_hovered_index({});
+ if (!selection().is_empty()) {
+ int items_per_page = visible_content_rect().height() / item_height();
+ auto old_index = selection().first();
+ new_index = model.index(max(0, old_index.row() - items_per_page), old_index.column());
+ } else {
+ new_index = model.index(0, 0);
+ }
if (model.is_valid(new_index)) {
selection().set(new_index);
scroll_into_view(new_index, Orientation::Vertical);
@@ -228,9 +258,15 @@ void ListView::keydown_event(KeyEvent& event)
return;
}
if (event.key() == KeyCode::Key_PageDown) {
- int items_per_page = visible_content_rect().height() / item_height();
- auto old_index = selection().first();
- auto new_index = model.index(min(model.row_count() - 1, old_index.row() + items_per_page), old_index.column());
+ if (hover_highlighting())
+ set_last_valid_hovered_index({});
+ if (!selection().is_empty()) {
+ int items_per_page = visible_content_rect().height() / item_height();
+ auto old_index = selection().first();
+ new_index = model.index(min(model.row_count() - 1, old_index.row() + items_per_page), old_index.column());
+ } else {
+ new_index = model.index(0, 0);
+ }
if (model.is_valid(new_index)) {
selection().set(new_index);
scroll_into_view(new_index, Orientation::Vertical);
@@ -238,6 +274,11 @@ void ListView::keydown_event(KeyEvent& event)
}
return;
}
+ if (event.key() == KeyCode::Key_Escape) {
+ if (on_escape_pressed)
+ on_escape_pressed();
+ return;
+ }
return Widget::keydown_event(event);
}
diff --git a/Libraries/LibGUI/ListView.h b/Libraries/LibGUI/ListView.h
index 5194d2bd51..ebb2b04f9f 100644
--- a/Libraries/LibGUI/ListView.h
+++ b/Libraries/LibGUI/ListView.h
@@ -40,6 +40,9 @@ public:
bool alternating_row_colors() const { return m_alternating_row_colors; }
void set_alternating_row_colors(bool b) { m_alternating_row_colors = b; }
+ bool hover_highlighting() const { return m_hover_highlighting; }
+ void set_hover_highlighting(bool b) { m_hover_highlighting = b; }
+
int horizontal_padding() const { return m_horizontal_padding; }
void scroll_into_view(const ModelIndex&, Orientation);
@@ -56,6 +59,8 @@ public:
void move_selection(int steps);
+ Function<void()> on_escape_pressed;
+
private:
ListView();
@@ -72,6 +77,7 @@ private:
int m_horizontal_padding { 2 };
int m_model_column { 0 };
bool m_alternating_row_colors { true };
+ bool m_hover_highlighting { false };
};
}
diff --git a/Libraries/LibGUI/TextEditor.cpp b/Libraries/LibGUI/TextEditor.cpp
index 7533f9a4e1..d6ae4507d2 100644
--- a/Libraries/LibGUI/TextEditor.cpp
+++ b/Libraries/LibGUI/TextEditor.cpp
@@ -700,6 +700,10 @@ void TextEditor::keydown_event(KeyEvent& event)
}
}
return;
+ } else if (event.key() == KeyCode::Key_Up) {
+ if (on_up_pressed)
+ on_up_pressed();
+ return;
}
if (is_multi_line() && event.key() == KeyCode::Key_Down) {
if (m_cursor.line() < (line_count() - 1)) {
@@ -717,6 +721,10 @@ void TextEditor::keydown_event(KeyEvent& event)
}
}
return;
+ } else if (event.key() == KeyCode::Key_Down) {
+ if (on_down_pressed)
+ on_down_pressed();
+ return;
}
if (is_multi_line() && event.key() == KeyCode::Key_PageUp) {
if (m_cursor.line() > 0) {
@@ -731,6 +739,10 @@ void TextEditor::keydown_event(KeyEvent& event)
}
}
return;
+ } else if (event.key() == KeyCode::Key_PageUp) {
+ if (on_pageup_pressed)
+ on_pageup_pressed();
+ return;
}
if (is_multi_line() && event.key() == KeyCode::Key_PageDown) {
if (m_cursor.line() < (line_count() - 1)) {
@@ -744,6 +756,10 @@ void TextEditor::keydown_event(KeyEvent& event)
}
}
return;
+ } else if (event.key() == KeyCode::Key_PageDown) {
+ if (on_pagedown_pressed)
+ on_pagedown_pressed();
+ return;
}
if (event.key() == KeyCode::Key_Left) {
if (event.ctrl()) {
diff --git a/Libraries/LibGUI/TextEditor.h b/Libraries/LibGUI/TextEditor.h
index a3695090e5..97dc70e02c 100644
--- a/Libraries/LibGUI/TextEditor.h
+++ b/Libraries/LibGUI/TextEditor.h
@@ -127,8 +127,13 @@ public:
void redo() { document().redo(); }
Function<void()> on_change;
+ Function<void()> on_mousedown;
Function<void()> on_return_pressed;
Function<void()> on_escape_pressed;
+ Function<void()> on_up_pressed;
+ Function<void()> on_down_pressed;
+ Function<void()> on_pageup_pressed;
+ Function<void()> on_pagedown_pressed;
Action& undo_action() { return *m_undo_action; }
Action& redo_action() { return *m_redo_action; }