1 | // Aseprite |
2 | // Copyright (C) 2018-2021 Igara Studio S.A. |
3 | // |
4 | // This program is distributed under the terms of |
5 | // the End-User License Agreement for Aseprite. |
6 | |
7 | #ifndef APP_UI_DRAGGABLE_WIDGET_H_INCLUDED |
8 | #define APP_UI_DRAGGABLE_WIDGET_H_INCLUDED |
9 | #pragma once |
10 | |
11 | #include "os/surface.h" |
12 | #include "os/system.h" |
13 | #include "ui/graphics.h" |
14 | #include "ui/message.h" |
15 | #include "ui/overlay.h" |
16 | #include "ui/overlay_manager.h" |
17 | #include "ui/paint_event.h" |
18 | #include "ui/system.h" |
19 | #include "ui/view.h" |
20 | |
21 | namespace app { |
22 | |
23 | template<typename Base> |
24 | class DraggableWidget : public Base { |
25 | public: |
26 | template<typename...Args> |
27 | DraggableWidget(Args...args) : Base(args...) { } |
28 | |
29 | bool onProcessMessage(ui::Message* msg) override { |
30 | switch (msg->type()) { |
31 | |
32 | case ui::kSetCursorMessage: |
33 | if (m_floatingOverlay) { |
34 | const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); |
35 | const gfx::Point mousePos = mouseMsg->position(); |
36 | if (onCanDropItemsOutside() && |
37 | !getParentBounds().contains(mousePos)) { |
38 | ui::set_mouse_cursor(ui::kForbiddenCursor); |
39 | } |
40 | else { |
41 | ui::set_mouse_cursor(ui::kMoveCursor); |
42 | } |
43 | return true; |
44 | } |
45 | break; |
46 | |
47 | case ui::kMouseDownMessage: { |
48 | const bool wasCaptured = this->hasCapture(); |
49 | const bool result = Base::onProcessMessage(msg); |
50 | |
51 | if (!wasCaptured && this->hasCapture()) { |
52 | const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); |
53 | const gfx::Point mousePos = mouseMsg->position(); |
54 | m_dragMousePos = mousePos; |
55 | m_floatingOffset = mouseMsg->position() - this->bounds().origin(); |
56 | m_createFloatingOverlay = true; |
57 | } |
58 | return result; |
59 | } |
60 | |
61 | case ui::kMouseMoveMessage: { |
62 | const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); |
63 | const gfx::Point mousePos = mouseMsg->position(); |
64 | |
65 | if (this->hasCapture() && m_createFloatingOverlay) { |
66 | if (this->manager()->pick(mousePos) != this) { |
67 | m_createFloatingOverlay = false; |
68 | if (!m_floatingOverlay) |
69 | createFloatingOverlay(); |
70 | } |
71 | } |
72 | |
73 | if (m_floatingOverlay) { |
74 | m_floatingOverlay->moveOverlay(mousePos - m_floatingOffset); |
75 | |
76 | bool inside = true; |
77 | if (onCanDropItemsOutside()) { |
78 | inside = getParentBounds().contains(mousePos); |
79 | if (inside) { |
80 | if (this->hasFlags(ui::HIDDEN)) { |
81 | this->disableFlags(ui::HIDDEN); |
82 | layoutParent(); |
83 | } |
84 | } |
85 | else { |
86 | if (!this->hasFlags(ui::HIDDEN)) { |
87 | this->enableFlags(ui::HIDDEN); |
88 | layoutParent(); |
89 | } |
90 | } |
91 | } |
92 | |
93 | onReorderWidgets(mousePos, inside); |
94 | } |
95 | break; |
96 | } |
97 | |
98 | case ui::kMouseUpMessage: { |
99 | const ui::MouseMessage* mouseMsg = static_cast<ui::MouseMessage*>(msg); |
100 | const gfx::Point mousePos = mouseMsg->position(); |
101 | |
102 | m_wasDragged = (this->hasCapture() && m_floatingOverlay); |
103 | const bool result = Base::onProcessMessage(msg); |
104 | |
105 | if (!this->hasCapture()) { |
106 | if (m_floatingOverlay) { |
107 | destroyFloatingOverlay(); |
108 | ASSERT(!m_createFloatingOverlay); |
109 | onFinalDrop(getParentBounds().contains(mousePos)); |
110 | } |
111 | else if (m_createFloatingOverlay) |
112 | m_createFloatingOverlay = false; |
113 | } |
114 | |
115 | m_wasDragged = false; |
116 | return result; |
117 | } |
118 | |
119 | } |
120 | return Base::onProcessMessage(msg); |
121 | } |
122 | |
123 | bool wasDragged() const { |
124 | return m_wasDragged; |
125 | } |
126 | |
127 | bool isDragging() const { |
128 | return m_isDragging; |
129 | } |
130 | |
131 | private: |
132 | |
133 | void createFloatingOverlay() { |
134 | ASSERT(!m_floatingOverlay); |
135 | |
136 | m_isDragging = true; |
137 | |
138 | gfx::Size sz = getFloatingOverlaySize(); |
139 | sz.w = std::max(1, sz.w); |
140 | sz.h = std::max(1, sz.h); |
141 | os::SurfaceRef surface = os::instance()->makeRgbaSurface(sz.w, sz.h); |
142 | |
143 | { |
144 | os::SurfaceLock lock(surface.get()); |
145 | os::Paint paint; |
146 | paint.color(gfx::rgba(0, 0, 0, 0)); |
147 | paint.style(os::Paint::Fill); |
148 | surface->drawRect(gfx::Rect(0, 0, surface->width(), surface->height()), paint); |
149 | } |
150 | |
151 | ui::Display* display = this->Base::display(); |
152 | { |
153 | ui::Graphics g(display, surface, 0, 0); |
154 | g.setFont(AddRef(this->font())); |
155 | drawFloatingOverlay(g); |
156 | } |
157 | |
158 | m_floatingOverlay = base::make_ref<ui::Overlay>( |
159 | display, surface, gfx::Point(), |
160 | (ui::Overlay::ZOrder)(ui::Overlay::MouseZOrder-1)); |
161 | ui::OverlayManager::instance()->addOverlay(m_floatingOverlay); |
162 | } |
163 | |
164 | void destroyFloatingOverlay() { |
165 | ui::OverlayManager::instance()->removeOverlay(m_floatingOverlay); |
166 | m_floatingOverlay.reset(); |
167 | m_isDragging = false; |
168 | } |
169 | |
170 | gfx::Size getFloatingOverlaySize() { |
171 | auto view = ui::View::getView(this->parent()); |
172 | if (view) |
173 | return (view->viewportBounds().offset(view->viewScroll()) & this->bounds()).size(); |
174 | else |
175 | return this->size(); |
176 | } |
177 | |
178 | gfx::Rect getParentBounds() { |
179 | auto view = ui::View::getView(this->parent()); |
180 | if (view) |
181 | return view->viewportBounds(); |
182 | else |
183 | return this->parent()->bounds(); |
184 | } |
185 | |
186 | void layoutParent() { |
187 | this->parent()->layout(); |
188 | auto view = ui::View::getView(this->parent()); |
189 | if (view) |
190 | return view->updateView(); |
191 | } |
192 | |
193 | void drawFloatingOverlay(ui::Graphics& g) { |
194 | ui::PaintEvent ev(this, &g); |
195 | this->onPaint(ev); |
196 | } |
197 | |
198 | virtual bool onCanDropItemsOutside() { return true; } |
199 | virtual void onReorderWidgets(const gfx::Point& mousePos, bool inside) { } |
200 | virtual void onFinalDrop(bool inside) { } |
201 | |
202 | // True if we should create the floating overlay after leaving the |
203 | // widget bounds. |
204 | bool m_createFloatingOverlay = false; |
205 | |
206 | bool m_isDragging = false; |
207 | |
208 | // True when the mouse button is released (drop operation) and we've |
209 | // dragged the widget to other position. Can be used to avoid |
210 | // triggering the default click operation by derived classes when |
211 | // we've dragged the widget. |
212 | bool m_wasDragged = false; |
213 | |
214 | // Initial mouse position when we start the dragging process. |
215 | gfx::Point m_dragMousePos; |
216 | |
217 | // Overlay used to show the floating widget (this overlay floats |
218 | // next to the mouse cursor). |
219 | ui::OverlayRef m_floatingOverlay; |
220 | |
221 | // Relative mouse position between the widget and the overlay. |
222 | gfx::Point m_floatingOffset; |
223 | }; |
224 | |
225 | } // namespace app |
226 | |
227 | #endif |
228 | |