]> git.tdb.fi Git - libs/gltk.git/commitdiff
Implement keyboard navigation for most widgets
authorMikko Rasa <tdb@tdb.fi>
Wed, 31 Aug 2016 08:52:45 +0000 (11:52 +0300)
committerMikko Rasa <tdb@tdb.fi>
Wed, 31 Aug 2016 08:52:45 +0000 (11:52 +0300)
16 files changed:
source/button.cpp
source/button.h
source/container.cpp
source/container.h
source/dropdown.cpp
source/dropdown.h
source/entry.cpp
source/entry.h
source/inputmethod.h
source/list.cpp
source/list.h
source/panel.cpp
source/systemkeyboardinput.cpp
source/toggle.cpp
source/toggle.h
source/widget.h

index 86b73c9daa0c3f8ba9a689ebf628f102667c97f7..fadd00a0cafedc4dd0d1996f8be7a0ec6ae8fdaf 100644 (file)
@@ -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);
index 79045150a71a28f98d3a996ffa7cc907487dad42..844fe88c30fe786d9c460030f35a87ad11f78a10 100644 (file)
@@ -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();
 };
index 068986b15aca7b329626974c6349941a8d68422f..b50bc29e2f9c120c5fee986fa319cafde6d8c459 100644 (file)
@@ -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<Child *>::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 || score<best_score))
+                       {
+                               sibling = (*i)->widget;
+                               best_score = score;
+                       }
+               }
+
+               if(sibling)
+               {
+                       set_input_focus(sibling);
+                       if(Container *container = dynamic_cast<Container *>(sibling))
+                               container->navigate(nav);
+                       return true;
+               }
+       }
+
+       return false;
+}
+
 void Container::on_reparent()
 {
        for(list<Child *>::iterator i=children.begin(); i!=children.end(); ++i)
index 3907bde04e1376a25c29ba55d03f4c8a22cd11e2..b463ff2273815874f1df71ef2358f9f4ac6ba028 100644 (file)
@@ -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 &) { }
index 8cc6187f2243a552b0943dde919dff6e6c0d18cb..ef0fa22f97c8f150009988b3487c716b1e576cf2 100644 (file)
@@ -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)
index 78c52b763a6215aab975904c02be782713628056..9b8a9232e1d1b81be3f67f7662e4a2a548555804 100644 (file)
@@ -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);
index 16954a70f5774858b34b86622de200abe2a4376b..6616054f799b2be0f5dd1cf445989a6771ff30bd 100644 (file)
@@ -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_pos<text.size())
-                       set_edit_position(edit_pos+1);
-       }
-       else if(key==Input::KEY_DOWN && multiline)
-       {
-               unsigned row, col;
-               text.offset_to_coords(edit_pos, row, col);
-               set_edit_position(text.coords_to_offset(row+1, col));
-       }
-       else if(key==Input::KEY_UP && multiline)
-       {
-               unsigned row, col;
-               text.offset_to_coords(edit_pos, row, col);
-               set_edit_position(row>0 ? 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_pos<text.size())
+                       set_edit_position(edit_pos+1);
+       }
+       else if(nav==NAV_DOWN && multiline)
+       {
+               unsigned row, col;
+               text.offset_to_coords(edit_pos, row, col);
+               set_edit_position(text.coords_to_offset(row+1, col));
+       }
+       else if(nav==NAV_UP && multiline)
+       {
+               unsigned row, col;
+               text.offset_to_coords(edit_pos, row, col);
+               set_edit_position(row>0 ? 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)
index 72345b797001c36aa3da241f3a43ef1147ff4a0e..82a9d79f259e62d6969ee82aba7671a33c316c78 100644 (file)
@@ -31,6 +31,7 @@ public:
                void multiline(bool);
        };
 
+       // Deprecated
        sigc::signal<void> 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();
index 31cbaf3d90c7be1ebd67a6737efd45a6ce473c1b..441007568dd99644926dcae874bd6c43744d6895 100644 (file)
@@ -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:
index b9713422f11236fa86bc23a87a64f59523365569..6496018380ffbb12411d4a24eb26caa50c9995cd 100644 (file)
@@ -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<unsigned>(focus_index+1)<items.size())
+                       ++focus_index;
+               else
+                       return false;
+
+               scroll_to_focus();
+               set_input_focus(items[focus_index]);
+       }
+       else if(nav==NAV_ACTIVATE)
+               set_selected_index(focus_index);
+       else
+               return false;
+
+       return true;
+}
+
 void List::item_autosize_changed(Item *item)
 {
        item->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<unsigned>(focus_index)<first)
+               slider.set_value(max_scroll-focus_index);
+       else
+               slider.set_value(max_scroll-last_to_first(focus_index));
+}
+
 void List::slider_value_changed(double value)
 {
        if(max_scroll>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<int>(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<int>(i))
-               --list.sel_index;
-       else if(list.sel_index==static_cast<int>(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<Item *>::iterator i=list.items.begin(); i!=list.items.end(); ++i)
                delete *i;
        list.items.clear();
index ad12786bc7d962f3c43f0d06258781b60ae1f129..78954fc0fcc60ebf2fa2d2ab68783f7f61ef03bc 100644 (file)
@@ -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
index 44c41eac98fd6e62d0d69e2ace47c5a52e6910af..c17fe4b261e74650a55349c62f314b39d710b06f 100644 (file)
@@ -27,7 +27,7 @@ namespace GLtk {
 Panel::Panel():
        layout(0)
 {
-       input_type = INPUT_TEXT;
+       input_type = INPUT_NAVIGATION;
 }
 
 Panel::~Panel()
index cedddd1817d170b21c6f34eb3ac7fec07d3951ec..fe803a6ddd2e407abbe1cdbcadaed5ec4ce989da 100644 (file)
@@ -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;
 }
 
index b597eb99d32ad73eb02123e2fa83429d7836977f..b0219fc82f8ec764b072404d067a2a1009daa392 100644 (file)
@@ -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);
index 54becb9a3d1200c6b600d144875c2bf4ce53daa2..4845796c854cfd882387f0e780d5b51585142a6e 100644 (file)
@@ -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();
 };
index f1a83f78a5532c0e355306dcd826369d31caa1e3..eb8fa2324350b42335eb92a0bd8aec7fc70e2f7b 100644 (file)
@@ -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() { }