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