Serenity Operating System
at master 226 lines 6.8 kB view raw
1/* 2 * Copyright (c) 2021, Marcus Nilsson <brainbomb@gmail.com> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <LibGUI/BoxLayout.h> 9#include <LibGUI/Painter.h> 10#include <LibGUI/TextBox.h> 11#include <LibGUI/ValueSlider.h> 12#include <LibGfx/Font/FontDatabase.h> 13#include <LibGfx/Palette.h> 14#include <LibGfx/StylePainter.h> 15 16REGISTER_WIDGET(GUI, ValueSlider) 17 18namespace GUI { 19 20ValueSlider::ValueSlider(Gfx::Orientation orientation, String suffix) 21 : AbstractSlider(orientation) 22 , m_suffix(move(suffix)) 23{ 24 // FIXME: Implement vertical mode 25 VERIFY(orientation == Orientation::Horizontal); 26 27 set_preferred_size(SpecialDimension::Fit); 28 29 m_textbox = add<GUI::TextBox>(); 30 m_textbox->set_relative_rect({ 0, 0, 34, 20 }); 31 m_textbox->set_font_fixed_width(true); 32 m_textbox->set_font_size(8); 33 34 m_textbox->on_change = [&]() { 35 DeprecatedString value = m_textbox->text(); 36 if (value.ends_with(m_suffix, AK::CaseSensitivity::CaseInsensitive)) 37 value = value.substring_view(0, value.length() - m_suffix.bytes_as_string_view().length()); 38 auto integer_value = value.to_int(); 39 if (integer_value.has_value()) 40 AbstractSlider::set_value(integer_value.value()); 41 }; 42 43 m_textbox->on_return_pressed = [&]() { 44 m_textbox->on_change(); 45 m_textbox->set_text(formatted_value()); 46 }; 47 48 m_textbox->on_up_pressed = [&]() { 49 if (value() < max()) 50 AbstractSlider::increase_slider_by(1); 51 m_textbox->set_text(formatted_value()); 52 }; 53 54 m_textbox->on_down_pressed = [&]() { 55 if (value() > min()) 56 AbstractSlider::decrease_slider_by(1); 57 m_textbox->set_text(formatted_value()); 58 }; 59 60 m_textbox->on_focusout = [&]() { 61 m_textbox->on_return_pressed(); 62 }; 63 64 m_textbox->on_escape_pressed = [&]() { 65 m_textbox->clear_selection(); 66 m_textbox->set_text(formatted_value()); 67 parent_widget()->set_focus(true); 68 }; 69} 70 71DeprecatedString ValueSlider::formatted_value() const 72{ 73 return DeprecatedString::formatted("{:2}{}", value(), m_suffix); 74} 75 76void ValueSlider::paint_event(PaintEvent& event) 77{ 78 GUI::Painter painter(*this); 79 painter.add_clip_rect(event.rect()); 80 81 if (is_enabled()) 82 painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().active_window_border1(), palette().active_window_border2()); 83 else 84 painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().inactive_window_border1(), palette().inactive_window_border2()); 85 86 auto unfilled_rect = bar_rect(); 87 unfilled_rect.set_left(knob_rect().right()); 88 painter.fill_rect(unfilled_rect, palette().base()); 89 90 Gfx::StylePainter::paint_frame(painter, bar_rect(), palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2); 91 Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_hovered); 92 93 auto paint_knurl = [&](int x, int y) { 94 painter.set_pixel(x, y, palette().threed_shadow1()); 95 painter.set_pixel(x + 1, y, palette().threed_shadow1()); 96 painter.set_pixel(x, y + 1, palette().threed_shadow1()); 97 painter.set_pixel(x + 1, y + 1, palette().threed_highlight()); 98 }; 99 100 auto knurl_rect = knob_rect().shrunken(4, 8); 101 102 if (m_knob_style == KnobStyle::Wide) { 103 for (int i = 0; i < 4; ++i) { 104 paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3)); 105 paint_knurl(knurl_rect.x() + 3, knurl_rect.y() + (i * 3)); 106 paint_knurl(knurl_rect.x() + 6, knurl_rect.y() + (i * 3)); 107 } 108 } else { 109 for (int i = 0; i < 4; ++i) 110 paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3)); 111 } 112} 113 114Gfx::IntRect ValueSlider::bar_rect() const 115{ 116 auto bar_rect = rect(); 117 bar_rect.set_width(rect().width() - m_textbox->width()); 118 bar_rect.set_x(m_textbox->width()); 119 return bar_rect; 120} 121 122int ValueSlider::knob_length() const 123{ 124 return m_knob_style == KnobStyle::Wide ? 13 : 7; 125} 126 127Gfx::IntRect ValueSlider::knob_rect() const 128{ 129 int knob_thickness = knob_length(); 130 131 Gfx::IntRect knob_rect = bar_rect(); 132 knob_rect.set_width(knob_thickness); 133 134 int knob_offset = (int)((float)bar_rect().left() + (float)(value() - min()) / (float)(max() - min()) * (float)(bar_rect().width() - knob_thickness)); 135 knob_rect.set_left(knob_offset); 136 knob_rect.center_vertically_within(bar_rect()); 137 return knob_rect; 138} 139 140int ValueSlider::value_at(Gfx::IntPoint position) const 141{ 142 if (position.x() < bar_rect().left()) 143 return min(); 144 if (position.x() > bar_rect().right()) 145 return max(); 146 float relative_offset = (float)(position.x() - bar_rect().left()) / (float)bar_rect().width(); 147 148 int range = max() - min(); 149 return min() + (int)(relative_offset * (float)range); 150} 151 152void ValueSlider::set_value(int value, AllowCallback allow_callback, DoClamp do_clamp) 153{ 154 AbstractSlider::set_value(value, allow_callback, do_clamp); 155 m_textbox->set_text(formatted_value()); 156} 157 158void ValueSlider::leave_event(Core::Event&) 159{ 160 if (!m_hovered) 161 return; 162 163 m_hovered = false; 164 update(knob_rect()); 165} 166 167void ValueSlider::mousewheel_event(MouseEvent& event) 168{ 169 if (event.wheel_delta_y() < 0) 170 increase_slider_by(1); 171 else 172 decrease_slider_by(1); 173} 174 175void ValueSlider::mousemove_event(MouseEvent& event) 176{ 177 bool is_hovered = knob_rect().contains(event.position()); 178 if (is_hovered != m_hovered) { 179 m_hovered = is_hovered; 180 update(knob_rect()); 181 } 182 183 if (!m_dragging) 184 return; 185 186 set_value(value_at(event.position())); 187} 188 189void ValueSlider::mousedown_event(MouseEvent& event) 190{ 191 if (event.button() != MouseButton::Primary) 192 return; 193 194 m_textbox->set_focus(true); 195 196 if (bar_rect().contains(event.position())) { 197 m_dragging = true; 198 set_value(value_at(event.position())); 199 } 200} 201 202void ValueSlider::mouseup_event(MouseEvent& event) 203{ 204 if (event.button() != MouseButton::Primary) 205 return; 206 207 m_dragging = false; 208} 209 210Optional<UISize> ValueSlider::calculated_min_size() const 211{ 212 auto content_min_size = m_textbox->effective_min_size(); 213 214 if (orientation() == Gfx::Orientation::Vertical) 215 return { { content_min_size.width(), content_min_size.height().as_int() + knob_length() } }; 216 return { { content_min_size.width().as_int() + knob_length(), content_min_size.height() } }; 217} 218 219Optional<UISize> ValueSlider::calculated_preferred_size() const 220{ 221 if (orientation() == Gfx::Orientation::Vertical) 222 return { { SpecialDimension::Shrink, SpecialDimension::OpportunisticGrow } }; 223 return { { SpecialDimension::OpportunisticGrow, SpecialDimension::Shrink } }; 224} 225 226}