Serenity Operating System
at master 279 lines 9.2 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * Copyright (c) 2022, networkException <networkexception@serenityos.org> 5 * 6 * SPDX-License-Identifier: BSD-2-Clause 7 */ 8 9#include <AK/JsonObject.h> 10#include <LibCore/Timer.h> 11#include <LibGUI/AbstractButton.h> 12#include <LibGUI/Painter.h> 13#include <LibGUI/Window.h> 14#include <LibGfx/Palette.h> 15 16namespace GUI { 17 18AbstractButton::AbstractButton(String text) 19{ 20 set_text(move(text)); 21 22 set_focus_policy(GUI::FocusPolicy::StrongFocus); 23 set_background_role(Gfx::ColorRole::Button); 24 set_foreground_role(Gfx::ColorRole::ButtonText); 25 26 m_auto_repeat_timer = add<Core::Timer>(); 27 m_auto_repeat_timer->on_timeout = [this] { 28 click(); 29 }; 30 31 // FIXME: Port JsonValue to the new String class. 32 register_property( 33 "text", 34 [this]() { return this->text().to_deprecated_string(); }, 35 [this](auto& value) { 36 this->set_text(String::from_deprecated_string(value.to_deprecated_string()).release_value_but_fixme_should_propagate_errors()); 37 return true; 38 }); 39 REGISTER_BOOL_PROPERTY("checked", is_checked, set_checked); 40 REGISTER_BOOL_PROPERTY("checkable", is_checkable, set_checkable); 41 REGISTER_BOOL_PROPERTY("exclusive", is_exclusive, set_exclusive); 42} 43 44void AbstractButton::set_text(String text) 45{ 46 if (m_text == text) 47 return; 48 m_text = move(text); 49 update(); 50} 51 52void AbstractButton::set_checked(bool checked, AllowCallback allow_callback) 53{ 54 if (m_checked == checked) 55 return; 56 m_checked = checked; 57 58 if (is_exclusive() && checked && parent_widget()) { 59 bool sibling_had_focus = false; 60 parent_widget()->for_each_child_of_type<AbstractButton>([&](auto& sibling) { 61 if (!sibling.is_exclusive()) 62 return IterationDecision::Continue; 63 if (window() && window()->focused_widget() == &sibling) 64 sibling_had_focus = true; 65 if (!sibling.is_checked()) 66 return IterationDecision::Continue; 67 sibling.m_checked = false; 68 sibling.update(); 69 if (sibling.on_checked) 70 sibling.on_checked(false); 71 return IterationDecision::Continue; 72 }); 73 m_checked = true; 74 if (sibling_had_focus) 75 set_focus(true); 76 } 77 78 if (is_exclusive() && parent_widget()) { 79 // In a group of exclusive checkable buttons, only the currently checked button is focusable. 80 parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& button) { 81 if (button.is_exclusive() && button.is_checkable()) 82 button.set_focus_policy(button.is_checked() ? GUI::FocusPolicy::StrongFocus : GUI::FocusPolicy::NoFocus); 83 return IterationDecision::Continue; 84 }); 85 } 86 87 update(); 88 if (on_checked && allow_callback == AllowCallback::Yes) 89 on_checked(checked); 90} 91 92void AbstractButton::set_checkable(bool checkable) 93{ 94 if (m_checkable == checkable) 95 return; 96 m_checkable = checkable; 97 update(); 98} 99 100void AbstractButton::mousemove_event(MouseEvent& event) 101{ 102 bool is_over = rect().contains(event.position()); 103 m_hovered = is_over; 104 if (event.buttons() & m_pressed_mouse_button) { 105 bool being_pressed = is_over; 106 if (being_pressed != m_being_pressed) { 107 m_being_pressed = being_pressed; 108 if (m_auto_repeat_interval) { 109 if (!m_being_pressed) 110 m_auto_repeat_timer->stop(); 111 else 112 m_auto_repeat_timer->start(m_auto_repeat_interval); 113 } 114 update(); 115 } 116 } 117 Widget::mousemove_event(event); 118} 119 120void AbstractButton::mousedown_event(MouseEvent& event) 121{ 122 if (event.button() & m_allowed_mouse_buttons_for_pressing) { 123 m_being_pressed = true; 124 m_pressed_mouse_button = event.button(); 125 repaint(); 126 127 if (m_auto_repeat_interval) { 128 click(); 129 m_auto_repeat_timer->start(m_auto_repeat_interval); 130 } 131 event.accept(); 132 } 133 Widget::mousedown_event(event); 134} 135 136void AbstractButton::mouseup_event(MouseEvent& event) 137{ 138 if (event.button() == m_pressed_mouse_button && m_being_pressed) { 139 bool was_auto_repeating = m_auto_repeat_timer->is_active(); 140 m_auto_repeat_timer->stop(); 141 m_was_being_pressed = m_being_pressed; 142 ScopeGuard update_was_being_pressed { [this] { m_was_being_pressed = m_being_pressed; } }; 143 m_being_pressed = false; 144 m_pressed_mouse_button = MouseButton::None; 145 if (!is_checkable() || is_checked()) 146 repaint(); 147 if (m_was_being_pressed && !was_auto_repeating) { 148 switch (event.button()) { 149 case MouseButton::Primary: 150 click(event.modifiers()); 151 break; 152 case MouseButton::Middle: 153 middle_mouse_click(event.modifiers()); 154 break; 155 default: 156 VERIFY_NOT_REACHED(); 157 } 158 } 159 } 160 Widget::mouseup_event(event); 161} 162 163void AbstractButton::doubleclick_event(GUI::MouseEvent& event) 164{ 165 double_click(event.modifiers()); 166 Widget::doubleclick_event(event); 167} 168 169void AbstractButton::enter_event(Core::Event&) 170{ 171 m_hovered = true; 172 update(); 173} 174 175void AbstractButton::leave_event(Core::Event& event) 176{ 177 m_hovered = false; 178 if (m_being_keyboard_pressed) 179 m_being_keyboard_pressed = m_being_pressed = false; 180 update(); 181 event.accept(); 182 Widget::leave_event(event); 183} 184 185void AbstractButton::focusout_event(GUI::FocusEvent& event) 186{ 187 if (m_being_keyboard_pressed) { 188 m_being_pressed = m_being_keyboard_pressed = false; 189 event.accept(); 190 update(); 191 } 192 Widget::focusout_event(event); 193} 194 195void AbstractButton::keydown_event(KeyEvent& event) 196{ 197 if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space) { 198 m_being_pressed = m_being_keyboard_pressed = true; 199 update(); 200 event.accept(); 201 return; 202 } else if (m_being_pressed && event.key() == KeyCode::Key_Escape) { 203 m_being_pressed = m_being_keyboard_pressed = false; 204 update(); 205 event.accept(); 206 return; 207 } 208 209 // Arrow keys switch the currently checked option within an exclusive group of checkable buttons. 210 if (event.is_arrow_key() && !event.modifiers() && is_exclusive() && is_checkable() && parent_widget()) { 211 event.accept(); 212 Vector<GUI::AbstractButton&> exclusive_siblings; 213 size_t this_index = 0; 214 parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& sibling) { 215 if (&sibling == this) { 216 VERIFY(is_enabled()); 217 this_index = exclusive_siblings.size(); 218 } 219 if (sibling.is_exclusive() && sibling.is_checkable() && sibling.is_enabled()) 220 exclusive_siblings.append(sibling); 221 return IterationDecision::Continue; 222 }); 223 if (exclusive_siblings.size() <= 1) 224 return; 225 size_t new_checked_index; 226 if (event.key() == KeyCode::Key_Left || event.key() == KeyCode::Key_Up) 227 new_checked_index = this_index == 0 ? exclusive_siblings.size() - 1 : this_index - 1; 228 else 229 new_checked_index = this_index == exclusive_siblings.size() - 1 ? 0 : this_index + 1; 230 exclusive_siblings[new_checked_index].click(); 231 return; 232 } 233 Widget::keydown_event(event); 234} 235 236void AbstractButton::keyup_event(KeyEvent& event) 237{ 238 bool was_being_pressed = m_being_pressed; 239 if (was_being_pressed && (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space)) { 240 m_being_pressed = m_being_keyboard_pressed = false; 241 click(event.modifiers()); 242 update(); 243 event.accept(); 244 return; 245 } 246 Widget::keyup_event(event); 247} 248 249void AbstractButton::paint_text(Painter& painter, Gfx::IntRect const& rect, Gfx::Font const& font, Gfx::TextAlignment text_alignment, Gfx::TextWrapping text_wrapping) 250{ 251 auto clipped_rect = rect.intersected(this->rect()); 252 253 if (!is_enabled()) { 254 painter.draw_text(clipped_rect.translated(1, 1), text(), font, text_alignment, palette().disabled_text_back(), Gfx::TextElision::Right, text_wrapping); 255 painter.draw_text(clipped_rect, text(), font, text_alignment, palette().disabled_text_front(), Gfx::TextElision::Right, text_wrapping); 256 return; 257 } 258 259 if (text().is_empty()) 260 return; 261 painter.draw_text(clipped_rect, text(), font, text_alignment, palette().color(foreground_role()), Gfx::TextElision::Right, text_wrapping); 262} 263 264void AbstractButton::change_event(Event& event) 265{ 266 if (event.type() == Event::Type::EnabledChange) { 267 if (m_auto_repeat_timer->is_active()) 268 m_auto_repeat_timer->stop(); 269 if (!is_enabled()) { 270 bool was_being_pressed = m_being_pressed; 271 m_being_pressed = false; 272 if (was_being_pressed) 273 update(); 274 } 275 } 276 Widget::change_event(event); 277} 278 279}