1 | // Aseprite UI Library |
2 | // Copyright (C) 2019-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/splitter.h" |
13 | |
14 | #include "ui/load_layout_event.h" |
15 | #include "ui/manager.h" |
16 | #include "ui/message.h" |
17 | #include "ui/resize_event.h" |
18 | #include "ui/save_layout_event.h" |
19 | #include "ui/scale.h" |
20 | #include "ui/size_hint_event.h" |
21 | #include "ui/system.h" |
22 | #include "ui/theme.h" |
23 | |
24 | #include <algorithm> |
25 | #include <sstream> |
26 | |
27 | namespace ui { |
28 | |
29 | using namespace gfx; |
30 | |
31 | Splitter::Splitter(Type type, int align) |
32 | : Widget(kSplitterWidget) |
33 | , m_type(type) |
34 | , m_userPos(50) |
35 | , m_pos(50) |
36 | , m_guiscale(guiscale()) |
37 | { |
38 | setAlign(align); |
39 | initTheme(); |
40 | } |
41 | |
42 | void Splitter::setPosition(double pos) |
43 | { |
44 | m_userPos = pos; |
45 | calcPos(); |
46 | onPositionChange(); |
47 | |
48 | invalidate(); |
49 | } |
50 | |
51 | bool Splitter::onProcessMessage(Message* msg) |
52 | { |
53 | switch (msg->type()) { |
54 | |
55 | case kMouseDownMessage: |
56 | if (!isEnabled()) |
57 | break; |
58 | else { |
59 | Widget* c1, *c2; |
60 | int x1, y1, x2, y2; |
61 | int bar, click_bar; |
62 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
63 | |
64 | bar = click_bar = 0; |
65 | |
66 | for (auto it=children().begin(), |
67 | end=children().end(); |
68 | it != end; ) { |
69 | auto next = it; |
70 | ++next; |
71 | |
72 | if (next != end) { |
73 | c1 = *it; |
74 | c2 = *next; |
75 | |
76 | ++bar; |
77 | |
78 | if (this->align() & HORIZONTAL) { |
79 | x1 = c1->bounds().x2(); |
80 | y1 = bounds().y; |
81 | x2 = c2->bounds().x; |
82 | y2 = bounds().y2(); |
83 | } |
84 | else { |
85 | x1 = bounds().x; |
86 | y1 = c1->bounds().y2(); |
87 | x2 = bounds().x2(); |
88 | y2 = c2->bounds().y; |
89 | } |
90 | |
91 | if ((mousePos.x >= x1) && (mousePos.x < x2) && |
92 | (mousePos.y >= y1) && (mousePos.y < y2)) |
93 | click_bar = bar; |
94 | } |
95 | |
96 | it = next; |
97 | } |
98 | |
99 | if (!click_bar) |
100 | break; |
101 | |
102 | captureMouse(); |
103 | |
104 | // Continue with motion message... |
105 | [[fallthrough]]; |
106 | } |
107 | |
108 | case kMouseMoveMessage: |
109 | if (hasCapture()) { |
110 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
111 | |
112 | if (align() & HORIZONTAL) { |
113 | switch (m_type) { |
114 | case ByPercentage: |
115 | m_userPos = 100.0 * (mousePos.x - bounds().x) / bounds().w; |
116 | break; |
117 | case ByPixel: |
118 | m_userPos = mousePos.x - bounds().x; |
119 | break; |
120 | } |
121 | } |
122 | else { |
123 | switch (m_type) { |
124 | case ByPercentage: |
125 | m_userPos = 100.0 * (mousePos.y - bounds().y) / bounds().h; |
126 | break; |
127 | case ByPixel: |
128 | m_userPos = mousePos.y - bounds().y; |
129 | break; |
130 | } |
131 | } |
132 | |
133 | calcPos(); |
134 | onPositionChange(); |
135 | return true; |
136 | } |
137 | break; |
138 | |
139 | case kMouseUpMessage: |
140 | if (hasCapture()) { |
141 | releaseMouse(); |
142 | return true; |
143 | } |
144 | break; |
145 | |
146 | case kSetCursorMessage: |
147 | if (isEnabled() && (!manager()->getCapture() || hasCapture())) { |
148 | gfx::Point mousePos = static_cast<MouseMessage*>(msg)->position(); |
149 | Widget* c1, *c2; |
150 | int x1, y1, x2, y2; |
151 | bool change_cursor = false; |
152 | |
153 | for (auto it=children().begin(), |
154 | end=children().end(); |
155 | it != end; ) { |
156 | auto next = it; |
157 | ++next; |
158 | |
159 | if (next != end) { |
160 | c1 = *it; |
161 | c2 = *(it+1); |
162 | |
163 | if (this->align() & HORIZONTAL) { |
164 | x1 = c1->bounds().x2(); |
165 | y1 = bounds().y; |
166 | x2 = c2->bounds().x; |
167 | y2 = bounds().y2(); |
168 | } |
169 | else { |
170 | x1 = bounds().x; |
171 | y1 = c1->bounds().y2(); |
172 | x2 = bounds().x2(); |
173 | y2 = c2->bounds().y; |
174 | } |
175 | |
176 | if ((mousePos.x >= x1) && (mousePos.x < x2) && |
177 | (mousePos.y >= y1) && (mousePos.y < y2)) { |
178 | change_cursor = true; |
179 | break; |
180 | } |
181 | } |
182 | |
183 | it = next; |
184 | } |
185 | |
186 | if (change_cursor) { |
187 | if (align() & HORIZONTAL) |
188 | set_mouse_cursor(kSizeWECursor); |
189 | else |
190 | set_mouse_cursor(kSizeNSCursor); |
191 | return true; |
192 | } |
193 | } |
194 | break; |
195 | |
196 | } |
197 | |
198 | return Widget::onProcessMessage(msg); |
199 | } |
200 | |
201 | void Splitter::onInitTheme(InitThemeEvent& ev) |
202 | { |
203 | if (m_type == ByPixel) m_pos /= m_guiscale; |
204 | m_guiscale = ui::guiscale(); |
205 | if (m_type == ByPixel) m_pos *= m_guiscale; |
206 | |
207 | Widget::onInitTheme(ev); |
208 | } |
209 | |
210 | void Splitter::onResize(ResizeEvent& ev) |
211 | { |
212 | #define LAYOUT_TWO_CHILDREN(x, y, w, h, l, t, r, b) \ |
213 | { \ |
214 | avail = rc.w - childSpacing(); \ |
215 | \ |
216 | pos.x = rc.x; \ |
217 | pos.y = rc.y; \ |
218 | switch (m_type) { \ |
219 | case ByPercentage: \ |
220 | pos.w = int(avail*m_pos/100); \ |
221 | break; \ |
222 | case ByPixel: \ |
223 | pos.w = int(m_pos); \ |
224 | break; \ |
225 | } \ |
226 | \ |
227 | /* TODO uncomment this to make a restricted splitter */ \ |
228 | /* pos.w = std::clamp(pos.w, reqSize1.w, avail-reqSize2.w); */ \ |
229 | pos.h = rc.h; \ |
230 | \ |
231 | child1->setBounds(pos); \ |
232 | gfx::Rect child1Pos = child1->bounds(); \ |
233 | \ |
234 | pos.x = child1Pos.x + child1Pos.w + childSpacing(); \ |
235 | pos.y = rc.y; \ |
236 | pos.w = avail - child1Pos.w; \ |
237 | pos.h = rc.h; \ |
238 | \ |
239 | child2->setBounds(pos); \ |
240 | } |
241 | |
242 | gfx::Rect rc(ev.bounds()); |
243 | gfx::Rect pos(0, 0, 0, 0); |
244 | int avail; |
245 | |
246 | setBoundsQuietly(rc); |
247 | calcPos(); |
248 | |
249 | Widget* child1 = panel1(); |
250 | Widget* child2 = panel2(); |
251 | |
252 | if (child1 && child2) { |
253 | if (align() & HORIZONTAL) { |
254 | LAYOUT_TWO_CHILDREN(x, y, w, h, l, t, r, b); |
255 | } |
256 | else { |
257 | LAYOUT_TWO_CHILDREN(y, x, h, w, t, l, b, r); |
258 | } |
259 | } |
260 | else if (child1) |
261 | child1->setBounds(rc); |
262 | else if (child2) |
263 | child2->setBounds(rc); |
264 | } |
265 | |
266 | void Splitter::onSizeHint(SizeHintEvent& ev) |
267 | { |
268 | #define GET_CHILD_SIZE(w, h) \ |
269 | do { \ |
270 | w = std::max(w, reqSize.w); \ |
271 | h = std::max(h, reqSize.h); \ |
272 | } while(0) |
273 | |
274 | #define FINAL_SIZE(w) \ |
275 | do { \ |
276 | w *= visibleChildren; \ |
277 | w += childSpacing() * (visibleChildren-1); \ |
278 | } while(0) |
279 | |
280 | int visibleChildren; |
281 | Size reqSize; |
282 | |
283 | visibleChildren = 0; |
284 | for (auto child : children()) { |
285 | if (child->isVisible()) |
286 | visibleChildren++; |
287 | } |
288 | |
289 | int w, h; |
290 | w = h = 0; |
291 | |
292 | for (auto child : children()) { |
293 | if (!child->isVisible()) |
294 | continue; |
295 | |
296 | reqSize = child->sizeHint(); |
297 | |
298 | if (this->align() & HORIZONTAL) |
299 | GET_CHILD_SIZE(w, h); |
300 | else |
301 | GET_CHILD_SIZE(h, w); |
302 | } |
303 | |
304 | if (visibleChildren > 0) { |
305 | if (this->align() & HORIZONTAL) |
306 | FINAL_SIZE(w); |
307 | else |
308 | FINAL_SIZE(h); |
309 | } |
310 | |
311 | w += border().width(); |
312 | h += border().height(); |
313 | |
314 | ev.setSizeHint(Size(w, h)); |
315 | } |
316 | |
317 | void Splitter::onLoadLayout(LoadLayoutEvent& ev) |
318 | { |
319 | ev.stream() >> m_userPos; |
320 | if (m_userPos < 0) m_userPos = 0; |
321 | if (m_type == ByPixel) |
322 | m_userPos *= m_guiscale; |
323 | |
324 | calcPos(); |
325 | } |
326 | |
327 | void Splitter::onSaveLayout(SaveLayoutEvent& ev) |
328 | { |
329 | double pos = (m_type == ByPixel ? m_userPos / m_guiscale: |
330 | m_userPos); |
331 | ev.stream() << pos; |
332 | } |
333 | |
334 | void Splitter::onPositionChange() |
335 | { |
336 | layout(); |
337 | } |
338 | |
339 | Widget* Splitter::panel1() const |
340 | { |
341 | const WidgetsList& list = children(); |
342 | if (list.size() >= 1 && list[0]->isVisible()) |
343 | return list[0]; |
344 | else |
345 | return nullptr; |
346 | } |
347 | |
348 | Widget* Splitter::panel2() const |
349 | { |
350 | const WidgetsList& list = children(); |
351 | if (list.size() >= 2 && list[1]->isVisible()) |
352 | return list[1]; |
353 | else |
354 | return nullptr; |
355 | } |
356 | |
357 | void Splitter::calcPos() |
358 | { |
359 | if (align() & HORIZONTAL) { |
360 | switch (m_type) { |
361 | case ByPercentage: |
362 | m_pos = std::clamp<double>(m_userPos, 0, 100); |
363 | break; |
364 | case ByPixel: |
365 | if (isVisible()) |
366 | m_pos = std::clamp<double>(m_userPos, 0, bounds().w); |
367 | break; |
368 | } |
369 | } |
370 | else { |
371 | switch (m_type) { |
372 | case ByPercentage: |
373 | m_pos = std::clamp<double>(m_userPos, 0, 100); |
374 | break; |
375 | case ByPixel: |
376 | if (isVisible()) |
377 | m_pos = std::clamp<double>(m_userPos, 0, bounds().h); |
378 | break; |
379 | } |
380 | } |
381 | } |
382 | |
383 | } // namespace ui |
384 | |