diff options
Diffstat (limited to 'src/ui/widget.c')
-rw-r--r-- | src/ui/widget.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/src/ui/widget.c b/src/ui/widget.c new file mode 100644 index 00000000..49fcd7c0 --- /dev/null +++ b/src/ui/widget.c | |||
@@ -0,0 +1,618 @@ | |||
1 | #include "widget.h" | ||
2 | |||
3 | #include "app.h" | ||
4 | #include "command.h" | ||
5 | #include "paint.h" | ||
6 | #include "util.h" | ||
7 | #include "window.h" | ||
8 | |||
9 | #include <the_Foundation/ptrset.h> | ||
10 | #include <SDL_mouse.h> | ||
11 | #include <stdarg.h> | ||
12 | |||
13 | iDeclareType(RootData) | ||
14 | |||
15 | struct Impl_RootData { | ||
16 | iWidget *hover; | ||
17 | iWidget *mouseGrab; | ||
18 | iWidget *focus; | ||
19 | iPtrSet *onTop; | ||
20 | iPtrSet *pendingDestruction; | ||
21 | }; | ||
22 | |||
23 | static iRootData rootData_; | ||
24 | |||
25 | iPtrSet *onTop_RootData_(void) { | ||
26 | if (!rootData_.onTop) { | ||
27 | rootData_.onTop = new_PtrSet(); | ||
28 | } | ||
29 | return rootData_.onTop; | ||
30 | } | ||
31 | |||
32 | void destroyPending_Widget(void) { | ||
33 | iForEach(PtrSet, i, rootData_.pendingDestruction) { | ||
34 | iWidget *widget = *i.value; | ||
35 | remove_PtrSet(onTop_RootData_(), widget); | ||
36 | iRelease(removeChild_Widget(widget->parent, widget)); | ||
37 | remove_PtrSetIterator(&i); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | iDefineObjectConstruction(Widget) | ||
42 | |||
43 | void init_Widget(iWidget *d) { | ||
44 | init_String(&d->id); | ||
45 | d->flags = 0; | ||
46 | d->rect = zero_Rect(); | ||
47 | d->bgColor = none_ColorId; | ||
48 | d->children = NULL; | ||
49 | d->parent = NULL; | ||
50 | d->commandHandler = NULL; | ||
51 | } | ||
52 | |||
53 | void deinit_Widget(iWidget *d) { | ||
54 | iReleasePtr(&d->children); | ||
55 | deinit_String(&d->id); | ||
56 | } | ||
57 | |||
58 | static void aboutToBeDestroyed_Widget_(iWidget *d) { | ||
59 | if (isFocused_Widget(d)) { | ||
60 | setFocus_Widget(NULL); | ||
61 | return; | ||
62 | } | ||
63 | if (isHover_Widget(d)) { | ||
64 | rootData_.hover = NULL; | ||
65 | } | ||
66 | iForEach(ObjectList, i, d->children) { | ||
67 | aboutToBeDestroyed_Widget_(as_Widget(i.object)); | ||
68 | } | ||
69 | } | ||
70 | |||
71 | void destroy_Widget(iWidget *d) { | ||
72 | aboutToBeDestroyed_Widget_(d); | ||
73 | if (!rootData_.pendingDestruction) { | ||
74 | rootData_.pendingDestruction = new_PtrSet(); | ||
75 | } | ||
76 | insert_PtrSet(rootData_.pendingDestruction, d); | ||
77 | } | ||
78 | |||
79 | void setId_Widget(iWidget *d, const char *id) { | ||
80 | setCStr_String(&d->id, id); | ||
81 | } | ||
82 | |||
83 | const iString *id_Widget(const iWidget *d) { | ||
84 | return &d->id; | ||
85 | } | ||
86 | |||
87 | int flags_Widget(const iWidget *d) { | ||
88 | return d->flags; | ||
89 | } | ||
90 | |||
91 | void setFlags_Widget(iWidget *d, int flags, iBool set) { | ||
92 | iChangeFlags(d->flags, flags, set); | ||
93 | if (flags & keepOnTop_WidgetFlag) { | ||
94 | if (set) { | ||
95 | insert_PtrSet(onTop_RootData_(), d); | ||
96 | } | ||
97 | else { | ||
98 | remove_PtrSet(onTop_RootData_(), d); | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | void setPos_Widget(iWidget *d, iInt2 pos) { | ||
104 | d->rect.pos = pos; | ||
105 | } | ||
106 | |||
107 | void setSize_Widget(iWidget *d, iInt2 size) { | ||
108 | d->rect.size = size; | ||
109 | setFlags_Widget(d, fixedSize_WidgetFlag, iTrue); | ||
110 | } | ||
111 | |||
112 | void setBackgroundColor_Widget(iWidget *d, int bgColor) { | ||
113 | d->bgColor = bgColor; | ||
114 | } | ||
115 | |||
116 | void setCommandHandler_Widget(iWidget *d, iBool (*handler)(iWidget *, const char *)) { | ||
117 | d->commandHandler = handler; | ||
118 | } | ||
119 | |||
120 | static int numExpandingChildren_Widget_(const iWidget *d) { | ||
121 | int count = 0; | ||
122 | iConstForEach(ObjectList, i, d->children) { | ||
123 | const iWidget *child = constAs_Widget(i.object); | ||
124 | if (flags_Widget(child) & expand_WidgetFlag) { | ||
125 | count++; | ||
126 | } | ||
127 | } | ||
128 | return count; | ||
129 | } | ||
130 | |||
131 | static int widestChild_Widget_(const iWidget *d) { | ||
132 | int width = 0; | ||
133 | iConstForEach(ObjectList, i, d->children) { | ||
134 | const iWidget *child = constAs_Widget(i.object); | ||
135 | width = iMax(width, child->rect.size.x); | ||
136 | } | ||
137 | return width; | ||
138 | } | ||
139 | |||
140 | static void setWidth_Widget_(iWidget *d, int width) { | ||
141 | if (~d->flags & fixedWidth_WidgetFlag) { | ||
142 | d->rect.size.x = width; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | static void setHeight_Widget_(iWidget *d, int height) { | ||
147 | if (~d->flags & fixedHeight_WidgetFlag) { | ||
148 | d->rect.size.y = height; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | void arrange_Widget(iWidget *d) { | ||
153 | if (d->flags & resizeToParentWidth_WidgetFlag) { | ||
154 | setWidth_Widget_(d, d->parent->rect.size.x); | ||
155 | } | ||
156 | if (d->flags & resizeToParentHeight_WidgetFlag) { | ||
157 | setHeight_Widget_(d, d->parent->rect.size.y); | ||
158 | } | ||
159 | /* The rest of the arrangement depends on child widgets. */ | ||
160 | if (!d->children) { | ||
161 | return; | ||
162 | } | ||
163 | /* Resize children to fill the parent widget. */ | ||
164 | const size_t childCount = size_ObjectList(d->children); | ||
165 | if (d->flags & resizeChildren_WidgetFlag) { | ||
166 | const int expCount = numExpandingChildren_Widget_(d); | ||
167 | /* Only resize the expanding children, not touching the others. */ | ||
168 | if (expCount > 0) { | ||
169 | iInt2 avail = d->rect.size; | ||
170 | iConstForEach(ObjectList, i, d->children) { | ||
171 | const iWidget *child = constAs_Widget(i.object); | ||
172 | if (~child->flags & expand_WidgetFlag) { | ||
173 | subv_I2(&avail, child->rect.size); | ||
174 | } | ||
175 | } | ||
176 | avail = divi_I2(avail, expCount); | ||
177 | iForEach(ObjectList, j, d->children) { | ||
178 | iWidget *child = as_Widget(j.object); | ||
179 | if (child->flags & expand_WidgetFlag) { | ||
180 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
181 | setWidth_Widget_(child, avail.x); | ||
182 | setHeight_Widget_(child, d->rect.size.y); | ||
183 | } | ||
184 | else if (d->flags & arrangeVertical_WidgetFlag) { | ||
185 | setWidth_Widget_(child, d->rect.size.x); | ||
186 | setHeight_Widget_(child, avail.y); | ||
187 | } | ||
188 | } | ||
189 | else { | ||
190 | /* Fill the off axis, though. */ | ||
191 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
192 | setHeight_Widget_(child, d->rect.size.y); | ||
193 | } | ||
194 | else if (d->flags & arrangeVertical_WidgetFlag) { | ||
195 | setWidth_Widget_(child, d->rect.size.x); | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | else { | ||
201 | /* Evenly size all children. */ | ||
202 | iInt2 childSize = d->rect.size; | ||
203 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
204 | childSize.x /= childCount; | ||
205 | } | ||
206 | else if (d->flags & arrangeVertical_WidgetFlag) { | ||
207 | childSize.y /= childCount; | ||
208 | } | ||
209 | iForEach(ObjectList, i, d->children) { | ||
210 | iWidget *child = as_Widget(i.object); | ||
211 | setWidth_Widget_(child, childSize.x); | ||
212 | setHeight_Widget_(child, childSize.y); | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | if (d->flags & resizeChildrenToWidestChild_WidgetFlag) { | ||
217 | const int widest = widestChild_Widget_(d); | ||
218 | iForEach(ObjectList, i, d->children) { | ||
219 | setWidth_Widget_(as_Widget(i.object), widest); | ||
220 | } | ||
221 | } | ||
222 | iInt2 pos = zero_I2(); | ||
223 | iForEach(ObjectList, i, d->children) { | ||
224 | iWidget *child = as_Widget(i.object); | ||
225 | arrange_Widget(child); | ||
226 | if (d->flags & (arrangeHorizontal_WidgetFlag | arrangeVertical_WidgetFlag)) { | ||
227 | child->rect.pos = pos; | ||
228 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
229 | pos.x += child->rect.size.x; | ||
230 | } | ||
231 | else { | ||
232 | pos.y += child->rect.size.y; | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | /* Update the size of the widget according to the arrangement. */ | ||
237 | if (d->flags & arrangeSize_WidgetFlag) { | ||
238 | iRect bounds = zero_Rect(); | ||
239 | iConstForEach(ObjectList, i, d->children) { | ||
240 | const iWidget *child = constAs_Widget(i.object); | ||
241 | if (isEmpty_Rect(bounds)) { | ||
242 | bounds = child->rect; | ||
243 | } | ||
244 | else { | ||
245 | bounds = union_Rect(bounds, child->rect); | ||
246 | } | ||
247 | } | ||
248 | if (d->flags & arrangeWidth_WidgetFlag) { | ||
249 | setWidth_Widget_(d, bounds.size.x); | ||
250 | /* Parent size changed, must update the children.*/ | ||
251 | iForEach(ObjectList, j, d->children) { | ||
252 | iWidget *child = as_Widget(j.object); | ||
253 | if (child->flags & resizeToParentWidth_WidgetFlag) { | ||
254 | arrange_Widget(child); | ||
255 | } | ||
256 | } | ||
257 | } | ||
258 | if (d->flags & arrangeHeight_WidgetFlag) { | ||
259 | setHeight_Widget_(d, bounds.size.y); | ||
260 | /* Parent size changed, must update the children.*/ | ||
261 | iForEach(ObjectList, j, d->children) { | ||
262 | iWidget *child = as_Widget(j.object); | ||
263 | if (child->flags & resizeToParentHeight_WidgetFlag) { | ||
264 | arrange_Widget(child); | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | } | ||
269 | } | ||
270 | |||
271 | iRect bounds_Widget(const iWidget *d) { | ||
272 | iRect bounds = d->rect; | ||
273 | for (const iWidget *w = d->parent; w; w = w->parent) { | ||
274 | addv_I2(&bounds.pos, w->rect.pos); | ||
275 | } | ||
276 | return bounds; | ||
277 | } | ||
278 | |||
279 | iInt2 localCoord_Widget(const iWidget *d, iInt2 coord) { | ||
280 | for (const iWidget *w = d; w; w = w->parent) { | ||
281 | subv_I2(&coord, w->rect.pos); | ||
282 | } | ||
283 | return coord; | ||
284 | } | ||
285 | |||
286 | iBool contains_Widget(const iWidget *d, iInt2 coord) { | ||
287 | const iRect bounds = { zero_I2(), d->rect.size }; | ||
288 | return contains_Rect(bounds, localCoord_Widget(d, coord)); | ||
289 | } | ||
290 | |||
291 | iLocalDef iBool isKeyboardEvent_(const SDL_Event *ev) { | ||
292 | return (ev->type == SDL_KEYUP || ev->type == SDL_KEYDOWN || ev->type == SDL_TEXTINPUT); | ||
293 | } | ||
294 | |||
295 | iLocalDef iBool isMouseEvent_(const SDL_Event *ev) { | ||
296 | return (ev->type == SDL_MOUSEWHEEL || ev->type == SDL_MOUSEMOTION || | ||
297 | ev->type == SDL_MOUSEBUTTONUP || ev->type == SDL_MOUSEBUTTONDOWN); | ||
298 | } | ||
299 | |||
300 | static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) { | ||
301 | const iBool isKey = isKeyboardEvent_(ev); | ||
302 | const iBool isMouse = isMouseEvent_(ev); | ||
303 | if (d->flags & disabled_WidgetFlag) { | ||
304 | if (isKey || isMouse) return iFalse; | ||
305 | } | ||
306 | if (d->flags & hidden_WidgetFlag) { | ||
307 | if (isMouse) return iFalse; | ||
308 | } | ||
309 | return iTrue; | ||
310 | } | ||
311 | |||
312 | void unhover_Widget(void) { | ||
313 | rootData_.hover = NULL; | ||
314 | } | ||
315 | |||
316 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | ||
317 | if (!d->parent) { | ||
318 | if (ev->type == SDL_MOUSEMOTION) { | ||
319 | /* Hover widget may change. */ | ||
320 | rootData_.hover = NULL; | ||
321 | } | ||
322 | if (rootData_.focus && isKeyboardEvent_(ev)) { | ||
323 | /* Root dispatches keyboard events directly to the focused widget. */ | ||
324 | if (dispatchEvent_Widget(rootData_.focus, ev)) { | ||
325 | return iTrue; | ||
326 | } | ||
327 | } | ||
328 | /* Root offers events first to widgets on top. */ | ||
329 | iForEach(PtrSet, i, rootData_.onTop) { | ||
330 | iWidget *widget = *i.value; | ||
331 | if (isVisible_Widget(widget) && dispatchEvent_Widget(widget, ev)) { | ||
332 | return iTrue; | ||
333 | } | ||
334 | } | ||
335 | } | ||
336 | else if (ev->type == SDL_MOUSEMOTION && !rootData_.hover && | ||
337 | flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && | ||
338 | ~flags_Widget(d) & disabled_WidgetFlag) { | ||
339 | if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { | ||
340 | rootData_.hover = d; | ||
341 | } | ||
342 | } | ||
343 | if (filterEvent_Widget_(d, ev)) { | ||
344 | /* Children may handle it first. Done in reverse so children drawn on top get to | ||
345 | handle the events first. */ | ||
346 | iReverseForEach(ObjectList, i, d->children) { | ||
347 | iWidget *child = as_Widget(i.object); | ||
348 | if (child == rootData_.focus && isKeyboardEvent_(ev)) { | ||
349 | continue; /* Already dispatched. */ | ||
350 | } | ||
351 | if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { | ||
352 | /* Already dispatched. */ | ||
353 | continue; | ||
354 | } | ||
355 | if (dispatchEvent_Widget(child, ev)) { | ||
356 | return iTrue; | ||
357 | } | ||
358 | } | ||
359 | if (class_Widget(d)->processEvent(d, ev)) { | ||
360 | return iTrue; | ||
361 | } | ||
362 | } | ||
363 | return iFalse; | ||
364 | } | ||
365 | |||
366 | iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | ||
367 | if (ev->type == SDL_KEYDOWN) { | ||
368 | if (ev->key.keysym.sym == SDLK_TAB) { | ||
369 | setFocus_Widget(findFocusable_Widget(focus_Widget(), | ||
370 | ev->key.keysym.mod & KMOD_SHIFT | ||
371 | ? backward_WidgetFocusDir | ||
372 | : forward_WidgetFocusDir)); | ||
373 | return iTrue; | ||
374 | } | ||
375 | } | ||
376 | switch (ev->type) { | ||
377 | case SDL_USEREVENT: { | ||
378 | if (ev->user.code == command_UserEventCode && d->commandHandler && | ||
379 | d->commandHandler(d, ev->user.data1)) { | ||
380 | return iTrue; | ||
381 | } | ||
382 | break; | ||
383 | } | ||
384 | } | ||
385 | return iFalse; | ||
386 | } | ||
387 | |||
388 | void draw_Widget(const iWidget *d) { | ||
389 | if (d->flags & hidden_WidgetFlag) return; | ||
390 | if (d->bgColor >= 0) { | ||
391 | iPaint p; | ||
392 | init_Paint(&p); | ||
393 | fillRect_Paint(&p, bounds_Widget(d), d->bgColor); | ||
394 | } | ||
395 | iConstForEach(ObjectList, i, d->children) { | ||
396 | const iWidget *child = constAs_Widget(i.object); | ||
397 | if (~child->flags & keepOnTop_WidgetFlag && ~child->flags & hidden_WidgetFlag) { | ||
398 | class_Widget(child)->draw(child); | ||
399 | } | ||
400 | } | ||
401 | /* Root draws the on-top widgets on top of everything else. */ | ||
402 | if (!d->parent) { | ||
403 | iConstForEach(PtrSet, i, onTop_RootData_()) { | ||
404 | draw_Widget(*i.value); | ||
405 | } | ||
406 | } | ||
407 | } | ||
408 | |||
409 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { | ||
410 | return addChildPos_Widget(d, child, back_WidgetAddPos); | ||
411 | } | ||
412 | |||
413 | iAny *addChildPos_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos addPos) { | ||
414 | iAssert(child); | ||
415 | iAssert(d != child); | ||
416 | iWidget *widget = as_Widget(child); | ||
417 | iAssert(!widget->parent); | ||
418 | if (!d->children) { | ||
419 | d->children = new_ObjectList(); | ||
420 | } | ||
421 | if (addPos == back_WidgetAddPos) { | ||
422 | pushBack_ObjectList(d->children, widget); /* ref */ | ||
423 | } | ||
424 | else { | ||
425 | pushFront_ObjectList(d->children, widget); /* ref */ | ||
426 | } | ||
427 | widget->parent = d; | ||
428 | return child; | ||
429 | } | ||
430 | |||
431 | iAny *addChildFlags_Widget(iWidget *d, iAnyObject *child, int childFlags) { | ||
432 | setFlags_Widget(child, childFlags, iTrue); | ||
433 | return addChild_Widget(d, child); | ||
434 | } | ||
435 | |||
436 | iAny *removeChild_Widget(iWidget *d, iAnyObject *child) { | ||
437 | ref_Object(child); | ||
438 | iBool found = iFalse; | ||
439 | iForEach(ObjectList, i, d->children) { | ||
440 | if (i.object == child) { | ||
441 | remove_ObjectListIterator(&i); | ||
442 | found = iTrue; | ||
443 | break; | ||
444 | } | ||
445 | } | ||
446 | iAssert(found); | ||
447 | ((iWidget *) child)->parent = NULL; | ||
448 | return child; | ||
449 | } | ||
450 | |||
451 | iAny *child_Widget(iWidget *d, size_t index) { | ||
452 | iForEach(ObjectList, i, d->children) { | ||
453 | if (index-- == 0) { | ||
454 | return i.object; | ||
455 | } | ||
456 | } | ||
457 | return NULL; | ||
458 | } | ||
459 | |||
460 | iAny *findChild_Widget(const iWidget *d, const char *id) { | ||
461 | if (cmp_String(id_Widget(d), id) == 0) { | ||
462 | return iConstCast(iAny *, d); | ||
463 | } | ||
464 | iConstForEach(ObjectList, i, d->children) { | ||
465 | iAny *found = findChild_Widget(constAs_Widget(i.object), id); | ||
466 | if (found) return found; | ||
467 | } | ||
468 | return NULL; | ||
469 | } | ||
470 | |||
471 | size_t childCount_Widget(const iWidget *d) { | ||
472 | if (!d->children) return 0; | ||
473 | return size_ObjectList(d->children); | ||
474 | } | ||
475 | |||
476 | iBool isVisible_Widget(const iWidget *d) { | ||
477 | for (const iWidget *w = d; w; w = w->parent) { | ||
478 | if (w->flags & hidden_WidgetFlag) { | ||
479 | return iFalse; | ||
480 | } | ||
481 | } | ||
482 | return iTrue; | ||
483 | } | ||
484 | |||
485 | iBool isDisabled_Widget(const iWidget *d) { | ||
486 | for (const iWidget *w = d; w; w = w->parent) { | ||
487 | if (w->flags & disabled_WidgetFlag) { | ||
488 | return iTrue; | ||
489 | } | ||
490 | } | ||
491 | return iFalse; | ||
492 | } | ||
493 | |||
494 | iBool isFocused_Widget(const iWidget *d) { | ||
495 | return rootData_.focus == d; | ||
496 | } | ||
497 | |||
498 | iBool isHover_Widget(const iWidget *d) { | ||
499 | return rootData_.hover == d; | ||
500 | } | ||
501 | |||
502 | iBool isSelected_Widget(const iWidget *d) { | ||
503 | return (d->flags & selected_WidgetFlag) != 0; | ||
504 | } | ||
505 | |||
506 | iBool isCommand_Widget(const iWidget *d, const SDL_Event *ev, const char *cmd) { | ||
507 | if (isCommand_UserEvent(ev, cmd)) { | ||
508 | const iWidget *src = pointer_Command(command_UserEvent(ev)); | ||
509 | iAssert(!src || strstr(ev->user.data1, " ptr:")); | ||
510 | return src == d || hasParent_Widget(src, d); | ||
511 | } | ||
512 | return iFalse; | ||
513 | } | ||
514 | |||
515 | iBool hasParent_Widget(const iWidget *d, const iWidget *someParent) { | ||
516 | if (d) { | ||
517 | for (const iWidget *w = d->parent; w; w = w->parent) { | ||
518 | if (w == someParent) return iTrue; | ||
519 | } | ||
520 | } | ||
521 | return iFalse; | ||
522 | } | ||
523 | |||
524 | void setFocus_Widget(iWidget *d) { | ||
525 | if (rootData_.focus != d) { | ||
526 | if (rootData_.focus) { | ||
527 | iAssert(!contains_PtrSet(rootData_.pendingDestruction, rootData_.focus)); | ||
528 | postCommand_Widget(rootData_.focus, "focus.lost"); | ||
529 | } | ||
530 | rootData_.focus = d; | ||
531 | if (d) { | ||
532 | iAssert(flags_Widget(d) & focusable_WidgetFlag); | ||
533 | postCommand_Widget(d, "focus.gained"); | ||
534 | } | ||
535 | } | ||
536 | } | ||
537 | |||
538 | iWidget *focus_Widget(void) { | ||
539 | return rootData_.focus; | ||
540 | } | ||
541 | |||
542 | iWidget *hover_Widget(void) { | ||
543 | return rootData_.hover; | ||
544 | } | ||
545 | |||
546 | static const iWidget *findFocusable_Widget_(const iWidget *d, const iWidget *startFrom, | ||
547 | iBool *getNext, enum iWidgetFocusDir focusDir) { | ||
548 | if (startFrom == d) { | ||
549 | *getNext = iTrue; | ||
550 | return NULL; | ||
551 | } | ||
552 | if ((d->flags & focusable_WidgetFlag) && isVisible_Widget(d) && !isDisabled_Widget(d) && | ||
553 | *getNext) { | ||
554 | return d; | ||
555 | } | ||
556 | if (focusDir == forward_WidgetFocusDir) { | ||
557 | iConstForEach(ObjectList, i, d->children) { | ||
558 | const iWidget *found = | ||
559 | findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, focusDir); | ||
560 | if (found) return found; | ||
561 | } | ||
562 | } | ||
563 | else { | ||
564 | iReverseConstForEach(ObjectList, i, d->children) { | ||
565 | const iWidget *found = | ||
566 | findFocusable_Widget_(constAs_Widget(i.object), startFrom, getNext, focusDir); | ||
567 | if (found) return found; | ||
568 | } | ||
569 | } | ||
570 | return NULL; | ||
571 | } | ||
572 | |||
573 | iAny *findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focusDir) { | ||
574 | iWidget *root = get_Window()->root; | ||
575 | iBool getNext = (startFrom ? iFalse : iTrue); | ||
576 | const iWidget *found = findFocusable_Widget_(root, startFrom, &getNext, focusDir); | ||
577 | if (!found && startFrom) { | ||
578 | getNext = iTrue; | ||
579 | found = findFocusable_Widget_(root, NULL, &getNext, focusDir); | ||
580 | } | ||
581 | return iConstCast(iWidget *, found); | ||
582 | } | ||
583 | |||
584 | void setMouseGrab_Widget(iWidget *d) { | ||
585 | if (rootData_.mouseGrab != d) { | ||
586 | rootData_.mouseGrab = d; | ||
587 | SDL_CaptureMouse(d != NULL); | ||
588 | } | ||
589 | } | ||
590 | |||
591 | iWidget *mouseGrab_Widget(void) { | ||
592 | return rootData_.mouseGrab; | ||
593 | } | ||
594 | |||
595 | void postCommand_Widget(const iWidget *d, const char *cmd, ...) { | ||
596 | iString str; | ||
597 | init_String(&str); { | ||
598 | va_list args; | ||
599 | va_start(args, cmd); | ||
600 | vprintf_Block(&str.chars, cmd, args); | ||
601 | va_end(args); | ||
602 | } | ||
603 | iBool isGlobal = iFalse; | ||
604 | if (*cstr_String(&str) == '!') { | ||
605 | isGlobal = iTrue; | ||
606 | remove_Block(&str.chars, 0, 1); | ||
607 | } | ||
608 | if (!isGlobal) { | ||
609 | appendFormat_String(&str, " ptr:%p", d); | ||
610 | } | ||
611 | postCommandString_App(&str); | ||
612 | deinit_String(&str); | ||
613 | } | ||
614 | |||
615 | iBeginDefineClass(Widget) | ||
616 | .processEvent = processEvent_Widget, | ||
617 | .draw = draw_Widget, | ||
618 | iEndDefineClass(Widget) | ||