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.
This commit is contained in:
thankyouverycool 2020-07-14 17:18:12 -04:00 committed by Andreas Kling
parent b2783a234a
commit 6a78db07f1
Notes: sideshowbarker 2024-07-19 04:48:58 +09:00
7 changed files with 142 additions and 15 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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 };
};
}

View file

@ -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()) {

View file

@ -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; }