Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/JsonObject.h>
28#include <LibCore/Timer.h>
29#include <LibGUI/AbstractButton.h>
30#include <LibGUI/Painter.h>
31#include <LibGfx/Palette.h>
32
33namespace GUI {
34
35AbstractButton::AbstractButton(const StringView& text)
36 : m_text(text)
37{
38 m_auto_repeat_timer = add<Core::Timer>();
39 m_auto_repeat_timer->on_timeout = [this] {
40 click();
41 };
42}
43
44AbstractButton::~AbstractButton()
45{
46}
47
48void AbstractButton::set_text(const StringView& text)
49{
50 if (m_text == text)
51 return;
52 m_text = text;
53 update();
54}
55
56void AbstractButton::set_checked(bool checked)
57{
58 if (m_checked == checked)
59 return;
60 m_checked = checked;
61
62 if (is_exclusive() && checked) {
63 parent_widget()->for_each_child_of_type<AbstractButton>([&](auto& sibling) {
64 if (!sibling.is_exclusive() || !sibling.is_checked())
65 return IterationDecision::Continue;
66 sibling.m_checked = false;
67 sibling.update();
68 if (sibling.on_checked)
69 sibling.on_checked(false);
70 return IterationDecision::Continue;
71 });
72 m_checked = true;
73 }
74
75 update();
76 if (on_checked)
77 on_checked(checked);
78}
79
80void AbstractButton::set_checkable(bool checkable)
81{
82 if (m_checkable == checkable)
83 return;
84 m_checkable = checkable;
85 update();
86}
87
88void AbstractButton::mousemove_event(MouseEvent& event)
89{
90 bool is_over = rect().contains(event.position());
91 m_hovered = is_over;
92 if (event.buttons() & MouseButton::Left) {
93 if (is_enabled()) {
94 bool being_pressed = is_over;
95 if (being_pressed != m_being_pressed) {
96 m_being_pressed = being_pressed;
97 if (m_auto_repeat_interval) {
98 if (!m_being_pressed)
99 m_auto_repeat_timer->stop();
100 else
101 m_auto_repeat_timer->start(m_auto_repeat_interval);
102 }
103 update();
104 }
105 }
106 }
107 Widget::mousemove_event(event);
108}
109
110void AbstractButton::mousedown_event(MouseEvent& event)
111{
112#ifdef GABSTRACTBUTTON_DEBUG
113 dbgprintf("AbstractButton::mouse_down_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
114#endif
115 if (event.button() == MouseButton::Left) {
116 if (is_enabled()) {
117 m_being_pressed = true;
118 update();
119
120 if (m_auto_repeat_interval) {
121 click();
122 m_auto_repeat_timer->start(m_auto_repeat_interval);
123 }
124 }
125 }
126 Widget::mousedown_event(event);
127}
128
129void AbstractButton::mouseup_event(MouseEvent& event)
130{
131#ifdef GABSTRACTBUTTON_DEBUG
132 dbgprintf("AbstractButton::mouse_up_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
133#endif
134 if (event.button() == MouseButton::Left) {
135 bool was_auto_repeating = m_auto_repeat_timer->is_active();
136 m_auto_repeat_timer->stop();
137 if (is_enabled()) {
138 bool was_being_pressed = m_being_pressed;
139 m_being_pressed = false;
140 update();
141 if (was_being_pressed && !was_auto_repeating)
142 click();
143 }
144 }
145 Widget::mouseup_event(event);
146}
147
148void AbstractButton::enter_event(Core::Event&)
149{
150 m_hovered = true;
151 update();
152}
153
154void AbstractButton::leave_event(Core::Event&)
155{
156 m_hovered = false;
157 update();
158}
159
160void AbstractButton::keydown_event(KeyEvent& event)
161{
162 if (event.key() == KeyCode::Key_Return) {
163 click();
164 event.accept();
165 return;
166 }
167 Widget::keydown_event(event);
168}
169
170void AbstractButton::paint_text(Painter& painter, const Gfx::Rect& rect, const Gfx::Font& font, Gfx::TextAlignment text_alignment)
171{
172 auto clipped_rect = rect.intersected(this->rect());
173
174 if (!is_enabled()) {
175 painter.draw_text(clipped_rect.translated(1, 1), text(), font, text_alignment, Color::White, Gfx::TextElision::Right);
176 painter.draw_text(clipped_rect, text(), font, text_alignment, Color::from_rgb(0x808080), Gfx::TextElision::Right);
177 return;
178 }
179
180 if (text().is_empty())
181 return;
182 painter.draw_text(clipped_rect, text(), font, text_alignment, palette().button_text(), Gfx::TextElision::Right);
183 if (is_focused())
184 painter.draw_rect(clipped_rect.inflated(6, 4), palette().focus_outline());
185}
186
187void AbstractButton::change_event(Event& event)
188{
189 if (event.type() == Event::Type::EnabledChange) {
190 if (!is_enabled()) {
191 bool was_being_pressed = m_being_pressed;
192 m_being_pressed = false;
193 if (was_being_pressed)
194 update();
195 }
196 }
197 Widget::change_event(event);
198}
199
200void AbstractButton::save_to(JsonObject& json)
201{
202 json.set("text", m_text);
203 json.set("checked", m_checked);
204 json.set("checkable", m_checkable);
205 json.set("exclusive", m_exclusive);
206 Widget::save_to(json);
207}
208
209bool AbstractButton::set_property(const StringView& name, const JsonValue& value)
210{
211 if (name == "text") {
212 set_text(value.to_string());
213 return true;
214 }
215 if (name == "checked") {
216 set_checked(value.to_bool());
217 return true;
218 }
219 if (name == "checkable") {
220 set_checkable(value.to_bool());
221 return true;
222 }
223 if (name == "exclusive") {
224 set_exclusive(value.to_bool());
225 return true;
226 }
227
228 return Widget::set_property(name, value);
229}
230
231}