From 1597579a34a8d87d4dea0a0cdc0895e6247b6126 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Wed, 31 Aug 2016 11:52:45 +0300 Subject: [PATCH] Implement keyboard navigation for most widgets --- source/button.cpp | 11 +++++ source/button.h | 1 + source/container.cpp | 64 ++++++++++++++++++++++++++ source/container.h | 1 + source/dropdown.cpp | 49 ++++++++++++++------ source/dropdown.h | 3 ++ source/entry.cpp | 69 +++++++++++++++------------- source/entry.h | 2 + source/inputmethod.h | 14 ++++++ source/list.cpp | 83 +++++++++++++++++++++++++++++++--- source/list.h | 5 ++ source/panel.cpp | 2 +- source/systemkeyboardinput.cpp | 12 +++++ source/toggle.cpp | 13 ++++++ source/toggle.h | 1 + source/widget.h | 1 + 16 files changed, 279 insertions(+), 52 deletions(-) diff --git a/source/button.cpp b/source/button.cpp index 86b73c9..fadd00a 100644 --- a/source/button.cpp +++ b/source/button.cpp @@ -13,6 +13,7 @@ Button::Button(const std::string &t): icon(0), pressed(false) { + input_type = INPUT_NAVIGATION; set_text(t); } @@ -102,6 +103,16 @@ void Button::pointer_motion(int x, int y) } } +bool Button::navigate(Navigation nav) +{ + if(nav==NAV_ACTIVATE) + signal_clicked.emit(); + else + return false; + + return true; +} + void Button::on_style_change() { text.set_style(style); diff --git a/source/button.h b/source/button.h index 7904515..844fe88 100644 --- a/source/button.h +++ b/source/button.h @@ -50,6 +50,7 @@ public: virtual void button_press(int, int, unsigned); virtual void button_release(int, int, unsigned); virtual void pointer_motion(int, int); + virtual bool navigate(Navigation); private: virtual void on_style_change(); }; diff --git a/source/container.cpp b/source/container.cpp index 068986b..b50bc29 100644 --- a/source/container.cpp +++ b/source/container.cpp @@ -302,6 +302,70 @@ void Container::focus_out() Widget::focus_out(); } +bool Container::navigate(Navigation nav) +{ + if(input_focus && input_focus->navigate(nav)) + return true; + + if(nav==NAV_UP || nav==NAV_DOWN || nav==NAV_LEFT || nav==NAV_RIGHT) + { + int x = geom.w/2; + int y = geom.h/2; + if(input_focus) + { + const Geometry &fgeom = input_focus->get_geometry(); + x = fgeom.x+fgeom.w/2; + y = fgeom.y+fgeom.h/2; + } + else if(nav==NAV_UP) + y = 0; + else if(nav==NAV_DOWN) + y = geom.h; + else if(nav==NAV_RIGHT) + x = 0; + else if(nav==NAV_LEFT) + x = geom.w; + + Widget *sibling = 0; + int best_score = 0; + for(list::const_iterator i=children.begin(); i!=children.end(); ++i) + { + if((*i)->widget==input_focus || !(*i)->widget->is_focusable()) + continue; + + const Geometry &cgeom = (*i)->widget->get_geometry(); + int dx = cgeom.x+cgeom.w/2-x; + int dy = cgeom.y+cgeom.h/2-y; + + int score = -1; + if(nav==NAV_UP && dy>0) + score = dy+abs(dx)*4; + else if(nav==NAV_DOWN && dy<0) + score = -dy+abs(dx)*4; + else if(nav==NAV_RIGHT && dx>0) + score = dx+abs(dy)*4; + else if(nav==NAV_LEFT && dx<0) + score = -dx+abs(dy)*4; + + if(score>0 && (!sibling || scorewidget; + best_score = score; + } + } + + if(sibling) + { + set_input_focus(sibling); + if(Container *container = dynamic_cast(sibling)) + container->navigate(nav); + return true; + } + } + + return false; +} + void Container::on_reparent() { for(list::iterator i=children.begin(); i!=children.end(); ++i) diff --git a/source/container.h b/source/container.h index 3907bde..b463ff2 100644 --- a/source/container.h +++ b/source/container.h @@ -80,6 +80,7 @@ public: virtual bool key_release(unsigned, unsigned); virtual bool character(wchar_t); virtual void focus_out(); + virtual bool navigate(Navigation); protected: virtual void on_reparent(); virtual void on_child_added(Widget &) { } diff --git a/source/dropdown.cpp b/source/dropdown.cpp index 8cc6187..ef0fa22 100644 --- a/source/dropdown.cpp +++ b/source/dropdown.cpp @@ -25,8 +25,7 @@ Dropdown::Dropdown(ListData &d): void Dropdown::init() { - // Necessary to have the parent container raise the dropdown on click - input_type = INPUT_TEXT; + input_type = INPUT_NAVIGATION; dropped = false; @@ -86,21 +85,27 @@ void Dropdown::button_press(int x, int y, unsigned btn) { Container::button_press(x, y, btn); if(!click_focus) - { - dropped = false; - list.set_visible(false); - clear_state(ACTIVE); - signal_ungrab_pointer.emit(); - } + close_list(); } else if(btn==1) + open_list(); +} + +bool Dropdown::navigate(Navigation nav) +{ + if(dropped) { - dropped = true; - list.set_visible(true); - resize_list(); - set_state(ACTIVE); - signal_grab_pointer.emit(); + if(nav==NAV_CANCEL) + close_list(); + else + list.navigate(nav); } + else if(nav==NAV_ACTIVATE) + open_list(); + else + return false; + + return true; } void Dropdown::on_geometry_change() @@ -116,6 +121,24 @@ void Dropdown::on_style_change() resize_list(); } +void Dropdown::open_list() +{ + dropped = true; + list.set_visible(true); + resize_list(); + set_state(ACTIVE); + set_input_focus(&list); + signal_grab_pointer.emit(); +} + +void Dropdown::close_list() +{ + dropped = false; + list.set_visible(false); + clear_state(ACTIVE); + signal_ungrab_pointer.emit(); +} + void Dropdown::list_autosize_changed() { if(dropped) diff --git a/source/dropdown.h b/source/dropdown.h index 78c52b7..9b8a923 100644 --- a/source/dropdown.h +++ b/source/dropdown.h @@ -58,10 +58,13 @@ private: public: virtual void button_press(int, int, unsigned); + virtual bool navigate(Navigation); private: virtual void on_geometry_change(); virtual void on_style_change(); + void open_list(); + void close_list(); void list_autosize_changed(); void resize_list(); void list_item_selected(unsigned); diff --git a/source/entry.cpp b/source/entry.cpp index 16954a7..6616054 100644 --- a/source/entry.cpp +++ b/source/entry.cpp @@ -129,29 +129,7 @@ void Entry::render_special(const Part &part, GL::Renderer &renderer) const bool Entry::key_press(unsigned key, unsigned) { got_key_press = true; - if(key==Input::KEY_LEFT) - { - if(edit_pos>0) - set_edit_position(edit_pos-1); - } - else if(key==Input::KEY_RIGHT) - { - if(edit_pos0 ? text.coords_to_offset(row-1, col) : 0); - } - else if(key==Input::KEY_BACKSPACE) + if(key==Input::KEY_BACKSPACE) { if(edit_pos>0) { @@ -160,16 +138,11 @@ bool Entry::key_press(unsigned key, unsigned) rebuild(); } } - else if(key==Input::KEY_ENTER) + else if(key==Input::KEY_ENTER && multiline) { - if(multiline) - { - text.insert(edit_pos++, "\n"); - check_view_range(); - rebuild(); - } - else - signal_enter.emit(); + text.insert(edit_pos++, "\n"); + check_view_range(); + rebuild(); } else return false; @@ -196,6 +169,38 @@ void Entry::focus_out() got_key_press = false; } +bool Entry::navigate(Navigation nav) +{ + if(nav==NAV_LEFT) + { + if(edit_pos>0) + set_edit_position(edit_pos-1); + } + else if(nav==NAV_RIGHT) + { + if(edit_pos0 ? text.coords_to_offset(row-1, col) : 0); + } + else if(nav==NAV_ACCEPT && !signal_enter.empty()) + signal_enter.emit(); + else + return false; + + return true; +} + void Entry::on_geometry_change() { if(multiline) diff --git a/source/entry.h b/source/entry.h index 72345b7..82a9d79 100644 --- a/source/entry.h +++ b/source/entry.h @@ -31,6 +31,7 @@ public: void multiline(bool); }; + // Deprecated sigc::signal signal_enter; private: @@ -72,6 +73,7 @@ public: virtual bool key_press(unsigned, unsigned); virtual bool character(wchar_t); virtual void focus_out(); + virtual bool navigate(Navigation); private: virtual void on_geometry_change(); virtual void on_style_change(); diff --git a/source/inputmethod.h b/source/inputmethod.h index 31cbaf3..4410075 100644 --- a/source/inputmethod.h +++ b/source/inputmethod.h @@ -9,9 +9,23 @@ class Root; enum InputType { INPUT_NONE, + INPUT_NAVIGATION, INPUT_TEXT }; +enum Navigation +{ + NAV_LEFT, + NAV_RIGHT, + NAV_UP, + NAV_DOWN, + NAV_NEXT, + NAV_PREVIOUS, + NAV_ACTIVATE, + NAV_ACCEPT, + NAV_CANCEL +}; + class InputMethod { protected: diff --git a/source/list.cpp b/source/list.cpp index b971342..6496018 100644 --- a/source/list.cpp +++ b/source/list.cpp @@ -35,8 +35,11 @@ List::List(ListData &d): void List::init() { + input_type = INPUT_NAVIGATION; + item_factory = 0; sel_index = -1; + focus_index = -1; first = 0; max_scroll = 0; view_size = 5; @@ -155,12 +158,16 @@ void List::set_selected_index(int i) if(i<0) { sel_index = -1; + focus_index = -1; + set_input_focus(0); signal_selection_cleared.emit(); } else { sel_index = i; + focus_index = i; items[sel_index]->set_active(true); + set_input_focus(items[focus_index]); signal_item_selected.emit(sel_index); } } @@ -247,6 +254,52 @@ void List::button_press(int x, int y, unsigned btn) } } +void List::focus_in() +{ + if(focus_index<0) + focus_index = sel_index; + + if(focus_index>=0) + { + scroll_to_focus(); + set_input_focus(items[focus_index]); + } +} + +bool List::navigate(Navigation nav) +{ + if(nav==NAV_UP) + { + if(focus_index<0 && !items.empty()) + focus_index = items.size()-1; + else if(focus_index>0) + --focus_index; + else + return false; + + scroll_to_focus(); + set_input_focus(items[focus_index]); + } + else if(nav==NAV_DOWN) + { + if(focus_index<0 && !items.empty()) + focus_index = 0; + else if(static_cast(focus_index+1)autosize(); @@ -294,6 +347,17 @@ void List::check_view_range() slider.set_value(max_scroll-first); } +void List::scroll_to_focus() +{ + if(focus_index<0 || items[focus_index]->is_visible()) + return; + + if(static_cast(focus_index)0 && !ignore_slider_change) @@ -303,6 +367,14 @@ void List::slider_value_changed(double value) } } +void List::adjust_index(int &index, int pos, int change) +{ + if(index>pos) + index += change; + else if(index==pos) + index = (change>0 ? index+change : -1); +} + List::DataObserver::DataObserver(List &l): list(l) @@ -315,8 +387,8 @@ List::DataObserver::DataObserver(List &l): void List::DataObserver::item_added(unsigned i) { - if(list.sel_index>=static_cast(i)) - ++list.sel_index; + adjust_index(list.sel_index, i, 1); + adjust_index(list.focus_index, i, 1); Item *item = list.create_item(i); list.items.insert(list.items.begin()+i, item); @@ -326,10 +398,8 @@ void List::DataObserver::item_added(unsigned i) void List::DataObserver::item_removed(unsigned i) { bool had_selection = (list.sel_index>=0); - if(list.sel_index>static_cast(i)) - --list.sel_index; - else if(list.sel_index==static_cast(i)) - list.sel_index = -1; + adjust_index(list.sel_index, i, -1); + adjust_index(list.focus_index, i, -1); delete list.items[i]; list.items.erase(list.items.begin()+i); @@ -342,6 +412,7 @@ void List::DataObserver::item_removed(unsigned i) void List::DataObserver::cleared() { list.sel_index = -1; + list.focus_index = -1; for(vector::iterator i=list.items.begin(); i!=list.items.end(); ++i) delete *i; list.items.clear(); diff --git a/source/list.h b/source/list.h index ad12786..78954fc 100644 --- a/source/list.h +++ b/source/list.h @@ -138,6 +138,7 @@ private: DataObserver *observer; ItemFactory *item_factory; int sel_index; + int focus_index; unsigned first; unsigned max_scroll; unsigned view_size; @@ -190,12 +191,16 @@ private: public: virtual void button_press(int, int, unsigned); + virtual void focus_in(); + virtual bool navigate(Navigation); private: void item_autosize_changed(Item *); unsigned last_to_first(unsigned) const; void check_view_range(); + void scroll_to_focus(); void slider_value_changed(double); + static void adjust_index(int &, int, int); }; } // namespace GLtk diff --git a/source/panel.cpp b/source/panel.cpp index 44c41ea..c17fe4b 100644 --- a/source/panel.cpp +++ b/source/panel.cpp @@ -27,7 +27,7 @@ namespace GLtk { Panel::Panel(): layout(0) { - input_type = INPUT_TEXT; + input_type = INPUT_NAVIGATION; } Panel::~Panel() diff --git a/source/systemkeyboardinput.cpp b/source/systemkeyboardinput.cpp index cedddd1..fe803a6 100644 --- a/source/systemkeyboardinput.cpp +++ b/source/systemkeyboardinput.cpp @@ -20,6 +20,18 @@ bool SystemKeyboardInput::key_press(unsigned key) if(root.key_press(key, 0)) return true; + switch(key) + { + case Input::KEY_LEFT: return root.navigate(NAV_LEFT); + case Input::KEY_RIGHT: return root.navigate(NAV_RIGHT); + case Input::KEY_UP: return root.navigate(NAV_UP); + case Input::KEY_DOWN: return root.navigate(NAV_DOWN); + case Input::KEY_TAB: return root.navigate(NAV_NEXT); + case Input::KEY_SPACE: return root.navigate(NAV_ACTIVATE); + case Input::KEY_ENTER: return root.navigate(NAV_ACCEPT); + case Input::KEY_ESC: return root.navigate(NAV_CANCEL); + } + return false; } diff --git a/source/toggle.cpp b/source/toggle.cpp index b597eb9..b0219fc 100644 --- a/source/toggle.cpp +++ b/source/toggle.cpp @@ -14,6 +14,7 @@ Toggle::Toggle(const string &t): value(false), exclusive(false) { + input_type = INPUT_NAVIGATION; set_text(t); } @@ -87,6 +88,18 @@ void Toggle::button_release(int x, int y, unsigned btn) } } +bool Toggle::navigate(Navigation nav) +{ + if(nav==NAV_ACTIVATE) + { + if(!value || !exclusive) + set_value(!value); + return true; + } + + return false; +} + void Toggle::on_style_change() { text.set_style(style); diff --git a/source/toggle.h b/source/toggle.h index 54becb9..4845796 100644 --- a/source/toggle.h +++ b/source/toggle.h @@ -55,6 +55,7 @@ private: public: virtual void button_press(int, int, unsigned); virtual void button_release(int, int, unsigned); + virtual bool navigate(Navigation); private: virtual void on_style_change(); }; diff --git a/source/widget.h b/source/widget.h index f1a83f7..eb8fa23 100644 --- a/source/widget.h +++ b/source/widget.h @@ -148,6 +148,7 @@ public: virtual bool character(wchar_t) { return false; } virtual void focus_in(); virtual void focus_out(); + virtual bool navigate(Navigation) { return false; } protected: virtual void on_geometry_change() { } virtual void on_style_change() { } -- 2.43.0