1 | // Aseprite UI Library |
2 | // Copyright (C) 2019-2021 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 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "ui/button.h" |
13 | #include "ui/manager.h" |
14 | #include "ui/message.h" |
15 | #include "ui/size_hint_event.h" |
16 | #include "ui/theme.h" |
17 | #include "ui/widget.h" |
18 | #include "ui/window.h" |
19 | |
20 | #include <queue> |
21 | #include <cstring> |
22 | |
23 | namespace ui { |
24 | |
25 | ButtonBase::ButtonBase(const std::string& text, |
26 | const WidgetType type, |
27 | const WidgetType behaviorType, |
28 | const WidgetType drawType) |
29 | : Widget(type) |
30 | , m_pressedStatus(false) |
31 | , m_behaviorType(behaviorType) |
32 | , m_handleSelect(true) |
33 | { |
34 | setAlign(CENTER | MIDDLE); |
35 | setText(text); |
36 | setFocusStop(true); |
37 | |
38 | // Initialize theme |
39 | setType(drawType); // TODO Fix this nasty trick |
40 | initTheme(); |
41 | setType(type); |
42 | } |
43 | |
44 | ButtonBase::~ButtonBase() |
45 | { |
46 | } |
47 | |
48 | WidgetType ButtonBase::behaviorType() const |
49 | { |
50 | return m_behaviorType; |
51 | } |
52 | |
53 | void ButtonBase::onClick(Event& ev) |
54 | { |
55 | // Fire Click() signal |
56 | Click(ev); |
57 | } |
58 | |
59 | bool ButtonBase::onProcessMessage(Message* msg) |
60 | { |
61 | switch (msg->type()) { |
62 | |
63 | case kFocusEnterMessage: |
64 | case kFocusLeaveMessage: |
65 | if (isEnabled()) { |
66 | if (m_behaviorType == kButtonWidget) { |
67 | // Deselect the widget (maybe the user press the key, but |
68 | // before release it, changes the focus). |
69 | if (isSelected()) |
70 | setSelected(false); |
71 | } |
72 | |
73 | // TODO theme specific stuff |
74 | invalidate(); |
75 | } |
76 | break; |
77 | |
78 | case kKeyDownMessage: { |
79 | KeyMessage* keymsg = static_cast<KeyMessage*>(msg); |
80 | KeyScancode scancode = keymsg->scancode(); |
81 | |
82 | if (isEnabled() && isVisible()) { |
83 | bool mnemonicPressed = |
84 | ((msg->altPressed() || msg->cmdPressed()) && |
85 | isMnemonicPressed(keymsg)); |
86 | |
87 | // For kButtonWidget |
88 | if (m_behaviorType == kButtonWidget) { |
89 | // Has focus and press enter/space |
90 | if (hasFocus()) { |
91 | if ((scancode == kKeyEnter) || |
92 | (scancode == kKeyEnterPad) || |
93 | (scancode == kKeySpace)) { |
94 | setSelected(true); |
95 | return true; |
96 | } |
97 | } |
98 | |
99 | if (// Check if the user pressed mnemonic |
100 | mnemonicPressed || |
101 | // Magnetic widget catches ENTERs |
102 | (isFocusMagnet() && |
103 | ((scancode == kKeyEnter) || |
104 | (scancode == kKeyEnterPad)))) { |
105 | manager()->setFocus(this); |
106 | |
107 | // Dispatch focus movement messages (because the buttons |
108 | // process them) |
109 | manager()->dispatchMessages(); |
110 | |
111 | setSelected(true); |
112 | return true; |
113 | } |
114 | } |
115 | // For kCheckWidget or kRadioWidget |
116 | else { |
117 | // If the widget has the focus and the user press space or |
118 | // if the user press Alt+the underscored letter of the button |
119 | if ((hasFocus() && (scancode == kKeySpace)) || mnemonicPressed) { |
120 | if (m_behaviorType == kCheckWidget) { |
121 | // Swap the select status |
122 | setSelected(!isSelected()); |
123 | invalidate(); |
124 | } |
125 | else if (m_behaviorType == kRadioWidget) { |
126 | if (!isSelected()) { |
127 | setSelected(true); |
128 | } |
129 | } |
130 | return true; |
131 | } |
132 | } |
133 | } |
134 | break; |
135 | } |
136 | |
137 | case kKeyUpMessage: |
138 | if (isEnabled() && hasFocus()) { |
139 | switch (m_behaviorType) { |
140 | |
141 | case kButtonWidget: |
142 | if (isSelected()) { |
143 | generateButtonSelectSignal(); |
144 | return true; |
145 | } |
146 | break; |
147 | |
148 | case kCheckWidget: { |
149 | // Fire onClick() event |
150 | Event ev(this); |
151 | onClick(ev); |
152 | return true; |
153 | } |
154 | |
155 | } |
156 | } |
157 | break; |
158 | |
159 | case kMouseDownMessage: |
160 | switch (m_behaviorType) { |
161 | |
162 | case kButtonWidget: |
163 | if (isEnabled()) { |
164 | setSelected(true); |
165 | |
166 | m_pressedStatus = isSelected(); |
167 | captureMouse(); |
168 | |
169 | onStartDrag(); |
170 | } |
171 | return true; |
172 | |
173 | case kCheckWidget: |
174 | if (isEnabled()) { |
175 | setSelected(!isSelected()); |
176 | |
177 | m_pressedStatus = isSelected(); |
178 | captureMouse(); |
179 | |
180 | onStartDrag(); |
181 | } |
182 | return true; |
183 | |
184 | case kRadioWidget: |
185 | if (isEnabled()) { |
186 | if (!isSelected()) { |
187 | m_handleSelect = false; |
188 | setSelected(true); |
189 | m_handleSelect = true; |
190 | |
191 | m_pressedStatus = isSelected(); |
192 | captureMouse(); |
193 | |
194 | onStartDrag(); |
195 | } |
196 | } |
197 | return true; |
198 | } |
199 | break; |
200 | |
201 | case kMouseUpMessage: |
202 | if (hasCapture()) { |
203 | releaseMouse(); |
204 | |
205 | if (hasMouseOver()) { |
206 | switch (m_behaviorType) { |
207 | |
208 | case kButtonWidget: |
209 | generateButtonSelectSignal(); |
210 | break; |
211 | |
212 | case kCheckWidget: |
213 | { |
214 | // Fire onClick() event |
215 | Event ev(this); |
216 | onClick(ev); |
217 | |
218 | invalidate(); |
219 | } |
220 | break; |
221 | |
222 | case kRadioWidget: |
223 | { |
224 | setSelected(false); |
225 | setSelected(true); |
226 | |
227 | // Fire onClick() event |
228 | Event ev(this); |
229 | onClick(ev); |
230 | } |
231 | break; |
232 | } |
233 | } |
234 | return true; |
235 | } |
236 | break; |
237 | |
238 | case kMouseMoveMessage: |
239 | if (isEnabled() && hasCapture()) { |
240 | m_handleSelect = false; |
241 | onSelectWhenDragging(); |
242 | m_handleSelect = true; |
243 | } |
244 | break; |
245 | |
246 | case kMouseEnterMessage: |
247 | case kMouseLeaveMessage: |
248 | // TODO theme stuff |
249 | if (isEnabled()) |
250 | invalidate(); |
251 | break; |
252 | } |
253 | |
254 | return Widget::onProcessMessage(msg); |
255 | } |
256 | |
257 | void ButtonBase::generateButtonSelectSignal() |
258 | { |
259 | // Deselect |
260 | setSelected(false); |
261 | |
262 | // Fire onClick() event |
263 | Event ev(this); |
264 | onClick(ev); |
265 | } |
266 | |
267 | void ButtonBase::onStartDrag() |
268 | { |
269 | // Do nothing |
270 | } |
271 | |
272 | void ButtonBase::onSelectWhenDragging() |
273 | { |
274 | bool hasMouse = hasMouseOver(); |
275 | |
276 | // Switch state when the mouse go out |
277 | if ((hasMouse && isSelected() != m_pressedStatus) || |
278 | (!hasMouse && isSelected() == m_pressedStatus)) { |
279 | if (hasMouse) |
280 | setSelected(m_pressedStatus); |
281 | else |
282 | setSelected(!m_pressedStatus); |
283 | } |
284 | } |
285 | |
286 | // ====================================================================== |
287 | // Button class |
288 | // ====================================================================== |
289 | |
290 | Button::Button(const std::string& text) |
291 | : ButtonBase(text, kButtonWidget, kButtonWidget, kButtonWidget) |
292 | { |
293 | setAlign(CENTER | MIDDLE); |
294 | } |
295 | |
296 | // ====================================================================== |
297 | // CheckBox class |
298 | // ====================================================================== |
299 | |
300 | CheckBox::CheckBox(const std::string& text, |
301 | const WidgetType drawType) |
302 | : ButtonBase(text, kCheckWidget, kCheckWidget, drawType) |
303 | { |
304 | setAlign(LEFT | MIDDLE); |
305 | } |
306 | |
307 | // ====================================================================== |
308 | // RadioButton class |
309 | // ====================================================================== |
310 | |
311 | RadioButton::RadioButton(const std::string& text, |
312 | const int radioGroup, |
313 | const WidgetType drawType) |
314 | : ButtonBase(text, kRadioWidget, kRadioWidget, drawType) |
315 | { |
316 | setAlign(LEFT | MIDDLE); |
317 | setRadioGroup(radioGroup); |
318 | } |
319 | |
320 | void RadioButton::setRadioGroup(int radioGroup) |
321 | { |
322 | m_radioGroup = radioGroup; |
323 | |
324 | // TODO: Update old and new groups |
325 | } |
326 | |
327 | int RadioButton::getRadioGroup() const |
328 | { |
329 | return m_radioGroup; |
330 | } |
331 | |
332 | void RadioButton::deselectRadioGroup() |
333 | { |
334 | Widget* widget = window(); |
335 | if (!widget) |
336 | return; |
337 | |
338 | std::queue<Widget*> allChildrens; |
339 | allChildrens.push(widget); |
340 | |
341 | while (!allChildrens.empty()) { |
342 | widget = allChildrens.front(); |
343 | allChildrens.pop(); |
344 | |
345 | if (RadioButton* radioButton = dynamic_cast<RadioButton*>(widget)) { |
346 | if (radioButton->getRadioGroup() == m_radioGroup) |
347 | radioButton->setSelected(false); |
348 | } |
349 | |
350 | for (auto child : widget->children()) { |
351 | allChildrens.push(child); |
352 | } |
353 | } |
354 | } |
355 | |
356 | void RadioButton::onSelect(bool selected) |
357 | { |
358 | ButtonBase::onSelect(selected); |
359 | if (!selected) |
360 | return; |
361 | |
362 | if (!m_handleSelect) |
363 | return; |
364 | |
365 | if (behaviorType() == kRadioWidget) { |
366 | deselectRadioGroup(); |
367 | |
368 | m_handleSelect = false; |
369 | setSelected(true); |
370 | m_handleSelect = true; |
371 | } |
372 | } |
373 | |
374 | } // namespace ui |
375 | |