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
23namespace ui {
24
25ButtonBase::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
44ButtonBase::~ButtonBase()
45{
46}
47
48WidgetType ButtonBase::behaviorType() const
49{
50 return m_behaviorType;
51}
52
53void ButtonBase::onClick(Event& ev)
54{
55 // Fire Click() signal
56 Click(ev);
57}
58
59bool 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
257void ButtonBase::generateButtonSelectSignal()
258{
259 // Deselect
260 setSelected(false);
261
262 // Fire onClick() event
263 Event ev(this);
264 onClick(ev);
265}
266
267void ButtonBase::onStartDrag()
268{
269 // Do nothing
270}
271
272void 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
290Button::Button(const std::string& text)
291 : ButtonBase(text, kButtonWidget, kButtonWidget, kButtonWidget)
292{
293 setAlign(CENTER | MIDDLE);
294}
295
296// ======================================================================
297// CheckBox class
298// ======================================================================
299
300CheckBox::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
311RadioButton::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
320void RadioButton::setRadioGroup(int radioGroup)
321{
322 m_radioGroup = radioGroup;
323
324 // TODO: Update old and new groups
325}
326
327int RadioButton::getRadioGroup() const
328{
329 return m_radioGroup;
330}
331
332void 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
356void 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