1// Aseprite UI Library
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8// #define REPORT_SIGNALS
9
10#ifdef HAVE_CONFIG_H
11#include "config.h"
12#endif
13
14#include "ui/widget.h"
15
16#include "base/memory.h"
17#include "base/string.h"
18#include "base/utf8_decode.h"
19#include "os/font.h"
20#include "os/surface.h"
21#include "os/system.h"
22#include "os/window.h"
23#include "ui/app_state.h"
24#include "ui/init_theme_event.h"
25#include "ui/intern.h"
26#include "ui/layout_io.h"
27#include "ui/load_layout_event.h"
28#include "ui/manager.h"
29#include "ui/message.h"
30#include "ui/move_region.h"
31#include "ui/paint_event.h"
32#include "ui/resize_event.h"
33#include "ui/save_layout_event.h"
34#include "ui/size_hint_event.h"
35#include "ui/system.h"
36#include "ui/theme.h"
37#include "ui/view.h"
38#include "ui/window.h"
39
40#include <algorithm>
41#include <cctype>
42#include <cstdarg>
43#include <cstdio>
44#include <cstring>
45#include <limits>
46#include <queue>
47#include <sstream>
48
49namespace ui {
50
51using namespace gfx;
52
53WidgetType register_widget_type()
54{
55 static int type = (int)kFirstUserWidget;
56 return (WidgetType)type++;
57}
58
59Widget::Widget(WidgetType type)
60 : m_type(type)
61 , m_flags(0)
62 , m_theme(get_theme())
63 , m_style(nullptr)
64 , m_font(nullptr)
65 , m_bgColor(gfx::ColorNone)
66 , m_bounds(0, 0, 0, 0)
67 , m_parent(nullptr)
68 , m_parentIndex(-1)
69 , m_sizeHint(nullptr)
70 , m_mnemonic(0)
71 , m_minSize(0, 0)
72 , m_maxSize(std::numeric_limits<int>::max(),
73 std::numeric_limits<int>::max())
74 , m_childSpacing(0)
75{
76 details::addWidget(this);
77}
78
79Widget::~Widget()
80{
81 // First, we remove children (so children's ~Widget() can access to
82 // the manager()).
83 while (!m_children.empty())
84 delete m_children.front();
85
86 // Break relationship with the manager. This cannot be before
87 // deleting children, if we delete children after releasing the
88 // parent, a children deletion could generate a kMouseLeaveMessage
89 // for the parent that will be deleted too.
90 Manager* manager = this->manager();
91 ASSERT(manager);
92 if (manager) {
93 manager->freeWidget(this);
94 manager->removeMessagesFor(this);
95 manager->removeMessageFilterFor(this);
96 }
97
98 // Remove this widget from parent.
99 if (m_parent)
100 m_parent->removeChild(this);
101
102 // Delete fixed size hint if it isn't nullptr
103 delete m_sizeHint;
104
105 // Low level free
106 details::removeWidget(this);
107}
108
109void Widget::deferDelete()
110{
111 if (auto man = manager())
112 man->addToGarbage(this);
113 else {
114 ASSERT(false);
115 }
116}
117
118void Widget::initTheme()
119{
120 InitThemeEvent ev(this, m_theme);
121 onInitTheme(ev);
122}
123
124int Widget::textInt() const
125{
126 return onGetTextInt();
127}
128
129double Widget::textDouble() const
130{
131 return onGetTextDouble();
132}
133
134void Widget::setText(const std::string& text)
135{
136 setTextQuiet(text);
137 onSetText();
138}
139
140void Widget::setTextf(const char *format, ...)
141{
142 // formatted string
143 if (format) {
144 va_list ap;
145 va_start(ap, format);
146 char buf[4096];
147 vsprintf(buf, format, ap);
148 va_end(ap);
149
150 setText(buf);
151 }
152 // empty string
153 else {
154 setText("");
155 }
156}
157
158void Widget::setTextQuiet(const std::string& text)
159{
160 assert_ui_thread();
161
162 m_text = text;
163 enableFlags(HAS_TEXT);
164}
165
166os::Font* Widget::font() const
167{
168 if (!m_font && m_theme)
169 m_font = AddRef(m_theme->getWidgetFont(this));
170 return m_font.get();
171}
172
173void Widget::setBgColor(gfx::Color color)
174{
175 assert_ui_thread();
176
177 m_bgColor = color;
178 onSetBgColor();
179
180#ifdef _DEBUG
181 if (m_style) {
182 LOG(WARNING, "UI: %s: Warning setting bgColor to a widget with style (%s)\n",
183 typeid(*this).name(), m_style->id().c_str());
184 }
185#endif
186}
187
188void Widget::setTheme(Theme* theme)
189{
190 assert_ui_thread();
191
192 m_theme = theme;
193 m_font = nullptr;
194
195 for (auto child : children())
196 child->setTheme(theme);
197}
198
199void Widget::setStyle(Style* style)
200{
201 assert_ui_thread();
202
203 m_style = style;
204 m_border = m_theme->calcBorder(this, style);
205 m_bgColor = m_theme->calcBgColor(this, style);
206 if (style->font())
207 m_font = AddRef(style->font());
208}
209
210// ===============================================================
211// COMMON PROPERTIES
212// ===============================================================
213
214void Widget::setVisible(bool state)
215{
216 assert_ui_thread();
217
218 if (state) {
219 if (hasFlags(HIDDEN)) {
220 disableFlags(HIDDEN);
221 invalidate();
222
223 onVisible(true);
224 }
225 }
226 else {
227 if (!hasFlags(HIDDEN)) {
228 if (auto man = manager())
229 man->freeWidget(this); // Free from manager
230 enableFlags(HIDDEN);
231
232 onVisible(false);
233 }
234 }
235}
236
237void Widget::setEnabled(bool state)
238{
239 assert_ui_thread();
240
241 if (state) {
242 if (hasFlags(DISABLED)) {
243 disableFlags(DISABLED);
244 invalidate();
245
246 onEnable(true);
247 }
248 }
249 else {
250 if (!hasFlags(DISABLED)) {
251 if (auto man = manager())
252 man->freeWidget(this); // Free from the manager
253
254 enableFlags(DISABLED);
255 invalidate();
256
257 onEnable(false);
258 }
259 }
260}
261
262void Widget::setSelected(bool state)
263{
264 assert_ui_thread();
265
266 if (state) {
267 if (!hasFlags(SELECTED)) {
268 enableFlags(SELECTED);
269 invalidate();
270
271 onSelect(true);
272 }
273 }
274 else {
275 if (hasFlags(SELECTED)) {
276 disableFlags(SELECTED);
277 invalidate();
278
279 onSelect(false);
280 }
281 }
282}
283
284void Widget::setExpansive(bool state)
285{
286 assert_ui_thread();
287
288 if (state)
289 enableFlags(EXPANSIVE);
290 else
291 disableFlags(EXPANSIVE);
292}
293
294void Widget::setDecorative(bool state)
295{
296 assert_ui_thread();
297
298 if (state)
299 enableFlags(DECORATIVE);
300 else
301 disableFlags(DECORATIVE);
302}
303
304void Widget::setFocusStop(bool state)
305{
306 assert_ui_thread();
307
308 if (state)
309 enableFlags(FOCUS_STOP);
310 else
311 disableFlags(FOCUS_STOP);
312}
313
314void Widget::setFocusMagnet(bool state)
315{
316 assert_ui_thread();
317
318 if (state)
319 enableFlags(FOCUS_MAGNET);
320 else
321 disableFlags(FOCUS_MAGNET);
322}
323
324bool Widget::isVisible() const
325{
326 assert_ui_thread();
327
328 const Widget* widget = this;
329 const Widget* lastWidget = nullptr;
330
331 do {
332 if (widget->hasFlags(HIDDEN))
333 return false;
334
335 lastWidget = widget;
336 widget = widget->m_parent;
337 } while (widget);
338
339 // The widget is visible if it's inside a visible manager
340 return (lastWidget ? lastWidget->type() == kManagerWidget: false);
341}
342
343bool Widget::isEnabled() const
344{
345 assert_ui_thread();
346
347 const Widget* widget = this;
348
349 do {
350 if (widget->hasFlags(DISABLED))
351 return false;
352
353 widget = widget->m_parent;
354 } while (widget);
355
356 return true;
357}
358
359bool Widget::isSelected() const
360{
361 assert_ui_thread();
362 return hasFlags(SELECTED);
363}
364
365bool Widget::isExpansive() const
366{
367 assert_ui_thread();
368 return hasFlags(EXPANSIVE);
369}
370
371bool Widget::isDecorative() const
372{
373 assert_ui_thread();
374 return hasFlags(DECORATIVE);
375}
376
377bool Widget::isFocusStop() const
378{
379 assert_ui_thread();
380 return hasFlags(FOCUS_STOP);
381}
382
383bool Widget::isFocusMagnet() const
384{
385 assert_ui_thread();
386 return hasFlags(FOCUS_MAGNET);
387}
388
389// ===============================================================
390// PARENTS & CHILDREN
391// ===============================================================
392
393Window* Widget::window() const
394{
395 assert_ui_thread();
396
397 const Widget* widget = this;
398
399 while (widget) {
400 if (widget->type() == kWindowWidget)
401 return static_cast<Window*>(const_cast<Widget*>(widget));
402
403 widget = widget->m_parent;
404 }
405
406 return nullptr;
407}
408
409Manager* Widget::manager() const
410{
411 assert_ui_thread();
412
413 const Widget* widget = this;
414
415 while (widget) {
416 if (widget->type() == kManagerWidget)
417 return static_cast<Manager*>(const_cast<Widget*>(widget));
418
419 widget = widget->m_parent;
420 }
421
422 return Manager::getDefault();
423}
424
425Display* Widget::display() const
426{
427 if (Window* win = this->window())
428 return win->display();
429 else
430 return manager()->display();
431}
432
433int Widget::getChildIndex(Widget* child)
434{
435 if (!child)
436 return -1;
437
438#ifdef _DEBUG
439 ASSERT(child->parent() == this);
440 auto it = std::find(m_children.begin(), m_children.end(), child);
441 ASSERT(it != m_children.end());
442 ASSERT(child->parentIndex() == (it - m_children.begin()));
443#endif
444
445 return child->parentIndex();
446}
447
448Widget* Widget::nextSibling()
449{
450 assert_ui_thread();
451
452 if (!m_parent)
453 return nullptr;
454
455 auto begin = m_parent->m_children.begin();
456 auto end = m_parent->m_children.end();
457 auto it = begin + m_parentIndex;
458
459 if (++it == end)
460 return nullptr;
461
462 return *it;
463}
464
465Widget* Widget::previousSibling()
466{
467 assert_ui_thread();
468
469 if (!m_parent)
470 return nullptr;
471
472 auto begin = m_parent->m_children.begin();
473 auto it = begin + m_parentIndex;
474
475 if (it == begin)
476 return nullptr;
477
478 return *(--it);
479}
480
481Widget* Widget::pick(const gfx::Point& pt,
482 const bool checkParentsVisibility) const
483{
484 assert_ui_thread();
485
486 const Widget* inside, *picked = nullptr;
487
488 // isVisible() checks visibility of widget's parent.
489 if (((checkParentsVisibility && isVisible()) ||
490 (!checkParentsVisibility && !hasFlags(HIDDEN))) &&
491 (bounds().contains(pt))) {
492 picked = this;
493
494 for (Widget* child : m_children) {
495 inside = child->pick(pt, false);
496 if (inside) {
497 picked = inside;
498 break;
499 }
500 }
501 }
502
503 return const_cast<Widget*>(picked);
504}
505
506Widget* Widget::pickFromScreenPos(const gfx::Point& screenPos) const
507{
508 return pick(display()->nativeWindow()->pointFromScreen(screenPos));
509}
510
511bool Widget::hasChild(Widget* child)
512{
513 ASSERT_VALID_WIDGET(child);
514 assert_ui_thread();
515
516 return std::find(m_children.begin(), m_children.end(), child) != m_children.end();
517}
518
519bool Widget::hasAncestor(Widget* ancestor)
520{
521 for (Widget* widget=m_parent; widget; widget=widget->m_parent) {
522 if (widget == ancestor)
523 return true;
524 }
525 return false;
526}
527
528Widget* Widget::findChild(const char* id) const
529{
530 for (auto child : m_children) {
531 if (child->id() == id)
532 return child;
533 }
534
535 for (auto child : m_children) {
536 auto subchild = child->findChild(id);
537 if (subchild)
538 return subchild;
539 }
540
541 return nullptr;
542}
543
544Widget* Widget::findSibling(const char* id) const
545{
546 return window()->findChild(id);
547}
548
549void Widget::addChild(Widget* child)
550{
551 ASSERT_VALID_WIDGET(this);
552 ASSERT_VALID_WIDGET(child);
553
554 // Remove the widget from its current parent
555 if (child->m_parent)
556 child->m_parent->removeChild(child);
557
558 int i = int(m_children.size());
559 m_children.push_back(child);
560 child->m_parent = this;
561 child->m_parentIndex = i;
562}
563
564void Widget::removeChild(const WidgetsList::iterator& it)
565{
566 Widget* child = nullptr;
567
568 ASSERT(it != m_children.end());
569 if (it != m_children.end()) {
570 child = *it;
571
572 auto it2 = m_children.erase(it);
573 for (auto end=m_children.end(); it2!=end; ++it2)
574 --(*it2)->m_parentIndex;
575
576 ASSERT(child);
577 if (!child)
578 return;
579 }
580 else
581 return;
582
583 // Free child from manager
584 if (auto man = manager())
585 man->freeWidget(child);
586
587 child->m_parent = nullptr;
588 child->m_parentIndex = -1;
589}
590
591void Widget::removeChild(Widget* child)
592{
593 ASSERT_VALID_WIDGET(this);
594 ASSERT_VALID_WIDGET(child);
595 ASSERT(child->parent() == this);
596 if (child->parent() == this)
597 removeChild(m_children.begin() + child->m_parentIndex);
598}
599
600void Widget::removeAllChildren()
601{
602 while (!m_children.empty())
603 removeChild(--m_children.end());
604}
605
606void Widget::replaceChild(Widget* oldChild, Widget* newChild)
607{
608 ASSERT_VALID_WIDGET(oldChild);
609 ASSERT_VALID_WIDGET(newChild);
610
611#if _DEBUG
612 {
613 auto it = std::find(m_children.begin(), m_children.end(), oldChild);
614 ASSERT(it != m_children.end());
615 ASSERT(oldChild->m_parentIndex == (it - m_children.begin()));
616 }
617#endif
618
619 if (oldChild->parent() != this) {
620 ASSERT(false);
621 return;
622 }
623 int index = oldChild->m_parentIndex;
624
625 removeChild(oldChild);
626
627 auto it = m_children.begin() + index;
628 it = m_children.insert(it, newChild);
629 for (auto end=m_children.end(); it!=end; ++it)
630 ++(*it)->m_parentIndex;
631
632 newChild->m_parent = this;
633 newChild->m_parentIndex = index;
634}
635
636void Widget::insertChild(int index, Widget* child)
637{
638 ASSERT_VALID_WIDGET(this);
639 ASSERT_VALID_WIDGET(child);
640
641 index = std::clamp(index, 0, int(m_children.size()));
642
643 auto it = m_children.begin() + index;
644 it = m_children.insert(it, child);
645 ++it;
646 for (auto end=m_children.end(); it!=end; ++it)
647 ++(*it)->m_parentIndex;
648
649 child->m_parent = this;
650 child->m_parentIndex = index;
651}
652
653void Widget::moveChildTo(Widget* thisChild, Widget* toThisPosition)
654{
655 ASSERT(thisChild->parent() == this);
656 ASSERT(toThisPosition->parent() == this);
657
658 const int from = thisChild->m_parentIndex;
659 const int to = toThisPosition->m_parentIndex;
660
661 auto it = m_children.begin() + from;
662 it = m_children.erase(it);
663 auto end = m_children.end();
664 for (; it!=end; ++it)
665 --(*it)->m_parentIndex;
666
667 it = m_children.begin() + to;
668 it = m_children.insert(it, thisChild);
669 thisChild->m_parentIndex = to;
670 for (++it, end=m_children.end(); it!=end; ++it)
671 ++(*it)->m_parentIndex;
672}
673
674// ===============================================================
675// LAYOUT & CONSTRAINT
676// ===============================================================
677
678void Widget::layout()
679{
680 setBounds(bounds());
681 invalidate();
682}
683
684void Widget::loadLayout()
685{
686 if (!m_id.empty()) {
687 auto man = manager();
688 LayoutIO* io = (man ? man->getLayoutIO(): nullptr);
689 if (io) {
690 std::string layout = io->loadLayout(this);
691 if (!layout.empty()) {
692 std::stringstream s(layout);
693 LoadLayoutEvent ev(this, s);
694 onLoadLayout(ev);
695 }
696 }
697 }
698
699 // Do for all children
700 for (auto child : m_children)
701 child->loadLayout();
702}
703
704void Widget::saveLayout()
705{
706 if (!m_id.empty()) {
707 auto man = manager();
708 LayoutIO* io = (man ? man->getLayoutIO(): nullptr);
709 if (io) {
710 std::stringstream s;
711 SaveLayoutEvent ev(this, s);
712 onSaveLayout(ev);
713
714 std::string layout = s.str();
715 if (!layout.empty())
716 io->saveLayout(this, layout);
717 }
718 }
719
720 // Do for all children
721 for (auto child : m_children)
722 child->saveLayout();
723}
724
725void Widget::setDecorativeWidgetBounds()
726{
727 onSetDecorativeWidgetBounds();
728}
729
730// ===============================================================
731// POSITION & GEOMETRY
732// ===============================================================
733
734Rect Widget::childrenBounds() const
735{
736 return Rect(m_bounds.x + border().left(),
737 m_bounds.y + border().top(),
738 m_bounds.w - border().width(),
739 m_bounds.h - border().height());
740}
741
742Rect Widget::clientChildrenBounds() const
743{
744 return Rect(border().left(),
745 border().top(),
746 m_bounds.w - border().width(),
747 m_bounds.h - border().height());
748}
749
750gfx::Rect Widget::boundsOnScreen() const
751{
752 gfx::Rect rc = bounds();
753 os::Window* nativeWindow = display()->nativeWindow();
754 rc = gfx::Rect(
755 nativeWindow->pointToScreen(rc.origin()),
756 nativeWindow->pointToScreen(rc.point2()));
757 return rc;
758}
759
760void Widget::setBounds(const Rect& rc)
761{
762 // Don't generate onResize() events if the app is being closed
763 if (is_app_state_closing())
764 return;
765
766 ResizeEvent ev(this, rc);
767 onResize(ev);
768}
769
770void Widget::setBoundsQuietly(const gfx::Rect& rc)
771{
772 if (m_bounds != rc) {
773 m_bounds = rc;
774
775 // Remove all paint messages for this widget.
776 if (Manager* man = manager())
777 man->removeMessagesFor(this, kPaintMessage);
778 }
779
780 // TODO Test moving this inside the if (m_bounds != rc) { ... }
781 // block, so the widget is invalidted only when the bounds are
782 // really changed.
783 invalidate();
784}
785
786void Widget::setBorder(const Border& br)
787{
788 m_border = br;
789
790#ifdef _DEBUG
791 if (m_style) {
792 LOG(WARNING, "UI: %s: Warning setting border to a widget with style (%s)\n",
793 typeid(*this).name(), m_style->id().c_str());
794 }
795#endif
796}
797
798void Widget::setChildSpacing(int childSpacing)
799{
800 m_childSpacing = childSpacing;
801
802#ifdef _DEBUG
803 if (m_style) {
804 LOG(WARNING, "UI: %s: Warning setting child spacing to a widget with style (%s)\n",
805 typeid(*this).name(), m_style->id().c_str());
806 }
807#endif
808}
809
810void Widget::noBorderNoChildSpacing()
811{
812 m_border = gfx::Border(0, 0, 0, 0);
813 m_childSpacing = 0;
814
815#ifdef _DEBUG
816 if (m_style) {
817 LOG(WARNING, "UI: %s: Warning setting no border to a widget with style (%s)\n",
818 typeid(*this).name(), m_style->id().c_str());
819 }
820#endif
821}
822
823void Widget::getRegion(gfx::Region& region)
824{
825 if (type() == kWindowWidget)
826 theme()->getWindowMask(this, region);
827 else
828 region = bounds();
829}
830
831void Widget::getDrawableRegion(gfx::Region& region, DrawableRegionFlags flags)
832{
833 Window* window = this->window();
834 Display* display = this->display();
835
836 getRegion(region);
837
838 // Cut the top windows areas
839 if (flags & kCutTopWindows) {
840 const auto& uiWindows = display->getWindows();
841
842 // Reverse iterator
843 auto it = std::find(uiWindows.rbegin(),
844 uiWindows.rend(), window);
845
846 if (!uiWindows.empty() &&
847 window != uiWindows.front() &&
848 it != uiWindows.rend()) {
849 // Subtract the rectangles of each window
850 for (++it; it != uiWindows.rend(); ++it) {
851 if (!(*it)->isVisible())
852 continue;
853
854 Region reg1;
855 (*it)->getRegion(reg1);
856 region -= reg1;
857 }
858 }
859 }
860
861 // Clip the areas where are children
862 if (!(flags & kUseChildArea) && !children().empty()) {
863 Region reg1;
864 Region reg2(childrenBounds());
865
866 for (auto child : children()) {
867 if (child->isVisible()) {
868 Region reg3;
869 child->getRegion(reg3);
870
871 if (child->hasFlags(DECORATIVE)) {
872 reg1 = bounds();
873 reg1.createIntersection(reg1, reg3);
874 }
875 else {
876 reg1.createIntersection(reg2, reg3);
877 }
878 region -= reg1;
879 }
880 }
881 }
882
883 // Intersect with the parent area
884 if (!hasFlags(DECORATIVE)) {
885 Widget* p = this->parent();
886 while (p && p->type() != kManagerWidget) {
887 region &= Region(p->childrenBounds());
888 p = p->parent();
889 }
890 }
891 else {
892 Widget* p = parent();
893 if (p) {
894 region &= Region(p->bounds());
895 }
896 }
897
898 // Limit to the displayable area
899 View* view = View::getView(display->containedWidget());
900 Rect cpos;
901 if (view)
902 cpos = static_cast<View*>(view)->viewportBounds();
903 else
904 cpos = display->containedWidget()->bounds();
905
906 region &= Region(cpos);
907}
908
909int Widget::textWidth() const
910{
911 return Graphics::measureUITextLength(text().c_str(), font());
912}
913
914int Widget::textHeight() const
915{
916 return font()->height();
917}
918
919void Widget::getTextIconInfo(
920 gfx::Rect* box,
921 gfx::Rect* text,
922 gfx::Rect* icon,
923 int icon_align, int icon_w, int icon_h)
924{
925#define SETRECT(r) \
926 if (r) { \
927 r->x = r##_x; \
928 r->y = r##_y; \
929 r->w = r##_w; \
930 r->h = r##_h; \
931 }
932
933 gfx::Rect bounds = clientBounds();
934 int box_x, box_y, box_w, box_h, icon_x, icon_y;
935 int text_x, text_y, text_w, text_h;
936
937 text_x = text_y = 0;
938
939 // Size of the text
940 if (hasText()) {
941 text_w = textWidth();
942 text_h = textHeight();
943 }
944 else {
945 text_w = text_h = 0;
946 }
947
948 // Box size
949 if (icon_align & CENTER) { // With the icon in the center
950 if (icon_align & MIDDLE) { // With the icon inside the text
951 box_w = std::max(icon_w, text_w);
952 box_h = std::max(icon_h, text_h);
953 }
954 // With the icon in the top or bottom
955 else {
956 box_w = std::max(icon_w, text_w);
957 box_h = icon_h + (hasText() ? childSpacing(): 0) + text_h;
958 }
959 }
960 // With the icon in left or right that doesn't care by now
961 else {
962 box_w = icon_w + (hasText() ? childSpacing(): 0) + text_w;
963 box_h = std::max(icon_h, text_h);
964 }
965
966 // Box position
967 if (align() & RIGHT)
968 box_x = bounds.x2() - box_w - border().right();
969 else if (align() & CENTER)
970 box_x = (bounds.x+bounds.x2())/2 - box_w/2;
971 else
972 box_x = bounds.x + border().left();
973
974 if (align() & BOTTOM)
975 box_y = bounds.y2() - box_h - border().bottom();
976 else if (align() & MIDDLE)
977 box_y = (bounds.y+bounds.y2())/2 - box_h/2;
978 else
979 box_y = bounds.y + border().top();
980
981 // With text
982 if (hasText()) {
983 // Text/icon X position
984 if (icon_align & RIGHT) {
985 text_x = box_x;
986 icon_x = box_x + box_w - icon_w;
987 }
988 else if (icon_align & CENTER) {
989 text_x = box_x + box_w/2 - text_w/2;
990 icon_x = box_x + box_w/2 - icon_w/2;
991 }
992 else {
993 text_x = box_x + box_w - text_w;
994 icon_x = box_x;
995 }
996
997 // Text Y position
998 if (icon_align & BOTTOM) {
999 text_y = box_y;
1000 icon_y = box_y + box_h - icon_h;
1001 }
1002 else if (icon_align & MIDDLE) {
1003 text_y = box_y + box_h/2 - text_h/2;
1004 icon_y = box_y + box_h/2 - icon_h/2;
1005 }
1006 else {
1007 text_y = box_y + box_h - text_h;
1008 icon_y = box_y;
1009 }
1010 }
1011 // Without text
1012 else {
1013 // Icon X/Y position
1014 icon_x = box_x;
1015 icon_y = box_y;
1016 }
1017
1018 SETRECT(box);
1019 SETRECT(text);
1020 SETRECT(icon);
1021}
1022
1023void Widget::setMinSize(const gfx::Size& sz)
1024{
1025 ASSERT(sz.w <= m_maxSize.w);
1026 ASSERT(sz.h <= m_maxSize.h);
1027 m_minSize = sz;
1028}
1029
1030void Widget::setMaxSize(const gfx::Size& sz)
1031{
1032 ASSERT(sz.w >= m_minSize.w);
1033 ASSERT(sz.h >= m_minSize.h);
1034 m_maxSize = sz;
1035}
1036
1037void Widget::resetMinSize()
1038{
1039 m_minSize = gfx::Size(0, 0);
1040}
1041
1042void Widget::resetMaxSize()
1043{
1044 m_maxSize = gfx::Size(std::numeric_limits<int>::max(),
1045 std::numeric_limits<int>::max());
1046}
1047
1048void Widget::flushRedraw()
1049{
1050 std::queue<Widget*> processing;
1051 Message* msg;
1052
1053 if (hasFlags(DIRTY)) {
1054 disableFlags(DIRTY);
1055 processing.push(this);
1056 }
1057
1058 Manager* manager = this->manager();
1059 ASSERT(manager);
1060 if (!manager)
1061 return;
1062
1063 while (!processing.empty()) {
1064 Widget* widget = processing.front();
1065 processing.pop();
1066
1067 ASSERT_VALID_WIDGET(widget);
1068
1069 // If the widget is hidden
1070 if (!widget->isVisible())
1071 continue;
1072
1073 for (auto child : widget->children()) {
1074 if (child->hasFlags(DIRTY)) {
1075 child->disableFlags(DIRTY);
1076 processing.push(child);
1077 }
1078 }
1079
1080 if (!widget->m_updateRegion.isEmpty()) {
1081 // Intersect m_updateRegion with drawable area.
1082 {
1083 Region drawable;
1084 widget->getDrawableRegion(drawable, kCutTopWindows);
1085 widget->m_updateRegion &= drawable;
1086 }
1087
1088 std::size_t c, nrects = widget->m_updateRegion.size();
1089 Region::const_iterator it = widget->m_updateRegion.begin();
1090
1091 // Draw the widget
1092 Display* display = widget->display();
1093 int count = nrects-1;
1094 for (c=0; c<nrects; ++c, ++it, --count) {
1095 // Create the draw message
1096 msg = new PaintMessage(count, *it);
1097 msg->setDisplay(display);
1098 msg->setRecipient(widget);
1099
1100 // Enqueue the draw message
1101 manager->enqueueMessage(msg);
1102 }
1103
1104 display->addInvalidRegion(widget->m_updateRegion);
1105 widget->m_updateRegion.clear();
1106 }
1107 }
1108}
1109
1110void Widget::paint(Graphics* graphics,
1111 const gfx::Region& drawRegion,
1112 const bool isBg)
1113{
1114 if (drawRegion.isEmpty())
1115 return;
1116
1117 std::queue<Widget*> processing;
1118 processing.push(this);
1119
1120 Display* display = this->display();
1121
1122 while (!processing.empty()) {
1123 Widget* widget = processing.front();
1124 processing.pop();
1125
1126 ASSERT_VALID_WIDGET(widget);
1127
1128 // If the widget is hidden
1129 if (!widget->isVisible())
1130 continue;
1131
1132 for (auto child : widget->children())
1133 processing.push(child);
1134
1135 // Intersect drawRegion with widget's drawable region.
1136 Region region;
1137 widget->getDrawableRegion(region, kCutTopWindows);
1138 region.createIntersection(region, drawRegion);
1139
1140 Graphics graphics2(
1141 display,
1142 base::AddRef(graphics->getInternalSurface()),
1143 widget->bounds().x,
1144 widget->bounds().y);
1145 graphics2.setFont(AddRef(widget->font()));
1146
1147 for (const gfx::Rect& rc : region) {
1148 IntersectClip clip(&graphics2,
1149 Rect(rc).offset(
1150 -widget->bounds().x,
1151 -widget->bounds().y));
1152 widget->paintEvent(&graphics2, isBg);
1153 }
1154 }
1155}
1156
1157bool Widget::paintEvent(Graphics* graphics,
1158 const bool isBg)
1159{
1160 // For transparent widgets we have to draw the parent first.
1161 if (isTransparent()) {
1162#if _DEBUG
1163 // In debug mode we can fill the area with Red so we know if the
1164 // we are drawing the parent correctly.
1165 if (window() && !window()->ownDisplay())
1166 graphics->fillRect(gfx::rgba(255, 0, 0), clientBounds());
1167#endif
1168
1169 enableFlags(HIDDEN);
1170
1171 Widget* parentWidget = nullptr;
1172 if (type() == kWindowWidget) {
1173 parentWidget = display()->containedWidget();
1174
1175 if (get_multiple_displays()) {
1176 // Draw the desktop window as the background, not the manager
1177 // (as the manager contains all windows as children and will
1178 // try to draw other foreground windows here).
1179 if (parentWidget->type() == kManagerWidget) {
1180 parentWidget = static_cast<Manager*>(parentWidget)->getDesktopWindow();
1181 }
1182 // If this ui::Window has it's own native window (os::Window),
1183 // we'll fill a transparent rectangle because we already have
1184 // a transparent native window.
1185 else if (static_cast<Window*>(this)->ownDisplay()) {
1186 parentWidget = nullptr;
1187 }
1188 }
1189 }
1190 else {
1191 parentWidget = parent();
1192 }
1193 if (parentWidget) {
1194 gfx::Region rgn(parentWidget->bounds());
1195 rgn &= gfx::Region(
1196 graphics->getClipBounds().offset(
1197 graphics->getInternalDeltaX(),
1198 graphics->getInternalDeltaY()));
1199 parentWidget->paint(graphics, rgn, true);
1200 }
1201 else {
1202 // Clear surface with transparent color
1203 os::Paint p;
1204 p.color(gfx::rgba(0, 0, 0, 0));
1205 p.blendMode(os::BlendMode::Src);
1206 graphics->drawRect(clientBounds(), p);
1207 }
1208
1209 disableFlags(HIDDEN);
1210 }
1211
1212 PaintEvent ev(this, graphics);
1213 ev.setTransparentBg(isBg);
1214 onPaint(ev); // Fire onPaint event
1215 return ev.isPainted();
1216}
1217
1218bool Widget::isDoubleBuffered() const
1219{
1220 return hasFlags(DOUBLE_BUFFERED);
1221}
1222
1223void Widget::setDoubleBuffered(bool doubleBuffered)
1224{
1225 enableFlags(DOUBLE_BUFFERED);
1226}
1227
1228bool Widget::isTransparent() const
1229{
1230 return hasFlags(TRANSPARENT);
1231}
1232
1233void Widget::setTransparent(bool transparent)
1234{
1235 enableFlags(TRANSPARENT);
1236}
1237
1238void Widget::invalidate()
1239{
1240 assert_ui_thread();
1241 if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets
1242 onInvalidateRegion(Region(bounds()));
1243}
1244
1245void Widget::invalidateRect(const gfx::Rect& rect)
1246{
1247 assert_ui_thread();
1248 if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets
1249 onInvalidateRegion(Region(rect));
1250}
1251
1252void Widget::invalidateRegion(const Region& region)
1253{
1254 assert_ui_thread();
1255 if (!hasFlags(HIDDEN)) // Quick filter for hidden widgets
1256 onInvalidateRegion(region);
1257}
1258
1259class DeleteGraphicsAndSurface {
1260public:
1261 DeleteGraphicsAndSurface(const gfx::Rect& clip,
1262 const os::SurfaceRef& surface,
1263 os::SurfaceRef& dst)
1264 : m_pt(clip.origin())
1265 , m_surface(surface)
1266 , m_dst(dst) {
1267 }
1268
1269 void operator()(Graphics* graphics) {
1270 {
1271 os::SurfaceLock lockSrc(m_surface.get());
1272 os::SurfaceLock lockDst(m_dst.get());
1273 m_surface->blitTo(
1274 m_dst.get(), 0, 0, m_pt.x, m_pt.y,
1275 m_surface->width(), m_surface->height());
1276 }
1277 m_surface.reset();
1278 delete graphics;
1279 }
1280
1281private:
1282 gfx::Point m_pt;
1283 os::SurfaceRef m_surface;
1284 os::SurfaceRef m_dst;
1285};
1286
1287GraphicsPtr Widget::getGraphics(const gfx::Rect& clip)
1288{
1289 GraphicsPtr graphics;
1290 Display* display = this->display();
1291 os::SurfaceRef dstSurface = AddRef(display->surface());
1292
1293 // In case of double-buffering, we need to create the temporary
1294 // buffer only if the default surface is the screen.
1295 if (isDoubleBuffered() && dstSurface->isDirectToScreen()) {
1296 os::SurfaceRef surface =
1297 os::instance()->makeSurface(clip.w, clip.h);
1298 graphics.reset(new Graphics(display, surface, -clip.x, -clip.y),
1299 DeleteGraphicsAndSurface(clip, surface,
1300 dstSurface));
1301 }
1302 // In other case, we can draw directly onto the screen.
1303 else {
1304 graphics.reset(new Graphics(display, dstSurface, bounds().x, bounds().y));
1305 }
1306
1307 graphics->setFont(AddRef(font()));
1308 return graphics;
1309}
1310
1311// ===============================================================
1312// GUI MANAGER
1313// ===============================================================
1314
1315bool Widget::sendMessage(Message* msg)
1316{
1317 ASSERT(msg);
1318 return onProcessMessage(msg);
1319}
1320
1321void Widget::closeWindow()
1322{
1323 if (Window* w = window())
1324 w->closeWindow(this);
1325}
1326
1327void Widget::broadcastMouseMessage(const gfx::Point& screenPos,
1328 WidgetsList& targets)
1329{
1330 onBroadcastMouseMessage(screenPos, targets);
1331}
1332
1333// ===============================================================
1334// SIZE & POSITION
1335// ===============================================================
1336
1337/**
1338 Returns the preferred size of the Widget.
1339
1340 It checks if the preferred size is static (it means when it was
1341 set through #setSizeHint before) or if it is dynamic (this is
1342 the default and is when the #onSizeHint is used to determined
1343 the preferred size).
1344
1345 In another words, if you do not use #setSizeHint to set a
1346 <em>static preferred size</em> for the widget then #onSizeHint
1347 will be used to calculate it.
1348
1349 @see setSizeHint, onSizeHint, #sizeHint(const Size &)
1350*/
1351Size Widget::sizeHint()
1352{
1353 if (m_sizeHint)
1354 return *m_sizeHint;
1355 else {
1356 SizeHintEvent ev(this, Size(0, 0));
1357 onSizeHint(ev);
1358
1359 Size sz(ev.sizeHint());
1360 sz.w = std::clamp(sz.w, m_minSize.w, m_maxSize.w);
1361 sz.h = std::clamp(sz.h, m_minSize.h, m_maxSize.h);
1362 return sz;
1363 }
1364}
1365
1366/**
1367 Returns the preferred size trying to fit in the specified size.
1368 Remember that if you use #setSizeHint this routine will
1369 return the static size which you specified manually.
1370
1371 @param fitIn
1372 This can have both attributes (width and height) in
1373 zero, which means that it'll behave same as #sizeHint().
1374 If the width is great than zero the #onSizeHint will try to
1375 fit in that width (this is useful to fit Label or Edit controls
1376 in a specified width and calculate the height it could occupy).
1377
1378 @see sizeHint
1379*/
1380Size Widget::sizeHint(const Size& fitIn)
1381{
1382 if (m_sizeHint)
1383 return *m_sizeHint;
1384 else {
1385 SizeHintEvent ev(this, fitIn);
1386 onSizeHint(ev);
1387
1388 Size sz(ev.sizeHint());
1389 sz.w = std::clamp(sz.w, m_minSize.w, m_maxSize.w);
1390 sz.h = std::clamp(sz.h, m_minSize.h, m_maxSize.h);
1391 return sz;
1392 }
1393}
1394
1395/**
1396 Sets a fixed preferred size specified by the user.
1397 Widget::sizeHint() will return this value if it's setted.
1398*/
1399void Widget::setSizeHint(const Size& fixedSize)
1400{
1401 delete m_sizeHint;
1402 m_sizeHint = new Size(fixedSize);
1403}
1404
1405void Widget::setSizeHint(int fixedWidth, int fixedHeight)
1406{
1407 setSizeHint(Size(fixedWidth, fixedHeight));
1408}
1409
1410void Widget::resetSizeHint()
1411{
1412 if (m_sizeHint) {
1413 delete m_sizeHint;
1414 m_sizeHint = nullptr;
1415 }
1416}
1417
1418// ===============================================================
1419// FOCUS & MOUSE
1420// ===============================================================
1421
1422void Widget::requestFocus()
1423{
1424 if (auto man = manager())
1425 man->setFocus(this);
1426}
1427
1428void Widget::releaseFocus()
1429{
1430 if (hasFocus()) {
1431 if (auto man = manager())
1432 man->freeFocus();
1433 }
1434}
1435
1436// Captures the mouse to send all the future mouse messsages to the
1437// specified widget (included the kMouseMoveMessage and kSetCursorMessage).
1438void Widget::captureMouse()
1439{
1440 if (auto man = manager()) {
1441 if (!man->getCapture()) {
1442 man->setCapture(this);
1443 }
1444 }
1445}
1446
1447// Releases the capture of the mouse events.
1448void Widget::releaseMouse()
1449{
1450 if (auto man = manager()) {
1451 if (man->getCapture() == this) {
1452 man->freeCapture();
1453 }
1454 }
1455}
1456
1457bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
1458{
1459 if (hasCapture()) {
1460 const gfx::Point screenPos = mouseMsg->display()->nativeWindow()->pointToScreen(mouseMsg->position());
1461 auto man = manager();
1462 Widget* pick = (man ? man->pickFromScreenPos(screenPos): nullptr);
1463 if (pick && pick != this && pick->type() == widget_type) {
1464 releaseMouse();
1465
1466 MouseMessage* mouseMsg2 = new MouseMessage(
1467 kMouseDownMessage,
1468 *mouseMsg,
1469 mouseMsg->positionForDisplay(pick->display()));
1470 mouseMsg2->setDisplay(pick->display());
1471 mouseMsg2->setRecipient(pick);
1472 man->enqueueMessage(mouseMsg2);
1473 return true;
1474 }
1475 }
1476 return false;
1477}
1478
1479bool Widget::hasMouseOver() const
1480{
1481 return (this == pickFromScreenPos(get_mouse_position()));
1482}
1483
1484gfx::Point Widget::mousePosInDisplay() const
1485{
1486 return display()->nativeWindow()->pointFromScreen(get_mouse_position());
1487}
1488
1489void Widget::setMnemonic(int mnemonic)
1490{
1491 m_mnemonic = mnemonic;
1492}
1493
1494void Widget::processMnemonicFromText(int escapeChar)
1495{
1496 // Avoid calling setText() when the widget doesn't have the HAS_TEXT flag
1497 if (!hasText())
1498 return;
1499
1500 std::wstring newText; // wstring is used to properly push_back() multibyte chars
1501 if (!m_text.empty())
1502 newText.reserve(m_text.size());
1503
1504 base::utf8_decode decode(m_text);
1505 while (int chr = decode.next()) {
1506 if (chr == escapeChar) {
1507 chr = decode.next();
1508 if (!chr) {
1509 break; // Ill-formed string (it ends with escape character)
1510 }
1511 else if (chr != escapeChar) {
1512 setMnemonic(chr);
1513 }
1514 }
1515 newText.push_back(chr);
1516 }
1517
1518 setText(base::to_utf8(newText));
1519}
1520
1521bool Widget::isMnemonicPressed(const KeyMessage* keyMsg) const
1522{
1523 int chr = std::tolower(mnemonic());
1524 return
1525 ((chr) &&
1526 ((chr == std::tolower(keyMsg->unicodeChar())) ||
1527 (chr >= 'a' && chr <= 'z' && keyMsg->scancode() == (kKeyA + chr - 'a')) ||
1528 (chr >= '0' && chr <= '9' && keyMsg->scancode() == (kKey0 + chr - '0'))));
1529}
1530
1531bool Widget::onProcessMessage(Message* msg)
1532{
1533 ASSERT(msg != nullptr);
1534
1535 switch (msg->type()) {
1536
1537 case kOpenMessage:
1538 case kCloseMessage:
1539 case kWinMoveMessage:
1540 // Broadcast the message to the children.
1541 for (auto child : m_children)
1542 child->sendMessage(msg);
1543 break;
1544
1545 case kPaintMessage: {
1546 const PaintMessage* ptmsg = static_cast<const PaintMessage*>(msg);
1547 ASSERT(ptmsg->rect().w > 0);
1548 ASSERT(ptmsg->rect().h > 0);
1549
1550 GraphicsPtr graphics = getGraphics(toClient(ptmsg->rect()));
1551 return paintEvent(graphics.get(), false);
1552 }
1553
1554 case kDoubleClickMessage: {
1555 // Convert double clicks into mouse down
1556 MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
1557 MouseMessage mouseMsg2(kMouseDownMessage,
1558 *mouseMsg,
1559 mouseMsg->position());
1560 mouseMsg2.setDisplay(mouseMsg->display());
1561 sendMessage(&mouseMsg2);
1562 break;
1563 }
1564
1565 case kMouseDownMessage:
1566 case kMouseUpMessage:
1567 case kMouseMoveMessage:
1568 case kMouseWheelMessage:
1569 // Propagate the message to the parent.
1570 if (parent())
1571 return parent()->sendMessage(msg);
1572 else
1573 break;
1574
1575 case kSetCursorMessage:
1576 // Propagate the message to the parent.
1577 if (parent())
1578 return parent()->sendMessage(msg);
1579 else {
1580 set_mouse_cursor(kArrowCursor);
1581 return true;
1582 }
1583 break;
1584
1585 }
1586
1587 // Broadcast the message to the children.
1588 if (msg->propagateToChildren()) {
1589 for (auto child : m_children)
1590 if (child->sendMessage(msg))
1591 return true;
1592 }
1593
1594 // Propagate the message to the parent.
1595 if (msg->propagateToParent() && parent() &&
1596 msg->commonAncestor() != parent()) {
1597 return parent()->sendMessage(msg);
1598 }
1599
1600 return false;
1601}
1602
1603// ===============================================================
1604// EVENTS
1605// ===============================================================
1606
1607void Widget::onInvalidateRegion(const Region& region)
1608{
1609 if (!isVisible() || region.contains(bounds()) == Region::Out)
1610 return;
1611
1612 Region reg1;
1613 reg1.createUnion(m_updateRegion, region);
1614 {
1615 Region reg2;
1616 getDrawableRegion(reg2, kCutTopWindows);
1617 m_updateRegion.createIntersection(reg1, reg2);
1618 }
1619 reg1.createSubtraction(region, m_updateRegion);
1620
1621 setDirtyFlag();
1622
1623 for (auto child : m_children)
1624 child->invalidateRegion(reg1);
1625}
1626
1627void Widget::onSizeHint(SizeHintEvent& ev)
1628{
1629 if (m_style) {
1630 ev.setSizeHint(m_theme->calcSizeHint(this, style()));
1631 }
1632 else {
1633 ev.setSizeHint(m_minSize);
1634 }
1635}
1636
1637void Widget::onLoadLayout(LoadLayoutEvent& ev)
1638{
1639 // Do nothing
1640}
1641
1642void Widget::onSaveLayout(SaveLayoutEvent& ev)
1643{
1644 // Do nothing
1645}
1646
1647void Widget::onResize(ResizeEvent& ev)
1648{
1649 setBoundsQuietly(ev.bounds());
1650
1651 // Set all the children to the same "cpos".
1652 gfx::Rect cpos = childrenBounds();
1653 for (auto child : m_children)
1654 child->setBounds(cpos);
1655}
1656
1657void Widget::onPaint(PaintEvent& ev)
1658{
1659 if (m_style)
1660 m_theme->paintWidget(ev.graphics(), this, style(),
1661 clientBounds());
1662}
1663
1664void Widget::onBroadcastMouseMessage(const gfx::Point& screenPos,
1665 WidgetsList& targets)
1666{
1667 // Do nothing
1668}
1669
1670void Widget::onInitTheme(InitThemeEvent& ev)
1671{
1672 for (auto child : children())
1673 child->initTheme();
1674
1675 if (m_theme) {
1676 m_theme->initWidget(this);
1677
1678 if (!hasFlags(INITIALIZED))
1679 enableFlags(INITIALIZED);
1680
1681 InitTheme();
1682 }
1683}
1684
1685void Widget::onSetDecorativeWidgetBounds()
1686{
1687 if (m_theme)
1688 m_theme->setDecorativeWidgetBounds(this);
1689}
1690
1691void Widget::onVisible(bool visible)
1692{
1693 // Do nothing
1694}
1695
1696void Widget::onEnable(bool enabled)
1697{
1698 // Do nothing
1699}
1700
1701void Widget::onSelect(bool selected)
1702{
1703 // Do nothing
1704}
1705
1706void Widget::onSetText()
1707{
1708 invalidate();
1709}
1710
1711void Widget::onSetBgColor()
1712{
1713 invalidate();
1714}
1715
1716int Widget::onGetTextInt() const
1717{
1718 return std::strtol(m_text.c_str(), nullptr, 10);
1719}
1720
1721double Widget::onGetTextDouble() const
1722{
1723 return std::strtod(m_text.c_str(), nullptr);
1724}
1725
1726void Widget::offsetWidgets(int dx, int dy)
1727{
1728 if (dx == 0 && dy == 0)
1729 return;
1730
1731 m_updateRegion.offset(dx, dy);
1732 m_bounds.offset(dx, dy);
1733
1734 // Remove all paint messages for this widget.
1735 if (auto man = manager())
1736 man->removeMessagesFor(this, kPaintMessage);
1737
1738 for (auto child : m_children)
1739 child->offsetWidgets(dx, dy);
1740}
1741
1742void Widget::setDirtyFlag()
1743{
1744 Widget* widget = this;
1745 while (widget) {
1746 widget->enableFlags(DIRTY);
1747 widget = widget->parent();
1748 }
1749}
1750
1751} // namespace ui
1752