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