Serenity Operating System
at master 218 lines 7.7 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <AK/StdLibExtras.h> 9#include <LibGUI/Painter.h> 10#include <LibGUI/Slider.h> 11#include <LibGfx/Palette.h> 12#include <LibGfx/StylePainter.h> 13 14REGISTER_WIDGET(GUI, HorizontalSlider) 15REGISTER_WIDGET(GUI, Slider) 16REGISTER_WIDGET(GUI, VerticalSlider) 17 18namespace GUI { 19 20Slider::Slider(Orientation orientation) 21 : AbstractSlider(orientation) 22{ 23 REGISTER_ENUM_PROPERTY("knob_size_mode", knob_size_mode, set_knob_size_mode, KnobSizeMode, 24 { KnobSizeMode::Fixed, "Fixed" }, 25 { KnobSizeMode::Proportional, "Proportional" }); 26 REGISTER_BOOL_PROPERTY("jump_to_cursor", jump_to_cursor, set_jump_to_cursor); 27 28 set_preferred_size(SpecialDimension::Fit); 29} 30 31void Slider::paint_event(PaintEvent& event) 32{ 33 Painter painter(*this); 34 painter.add_clip_rect(event.rect()); 35 36 Gfx::IntRect track_rect; 37 38 if (orientation() == Orientation::Horizontal) { 39 track_rect = { inner_rect().x(), 0, inner_rect().width(), track_size() }; 40 track_rect.center_vertically_within(inner_rect()); 41 } else { 42 track_rect = { 0, inner_rect().y(), track_size(), inner_rect().height() }; 43 track_rect.center_horizontally_within(inner_rect()); 44 } 45 Gfx::StylePainter::paint_frame(painter, track_rect, palette(), Gfx::FrameShape::Panel, Gfx::FrameShadow::Sunken, 1); 46 if (is_enabled()) 47 Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_knob_hovered); 48 else 49 Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, true, m_knob_hovered); 50} 51 52Gfx::IntRect Slider::knob_rect() const 53{ 54 auto inner_rect = this->inner_rect(); 55 Gfx::IntRect rect; 56 rect.set_secondary_offset_for_orientation(orientation(), 0); 57 rect.set_secondary_size_for_orientation(orientation(), knob_secondary_size()); 58 59 if (knob_size_mode() == KnobSizeMode::Fixed) { 60 if (max() - min()) { 61 float scale = (float)inner_rect.primary_size_for_orientation(orientation()) / (float)(max() - min()); 62 rect.set_primary_offset_for_orientation(orientation(), inner_rect.primary_offset_for_orientation(orientation()) + ((int)((value() - min()) * scale)) - (knob_fixed_primary_size() / 2)); 63 } else 64 rect.set_primary_size_for_orientation(orientation(), 0); 65 rect.set_primary_size_for_orientation(orientation(), knob_fixed_primary_size()); 66 } else { 67 float scale = (float)inner_rect.primary_size_for_orientation(orientation()) / (float)(max() - min() + 1); 68 rect.set_primary_offset_for_orientation(orientation(), inner_rect.primary_offset_for_orientation(orientation()) + ((int)((value() - min()) * scale))); 69 if (max() - min()) 70 rect.set_primary_size_for_orientation(orientation(), ::max((int)(scale), knob_fixed_primary_size())); 71 else 72 rect.set_primary_size_for_orientation(orientation(), inner_rect.primary_size_for_orientation(orientation())); 73 } 74 if (orientation() == Orientation::Horizontal) 75 rect.center_vertically_within(inner_rect); 76 else 77 rect.center_horizontally_within(inner_rect); 78 return rect; 79} 80 81void Slider::start_drag(Gfx::IntPoint start_position) 82{ 83 VERIFY(!m_dragging); 84 m_dragging = true; 85 m_drag_origin = start_position; 86 m_drag_origin_value = value(); 87 if (on_drag_start) 88 on_drag_start(); 89} 90 91void Slider::mousedown_event(MouseEvent& event) 92{ 93 if (event.button() == MouseButton::Primary) { 94 auto const mouse_offset = event.position().primary_offset_for_orientation(orientation()); 95 96 if (jump_to_cursor()) { 97 float normalized_mouse_offset = 0.0f; 98 if (orientation() == Orientation::Vertical) { 99 normalized_mouse_offset = static_cast<float>(mouse_offset - track_margin()) / static_cast<float>(inner_rect().height()); 100 } else { 101 normalized_mouse_offset = static_cast<float>(mouse_offset - track_margin()) / static_cast<float>(inner_rect().width()); 102 } 103 104 int new_value = static_cast<int>(min() + ((max() - min()) * normalized_mouse_offset)); 105 set_value(new_value, AllowCallback::No); 106 start_drag(event.position()); 107 // Delay the callback to make it aware that a drag has started. 108 if (on_change) 109 on_change(value()); 110 return; 111 } 112 113 if (knob_rect().contains(event.position())) { 114 start_drag(event.position()); 115 return; 116 } 117 118 auto knob_first_edge = knob_rect().first_edge_for_orientation(orientation()); 119 auto knob_last_edge = knob_rect().last_edge_for_orientation(orientation()); 120 if (mouse_offset > knob_last_edge) 121 increase_slider_by_page_steps(1); 122 else if (mouse_offset < knob_first_edge) 123 decrease_slider_by_page_steps(1); 124 } 125 return Widget::mousedown_event(event); 126} 127 128void Slider::mousemove_event(MouseEvent& event) 129{ 130 set_knob_hovered(knob_rect().contains(event.position())); 131 if (m_dragging) { 132 float delta = event.position().primary_offset_for_orientation(orientation()) - m_drag_origin.primary_offset_for_orientation(orientation()); 133 float scrubbable_range = inner_rect().primary_size_for_orientation(orientation()); 134 float value_steps_per_scrubbed_pixel = (max() - min()) / scrubbable_range; 135 float new_value = m_drag_origin_value + (value_steps_per_scrubbed_pixel * delta); 136 set_value((int)new_value); 137 return; 138 } 139 return Widget::mousemove_event(event); 140} 141 142void Slider::end_drag() 143{ 144 if (m_dragging) { 145 m_dragging = false; 146 if (on_drag_end) 147 on_drag_end(); 148 } 149} 150 151void Slider::mouseup_event(MouseEvent& event) 152{ 153 if (event.button() == MouseButton::Primary) { 154 end_drag(); 155 return; 156 } 157 158 return Widget::mouseup_event(event); 159} 160 161void Slider::mousewheel_event(MouseEvent& event) 162{ 163 auto acceleration_modifier = step(); 164 auto wheel_delta = event.wheel_delta_y(); 165 166 if (event.modifiers() == KeyModifier::Mod_Ctrl) 167 acceleration_modifier *= 6; 168 if (knob_size_mode() == KnobSizeMode::Proportional) 169 wheel_delta /= abs(wheel_delta); 170 171 if (orientation() == Orientation::Horizontal) 172 decrease_slider_by(wheel_delta * acceleration_modifier); 173 else 174 increase_slider_by(wheel_delta * acceleration_modifier); 175 176 Widget::mousewheel_event(event); 177} 178 179void Slider::leave_event(Core::Event& event) 180{ 181 if (!is_enabled()) 182 return; 183 set_knob_hovered(false); 184 Widget::leave_event(event); 185} 186 187void Slider::change_event(Event& event) 188{ 189 if (event.type() == Event::Type::EnabledChange) { 190 if (!is_enabled()) 191 m_dragging = false; 192 } 193 Widget::change_event(event); 194} 195 196void Slider::set_knob_hovered(bool hovered) 197{ 198 if (m_knob_hovered == hovered) 199 return; 200 m_knob_hovered = hovered; 201 update(knob_rect()); 202} 203 204Optional<UISize> Slider::calculated_min_size() const 205{ 206 if (orientation() == Gfx::Orientation::Vertical) 207 return { { knob_secondary_size(), knob_fixed_primary_size() * 2 + track_margin() * 2 } }; 208 return { { knob_fixed_primary_size() * 2 + track_margin() * 2, knob_secondary_size() } }; 209} 210 211Optional<UISize> Slider::calculated_preferred_size() const 212{ 213 if (orientation() == Gfx::Orientation::Vertical) 214 return { { SpecialDimension::Shrink, SpecialDimension::OpportunisticGrow } }; 215 return { { SpecialDimension::OpportunisticGrow, SpecialDimension::Shrink } }; 216} 217 218}