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