]> git.tdb.fi Git - libs/gltk.git/blob - source/panel.cpp
Rewrite Panel navigation logic so it makes more sense
[libs/gltk.git] / source / panel.cpp
1 #include <algorithm>
2 #include <msp/core/maputils.h>
3 #include <msp/core/refptr.h>
4 #include "button.h"
5 #include "column.h"
6 #include "draghandle.h"
7 #include "dropdown.h"
8 #include "entry.h"
9 #include "grid.h"
10 #include "hslider.h"
11 #include "image.h"
12 #include "indicator.h"
13 #include "label.h"
14 #include "list.h"
15 #include "panel.h"
16 #include "part.h"
17 #include "row.h"
18 #include "stack.h"
19 #include "toggle.h"
20 #include "vslider.h"
21
22 using namespace std;
23
24 namespace Msp {
25 namespace GLtk {
26
27 Panel::Panel():
28         layout(0)
29 {
30         input_type = INPUT_NAVIGATION;
31 }
32
33 Panel::~Panel()
34 {
35         delete layout;
36         layout = 0;
37 }
38
39 void Panel::set_layout(Layout *l)
40 {
41         l->set_container(*this);
42         delete layout;
43         layout = l;
44 }
45
46 void Panel::autosize_special(const Part &part, Geometry &ageom) const
47 {
48         if(part.get_name()=="children" && layout)
49                 layout->autosize(ageom);
50 }
51
52 void Panel::render_special(const Part &part, GL::Renderer &renderer) const
53 {
54         if(part.get_name()=="children")
55         {
56                 for(list<Container::Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
57                         if((*i)->widget->is_visible())
58                                 (*i)->widget->render(renderer);
59         }
60 }
61
62 bool Panel::navigate(Navigation nav)
63 {
64         if(Container::navigate(nav))
65                 return true;
66
67         if(nav==NAV_UP || nav==NAV_DOWN || nav==NAV_LEFT || nav==NAV_RIGHT)
68         {
69                 int nav_x = (nav==NAV_RIGHT ? 1 : nav==NAV_LEFT ? -1 : 0);
70                 int nav_y = (nav==NAV_UP ? 1 : nav==NAV_DOWN ? -1 : 0);
71
72                 int origin_x, origin_y, origin_dim;
73                 if(input_focus)
74                 {
75                         const Geometry &fgeom = input_focus->get_geometry();
76                         origin_x = fgeom.x+(nav_x*0.5+0.5)*fgeom.w;
77                         origin_y = fgeom.y+(nav_y*0.5+0.5)*fgeom.h;
78                         origin_dim = abs(nav_x)*fgeom.h+abs(nav_y)*fgeom.w;
79                 }
80                 else
81                 {
82                         origin_x = geom.w*(0.5-nav_x*0.5);
83                         origin_y = geom.h*(0.5-nav_y*0.5);
84                         origin_dim = abs(nav_x)*geom.h+abs(nav_y)*geom.w;
85                 }
86
87                 Widget *sibling = find_next_child(origin_x, origin_y, origin_dim, nav_x, nav_y);
88                 if(!sibling && input_focus)
89                 {
90                         const Geometry &fgeom = input_focus->get_geometry();
91                         origin_x -= fgeom.w*(nav_x*0.5);
92                         origin_y -= fgeom.h*(nav_y*0.5);
93                         sibling = find_next_child(origin_x, origin_y, origin_dim, nav_x, nav_y);
94                 }
95
96                 if(sibling)
97                 {
98                         set_input_focus(sibling);
99                         if(Panel *panel = dynamic_cast<Panel *>(sibling))
100                                 panel->navigate(nav);
101                         return true;
102                 }
103         }
104
105         return false;
106 }
107
108 Widget *Panel::find_next_child(int origin_x, int origin_y, int origin_dim, int nav_x, int nav_y) const
109 {
110         Widget *sibling = 0;
111         int best_score = 0;
112         for(list<Child *>::const_iterator i=children.begin(); i!=children.end(); ++i)
113         {
114                 if((*i)->widget==input_focus || !(*i)->widget->is_focusable())
115                         continue;
116
117                 const Geometry &cgeom = (*i)->widget->get_geometry();
118                 int dx = compute_delta(cgeom.x, cgeom.w, origin_x, origin_dim, nav_x);
119                 int dy = compute_delta(cgeom.y, cgeom.h, origin_y, origin_dim, nav_y);
120
121                 int score = -1;
122                 if(nav_y && nav_y*dy>=0)
123                         score = nav_y*dy+abs(dx)*4;
124                 else if(nav_x && nav_x*dx>=0)
125                         score = nav_x*dx+abs(dy)*4;
126
127                 if(score>=0 && (!sibling || score<best_score))
128                 {
129                         sibling = (*i)->widget;
130                         best_score = score;
131                 }
132         }
133
134         return sibling;
135 }
136
137 int Panel::compute_delta(int pos, int dim, int origin_pos, int origin_dim, int nav)
138 {
139         if(nav<0)
140                 return pos+dim-origin_pos;
141         else if(nav>0)
142                 return pos-origin_pos;
143         else if(pos+dim<origin_pos-origin_dim/2)
144                 return pos+dim+origin_dim/2-origin_pos;
145         else if(pos>origin_pos+origin_dim/2)
146                 return pos-origin_pos-origin_dim/2;
147         else
148                 return 0;
149 }
150
151 void Panel::on_geometry_change()
152 {
153         if(layout)
154                 layout->update();
155 }
156
157 void Panel::on_child_added(Widget &wdg)
158 {
159         if(layout)
160         {
161                 layout->add_widget(wdg);
162                 signal_autosize_changed.emit();
163         }
164 }
165
166 void Panel::on_child_removed(Widget &wdg)
167 {
168         if(layout)
169         {
170                 layout->remove_widget(wdg);
171                 signal_autosize_changed.emit();
172         }
173 }
174
175
176 Panel::Loader::Loader(Panel &p, map<string, Widget *> &m):
177         DataFile::DerivedObjectLoader<Panel, Widget::Loader>(p),
178         wdg_map(m),
179         last_widget(0)
180 {
181         add("button",    &Loader::child<Button>);
182         add("column",    &Loader::arrangement<Column>);
183         add("constraint",&Loader::constraint);
184         add("draghandle",&Loader::child<DragHandle>);
185         add("dropdown",  &Loader::child<Dropdown>);
186         add("entry",     &Loader::child<Entry>);
187         add("expand",    &Loader::expand);
188         add("ghost",     &Loader::ghost);
189         add("gravity",   &Loader::gravity);
190         add("grid",      &Loader::grid);
191         add("hslider",   &Loader::child<HSlider>);
192         add("image",     &Loader::child<Image>);
193         add("indicator", &Loader::child<Indicator>);
194         add("label",     &Loader::child<Label>);
195         add("layout",    &Loader::layout);
196         add("list",      &Loader::child<List>);
197         add("panel",     &Loader::panel);
198         add("row",       &Loader::arrangement<Row>);
199         add("stack",     &Loader::arrangement<Stack>);
200         add("toggle",    &Loader::child<Toggle>);
201         add("vslider",   &Loader::child<VSlider>);
202 }
203
204 Layout &Panel::Loader::get_layout()
205 {
206         if(!obj.layout)
207                 obj.set_layout(new Layout);
208
209         return *obj.layout;
210 }
211
212 Widget &Panel::Loader::get_last_widget()
213 {
214         if(!last_widget)
215                 throw logic_error("no widget loaded");
216
217         return *last_widget;
218 }
219
220 template<typename T>
221 void Panel::Loader::arrangement()
222 {
223         T arr(get_layout());
224         ArrangedLoader<T> ldr(*this, arr);
225         load_sub_with(ldr);
226 }
227
228 template<typename T>
229 void Panel::Loader::child(const string &n)
230 {
231         RefPtr<T> chl = new T();
232         load_sub(*chl);
233         obj.add(*chl.get());
234         last_widget = wdg_map[n] = chl.release();
235 }
236
237 void Panel::Loader::constraint(Layout::ConstraintType type, const string &n)
238 {
239         Widget &src = get_last_widget();
240         Widget &tgt = *get_item(wdg_map, n);
241         get_layout().add_constraint(src, type, tgt);
242 }
243
244 void Panel::Loader::expand(bool h, bool v)
245 {
246         get_layout().set_expand(get_last_widget(), h, v);
247 }
248
249 void Panel::Loader::ghost(bool g)
250 {
251         get_layout().set_ghost(get_last_widget(), g);
252 }
253
254 void Panel::Loader::gravity(int h, int v)
255 {
256         get_layout().set_gravity(get_last_widget(), h, v);
257 }
258
259 void Panel::Loader::grid(unsigned cols)
260 {
261         Grid grd(get_layout(), cols);
262         ArrangedLoader<Grid> ldr(*this, grd);
263         load_sub_with(ldr);
264 }
265
266 void Panel::Loader::layout()
267 {
268         Layout::Loader ldr(get_layout(), wdg_map);
269         load_sub_with(ldr);
270 }
271
272 void Panel::Loader::panel(const string &n)
273 {
274         RefPtr<Panel> p = new Panel();
275         load_sub(*p, wdg_map);
276         obj.add(*p.get());
277         last_widget = wdg_map[n] = p.release();
278 }
279
280
281 template<typename T>
282 Panel::ArrangedLoader<T>::ArrangedLoader(Loader &ldr, T &arr):
283         arr_loader(arr)
284 {
285         add_auxiliary_loader(ldr);
286         add_auxiliary_loader(arr_loader);
287 }
288
289 } // namespace GLtk
290 } // namespace Msp