]> git.tdb.fi Git - libs/gltk.git/blob - source/container.cpp
64e3a9a7e296ce374819abddc035112a18948e53
[libs/gltk.git] / source / container.cpp
1 #include <msp/core/algorithm.h>
2 #include "container.h"
3 #include "part.h"
4
5 using namespace std;
6
7 namespace Msp {
8 namespace GLtk {
9
10 hierarchy_error::hierarchy_error(const string &w):
11         logic_error(w)
12 { }
13
14
15 Container::~Container()
16 {
17         while(!children.empty())
18                 delete children.front()->widget;
19 }
20
21 void Container::add(Widget &wdg)
22 {
23         wdg.set_parent(this);
24         children.push_back(create_child(&wdg));
25         if(wdg.get_animation_interval())
26                 check_animation_interval();
27         children_rebuild_needed = true;
28         signal_rebuild_needed.emit();
29         on_child_added(wdg);
30 }
31
32 void Container::remove(Widget &wdg)
33 {
34         auto i = find_if(children, [&wdg](const Child *c){ return c->widget==&wdg; });
35         if(i==children.end())
36                 throw hierarchy_error("widget not in container");
37
38         if(&wdg==saved_input_focus)
39                 saved_input_focus = nullptr;
40         wdg.set_parent(nullptr);
41         delete *i;
42         children.erase(i);
43         if(wdg.get_animation_interval())
44                 check_animation_interval();
45         on_child_removed(wdg);
46 }
47
48 Container::Child *Container::create_child(Widget *wdg)
49 {
50         return new Child(*this, wdg);
51 }
52
53 Geometry Container::determine_child_geometry(const Widget &child, const Part &part) const
54 {
55         Geometry pgeom = part.get_geometry();
56         if(!pgeom.w || !pgeom.h)
57         {
58                 Geometry cgeom;
59                 child.autosize(cgeom);
60                 if(!pgeom.w)
61                         pgeom.w = cgeom.w;
62                 if(!pgeom.h)
63                         pgeom.h = cgeom.h;
64         }
65
66         part.get_alignment().apply(pgeom, geom, part.get_margin());
67         return pgeom;
68 }
69
70 void Container::autosize_child(const Widget &child, const Part &part, Geometry &ageom) const
71 {
72         Geometry cgeom = determine_child_geometry(child, part);
73         const Sides &margin = part.get_margin();
74         ageom.w = max(ageom.w, cgeom.w+margin.left+margin.right);
75         ageom.h = max(ageom.h, cgeom.h+margin.top+margin.bottom);
76 }
77
78 void Container::reposition_child(Widget &child, const Part &part) const
79 {
80         child.set_geometry(determine_child_geometry(child, part));
81 }
82
83 vector<Widget *> Container::get_children() const
84 {
85         vector<Widget *> result;
86         for(const Child *c: children)
87                 result.push_back(c->widget);
88         return result;
89 }
90
91 Widget *Container::find_child_at(int x, int y) const
92 {
93         for(auto i=children.end(); i!=children.begin();)
94                 if((*--i)->widget->is_visible() && (*i)->widget->get_geometry().is_inside(x, y))
95                         return (*i)->widget;
96
97         return nullptr;
98 }
99
100 Widget *Container::find_descendant_at(int x, int y) const
101 {
102         Widget *wdg = find_child_at(x, y);
103         if(Container *cont = dynamic_cast<Container *>(wdg))
104         {
105                 const Geometry &cgeom = wdg->get_geometry();
106                 Widget *wdg2 = cont->find_descendant_at(x-cgeom.x, y-cgeom.y);
107                 if(wdg2)
108                         return wdg2;
109         }
110         return wdg;
111 }
112
113 void Container::raise(Widget &wdg)
114 {
115         auto i = find_if(children, [&wdg](const Child *c){ return c->widget==&wdg; });
116         if(i==children.end())
117                 throw hierarchy_error("widget not in container");
118
119         Child *c = *i;
120         children.erase(i);
121         children.push_back(c);
122 }
123
124 void Container::set_pointer_focus(Widget *wdg, bool grab)
125 {
126         if(wdg!=pointer_focus)
127         {
128                 if(pointer_focus)
129                         pointer_focus->pointer_leave();
130
131                 pointer_focus = wdg;
132                 pointer_grabbed = grab;
133
134                 if(pointer_focus)
135                         pointer_focus->pointer_enter();
136         }
137         else
138                 pointer_grabbed = grab;
139 }
140
141 void Container::set_input_focus(Widget *wdg)
142 {
143         if(wdg!=input_focus)
144         {
145                 if(input_focus)
146                         input_focus->focus_out();
147
148                 input_focus = wdg;
149                 on_input_focus_changed(input_focus);
150
151                 if(input_focus)
152                         input_focus->focus_in();
153         }
154 }
155
156 Widget *Container::get_final_input_focus() const
157 {
158         if(Container *container = dynamic_cast<Container *>(input_focus))
159                 if(Widget *focus = container->get_final_input_focus())
160                         return focus;
161
162         return input_focus;
163 }
164
165 void Container::check_animation_interval()
166 {
167         Time::TimeDelta shortest;
168         for(const Child *c: children)
169         {
170                 const Time::TimeDelta &child_iv = c->widget->get_animation_interval();
171                 if(child_iv && (!shortest || child_iv<shortest))
172                         shortest = child_iv;
173         }
174
175         if(shortest!=anim_interval)
176                 set_animation_interval(shortest);
177 }
178
179 void Container::rebuild_hierarchy()
180 {
181         Widget::rebuild_hierarchy();
182
183         if(children_rebuild_needed)
184         {
185                 children_rebuild_needed = false;
186                 for(Child *c: children)
187                         c->widget->rebuild_hierarchy();
188         }
189 }
190
191 void Container::button_press(int x, int y, unsigned btn)
192 {
193         if(Widget *child = get_pointer_target(x, y, false))
194         {
195                 if(!click_focus)
196                 {
197                         set_pointer_focus(child);
198                         if(child->is_focusable())
199                                 set_input_focus(child);
200
201                         click_focus = child;
202                         click_button = btn;
203                 }
204
205                 const Geometry &cgeom = child->get_geometry();
206                 child->button_press(x-cgeom.x, y-cgeom.y, btn);
207         }
208 }
209
210 void Container::button_release(int x, int y, unsigned btn)
211 {
212         if(Widget *child = get_pointer_target(x, y, false))
213         {
214                 if(child==click_focus && btn==click_button)
215                 {
216                         click_focus = nullptr;
217                         if(!pointer_focus)
218                                 set_pointer_focus(find_child_at(x, y));
219                 }
220
221                 const Geometry &cgeom = child->get_geometry();
222                 child->button_release(x-cgeom.x, y-cgeom.y, btn);
223         }
224 }
225
226 void Container::pointer_motion(int x, int y)
227 {
228         Widget *child = get_pointer_target(x, y, false);
229         if(!pointer_grabbed)
230                 set_pointer_focus((child && child->get_geometry().is_inside(x, y)) ? child : nullptr);
231
232         if(child)
233         {
234                 const Geometry &cgeom = child->get_geometry();
235                 child->pointer_motion(x-cgeom.x, y-cgeom.y);
236         }
237 }
238
239 Widget *Container::get_pointer_target(int x, int y, bool touch) const
240 {
241         if(pointer_grabbed)
242                 return pointer_focus;
243         else if(!touch && click_focus)
244                 return click_focus;
245         else if(touch && touch_focus)
246                 return touch_focus;
247         else
248         {
249                 Widget *child = find_child_at(x, y);
250                 if(child && child->is_enabled())
251                         return child;
252                 else
253                         return nullptr;
254         }
255 }
256
257 void Container::pointer_leave()
258 {
259         Widget::pointer_leave();
260         set_pointer_focus(nullptr);
261 }
262
263 void Container::touch_press(int x, int y, unsigned finger)
264 {
265         if(Widget *child = get_pointer_target(x, y, true))
266         {
267                 // TODO track focus for each finger separately
268                 if(!touch_focus)
269                         touch_focus = child;
270
271                 const Geometry &cgeom = child->get_geometry();
272                 child->touch_press(x-cgeom.x, y-cgeom.y, finger);
273         }
274 }
275
276 void Container::touch_release(int x, int y, unsigned finger)
277 {
278         if(Widget *child = get_pointer_target(x, y, true))
279         {
280                 // TODO track focus for each finger separately
281                 if(child==touch_focus)
282                         touch_focus = nullptr;
283
284                 const Geometry &cgeom = child->get_geometry();
285                 child->touch_release(x-cgeom.x, y-cgeom.y, finger);
286         }
287 }
288
289 void Container::touch_motion(int x, int y, unsigned finger)
290 {
291         if(Widget *child = get_pointer_target(x, y, true))
292         {
293                 const Geometry &cgeom = child->get_geometry();
294                 child->touch_motion(x-cgeom.x, y-cgeom.y, finger);
295         }
296 }
297
298 bool Container::key_press(unsigned key, unsigned mod)
299 {
300         if(input_focus && input_focus->is_enabled())
301                 return input_focus->key_press(key, mod);
302         else
303                 return false;
304 }
305
306 bool Container::key_release(unsigned key, unsigned mod)
307 {
308         if(input_focus && input_focus->is_enabled())
309                 return input_focus->key_release(key, mod);
310         else
311                 return false;
312 }
313
314 bool Container::character(wchar_t ch)
315 {
316         if(input_focus && input_focus->is_enabled())
317                 return input_focus->character(ch);
318         else
319                 return false;
320 }
321
322 void Container::focus_in()
323 {
324         if(saved_input_focus)
325                 set_input_focus(saved_input_focus);
326         Widget::focus_in();
327 }
328
329 void Container::focus_out()
330 {
331         saved_input_focus = input_focus;
332         set_input_focus(nullptr);
333         Widget::focus_out();
334 }
335
336 bool Container::navigate(Navigation nav)
337 {
338         if(input_focus && input_focus->is_enabled())
339                 return input_focus->navigate(nav);
340         else
341                 return false;
342 }
343
344 void Container::animate(const Time::TimeDelta &dt)
345 {
346         for(Child *c: children)
347         {
348                 const Time::TimeDelta &child_iv = c->widget->get_animation_interval();
349                 if(!child_iv)
350                         continue;
351
352                 c->time_since_animate += dt;
353                 if(c->time_since_animate>=child_iv)
354                 {
355                         Time::TimeDelta child_dt = c->time_since_animate;
356                         c->time_since_animate = min(c->time_since_animate-child_iv, child_iv);
357                         c->widget->animate(child_dt);
358                 }
359         }
360 }
361
362 void Container::on_reparent()
363 {
364         for(const Child *c: children)
365         {
366                 if(Container *o = dynamic_cast<Container *>(c->widget))
367                         o->on_reparent();
368                 c->widget->update_style();
369         }
370 }
371
372 void Container::on_input_focus_changed(Widget *wdg)
373 {
374         if(wdg)
375                 raise(*wdg);
376 }
377
378
379 Container::Child::Child(Container &c, Widget *w):
380         container(c),
381         widget(w)
382 {
383         widget->signal_visibility_changed.connect(sigc::mem_fun(this, &Child::visibility_changed));
384         widget->signal_request_focus.connect(sigc::mem_fun(this, &Child::request_focus));
385         widget->signal_grab_pointer.connect(sigc::mem_fun(this, &Child::grab_pointer));
386         widget->signal_ungrab_pointer.connect(sigc::mem_fun(this, &Child::ungrab_pointer));
387         widget->signal_request_animation.connect(sigc::mem_fun(this, &Child::request_animation));
388         widget->signal_rebuild_needed.connect(sigc::mem_fun(this, &Child::rebuild_needed));
389 }
390
391 Container::Child::~Child()
392 {
393         visibility_changed(false);
394 }
395
396 void Container::Child::visibility_changed(bool v)
397 {
398         if(!v)
399         {
400                 if(widget==container.click_focus)
401                         container.click_focus = nullptr;
402                 if(widget==container.pointer_focus)
403                         container.set_pointer_focus(nullptr);
404                 if(widget==container.input_focus)
405                         container.set_input_focus(nullptr);
406         }
407 }
408
409 void Container::Child::request_focus()
410 {
411         if(container.parent && container.visible)
412                 container.set_focus();
413         if(container.state&FOCUS)
414                 container.set_input_focus(widget);
415 }
416
417 void Container::Child::grab_pointer()
418 {
419         if(!container.pointer_grabbed)
420         {
421                 container.set_pointer_focus(widget, true);
422                 container.signal_grab_pointer.emit();
423         }
424 }
425
426 void Container::Child::ungrab_pointer()
427 {
428         if(container.pointer_grabbed && container.pointer_focus==widget)
429         {
430                 // XXX Should set to the widget under pointer
431                 container.set_pointer_focus(nullptr);
432                 container.signal_ungrab_pointer.emit();
433         }
434 }
435
436 void Container::Child::request_animation(const Time::TimeDelta &interval)
437 {
438         if(!interval)
439                 time_since_animate = Time::zero;
440         container.check_animation_interval();
441 }
442
443 void Container::Child::rebuild_needed()
444 {
445         container.children_rebuild_needed = true;
446         container.signal_rebuild_needed.emit();
447 }
448
449 } // namespace GLtk
450 } // namespace Msp