1 | // Aseprite UI Library |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2017 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "ui/window.h" |
13 | |
14 | #include "gfx/size.h" |
15 | #include "ui/button.h" |
16 | #include "ui/display.h" |
17 | #include "ui/fit_bounds.h" |
18 | #include "ui/graphics.h" |
19 | #include "ui/intern.h" |
20 | #include "ui/label.h" |
21 | #include "ui/manager.h" |
22 | #include "ui/message.h" |
23 | #include "ui/message_loop.h" |
24 | #include "ui/move_region.h" |
25 | #include "ui/resize_event.h" |
26 | #include "ui/scale.h" |
27 | #include "ui/size_hint_event.h" |
28 | #include "ui/system.h" |
29 | #include "ui/theme.h" |
30 | |
31 | #include <algorithm> |
32 | |
33 | namespace ui { |
34 | |
35 | using namespace gfx; |
36 | |
37 | namespace { |
38 | |
39 | enum { |
40 | WINDOW_NONE = 0, |
41 | WINDOW_MOVE = 1, |
42 | WINDOW_RESIZE_LEFT = 2, |
43 | WINDOW_RESIZE_RIGHT = 4, |
44 | WINDOW_RESIZE_TOP = 8, |
45 | WINDOW_RESIZE_BOTTOM = 16, |
46 | }; |
47 | |
48 | gfx::Point clickedMousePos; |
49 | gfx::Rect* clickedWindowPos = nullptr; |
50 | |
51 | class WindowTitleLabel : public Label { |
52 | public: |
53 | WindowTitleLabel(const std::string& text) : Label(text) { |
54 | setDecorative(true); |
55 | setType(kWindowTitleLabelWidget); |
56 | initTheme(); |
57 | } |
58 | }; |
59 | |
60 | |
61 | // Controls the "X" button in a window to close it. |
62 | class WindowCloseButton : public ButtonBase { |
63 | public: |
64 | WindowCloseButton() |
65 | : ButtonBase("" , kWindowCloseButtonWidget, |
66 | kButtonWidget, kButtonWidget) { |
67 | setDecorative(true); |
68 | initTheme(); |
69 | } |
70 | |
71 | protected: |
72 | |
73 | void onClick(Event& ev) override { |
74 | ButtonBase::onClick(ev); |
75 | closeWindow(); |
76 | } |
77 | |
78 | bool onProcessMessage(Message* msg) override { |
79 | switch (msg->type()) { |
80 | |
81 | case kSetCursorMessage: |
82 | ui::set_mouse_cursor(kArrowCursor); |
83 | return true; |
84 | |
85 | case kKeyDownMessage: |
86 | if (window()->shouldProcessEscKeyToCloseWindow() && |
87 | static_cast<KeyMessage*>(msg)->scancode() == kKeyEsc) { |
88 | setSelected(true); |
89 | return true; |
90 | } |
91 | break; |
92 | |
93 | case kKeyUpMessage: |
94 | if (window()->shouldProcessEscKeyToCloseWindow() && |
95 | static_cast<KeyMessage*>(msg)->scancode() == kKeyEsc) { |
96 | if (isSelected()) { |
97 | setSelected(false); |
98 | closeWindow(); |
99 | return true; |
100 | } |
101 | } |
102 | break; |
103 | } |
104 | |
105 | return ButtonBase::onProcessMessage(msg); |
106 | } |
107 | }; |
108 | |
109 | } // anonymous namespace |
110 | |
111 | Window::Window(Type type, const std::string& text) |
112 | : Widget(kWindowWidget) |
113 | , m_display(nullptr) |
114 | , m_closer(nullptr) |
115 | , m_titleLabel(nullptr) |
116 | , m_closeButton(nullptr) |
117 | , m_ownDisplay(false) |
118 | , m_isDesktop(type == DesktopWindow) |
119 | , m_isMoveable(!m_isDesktop) |
120 | , m_isSizeable(!m_isDesktop) |
121 | , m_isOnTop(false) |
122 | , m_isWantFocus(true) |
123 | , m_isForeground(false) |
124 | , m_isAutoRemap(true) |
125 | { |
126 | setVisible(false); |
127 | setAlign(LEFT | MIDDLE); |
128 | if (type == WithTitleBar) { |
129 | setText(text); |
130 | addChild(m_closeButton = new WindowCloseButton); |
131 | } |
132 | |
133 | initTheme(); |
134 | } |
135 | |
136 | Window::~Window() |
137 | { |
138 | if (auto man = manager()) |
139 | man->_closeWindow(this, isVisible()); |
140 | } |
141 | |
142 | Display* Window::display() const |
143 | { |
144 | if (m_display) |
145 | return m_display; |
146 | else if (auto man = manager()) |
147 | return man->display(); |
148 | else |
149 | return nullptr; |
150 | } |
151 | |
152 | void Window::setDisplay(Display* display, const bool own) |
153 | { |
154 | if (m_display) |
155 | m_display->removeWindow(this); |
156 | |
157 | m_display = display; |
158 | m_ownDisplay = own; |
159 | |
160 | if (m_display) |
161 | m_display->addWindow(this); |
162 | } |
163 | |
164 | void Window::setAutoRemap(bool state) |
165 | { |
166 | m_isAutoRemap = state; |
167 | } |
168 | |
169 | void Window::setMoveable(bool state) |
170 | { |
171 | m_isMoveable = state; |
172 | } |
173 | |
174 | void Window::setSizeable(bool state) |
175 | { |
176 | m_isSizeable = state; |
177 | } |
178 | |
179 | void Window::setOnTop(bool state) |
180 | { |
181 | m_isOnTop = state; |
182 | } |
183 | |
184 | void Window::setWantFocus(bool state) |
185 | { |
186 | m_isWantFocus = state; |
187 | } |
188 | |
189 | HitTest Window::hitTest(const gfx::Point& point) |
190 | { |
191 | HitTestEvent ev(this, point, HitTestNowhere); |
192 | onHitTest(ev); |
193 | return ev.hit(); |
194 | } |
195 | |
196 | void Window::loadNativeFrame(const gfx::Rect& frame) |
197 | { |
198 | m_lastFrame = frame; |
199 | |
200 | // Just in case the saved value is too small, we can take the value |
201 | // as invalid. |
202 | gfx::Size sz = sizeHint() * guiscale(); |
203 | if (display()) |
204 | sz *= display()->scale(); |
205 | if (m_lastFrame.w < sz.w/5 || |
206 | m_lastFrame.h < sz.h/5) { |
207 | m_lastFrame.setSize(sz); |
208 | } |
209 | } |
210 | |
211 | void Window::onClose(CloseEvent& ev) |
212 | { |
213 | // Fire Close signal |
214 | Close(ev); |
215 | } |
216 | |
217 | void Window::onHitTest(HitTestEvent& ev) |
218 | { |
219 | HitTest ht = HitTestNowhere; |
220 | |
221 | // If this window is not movable |
222 | if (!m_isMoveable) { |
223 | ev.setHit(ht); |
224 | return; |
225 | } |
226 | |
227 | // TODO check why this is necessary, there should be a bug in |
228 | // the manager where we are receiving mouse events and are not |
229 | // the top most window. |
230 | Widget* picked = pick(ev.point()); |
231 | if (picked && |
232 | picked != this && |
233 | picked->type() != kWindowTitleLabelWidget) { |
234 | ev.setHit(ht); |
235 | return; |
236 | } |
237 | |
238 | int x = ev.point().x; |
239 | int y = ev.point().y; |
240 | gfx::Rect pos = bounds(); |
241 | gfx::Rect cpos = childrenBounds(); |
242 | |
243 | // Move |
244 | if ((hasText()) |
245 | && (((x >= cpos.x) && |
246 | (x < cpos.x2()) && |
247 | (y >= pos.y+border().bottom()) && |
248 | (y < cpos.y)))) { |
249 | ht = HitTestCaption; |
250 | } |
251 | // Resize |
252 | else if (m_isSizeable) { |
253 | #ifdef __APPLE__ |
254 | // TODO on macOS we cannot start resize actions on native windows |
255 | if (ownDisplay()) { |
256 | ev.setHit(ht); |
257 | return; |
258 | } |
259 | #endif |
260 | |
261 | if ((x >= pos.x) && (x < cpos.x)) { |
262 | if ((y >= pos.y) && (y < cpos.y)) |
263 | ht = HitTestBorderNW; |
264 | else if ((y > cpos.y2()-1) && (y <= pos.y2()-1)) |
265 | ht = HitTestBorderSW; |
266 | else |
267 | ht = HitTestBorderW; |
268 | } |
269 | else if ((y >= pos.y) && (y < cpos.y)) { |
270 | if ((x >= pos.x) && (x < cpos.x)) |
271 | ht = HitTestBorderNW; |
272 | else if ((x > cpos.x2()-1) && (x <= pos.x2()-1)) |
273 | ht = HitTestBorderNE; |
274 | else |
275 | ht = HitTestBorderN; |
276 | } |
277 | else if ((x > cpos.x2()-1) && (x <= pos.x2()-1)) { |
278 | if ((y >= pos.y) && (y < cpos.y)) |
279 | ht = HitTestBorderNE; |
280 | else if ((y > cpos.y2()-1) && (y <= pos.y2()-1)) |
281 | ht = HitTestBorderSE; |
282 | else |
283 | ht = HitTestBorderE; |
284 | } |
285 | else if ((y > cpos.y2()-1) && (y <= pos.y2()-1)) { |
286 | if ((x >= pos.x) && (x < cpos.x)) |
287 | ht = HitTestBorderSW; |
288 | else if ((x > cpos.x2()-1) && (x <= pos.x2()-1)) |
289 | ht = HitTestBorderSE; |
290 | else |
291 | ht = HitTestBorderS; |
292 | } |
293 | } |
294 | else { |
295 | // Client area |
296 | ht = HitTestClient; |
297 | } |
298 | |
299 | ev.setHit(ht); |
300 | } |
301 | |
302 | void Window::onOpen(Event& ev) |
303 | { |
304 | // Fire Open signal |
305 | Open(ev); |
306 | } |
307 | |
308 | void Window::onWindowResize() |
309 | { |
310 | // Do nothing |
311 | } |
312 | |
313 | void Window::onWindowMovement() |
314 | { |
315 | // Do nothing |
316 | } |
317 | |
318 | void Window::remapWindow() |
319 | { |
320 | if (m_isAutoRemap) { |
321 | m_isAutoRemap = false; |
322 | setVisible(true); |
323 | } |
324 | |
325 | expandWindow(sizeHint()); |
326 | |
327 | // load layout |
328 | loadLayout(); |
329 | |
330 | invalidate(); |
331 | } |
332 | |
333 | void Window::centerWindow(Display* parentDisplay) |
334 | { |
335 | if (m_isAutoRemap) |
336 | remapWindow(); |
337 | |
338 | if (!parentDisplay) |
339 | parentDisplay = manager()->getDefault()->display(); |
340 | |
341 | ASSERT(parentDisplay); |
342 | |
343 | if (m_isAutoRemap) |
344 | remapWindow(); |
345 | |
346 | const gfx::Size displaySize = parentDisplay->size(); |
347 | const gfx::Size windowSize = bounds().size(); |
348 | |
349 | fit_bounds(parentDisplay, |
350 | this, |
351 | gfx::Rect(displaySize.w/2 - windowSize.w/2, |
352 | displaySize.h/2 - windowSize.h/2, |
353 | windowSize.w, windowSize.h)); |
354 | } |
355 | |
356 | void Window::moveWindow(const gfx::Rect& rect) |
357 | { |
358 | moveWindow(rect, true); |
359 | } |
360 | |
361 | void Window::expandWindow(const gfx::Size& size) |
362 | { |
363 | const gfx::Rect oldBounds = bounds(); |
364 | |
365 | if (ownDisplay()) { |
366 | os::Window* nativeWindow = display()->nativeWindow(); |
367 | const int scale = nativeWindow->scale(); |
368 | gfx::Rect frame = nativeWindow->frame(); |
369 | frame.setSize(size * scale); |
370 | nativeWindow->setFrame(frame); |
371 | setBounds(gfx::Rect(bounds().origin(), size)); |
372 | |
373 | layout(); |
374 | invalidate(); |
375 | } |
376 | else { |
377 | setBounds(gfx::Rect(bounds().origin(), size)); |
378 | |
379 | layout(); |
380 | manager()->invalidateRect(oldBounds); |
381 | } |
382 | } |
383 | |
384 | void Window::openWindow() |
385 | { |
386 | if (!parent()) { |
387 | Manager::getDefault()->_openWindow(this, m_isAutoRemap); |
388 | |
389 | // Open event |
390 | Event ev(this); |
391 | onOpen(ev); |
392 | } |
393 | } |
394 | |
395 | void Window::openWindowInForeground() |
396 | { |
397 | m_isForeground = true; |
398 | |
399 | openWindow(); |
400 | |
401 | Manager::getDefault()->_runModalWindow(this); |
402 | |
403 | m_isForeground = false; |
404 | } |
405 | |
406 | void Window::closeWindow(Widget* closer) |
407 | { |
408 | // Close event |
409 | CloseEvent ev(closer); |
410 | onBeforeClose(ev); |
411 | if (ev.canceled()) |
412 | return; |
413 | |
414 | m_closer = closer; |
415 | if (m_ownDisplay) |
416 | m_lastFrame = m_display->nativeWindow()->frame(); |
417 | |
418 | if (auto man = manager()) |
419 | man->_closeWindow(this, true); |
420 | |
421 | onClose(ev); |
422 | } |
423 | |
424 | bool Window::isTopLevel() |
425 | { |
426 | Widget* manager = this->manager(); |
427 | if (!manager->children().empty()) |
428 | return (this == UI_FIRST_WIDGET(manager->children())); |
429 | else |
430 | return false; |
431 | } |
432 | |
433 | bool Window::onProcessMessage(Message* msg) |
434 | { |
435 | switch (msg->type()) { |
436 | |
437 | case kOpenMessage: |
438 | m_closer = nullptr; |
439 | break; |
440 | |
441 | case kCloseMessage: |
442 | saveLayout(); |
443 | break; |
444 | |
445 | case kMouseDownMessage: { |
446 | if (!m_isMoveable) |
447 | break; |
448 | |
449 | clickedMousePos = static_cast<MouseMessage*>(msg)->position(); |
450 | m_hitTest = hitTest(clickedMousePos); |
451 | |
452 | if (m_hitTest != HitTestNowhere && |
453 | m_hitTest != HitTestClient) { |
454 | if (clickedWindowPos == nullptr) |
455 | clickedWindowPos = new gfx::Rect(bounds()); |
456 | else |
457 | *clickedWindowPos = bounds(); |
458 | |
459 | // Handle native window action |
460 | if (ownDisplay()) { |
461 | os::WindowAction action = os::WindowAction::Cancel; |
462 | switch (m_hitTest) { |
463 | case HitTestCaption: action = os::WindowAction::Move; break; |
464 | case HitTestBorderNW: action = os::WindowAction::ResizeFromTopLeft; break; |
465 | case HitTestBorderN: action = os::WindowAction::ResizeFromTop; break; |
466 | case HitTestBorderNE: action = os::WindowAction::ResizeFromTopRight; break; |
467 | case HitTestBorderW: action = os::WindowAction::ResizeFromLeft; break; |
468 | case HitTestBorderE: action = os::WindowAction::ResizeFromRight; break; |
469 | case HitTestBorderSW: action = os::WindowAction::ResizeFromBottomLeft; break; |
470 | case HitTestBorderS: action = os::WindowAction::ResizeFromBottom; break; |
471 | case HitTestBorderSE: action = os::WindowAction::ResizeFromBottomRight; break; |
472 | } |
473 | if (action != os::WindowAction::Cancel) { |
474 | display()->nativeWindow()->performWindowAction(action, nullptr); |
475 | |
476 | // As Window::moveWindow() will not be called, we have to |
477 | // call onWindowMovement() event from here. |
478 | if (action == os::WindowAction::Move) |
479 | onWindowMovement(); |
480 | |
481 | return true; |
482 | } |
483 | } |
484 | |
485 | captureMouse(); |
486 | return true; |
487 | } |
488 | else |
489 | break; |
490 | } |
491 | |
492 | case kMouseUpMessage: |
493 | if (hasCapture()) { |
494 | releaseMouse(); |
495 | set_mouse_cursor(kArrowCursor); |
496 | |
497 | if (clickedWindowPos != nullptr) { |
498 | delete clickedWindowPos; |
499 | clickedWindowPos = nullptr; |
500 | } |
501 | |
502 | m_hitTest = HitTestNowhere; |
503 | return true; |
504 | } |
505 | break; |
506 | |
507 | case kMouseMoveMessage: |
508 | if (!m_isMoveable) |
509 | break; |
510 | |
511 | // Does it have the mouse captured? |
512 | if (hasCapture()) { |
513 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
514 | |
515 | // Reposition/resize |
516 | if (m_hitTest == HitTestCaption) { |
517 | int x = clickedWindowPos->x + (mousePos.x - clickedMousePos.x); |
518 | int y = clickedWindowPos->y + (mousePos.y - clickedMousePos.y); |
519 | moveWindow(gfx::Rect(x, y, |
520 | bounds().w, |
521 | bounds().h), true); |
522 | } |
523 | else { |
524 | int x, y, w, h; |
525 | |
526 | w = clickedWindowPos->w; |
527 | h = clickedWindowPos->h; |
528 | |
529 | bool hitLeft = (m_hitTest == HitTestBorderNW || |
530 | m_hitTest == HitTestBorderW || |
531 | m_hitTest == HitTestBorderSW); |
532 | bool hitTop = (m_hitTest == HitTestBorderNW || |
533 | m_hitTest == HitTestBorderN || |
534 | m_hitTest == HitTestBorderNE); |
535 | bool hitRight = (m_hitTest == HitTestBorderNE || |
536 | m_hitTest == HitTestBorderE || |
537 | m_hitTest == HitTestBorderSE); |
538 | bool hitBottom = (m_hitTest == HitTestBorderSW || |
539 | m_hitTest == HitTestBorderS || |
540 | m_hitTest == HitTestBorderSE); |
541 | |
542 | if (hitLeft) { |
543 | w += clickedMousePos.x - mousePos.x; |
544 | } |
545 | else if (hitRight) { |
546 | w += mousePos.x - clickedMousePos.x; |
547 | } |
548 | |
549 | if (hitTop) { |
550 | h += (clickedMousePos.y - mousePos.y); |
551 | } |
552 | else if (hitBottom) { |
553 | h += (mousePos.y - clickedMousePos.y); |
554 | } |
555 | |
556 | limitSize(&w, &h); |
557 | |
558 | if ((bounds().w != w) || |
559 | (bounds().h != h)) { |
560 | if (hitLeft) |
561 | x = clickedWindowPos->x - (w - clickedWindowPos->w); |
562 | else |
563 | x = bounds().x; |
564 | |
565 | if (hitTop) |
566 | y = clickedWindowPos->y - (h - clickedWindowPos->h); |
567 | else |
568 | y = bounds().y; |
569 | |
570 | moveWindow(gfx::Rect(x, y, w, h), false); |
571 | invalidate(); |
572 | } |
573 | } |
574 | } |
575 | break; |
576 | |
577 | case kSetCursorMessage: |
578 | if (m_isMoveable) { |
579 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
580 | HitTest ht = hitTest(mousePos); |
581 | CursorType cursor = kArrowCursor; |
582 | |
583 | switch (ht) { |
584 | case HitTestCaption: cursor = kArrowCursor; break; |
585 | case HitTestBorderNW: cursor = kSizeNWCursor; break; |
586 | case HitTestBorderW: cursor = kSizeWCursor; break; |
587 | case HitTestBorderSW: cursor = kSizeSWCursor; break; |
588 | case HitTestBorderNE: cursor = kSizeNECursor; break; |
589 | case HitTestBorderE: cursor = kSizeECursor; break; |
590 | case HitTestBorderSE: cursor = kSizeSECursor; break; |
591 | case HitTestBorderN: cursor = kSizeNCursor; break; |
592 | case HitTestBorderS: cursor = kSizeSCursor; break; |
593 | } |
594 | |
595 | set_mouse_cursor(cursor); |
596 | return true; |
597 | } |
598 | break; |
599 | |
600 | } |
601 | |
602 | return Widget::onProcessMessage(msg); |
603 | } |
604 | |
605 | // TODO similar to Manager::onInvalidateRegion |
606 | void Window::onInvalidateRegion(const gfx::Region& region) |
607 | { |
608 | if (!ownDisplay()) { |
609 | Widget::onInvalidateRegion(region); |
610 | return; |
611 | } |
612 | |
613 | if (!isVisible() || region.contains(bounds()) == gfx::Region::Out) |
614 | return; |
615 | |
616 | Display* display = this->display(); |
617 | |
618 | // Intersect only with window bounds, we don't need to use |
619 | // getDrawableRegion() because each sub-window in the display will |
620 | // be processed in the following for() loop |
621 | gfx::Region reg1; |
622 | reg1.createIntersection(region, gfx::Region(bounds())); |
623 | |
624 | // Redraw windows from top to background. |
625 | for (auto window : display->getWindows()) { |
626 | // Invalidating the manager only works for the main display, to |
627 | // invalidate windows you have to invalidate them. |
628 | if (window->ownDisplay()) { |
629 | ASSERT(this == window); |
630 | break; |
631 | } |
632 | |
633 | // Invalidate regions of this window |
634 | window->invalidateRegion(reg1); |
635 | |
636 | // Clip this window area for the next window. |
637 | gfx::Region reg2; |
638 | window->getRegion(reg2); |
639 | reg1 -= reg2; |
640 | } |
641 | |
642 | // TODO we should be able to modify m_updateRegion directly here, |
643 | // so we avoid the getDrawableRegion() call from |
644 | // Widget::onInvalidateRegion(). |
645 | if (!reg1.isEmpty()) |
646 | Widget::onInvalidateRegion(reg1); |
647 | } |
648 | |
649 | void Window::onResize(ResizeEvent& ev) |
650 | { |
651 | windowSetPosition(ev.bounds()); |
652 | } |
653 | |
654 | void Window::onSizeHint(SizeHintEvent& ev) |
655 | { |
656 | Widget* manager = this->manager(); |
657 | |
658 | if (m_isDesktop) { |
659 | Rect cpos = manager->childrenBounds(); |
660 | ev.setSizeHint(cpos.w, cpos.h); |
661 | } |
662 | else { |
663 | Size maxSize(0, 0); |
664 | Size reqSize; |
665 | |
666 | if (m_titleLabel) |
667 | maxSize.w = maxSize.h = 16*guiscale(); |
668 | |
669 | for (auto child : children()) { |
670 | if (!child->isDecorative()) { |
671 | reqSize = child->sizeHint(); |
672 | |
673 | maxSize.w = std::max(maxSize.w, reqSize.w); |
674 | maxSize.h = std::max(maxSize.h, reqSize.h); |
675 | } |
676 | } |
677 | |
678 | if (m_titleLabel) |
679 | maxSize.w = std::max(maxSize.w, m_titleLabel->sizeHint().w); |
680 | |
681 | ev.setSizeHint(maxSize.w + border().width(), |
682 | maxSize.h + border().height()); |
683 | } |
684 | } |
685 | |
686 | void Window::onBroadcastMouseMessage(const gfx::Point& screenPos, |
687 | WidgetsList& targets) |
688 | { |
689 | if (!ownDisplay() || display()->nativeWindow()->frame().contains(screenPos)) |
690 | targets.push_back(this); |
691 | |
692 | // Continue sending the message to siblings windows until a desktop |
693 | // or foreground window. |
694 | if (isForeground() || isDesktop()) |
695 | return; |
696 | |
697 | Widget* sibling = nextSibling(); |
698 | if (sibling) |
699 | sibling->broadcastMouseMessage(screenPos, targets); |
700 | } |
701 | |
702 | void Window::onSetText() |
703 | { |
704 | onBuildTitleLabel(); |
705 | Widget::onSetText(); |
706 | initTheme(); |
707 | } |
708 | |
709 | void Window::onBuildTitleLabel() |
710 | { |
711 | if (text().empty()) { |
712 | if (m_titleLabel) { |
713 | removeChild(m_titleLabel); |
714 | delete m_titleLabel; |
715 | m_titleLabel = nullptr; |
716 | } |
717 | } |
718 | else { |
719 | if (!m_titleLabel) { |
720 | m_titleLabel = new WindowTitleLabel(text()); |
721 | addChild(m_titleLabel); |
722 | } |
723 | else { |
724 | m_titleLabel->setText(text()); |
725 | m_titleLabel->setBounds( |
726 | gfx::Rect(m_titleLabel->bounds()).setSize( |
727 | m_titleLabel->sizeHint())); |
728 | } |
729 | } |
730 | } |
731 | |
732 | void Window::windowSetPosition(const gfx::Rect& rect) |
733 | { |
734 | // Copy the new position rectangle |
735 | setBoundsQuietly(rect); |
736 | Rect cpos = childrenBounds(); |
737 | |
738 | // Set all the children to the same "cpos" |
739 | for (auto child : children()) { |
740 | if (child->isDecorative()) |
741 | child->setDecorativeWidgetBounds(); |
742 | else |
743 | child->setBounds(cpos); |
744 | } |
745 | |
746 | onWindowResize(); |
747 | } |
748 | |
749 | void Window::limitSize(int* w, int* h) |
750 | { |
751 | *w = std::max(*w, border().width()); |
752 | *h = std::max(*h, border().height()); |
753 | } |
754 | |
755 | void Window::moveWindow(const gfx::Rect& rect, bool use_blit) |
756 | { |
757 | Manager* manager = this->manager(); |
758 | |
759 | // Discard enqueued kWinMoveMessage for this window because we are |
760 | // going to send a new kWinMoveMessage with the latest window |
761 | // bounds. |
762 | manager->removeMessagesFor(this, kWinMoveMessage); |
763 | |
764 | // Get the window's current position |
765 | Rect old_pos = bounds(); |
766 | int dx = rect.x - old_pos.x; |
767 | int dy = rect.y - old_pos.y; |
768 | |
769 | // Get the manager's current position |
770 | Rect man_pos = manager->bounds(); |
771 | |
772 | // Send a kWinMoveMessage message to the window |
773 | Message* msg = new Message(kWinMoveMessage); |
774 | msg->setRecipient(this); |
775 | manager->enqueueMessage(msg); |
776 | |
777 | // Get the region & the drawable region of the window |
778 | Region oldDrawableRegion; |
779 | getDrawableRegion(oldDrawableRegion, kCutTopWindowsAndUseChildArea); |
780 | |
781 | // If the size of the window changes... |
782 | if (old_pos.w != rect.w || old_pos.h != rect.h) { |
783 | // We have to change the position of all children. |
784 | windowSetPosition(rect); |
785 | } |
786 | else { |
787 | // We can just displace all the widgets by a delta (new_position - |
788 | // old_position)... |
789 | offsetWidgets(dx, dy); |
790 | } |
791 | |
792 | // Get the new drawable region of the window (it's new because we |
793 | // moved the window to "rect") |
794 | Region newDrawableRegion; |
795 | getDrawableRegion(newDrawableRegion, kCutTopWindowsAndUseChildArea); |
796 | |
797 | // First of all, we have to find the manager region to invalidate, |
798 | // it's the old window drawable region without the new window |
799 | // drawable region. |
800 | Region invalidManagerRegion; |
801 | invalidManagerRegion.createSubtraction( |
802 | oldDrawableRegion, |
803 | newDrawableRegion); |
804 | |
805 | // In second place, we have to setup the window invalid region... |
806 | |
807 | // If "use_blit" isn't activated, we have to redraw the whole window |
808 | // (sending kPaintMessage messages) in the new drawable region |
809 | if (!use_blit) { |
810 | invalidateRegion(newDrawableRegion); |
811 | } |
812 | // If "use_blit" is activated, we can move the old drawable to the |
813 | // new position (to redraw as little as possible). |
814 | else { |
815 | Region reg1; |
816 | reg1 = newDrawableRegion; |
817 | reg1.offset(-dx, -dy); |
818 | |
819 | Region moveableRegion; |
820 | moveableRegion.createIntersection(oldDrawableRegion, reg1); |
821 | |
822 | // Move the window's graphics |
823 | Display* display = this->display(); |
824 | ScreenGraphics g(display); |
825 | hide_mouse_cursor(); |
826 | { |
827 | IntersectClip clip(&g, man_pos); |
828 | if (clip) { |
829 | ui::move_region(display, moveableRegion, dx, dy); |
830 | } |
831 | } |
832 | show_mouse_cursor(); |
833 | |
834 | reg1.createSubtraction(reg1, moveableRegion); |
835 | reg1.offset(dx, dy); |
836 | invalidateRegion(reg1); |
837 | } |
838 | |
839 | manager->invalidateRegion(invalidManagerRegion); |
840 | |
841 | onWindowMovement(); |
842 | } |
843 | |
844 | } // namespace ui |
845 | |