1/*
2 * Copyright (c) 2010-2017 OTClient <https://github.com/edubart/otclient>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#include "uiwidget.h"
24#include "uimanager.h"
25#include "uianchorlayout.h"
26#include "uitranslator.h"
27
28#include <framework/core/eventdispatcher.h>
29#include <framework/otml/otmlnode.h>
30#include <framework/graphics/graphics.h>
31#include <framework/platform/platformwindow.h>
32#include <framework/graphics/texturemanager.h>
33#include <framework/core/application.h>
34#include <framework/luaengine/luainterface.h>
35#include <framework/util/stats.h>
36#include <framework/util/extras.h>
37#include <framework/input/mouse.h>
38
39UIWidget::UIWidget()
40{
41 // source for stats
42 m_source = g_lua.getSource(2);
43 // find correct source
44 int level = 3;
45 while((m_source.find("corelib") != std::string::npos || m_source.find("gamelib") != std::string::npos
46 || m_source.find("game_bot/functions/ui") != std::string::npos || m_source.find("[C]") != std::string::npos) && level < 8) {
47 std::string tmp_src = g_lua.getSource(level);
48 if (tmp_src.length() <= 3) break;
49 m_source = tmp_src;
50 level += 1;
51 }
52
53 m_lastFocusReason = Fw::ActiveFocusReason;
54 m_states = Fw::DefaultState;
55 m_autoFocusPolicy = Fw::AutoFocusLast;
56 m_clickTimer.stop();
57 m_autoRepeatDelay = 500;
58
59 initBaseStyle();
60 initText();
61 initImage();
62
63 g_stats.addWidget(this);
64}
65
66UIWidget::~UIWidget()
67{
68 VALIDATE(!g_app.isTerminated());
69 if(!m_destroyed)
70 g_logger.warning(stdext::format("widget '%s' was not explicitly destroyed", m_id));
71
72 g_stats.removeWidget(this);
73}
74
75void UIWidget::draw(const Rect& visibleRect, Fw::DrawPane drawPane)
76{
77 size_t drawQueueStart = g_drawQueue->size();
78
79 drawSelf(drawPane);
80 if (m_clipping) {
81 g_drawQueue->setClip(drawQueueStart, visibleRect);
82 }
83
84 if(m_children.size() > 0) {
85 size_t drawQueueChildStart = g_drawQueue->size();
86 drawChildren(visibleRect, drawPane);
87 if (m_clipping) {
88 g_drawQueue->setClip(drawQueueChildStart, visibleRect.intersection(getPaddingRect()));
89 }
90 }
91
92 if (getOpacity() < 0.99f) {
93 g_drawQueue->setOpacity(drawQueueStart, getOpacity());
94 }
95
96 if (m_rotation < -0.1f || m_rotation > 0.1f) {
97 g_drawQueue->setRotation(drawQueueStart, m_rect.center(), m_rotation * (Fw::pi / 180.0));
98 }
99}
100
101void UIWidget::drawSelf(Fw::DrawPane drawPane)
102{
103 if(drawPane != Fw::ForegroundPane)
104 return;
105
106 // draw style components in order
107 if(m_backgroundColor.aF() > Fw::MIN_ALPHA) {
108 Rect backgroundDestRect = m_rect;
109 backgroundDestRect.expand(-m_borderWidth.top, -m_borderWidth.right, -m_borderWidth.bottom, -m_borderWidth.left);
110 drawBackground(m_rect);
111 }
112
113 drawImage(m_rect);
114 drawIcon(m_rect);
115 drawText(m_rect);
116 drawBorder(m_rect);
117}
118
119void UIWidget::drawChildren(const Rect& visibleRect, Fw::DrawPane drawPane)
120{
121 // draw children
122 for(const UIWidgetPtr& child : m_children) {
123 if (!child->isAutoDraw())
124 continue;
125
126 // render only visible children with a valid rect inside parent rect
127 if(!child->isExplicitlyVisible() || !child->getRect().isValid() || child->getOpacity() < Fw::MIN_ALPHA)
128 continue;
129
130 Rect childVisibleRect = visibleRect.intersection(child->getRect());
131 if(!childVisibleRect.isValid())
132 continue;
133
134 child->draw(childVisibleRect, drawPane);
135
136 // debug draw box
137 if(g_ui.isDrawingDebugBoxes() && drawPane == Fw::ForegroundPane) {
138 g_drawQueue->addBoundingRect(child->getRect(), 1, Color::green);
139 if (m_font) {
140 m_font->drawText(child->getId(), child->getPosition() + Point(2, 0), Color::red);
141 }
142 }
143 }
144}
145
146void UIWidget::addChild(const UIWidgetPtr& child)
147{
148 if(!child) {
149 g_logger.traceWarning("attempt to add a null child into a UIWidget");
150 return;
151 }
152
153 if(child->isDestroyed()) {
154 g_logger.traceWarning("attemp to add a destroyed child into a UIWidget");
155 return;
156 }
157
158 if(hasChild(child)) {
159 g_logger.traceWarning("attempt to add a child again into a UIWidget");
160 return;
161 }
162
163 UIWidgetPtr oldLastChild = getLastChild();
164
165 m_children.push_back(child);
166 child->setParent(static_self_cast<UIWidget>());
167
168 // otml extension
169 std::string widgetId = child->getId();
170 if (!widgetId.empty()) {
171 if (!hasLuaField(widgetId)) {
172 setLuaField(widgetId, child);
173 m_childrenShortcuts[child] = widgetId;
174 }
175 }
176
177 // create default layout
178 if(!m_layout)
179 m_layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast<UIWidget>()));
180
181 // add to layout and updates it
182 m_layout->addWidget(child);
183
184 // update new child states
185 child->updateStates();
186
187 // update old child index states
188 if(oldLastChild) {
189 oldLastChild->updateState(Fw::MiddleState);
190 oldLastChild->updateState(Fw::LastState);
191 }
192
193 g_ui.onWidgetAppear(child);
194}
195
196void UIWidget::onChildIdChange(const UIWidgetPtr& child)
197{
198 if (!hasChild(child)) {
199 g_logger.traceWarning("onChildIdChange: invalid child");
200 return;
201 }
202
203 // update shortcut
204 auto shortcut = m_childrenShortcuts.find(child);
205 if (shortcut != m_childrenShortcuts.end()) {
206 setLuaField(shortcut->second, nullptr);
207 m_childrenShortcuts.erase(shortcut);
208 }
209
210 std::string widgetId = child->getId();
211 if (!widgetId.empty()) {
212 if (!hasLuaField(widgetId)) {
213 setLuaField(widgetId, child);
214 m_childrenShortcuts[child] = widgetId;
215 }
216 }
217}
218
219void UIWidget::insertChild(int index, const UIWidgetPtr& child)
220{
221 if(!child) {
222 g_logger.traceWarning("attempt to insert a null child into a UIWidget");
223 return;
224 }
225
226 if(hasChild(child)) {
227 g_logger.traceWarning("attempt to insert a child again into a UIWidget");
228 return;
229 }
230
231 index = index <= 0 ? (m_children.size() + index) : index-1;
232
233 if(!(index >= 0 && (uint)index <= m_children.size())) {
234 //g_logger.traceWarning("attempt to insert a child UIWidget into an invalid index, using nearest index...");
235 index = stdext::clamp<int>(index, 0, (int)m_children.size());
236 }
237
238 // retrieve child by index
239 auto it = m_children.begin() + index;
240 m_children.insert(it, child);
241 child->setParent(static_self_cast<UIWidget>());
242
243 // create default layout if needed
244 if(!m_layout)
245 m_layout = UIAnchorLayoutPtr(new UIAnchorLayout(static_self_cast<UIWidget>()));
246
247 // add to layout and updates it
248 m_layout->addWidget(child);
249
250 // update new child states
251 child->updateStates();
252 updateChildrenIndexStates();
253
254 g_ui.onWidgetAppear(child);
255}
256
257void UIWidget::removeChild(UIWidgetPtr child)
258{
259 // remove from children list
260 if(hasChild(child)) {
261 // defocus if needed
262 bool focusAnother = false;
263 if(m_focusedChild == child) {
264 focusChild(nullptr, Fw::ActiveFocusReason);
265 focusAnother = true;
266 }
267
268 if(isChildLocked(child))
269 unlockChild(child);
270
271 auto it = std::find(m_children.begin(), m_children.end(), child);
272 m_children.erase(it);
273
274 auto shortcut = m_childrenShortcuts.find(child);
275 if (shortcut != m_childrenShortcuts.end()) {
276 setLuaField(shortcut->second, nullptr);
277 m_childrenShortcuts.erase(shortcut);
278 }
279
280 // reset child parent
281 VALIDATE(child->getParent() == static_self_cast<UIWidget>());
282 child->setParent(nullptr);
283
284 m_layout->removeWidget(child);
285
286 // update child states
287 child->updateStates();
288 updateChildrenIndexStates();
289
290 if(m_autoFocusPolicy != Fw::AutoFocusNone && focusAnother && !m_focusedChild)
291 focusPreviousChild(Fw::ActiveFocusReason, true);
292
293 g_ui.onWidgetDisappear(child);
294 } else
295 g_logger.traceError("attempt to remove an unknown child from a UIWidget");
296}
297
298
299void UIWidget::focusChild(const UIWidgetPtr& child, Fw::FocusReason reason)
300{
301 if(m_destroyed)
302 return;
303
304 if(child == m_focusedChild)
305 return;
306
307 if(child && !hasChild(child)) {
308 g_logger.error("attempt to focus an unknown child in a UIWidget");
309 return;
310 }
311
312 UIWidgetPtr oldFocused = m_focusedChild;
313 m_focusedChild = child;
314
315 if(child) {
316 child->setLastFocusReason(reason);
317 child->updateState(Fw::FocusState);
318 child->updateState(Fw::ActiveState);
319
320 child->onFocusChange(true, reason);
321 }
322
323 if(oldFocused) {
324 oldFocused->setLastFocusReason(reason);
325 oldFocused->updateState(Fw::FocusState);
326 oldFocused->updateState(Fw::ActiveState);
327
328 oldFocused->onFocusChange(false, reason);
329 }
330
331 onChildFocusChange(child, oldFocused, reason);
332}
333
334void UIWidget::focusNextChild(Fw::FocusReason reason, bool rotate)
335{
336 if(m_destroyed)
337 return;
338
339 UIWidgetPtr toFocus;
340
341 if(rotate) {
342 UIWidgetList rotatedChildren(m_children);
343
344 if(m_focusedChild) {
345 auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild);
346 if(focusedIt != rotatedChildren.end()) {
347 std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end());
348 rotatedChildren.pop_front();
349 }
350 }
351
352 // finds next child to focus
353 for(const UIWidgetPtr& child : rotatedChildren) {
354 if(child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) {
355 toFocus = child;
356 break;
357 }
358 }
359 } else {
360 auto it = m_children.begin();
361 if(m_focusedChild)
362 it = std::find(m_children.begin(), m_children.end(), m_focusedChild);
363
364 for(; it != m_children.end(); ++it) {
365 const UIWidgetPtr& child = *it;
366 if(child != m_focusedChild && child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) {
367 toFocus = child;
368 break;
369 }
370 }
371 }
372
373 if(toFocus && toFocus != m_focusedChild)
374 focusChild(toFocus, reason);
375}
376
377void UIWidget::focusPreviousChild(Fw::FocusReason reason, bool rotate)
378{
379 if(m_destroyed)
380 return;
381
382 UIWidgetPtr toFocus;
383 if(rotate) {
384 UIWidgetList rotatedChildren(m_children);
385 std::reverse(rotatedChildren.begin(), rotatedChildren.end());
386
387 if(m_focusedChild) {
388 auto focusedIt = std::find(rotatedChildren.begin(), rotatedChildren.end(), m_focusedChild);
389 if(focusedIt != rotatedChildren.end()) {
390 std::rotate(rotatedChildren.begin(), focusedIt, rotatedChildren.end());
391 rotatedChildren.pop_front();
392 }
393 }
394
395 // finds next child to focus
396 for(const UIWidgetPtr& child : rotatedChildren) {
397 if(child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) {
398 toFocus = child;
399 break;
400 }
401 }
402 } else {
403 auto it = m_children.rbegin();
404 if(m_focusedChild)
405 it = std::find(m_children.rbegin(), m_children.rend(), m_focusedChild);
406
407 for(; it != m_children.rend(); ++it) {
408 const UIWidgetPtr& child = *it;
409 if(child != m_focusedChild && child->isFocusable() && child->isExplicitlyEnabled() && child->isVisible()) {
410 toFocus = child;
411 break;
412 }
413 }
414 }
415
416 if(toFocus && toFocus != m_focusedChild)
417 focusChild(toFocus, reason);
418}
419
420void UIWidget::lowerChild(UIWidgetPtr child)
421{
422 if(m_destroyed)
423 return;
424
425 if(!child)
426 return;
427
428 // remove and push child again
429 auto it = std::find(m_children.begin(), m_children.end(), child);
430 if(it == m_children.end()) {
431 g_logger.traceError("cannot find child");
432 return;
433 }
434
435 m_children.erase(it);
436 m_children.push_front(child);
437 updateChildrenIndexStates();
438}
439
440void UIWidget::raiseChild(UIWidgetPtr child)
441{
442 if(m_destroyed)
443 return;
444
445 if(!child)
446 return;
447
448 // remove and push child again
449 auto it = std::find(m_children.begin(), m_children.end(), child);
450 if(it == m_children.end()) {
451 g_logger.traceError("cannot find child");
452 return;
453 }
454 m_children.erase(it);
455 m_children.push_back(child);
456 updateChildrenIndexStates();
457}
458
459void UIWidget::moveChildToIndex(const UIWidgetPtr& child, int index)
460{
461 if(m_destroyed)
462 return;
463
464 if(!child)
465 return;
466
467 if((uint)index - 1 >= m_children.size()) {
468 g_logger.traceError(stdext::format("moving %s to index %d on %s", child->getId(), index, m_id));
469 return;
470 }
471
472 // remove and push child again
473 auto it = std::find(m_children.begin(), m_children.end(), child);
474 if(it == m_children.end()) {
475 g_logger.traceError("cannot find child");
476 return;
477 }
478 m_children.erase(it);
479 if (index >= (int)m_children.size() + 1) {
480 m_children.push_back(child);
481 } else {
482 m_children.insert(m_children.begin() + index - 1, child);
483 }
484
485 updateChildrenIndexStates();
486 updateLayout();
487}
488
489void UIWidget::reorderChildren(const std::vector<UIWidgetPtr>& childrens) {
490 if (m_children.size() != childrens.size()) {
491 g_logger.error("Invalid parameter for reorderChildren");
492 return;
493 }
494
495 m_children.clear();
496 for (size_t i = 0; i < childrens.size(); ++i) {
497 m_children.push_back(childrens[i]);
498 }
499
500 updateChildrenIndexStates();
501 updateLayout();
502}
503
504void UIWidget::lockChild(const UIWidgetPtr& child)
505{
506 if(m_destroyed)
507 return;
508
509 if(!child)
510 return;
511
512 if(!hasChild(child)) {
513 g_logger.traceError("cannot find child");
514 return;
515 }
516
517 // prevent double locks
518 if(isChildLocked(child))
519 unlockChild(child);
520
521 // disable all other children
522 for(const UIWidgetPtr& otherChild : m_children) {
523 if(otherChild == child)
524 child->setEnabled(true);
525 else
526 otherChild->setEnabled(false);
527 }
528
529 m_lockedChildren.push_front(child);
530
531 // lock child focus
532 if(child->isFocusable())
533 focusChild(child, Fw::ActiveFocusReason);
534}
535
536void UIWidget::unlockChild(const UIWidgetPtr& child)
537{
538 if(m_destroyed)
539 return;
540
541 if(!child)
542 return;
543
544 if(!hasChild(child)) {
545 g_logger.traceError("cannot find child");
546 return;
547 }
548
549 auto it = std::find(m_lockedChildren.begin(), m_lockedChildren.end(), child);
550 if(it == m_lockedChildren.end())
551 return;
552
553 m_lockedChildren.erase(it);
554
555 // find new child to lock
556 UIWidgetPtr lockedChild;
557 if(m_lockedChildren.size() > 0) {
558 lockedChild = m_lockedChildren.front();
559 VALIDATE(hasChild(lockedChild));
560 }
561
562 for(const UIWidgetPtr& otherChild : m_children) {
563 // lock new child
564 if(lockedChild) {
565 if(otherChild == lockedChild)
566 lockedChild->setEnabled(true);
567 else
568 otherChild->setEnabled(false);
569 }
570 // else unlock all
571 else
572 otherChild->setEnabled(true);
573 }
574
575 if(lockedChild) {
576 if(lockedChild->isFocusable())
577 focusChild(lockedChild, Fw::ActiveFocusReason);
578 }
579}
580
581void UIWidget::mergeStyle(const OTMLNodePtr& styleNode)
582{
583 applyStyle(styleNode);
584 std::string name = m_style->tag();
585 std::string source = m_style->source();
586 m_style->merge(styleNode);
587 m_style->setTag(name);
588 m_style->setSource(source);
589 updateStyle();
590}
591
592void UIWidget::applyStyle(const OTMLNodePtr& styleNode)
593{
594 if(m_destroyed)
595 return;
596
597 if(styleNode->size() == 0)
598 return;
599
600 m_loadingStyle = true;
601 try {
602 // translate ! style tags
603 for(const OTMLNodePtr& node : styleNode->children()) {
604 if(node->tag()[0] == '!') {
605 std::string tag = node->tag().substr(1);
606 std::string code = stdext::format("tostring(%s)", node->value());
607 std::string origin = std::string("@") + node->source() + ": [" + node->tag() + "]";
608 g_lua.evaluateExpression(code, origin);
609 std::string value = g_lua.popString();
610
611 node->setTag(tag);
612 node->setValue(value);
613 }
614 }
615
616 onStyleApply(styleNode->tag(), styleNode);
617 callLuaField("onStyleApply", styleNode->tag(), styleNode);
618
619 if(m_firstOnStyle) {
620 UIWidgetPtr parent = getParent();
621 if(isFocusable() && isExplicitlyVisible() && isExplicitlyEnabled() &&
622 parent && ((!parent->getFocusedChild() && parent->getAutoFocusPolicy() == Fw::AutoFocusFirst) ||
623 parent->getAutoFocusPolicy() == Fw::AutoFocusLast)) {
624 focus();
625 }
626 }
627
628 m_firstOnStyle = false;
629 } catch(stdext::exception& e) {
630 g_logger.traceError(stdext::format("failed to apply style to widget '%s': %s", m_id, e.what()));
631 }
632 m_loadingStyle = false;
633}
634
635void UIWidget::addAnchor(Fw::AnchorEdge anchoredEdge, const std::string& hookedWidgetId, Fw::AnchorEdge hookedEdge)
636{
637 if(m_destroyed)
638 return;
639
640 if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout())
641 anchorLayout->addAnchor(static_self_cast<UIWidget>(), anchoredEdge, hookedWidgetId, hookedEdge);
642 else
643 g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id));
644}
645
646void UIWidget::removeAnchor(Fw::AnchorEdge anchoredEdge)
647{
648 addAnchor(anchoredEdge, "none", Fw::AnchorNone);
649}
650
651void UIWidget::centerIn(const std::string& hookedWidgetId)
652{
653 if(m_destroyed)
654 return;
655
656 if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) {
657 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorHorizontalCenter, hookedWidgetId, Fw::AnchorHorizontalCenter);
658 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorVerticalCenter, hookedWidgetId, Fw::AnchorVerticalCenter);
659 } else
660 g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id));
661}
662
663void UIWidget::fill(const std::string& hookedWidgetId)
664{
665 if(m_destroyed)
666 return;
667
668 if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout()) {
669 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorLeft, hookedWidgetId, Fw::AnchorLeft);
670 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorRight, hookedWidgetId, Fw::AnchorRight);
671 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorTop, hookedWidgetId, Fw::AnchorTop);
672 anchorLayout->addAnchor(static_self_cast<UIWidget>(), Fw::AnchorBottom, hookedWidgetId, Fw::AnchorBottom);
673 } else
674 g_logger.traceError(stdext::format("cannot add anchors to widget '%s': the parent doesn't use anchors layout", m_id));
675}
676
677void UIWidget::breakAnchors()
678{
679 if(m_destroyed)
680 return;
681
682 if(UIAnchorLayoutPtr anchorLayout = getAnchoredLayout())
683 anchorLayout->removeAnchors(static_self_cast<UIWidget>());
684}
685
686void UIWidget::updateParentLayout()
687{
688 if(m_destroyed)
689 return;
690
691 if(UIWidgetPtr parent = getParent())
692 parent->updateLayout();
693 else
694 updateLayout();
695}
696
697void UIWidget::updateLayout()
698{
699 if(m_destroyed)
700 return;
701
702 if(m_layout)
703 m_layout->update();
704
705 // children can affect the parent layout
706 if(UIWidgetPtr parent = getParent())
707 if(UILayoutPtr parentLayout = parent->getLayout())
708 parentLayout->updateLater();
709}
710
711void UIWidget::lock()
712{
713 if(m_destroyed)
714 return;
715
716 if(UIWidgetPtr parent = getParent())
717 parent->lockChild(static_self_cast<UIWidget>());
718}
719
720void UIWidget::unlock()
721{
722 if(m_destroyed)
723 return;
724
725 if(UIWidgetPtr parent = getParent())
726 parent->unlockChild(static_self_cast<UIWidget>());
727}
728
729void UIWidget::focus()
730{
731 if(m_destroyed)
732 return;
733
734 if(!m_focusable)
735 return;
736
737 if(UIWidgetPtr parent = getParent())
738 parent->focusChild(static_self_cast<UIWidget>(), Fw::ActiveFocusReason);
739}
740
741void UIWidget::recursiveFocus(Fw::FocusReason reason)
742{
743 if(m_destroyed)
744 return;
745
746 if(UIWidgetPtr parent = getParent()) {
747 if(m_focusable)
748 parent->focusChild(static_self_cast<UIWidget>(), reason);
749 parent->recursiveFocus(reason);
750 }
751}
752
753void UIWidget::lower()
754{
755 if(m_destroyed)
756 return;
757
758 UIWidgetPtr parent = getParent();
759 if(parent)
760 parent->lowerChild(static_self_cast<UIWidget>());
761}
762
763void UIWidget::raise()
764{
765 if(m_destroyed)
766 return;
767
768 UIWidgetPtr parent = getParent();
769 if(parent)
770 parent->raiseChild(static_self_cast<UIWidget>());
771}
772
773void UIWidget::grabMouse()
774{
775 if(m_destroyed)
776 return;
777
778 g_ui.setMouseReceiver(static_self_cast<UIWidget>());
779}
780
781void UIWidget::ungrabMouse()
782{
783 if(g_ui.getMouseReceiver() == static_self_cast<UIWidget>())
784 g_ui.resetMouseReceiver();
785}
786
787void UIWidget::grabKeyboard()
788{
789 if(m_destroyed)
790 return;
791
792 g_ui.setKeyboardReceiver(static_self_cast<UIWidget>());
793}
794
795void UIWidget::ungrabKeyboard()
796{
797 if(g_ui.getKeyboardReceiver() == static_self_cast<UIWidget>())
798 g_ui.resetKeyboardReceiver();
799}
800
801void UIWidget::bindRectToParent()
802{
803 if(m_destroyed)
804 return;
805
806 Rect boundRect = m_rect;
807 UIWidgetPtr parent = getParent();
808 if(parent) {
809 Rect parentRect = parent->getPaddingRect();
810 boundRect.bind(parentRect);
811 }
812
813 setRect(boundRect);
814}
815
816void UIWidget::internalDestroy()
817{
818 if (!getText().empty()) {
819 setText("", true);
820 }
821
822 m_destroyed = true;
823 m_visible = false;
824 m_enabled = false;
825 m_focusedChild = nullptr;
826 if(m_layout) {
827 m_layout->setParent(nullptr);
828 m_layout = nullptr;
829 }
830 m_parent = nullptr;
831 m_lockedChildren.clear();
832
833 for(const UIWidgetPtr& child : m_children)
834 child->internalDestroy();
835 m_children.clear();
836
837 callLuaField("onDestroy");
838
839 releaseLuaFieldsTable();
840
841 g_ui.onWidgetDestroy(static_self_cast<UIWidget>());
842}
843
844void UIWidget::destroy()
845{
846 if(m_destroyed)
847 g_logger.warning(stdext::format("attempt to destroy widget '%s' two times", m_id));
848
849 // hold itself reference
850 UIWidgetPtr self = static_self_cast<UIWidget>();
851 m_destroyed = true;
852
853 // remove itself from parent
854 if(UIWidgetPtr parent = getParent())
855 parent->removeChild(self);
856 internalDestroy();
857}
858
859void UIWidget::destroyChildren()
860{
861 UILayoutPtr layout = getLayout();
862 if(layout)
863 layout->disableUpdates();
864
865 for(auto& field : m_childrenShortcuts) {
866 setLuaField(field.second, nullptr);
867 }
868 m_childrenShortcuts.clear();
869
870 m_focusedChild = nullptr;
871 m_lockedChildren.clear();
872 while (!m_children.empty()) {
873 UIWidgetPtr child = m_children.front();
874 m_children.pop_front();
875 child->setParent(nullptr);
876 m_layout->removeWidget(child);
877 child->destroy();
878 }
879
880 if(layout)
881 layout->enableUpdates();
882}
883
884void UIWidget::setId(const std::string& id)
885{
886 if(id != m_id) {
887 m_id = id;
888 callLuaField("onIdChange", id);
889 if (m_parent) {
890 m_parent->onChildIdChange(static_self_cast<UIWidget>());
891 }
892 }
893}
894
895void UIWidget::setParent(const UIWidgetPtr& parent)
896{
897 // remove from old parent
898 UIWidgetPtr oldParent = getParent();
899
900 // the parent is already the same
901 if(oldParent == parent)
902 return;
903
904 UIWidgetPtr self = static_self_cast<UIWidget>();
905 if(oldParent && oldParent->hasChild(self))
906 oldParent->removeChild(self);
907
908 // reset parent
909 m_parent.reset();
910
911 // set new parent
912 if(parent) {
913 m_parent = parent;
914 m_parentId = parent->getId();
915
916 // add to parent if needed
917 if(!parent->hasChild(self))
918 parent->addChild(self);
919 }
920}
921
922void UIWidget::setLayout(const UILayoutPtr& layout)
923{
924 if(!layout)
925 stdext::throw_exception("attempt to set a nil layout to a widget");
926
927 if(m_layout)
928 m_layout->disableUpdates();
929
930 layout->setParent(static_self_cast<UIWidget>());
931 layout->disableUpdates();
932
933 for(const UIWidgetPtr& child : m_children) {
934 if(m_layout)
935 m_layout->removeWidget(child);
936 layout->addWidget(child);
937 }
938
939 if(m_layout) {
940 m_layout->enableUpdates();
941 m_layout->setParent(nullptr);
942 m_layout->update();
943 }
944
945 layout->enableUpdates();
946 m_layout = layout;
947}
948
949bool UIWidget::setRect(const Rect& rect)
950{
951 /*
952 if(rect.width() > 8192 || rect.height() > 8192) {
953 g_logger.error(stdext::format("attempt to set huge rect size (%s) for %s", stdext::to_string(rect), m_id));
954 return false;
955 }
956 */
957 // only update if the rect really changed
958 Rect oldRect = m_rect;
959 if(rect == oldRect)
960 return false;
961
962 m_rect = rect;
963
964 // updates own layout
965 updateLayout();
966
967 // avoid massive update events
968 if(!m_updateEventScheduled) {
969 UIWidgetPtr self = static_self_cast<UIWidget>();
970 g_dispatcher.addEvent([self, oldRect]() {
971 self->m_updateEventScheduled = false;
972 if(oldRect != self->getRect())
973 self->onGeometryChange(oldRect, self->getRect());
974 });
975 m_updateEventScheduled = true;
976 }
977
978 // update hovered widget when moved behind mouse area
979 if(containsPoint(g_window.getMousePosition()))
980 g_ui.updateHoveredWidget();
981
982 return true;
983}
984
985void UIWidget::setStyle(const std::string& styleName)
986{
987 OTMLNodePtr styleNode = g_ui.getStyle(styleName);
988 if(!styleNode) {
989 g_logger.traceError(stdext::format("unable to retrieve style '%s': not a defined style", styleName));
990 return;
991 }
992 styleNode = styleNode->clone();
993 applyStyle(styleNode);
994 m_style = styleNode;
995 updateStyle();
996}
997
998void UIWidget::setStyleFromNode(const OTMLNodePtr& styleNode)
999{
1000 applyStyle(styleNode);
1001 m_style = styleNode;
1002 updateStyle();
1003}
1004
1005void UIWidget::setEnabled(bool enabled)
1006{
1007 if(enabled != m_enabled) {
1008 m_enabled = enabled;
1009
1010 updateState(Fw::DisabledState);
1011 updateState(Fw::ActiveState);
1012 }
1013}
1014
1015void UIWidget::setVisible(bool visible)
1016{
1017 if (m_visible != visible) {
1018 m_visible = visible;
1019
1020 // hiding a widget make it lose focus
1021 if (!visible && isFocused()) {
1022 if (UIWidgetPtr parent = getParent())
1023 parent->focusPreviousChild(Fw::ActiveFocusReason, true);
1024 }
1025
1026 // visibility can change change parent layout
1027 updateParentLayout();
1028
1029 updateState(Fw::ActiveState);
1030 updateState(Fw::HiddenState);
1031
1032 // visibility can change the current hovered widget
1033 if (visible)
1034 g_ui.onWidgetAppear(static_self_cast<UIWidget>());
1035 else
1036 g_ui.onWidgetDisappear(static_self_cast<UIWidget>());
1037 }
1038}
1039
1040void UIWidget::setAutoDraw(bool value)
1041{
1042 m_autoDraw = value;
1043}
1044
1045void UIWidget::setOn(bool on)
1046{
1047 setState(Fw::OnState, on);
1048}
1049
1050void UIWidget::setChecked(bool checked)
1051{
1052 if(setState(Fw::CheckedState, checked))
1053 callLuaField("onCheckChange", checked);
1054}
1055
1056void UIWidget::setFocusable(bool focusable)
1057{
1058 if(m_focusable != focusable) {
1059 m_focusable = focusable;
1060
1061 // make parent focus another child
1062 if(UIWidgetPtr parent = getParent()) {
1063 if(!focusable && isFocused()) {
1064 parent->focusPreviousChild(Fw::ActiveFocusReason, true);
1065 } else if(focusable && !parent->getFocusedChild() && parent->getAutoFocusPolicy() != Fw::AutoFocusNone) {
1066 focus();
1067 }
1068 }
1069 }
1070}
1071
1072void UIWidget::setPhantom(bool phantom)
1073{
1074 m_phantom = phantom;
1075}
1076
1077void UIWidget::setDraggable(bool draggable)
1078{
1079 m_draggable = draggable;
1080}
1081
1082void UIWidget::setFixedSize(bool fixed)
1083{
1084 m_fixedSize = fixed;
1085 updateParentLayout();
1086}
1087
1088void UIWidget::setLastFocusReason(Fw::FocusReason reason)
1089{
1090 m_lastFocusReason = reason;
1091}
1092
1093void UIWidget::setAutoFocusPolicy(Fw::AutoFocusPolicy policy)
1094{
1095 m_autoFocusPolicy = policy;
1096}
1097
1098void UIWidget::setVirtualOffset(const Point& offset)
1099{
1100 m_virtualOffset = offset;
1101 if(m_layout)
1102 m_layout->update();
1103}
1104
1105bool UIWidget::isAnchored()
1106{
1107 if(UIWidgetPtr parent = getParent())
1108 if(UIAnchorLayoutPtr anchorLayout = parent->getAnchoredLayout())
1109 return anchorLayout->hasAnchors(static_self_cast<UIWidget>());
1110 return false;
1111}
1112
1113bool UIWidget::isChildLocked(const UIWidgetPtr& child)
1114{
1115 auto it = std::find(m_lockedChildren.begin(), m_lockedChildren.end(), child);
1116 return it != m_lockedChildren.end();
1117}
1118
1119bool UIWidget::hasChild(const UIWidgetPtr& child)
1120{
1121 auto it = std::find(m_children.begin(), m_children.end(), child);
1122 if(it != m_children.end())
1123 return true;
1124 return false;
1125}
1126
1127int UIWidget::getChildIndex(const UIWidgetPtr& child)
1128{
1129 int index = 1;
1130 for(auto it = m_children.begin(); it != m_children.end(); ++it) {
1131 if(*it == child)
1132 return index;
1133 ++index;
1134 }
1135 return -1;
1136}
1137
1138Rect UIWidget::getPaddingRect()
1139{
1140 Rect rect = m_rect;
1141 rect.expand(-m_padding.top, -m_padding.right, -m_padding.bottom, -m_padding.left);
1142 return rect;
1143}
1144
1145Rect UIWidget::getMarginRect()
1146{
1147 Rect rect = m_rect;
1148 rect.expand(m_margin.top, m_margin.right, m_margin.bottom, m_margin.left);
1149 return rect;
1150}
1151
1152Rect UIWidget::getChildrenRect()
1153{
1154 Rect childrenRect;
1155 for(const UIWidgetPtr& child : m_children) {
1156 if(!child->isExplicitlyVisible() || !child->getRect().isValid())
1157 continue;
1158 Rect marginRect = child->getMarginRect();
1159 if(!childrenRect.isValid())
1160 childrenRect = marginRect;
1161 else
1162 childrenRect = childrenRect.united(marginRect);
1163 }
1164
1165 Rect myClippingRect = getPaddingRect();
1166 if(!childrenRect.isValid())
1167 childrenRect = myClippingRect;
1168 else {
1169 if(childrenRect.width() < myClippingRect.width())
1170 childrenRect.setWidth(myClippingRect.width());
1171 if(childrenRect.height() < myClippingRect.height())
1172 childrenRect.setHeight(myClippingRect.height());
1173 }
1174 return childrenRect;
1175}
1176
1177UIAnchorLayoutPtr UIWidget::getAnchoredLayout()
1178{
1179 UIWidgetPtr parent = getParent();
1180 if(!parent)
1181 return nullptr;
1182
1183 UILayoutPtr layout = parent->getLayout();
1184 if(layout->isUIAnchorLayout())
1185 return layout->static_self_cast<UIAnchorLayout>();
1186 return nullptr;
1187}
1188
1189UIWidgetPtr UIWidget::getRootParent()
1190{
1191 if(UIWidgetPtr parent = getParent())
1192 return parent->getRootParent();
1193 else
1194 return static_self_cast<UIWidget>();
1195}
1196
1197UIWidgetPtr UIWidget::getChildAfter(const UIWidgetPtr& relativeChild)
1198{
1199 auto it = std::find(m_children.begin(), m_children.end(), relativeChild);
1200 if(it != m_children.end() && ++it != m_children.end())
1201 return *it;
1202 return nullptr;
1203}
1204
1205UIWidgetPtr UIWidget::getChildBefore(const UIWidgetPtr& relativeChild)
1206{
1207 auto it = std::find(m_children.rbegin(), m_children.rend(), relativeChild);
1208 if(it != m_children.rend() && ++it != m_children.rend())
1209 return *it;
1210 return nullptr;
1211}
1212
1213UIWidgetPtr UIWidget::getChildById(const std::string& childId)
1214{
1215 for(const UIWidgetPtr& child : m_children) {
1216 if(child->getId() == childId)
1217 return child;
1218 }
1219 return nullptr;
1220}
1221
1222UIWidgetPtr UIWidget::getChildByPos(const Point& childPos)
1223{
1224 if(!containsPaddingPoint(childPos))
1225 return nullptr;
1226
1227 for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
1228 const UIWidgetPtr& child = (*it);
1229 if(child->isExplicitlyVisible() && child->containsPoint(childPos))
1230 return child;
1231 }
1232
1233 return nullptr;
1234}
1235
1236UIWidgetPtr UIWidget::getChildByIndex(int index)
1237{
1238 index = index <= 0 ? (m_children.size() + index) : index-1;
1239 if(index >= 0 && (uint)index < m_children.size())
1240 return m_children.at(index);
1241 return nullptr;
1242}
1243
1244UIWidgetPtr UIWidget::recursiveGetChildById(const std::string& id)
1245{
1246 UIWidgetPtr widget = getChildById(id);
1247 if(!widget) {
1248 for(const UIWidgetPtr& child : m_children) {
1249 widget = child->recursiveGetChildById(id);
1250 if(widget)
1251 break;
1252 }
1253 }
1254 return widget;
1255}
1256
1257UIWidgetPtr UIWidget::recursiveGetChildByPos(const Point& childPos, bool wantsPhantom)
1258{
1259 if(!containsPaddingPoint(childPos))
1260 return nullptr;
1261
1262 for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
1263 const UIWidgetPtr& child = (*it);
1264 if(child->isExplicitlyVisible() && child->containsPoint(childPos)) {
1265 UIWidgetPtr subChild = child->recursiveGetChildByPos(childPos, wantsPhantom);
1266 if(subChild)
1267 return subChild;
1268 else if(wantsPhantom || !child->isPhantom())
1269 return child;
1270 }
1271 }
1272 return nullptr;
1273}
1274
1275UIWidgetList UIWidget::recursiveGetChildren()
1276{
1277 UIWidgetList children;
1278 for(const UIWidgetPtr& child : m_children) {
1279 UIWidgetList subChildren = child->recursiveGetChildren();
1280 if(!subChildren.empty())
1281 children.insert(children.end(), subChildren.begin(), subChildren.end());
1282 children.push_back(child);
1283 }
1284 return children;
1285}
1286
1287UIWidgetList UIWidget::recursiveGetChildrenByPos(const Point& childPos)
1288{
1289 UIWidgetList children;
1290 if(!containsPaddingPoint(childPos))
1291 return children;
1292
1293 for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
1294 const UIWidgetPtr& child = (*it);
1295 if(child->isExplicitlyVisible() && child->containsPoint(childPos)) {
1296 UIWidgetList subChildren = child->recursiveGetChildrenByPos(childPos);
1297 if(!subChildren.empty())
1298 children.insert(children.end(), subChildren.begin(), subChildren.end());
1299 children.push_back(child);
1300 }
1301 }
1302 return children;
1303}
1304
1305UIWidgetList UIWidget::recursiveGetChildrenByMarginPos(const Point& childPos)
1306{
1307 UIWidgetList children;
1308 if(!containsPaddingPoint(childPos))
1309 return children;
1310
1311 for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
1312 const UIWidgetPtr& child = (*it);
1313 if(child->isExplicitlyVisible() && child->containsMarginPoint(childPos)) {
1314 UIWidgetList subChildren = child->recursiveGetChildrenByMarginPos(childPos);
1315 if(!subChildren.empty())
1316 children.insert(children.end(), subChildren.begin(), subChildren.end());
1317 children.push_back(child);
1318 }
1319 }
1320 return children;
1321}
1322
1323UIWidgetPtr UIWidget::backwardsGetWidgetById(const std::string& id)
1324{
1325 UIWidgetPtr widget = getChildById(id);
1326 if(!widget) {
1327 if(UIWidgetPtr parent = getParent())
1328 widget = parent->backwardsGetWidgetById(id);
1329 }
1330 return widget;
1331}
1332
1333bool UIWidget::setState(Fw::WidgetState state, bool on)
1334{
1335 if(state == Fw::InvalidState)
1336 return false;
1337
1338 int oldStates = m_states;
1339 if(on)
1340 m_states |= state;
1341 else
1342 m_states &= ~state;
1343
1344 if(oldStates != m_states) {
1345 updateStyle();
1346 return true;
1347 }
1348 return false;
1349}
1350
1351bool UIWidget::hasState(Fw::WidgetState state)
1352{
1353 if(state == Fw::InvalidState)
1354 return false;
1355 return (m_states & state);
1356}
1357
1358void UIWidget::updateState(Fw::WidgetState state)
1359{
1360 if(m_destroyed)
1361 return;
1362
1363 bool newStatus = true;
1364 bool oldStatus = hasState(state);
1365 bool updateChildren = false;
1366
1367 switch(state) {
1368 case Fw::ActiveState: {
1369 UIWidgetPtr widget = static_self_cast<UIWidget>();
1370 UIWidgetPtr parent;
1371 do {
1372 parent = widget->getParent();
1373 if(!widget->isExplicitlyEnabled() ||
1374 ((parent && parent->getFocusedChild() != widget))) {
1375 newStatus = false;
1376 break;
1377 }
1378 } while((widget = parent));
1379
1380 updateChildren = newStatus != oldStatus;
1381 break;
1382 }
1383 case Fw::FocusState: {
1384 newStatus = (getParent() && getParent()->getFocusedChild() == static_self_cast<UIWidget>());
1385 break;
1386 }
1387 case Fw::HoverState: {
1388 newStatus = (g_ui.getHoveredWidget() == static_self_cast<UIWidget>() && isEnabled());
1389 break;
1390 }
1391 case Fw::PressedState: {
1392 newStatus = (g_ui.getPressedWidget() == static_self_cast<UIWidget>());
1393 break;
1394 }
1395 case Fw::DraggingState: {
1396 newStatus = (g_ui.getDraggingWidget() == static_self_cast<UIWidget>());
1397 break;
1398 }
1399 case Fw::DisabledState: {
1400 bool enabled = true;
1401 UIWidgetPtr widget = static_self_cast<UIWidget>();
1402 do {
1403 if(!widget->isExplicitlyEnabled()) {
1404 enabled = false;
1405 break;
1406 }
1407 } while((widget = widget->getParent()));
1408 newStatus = !enabled;
1409 updateChildren = newStatus != oldStatus;
1410 break;
1411 }
1412 case Fw::FirstState: {
1413 newStatus = (getParent() && getParent()->getFirstChild() == static_self_cast<UIWidget>());
1414 break;
1415 }
1416 case Fw::MiddleState: {
1417 newStatus = (getParent() && getParent()->getFirstChild() != static_self_cast<UIWidget>() && getParent()->getLastChild() != static_self_cast<UIWidget>());
1418 break;
1419 }
1420 case Fw::LastState: {
1421 newStatus = (getParent() && getParent()->getLastChild() == static_self_cast<UIWidget>());
1422 break;
1423 }
1424 case Fw::AlternateState: {
1425 newStatus = (getParent() && (getParent()->getChildIndex(static_self_cast<UIWidget>()) % 2) == 1);
1426 break;
1427 }
1428 case Fw::HiddenState: {
1429 bool visible = true;
1430 UIWidgetPtr widget = static_self_cast<UIWidget>();
1431 do {
1432 if(!widget->isExplicitlyVisible()) {
1433 visible = false;
1434 break;
1435 }
1436 } while((widget = widget->getParent()));
1437 newStatus = !visible;
1438 updateChildren = newStatus != oldStatus;
1439 break;
1440 }
1441 case Fw::MobileState:
1442 {
1443 newStatus = g_app.isMobile();
1444 break;
1445 }
1446 default:
1447 return;
1448 }
1449
1450 if(updateChildren) {
1451 // do a backup of children list, because it may change while looping it
1452 UIWidgetList children = m_children;
1453 for(const UIWidgetPtr& child : children)
1454 child->updateState(state);
1455 }
1456
1457 if(setState(state, newStatus)) {
1458 // disabled widgets cannot have hover state
1459 if(state == Fw::DisabledState && !newStatus && isHovered()) {
1460 g_ui.updateHoveredWidget();
1461 } else if(state == Fw::HiddenState) {
1462 onVisibilityChange(!newStatus);
1463 }
1464 }
1465}
1466
1467void UIWidget::updateStates()
1468{
1469 if(m_destroyed)
1470 return;
1471
1472 for(int state = 1; state != Fw::LastWidgetState; state <<= 1)
1473 updateState((Fw::WidgetState)state);
1474}
1475
1476void UIWidget::updateChildrenIndexStates()
1477{
1478 if(m_destroyed)
1479 return;
1480
1481 for(const UIWidgetPtr& child : m_children) {
1482 child->updateState(Fw::FirstState);
1483 child->updateState(Fw::MiddleState);
1484 child->updateState(Fw::LastState);
1485 child->updateState(Fw::AlternateState);
1486 }
1487}
1488
1489void UIWidget::updateStyle()
1490{
1491 if(m_destroyed)
1492 return;
1493
1494 if(m_loadingStyle && !m_updateStyleScheduled) {
1495 UIWidgetPtr self = static_self_cast<UIWidget>();
1496 g_dispatcher.addEvent([self] {
1497 self->m_updateStyleScheduled = false;
1498 self->updateStyle();
1499 });
1500 m_updateStyleScheduled = true;
1501 return;
1502 }
1503
1504 if(!m_style)
1505 return;
1506
1507 OTMLNodePtr newStateStyle = OTMLNode::create();
1508
1509 // copy only the changed styles from default style
1510 if(m_stateStyle) {
1511 for(OTMLNodePtr node : m_stateStyle->children()) {
1512 if(OTMLNodePtr otherNode = m_style->get(node->tag()))
1513 newStateStyle->addChild(otherNode->clone());
1514 }
1515 }
1516
1517 // checks for states combination
1518 for(const OTMLNodePtr& style : m_style->children()) {
1519 if(stdext::starts_with(style->tag(), "$")) {
1520 std::string statesStr = style->tag().substr(1);
1521 std::vector<std::string> statesSplit = stdext::split(statesStr, " ");
1522
1523 bool match = true;
1524 for(std::string stateStr : statesSplit) {
1525 if(stateStr.length() == 0)
1526 continue;
1527
1528 bool notstate = (stateStr[0] == '!');
1529 if(notstate)
1530 stateStr = stateStr.substr(1);
1531
1532 bool stateOn = hasState(Fw::translateState(stateStr));
1533 if((!notstate && !stateOn) || (notstate && stateOn))
1534 match = false;
1535 }
1536
1537 // merge states styles
1538 if(match) {
1539 newStateStyle->merge(style);
1540 }
1541 }
1542 }
1543
1544 //TODO: prevent setting already set proprieties
1545
1546 applyStyle(newStateStyle);
1547 m_stateStyle = newStateStyle;
1548}
1549
1550void UIWidget::onStyleApply(const std::string& styleName, const OTMLNodePtr& styleNode)
1551{
1552 if(m_destroyed)
1553 return;
1554
1555 // first set id
1556 if(const OTMLNodePtr& node = styleNode->get("id"))
1557 setId(node->value());
1558
1559 parseBaseStyle(styleNode);
1560 parseImageStyle(styleNode);
1561 parseTextStyle(styleNode);
1562}
1563
1564void UIWidget::onGeometryChange(const Rect& oldRect, const Rect& newRect)
1565{
1566 if(m_textWrap && oldRect.size() != newRect.size())
1567 updateText();
1568
1569 // move children that is outside the parent rect to inside again
1570 for(const UIWidgetPtr& child : m_children) {
1571 if(!child->isAnchored() && child->isVisible())
1572 child->bindRectToParent();
1573 }
1574
1575 callLuaField("onGeometryChange", oldRect, newRect);
1576}
1577
1578void UIWidget::onLayoutUpdate()
1579{
1580 callLuaField("onLayoutUpdate");
1581}
1582
1583void UIWidget::onFocusChange(bool focused, Fw::FocusReason reason)
1584{
1585 callLuaField("onFocusChange", focused, reason);
1586}
1587
1588void UIWidget::onChildFocusChange(const UIWidgetPtr& focusedChild, const UIWidgetPtr& unfocusedChild, Fw::FocusReason reason)
1589{
1590 callLuaField("onChildFocusChange", focusedChild, unfocusedChild, reason);
1591}
1592
1593void UIWidget::onHoverChange(bool hovered)
1594{
1595 if (m_changeCursorImage) {
1596 if (hovered && !g_mouse.isCursorChanged())
1597 g_mouse.pushCursor(m_cursor);
1598 else
1599 g_mouse.popCursor(m_cursor);
1600 }
1601 callLuaField("onHoverChange", hovered);
1602}
1603
1604void UIWidget::onVisibilityChange(bool visible)
1605{
1606 if(!isAnchored())
1607 bindRectToParent();
1608 callLuaField("onVisibilityChange", visible);
1609}
1610
1611bool UIWidget::onDragEnter(const Point& mousePos)
1612{
1613 return callLuaField<bool>("onDragEnter", mousePos);
1614}
1615
1616bool UIWidget::onDragLeave(UIWidgetPtr droppedWidget, const Point& mousePos)
1617{
1618 return callLuaField<bool>("onDragLeave", droppedWidget, mousePos);
1619}
1620
1621bool UIWidget::onDragMove(const Point& mousePos, const Point& mouseMoved)
1622{
1623 return callLuaField<bool>("onDragMove", mousePos, mouseMoved);
1624}
1625
1626bool UIWidget::onDrop(UIWidgetPtr draggedWidget, const Point& mousePos)
1627{
1628 return callLuaField<bool>("onDrop", draggedWidget, mousePos);
1629}
1630
1631bool UIWidget::onKeyText(const std::string& keyText)
1632{
1633 return callLuaField<bool>("onKeyText", keyText);
1634}
1635
1636bool UIWidget::onKeyDown(uchar keyCode, int keyboardModifiers)
1637{
1638 return callLuaField<bool>("onKeyDown", keyCode, keyboardModifiers);
1639}
1640
1641bool UIWidget::onKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks)
1642{
1643 return callLuaField<bool>("onKeyPress", keyCode, keyboardModifiers, autoRepeatTicks);
1644}
1645
1646bool UIWidget::onKeyUp(uchar keyCode, int keyboardModifiers)
1647{
1648 return callLuaField<bool>("onKeyUp", keyCode, keyboardModifiers);
1649}
1650
1651bool UIWidget::onMousePress(const Point& mousePos, Fw::MouseButton button)
1652{
1653 if(button == Fw::MouseLeftButton) {
1654 if(m_clickTimer.running() && m_clickTimer.ticksElapsed() <= 200) {
1655 if(onDoubleClick(mousePos))
1656 return true;
1657 m_clickTimer.stop();
1658 } else
1659 m_clickTimer.restart();
1660 m_lastClickPosition = mousePos;
1661 }
1662
1663 if (button == Fw::MouseTouch || button == Fw::MouseTouch2 || button == Fw::MouseTouch3)
1664 return callLuaField<bool>("onTouchPress", mousePos, button);
1665 return callLuaField<bool>("onMousePress", mousePos, button);
1666}
1667
1668bool UIWidget::onMouseRelease(const Point& mousePos, Fw::MouseButton button)
1669{
1670 if (button == Fw::MouseTouch || button == Fw::MouseTouch2 || button == Fw::MouseTouch3)
1671 return callLuaField<bool>("onTouchRelease", mousePos, button);
1672 return callLuaField<bool>("onMouseRelease", mousePos, button);
1673}
1674
1675bool UIWidget::onMouseMove(const Point& mousePos, const Point& mouseMoved)
1676{
1677 return callLuaField<bool>("onMouseMove", mousePos, mouseMoved);
1678}
1679
1680bool UIWidget::onMouseWheel(const Point& mousePos, Fw::MouseWheelDirection direction)
1681{
1682 return callLuaField<bool>("onMouseWheel", mousePos, direction);
1683}
1684
1685bool UIWidget::onClick(const Point& mousePos)
1686{
1687 return callLuaField<bool>("onClick", mousePos);
1688}
1689
1690bool UIWidget::onDoubleClick(const Point& mousePos)
1691{
1692 return callLuaField<bool>("onDoubleClick", mousePos);
1693}
1694
1695bool UIWidget::propagateOnKeyText(const std::string& keyText)
1696{
1697 // do a backup of children list, because it may change while looping it
1698 UIWidgetList children;
1699 for(const UIWidgetPtr& child : m_children) {
1700 // events on hidden or disabled widgets are discarded
1701 if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible())
1702 continue;
1703
1704 // key events go only to containers or focused child
1705 if(child->isFocused())
1706 children.push_back(child);
1707 }
1708
1709 for(const UIWidgetPtr& child : children) {
1710 if(child->propagateOnKeyText(keyText))
1711 return true;
1712 }
1713
1714 return onKeyText(keyText);
1715}
1716
1717bool UIWidget::propagateOnKeyDown(uchar keyCode, int keyboardModifiers)
1718{
1719 // do a backup of children list, because it may change while looping it
1720 UIWidgetList children;
1721 for(const UIWidgetPtr& child : m_children) {
1722 // events on hidden or disabled widgets are discarded
1723 if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible())
1724 continue;
1725
1726 // key events go only to containers or focused child
1727 if(child->isFocused())
1728 children.push_back(child);
1729 }
1730
1731 for(const UIWidgetPtr& child : children) {
1732 if(child->propagateOnKeyDown(keyCode, keyboardModifiers))
1733 return true;
1734 }
1735
1736 return onKeyDown(keyCode, keyboardModifiers);
1737}
1738
1739bool UIWidget::propagateOnKeyPress(uchar keyCode, int keyboardModifiers, int autoRepeatTicks)
1740{
1741 // do a backup of children list, because it may change while looping it
1742 UIWidgetList children;
1743 for(const UIWidgetPtr& child : m_children) {
1744 // events on hidden or disabled widgets are discarded
1745 if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible())
1746 continue;
1747
1748 // key events go only to containers or focused child
1749 if(child->isFocused())
1750 children.push_back(child);
1751 }
1752
1753 for(const UIWidgetPtr& child : children) {
1754 if(child->propagateOnKeyPress(keyCode, keyboardModifiers, autoRepeatTicks))
1755 return true;
1756 }
1757
1758 if(autoRepeatTicks == 0 || autoRepeatTicks >= m_autoRepeatDelay)
1759 return onKeyPress(keyCode, keyboardModifiers, autoRepeatTicks);
1760 else
1761 return false;
1762}
1763
1764bool UIWidget::propagateOnKeyUp(uchar keyCode, int keyboardModifiers)
1765{
1766 // do a backup of children list, because it may change while looping it
1767 UIWidgetList children;
1768 for(const UIWidgetPtr& child : m_children) {
1769 // events on hidden or disabled widgets are discarded
1770 if(!child->isExplicitlyEnabled() || !child->isExplicitlyVisible())
1771 continue;
1772
1773 // key events go only to focused child
1774 if(child->isFocused())
1775 children.push_back(child);
1776 }
1777
1778 for(const UIWidgetPtr& child : children) {
1779 if(child->propagateOnKeyUp(keyCode, keyboardModifiers))
1780 return true;
1781 }
1782
1783 return onKeyUp(keyCode, keyboardModifiers);
1784}
1785
1786bool UIWidget::propagateOnMouseEvent(const Point& mousePos, UIWidgetList& widgetList)
1787{
1788 bool ret = false;
1789 if(containsPaddingPoint(mousePos)) {
1790 for(auto it = m_children.rbegin(); it != m_children.rend(); ++it) {
1791 const UIWidgetPtr& child = *it;
1792 if(child->isExplicitlyEnabled() && child->isExplicitlyVisible() && child->containsPoint(mousePos)) {
1793 if(child->propagateOnMouseEvent(mousePos, widgetList)) {
1794 ret = true;
1795 break;
1796 }
1797 }
1798 }
1799 }
1800
1801 widgetList.push_back(static_self_cast<UIWidget>());
1802
1803 if(!isPhantom())
1804 ret = true;
1805 return ret;
1806}
1807
1808bool UIWidget::propagateOnMouseMove(const Point& mousePos, const Point& mouseMoved, UIWidgetList& widgetList)
1809{
1810 if (containsPaddingPoint(mousePos)) {
1811 for (auto it = m_children.begin(); it != m_children.end(); ++it) {
1812 const UIWidgetPtr& child = *it;
1813 if (child->isExplicitlyVisible() && child->isExplicitlyEnabled() && child->containsPoint(mousePos))
1814 child->propagateOnMouseMove(mousePos, mouseMoved, widgetList);
1815
1816 widgetList.push_back(static_self_cast<UIWidget>());
1817 }
1818 }
1819
1820 return true;
1821}
1822
1823void UIWidget::setCursor(const std::string& cursor)
1824{
1825 if (m_cursor == cursor) return;
1826
1827 if (cursor.empty()) {
1828 m_cursor = "";
1829 m_changeCursorImage = false;
1830 return;
1831 }
1832
1833 m_cursor = cursor;
1834 m_changeCursorImage = true;
1835}