ladybird/Userland/Libraries/LibGUI/ValueSlider.cpp
Torstennator f9ec3b986e LibGUI: Fix {Value,Opacity}Slider value changes for values less than 0
This patch fixes a value glitch when changing the slider value via
dragging the knob with the mouse and having a min value smaller than 0.
Before this patch it was not possible to drag the value below 0 and it
just snapped to the configured min value if the mouse was at the most
left position. Now the value is calculated from the value range and
mouse position within the widget.
2022-05-08 17:17:56 +02:00

205 lines
5.9 KiB
C++

/*
* Copyright (c) 2021, Marcus Nilsson <brainbomb@gmail.com>
* Copyright (c) 2022, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Painter.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/ValueSlider.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Palette.h>
#include <LibGfx/StylePainter.h>
REGISTER_WIDGET(GUI, ValueSlider)
namespace GUI {
ValueSlider::ValueSlider(Gfx::Orientation orientation, String suffix)
: AbstractSlider(orientation)
, m_suffix(move(suffix))
{
// FIXME: Implement vertical mode
VERIFY(orientation == Orientation::Horizontal);
set_fixed_height(20);
m_textbox = add<GUI::TextBox>();
m_textbox->set_relative_rect({ 0, 0, 34, 20 });
m_textbox->set_font_fixed_width(true);
m_textbox->set_font_size(8);
m_textbox->on_change = [&]() {
String value = m_textbox->text();
if (value.ends_with(m_suffix, AK::CaseSensitivity::CaseInsensitive))
value = value.substring_view(0, value.length() - m_suffix.length());
auto integer_value = value.to_int();
if (integer_value.has_value())
AbstractSlider::set_value(integer_value.value());
};
m_textbox->on_return_pressed = [&]() {
m_textbox->on_change();
m_textbox->set_text(formatted_value());
};
m_textbox->on_up_pressed = [&]() {
if (value() < max())
AbstractSlider::increase_slider_by(1);
m_textbox->set_text(formatted_value());
};
m_textbox->on_down_pressed = [&]() {
if (value() > min())
AbstractSlider::decrease_slider_by(1);
m_textbox->set_text(formatted_value());
};
m_textbox->on_focusout = [&]() {
m_textbox->on_return_pressed();
};
m_textbox->on_escape_pressed = [&]() {
m_textbox->clear_selection();
m_textbox->set_text(formatted_value());
parent_widget()->set_focus(true);
};
}
String ValueSlider::formatted_value() const
{
return String::formatted("{:2}{}", value(), m_suffix);
}
void ValueSlider::paint_event(PaintEvent& event)
{
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
if (is_enabled())
painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().active_window_border1(), palette().active_window_border2());
else
painter.fill_rect_with_gradient(m_orientation, bar_rect(), palette().inactive_window_border1(), palette().inactive_window_border2());
auto unfilled_rect = bar_rect();
unfilled_rect.set_left(knob_rect().right());
painter.fill_rect(unfilled_rect, palette().base());
Gfx::StylePainter::paint_frame(painter, bar_rect(), palette(), Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
Gfx::StylePainter::paint_button(painter, knob_rect(), palette(), Gfx::ButtonStyle::Normal, false, m_hovered);
auto paint_knurl = [&](int x, int y) {
painter.set_pixel(x, y, palette().threed_shadow1());
painter.set_pixel(x + 1, y, palette().threed_shadow1());
painter.set_pixel(x, y + 1, palette().threed_shadow1());
painter.set_pixel(x + 1, y + 1, palette().threed_highlight());
};
auto knurl_rect = knob_rect().shrunken(4, 8);
if (m_knob_style == KnobStyle::Wide) {
for (int i = 0; i < 4; ++i) {
paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3));
paint_knurl(knurl_rect.x() + 3, knurl_rect.y() + (i * 3));
paint_knurl(knurl_rect.x() + 6, knurl_rect.y() + (i * 3));
}
} else {
for (int i = 0; i < 4; ++i)
paint_knurl(knurl_rect.x(), knurl_rect.y() + (i * 3));
}
}
Gfx::IntRect ValueSlider::bar_rect() const
{
auto bar_rect = rect();
bar_rect.set_width(rect().width() - m_textbox->width());
bar_rect.set_x(m_textbox->width());
return bar_rect;
}
Gfx::IntRect ValueSlider::knob_rect() const
{
int knob_thickness = m_knob_style == KnobStyle::Wide ? 13 : 7;
Gfx::IntRect knob_rect = bar_rect();
knob_rect.set_width(knob_thickness);
int knob_offset = (int)((float)bar_rect().left() + (float)(value() - min()) / (float)(max() - min()) * (float)(bar_rect().width() - knob_thickness));
knob_rect.set_left(knob_offset);
knob_rect.center_vertically_within(bar_rect());
return knob_rect;
}
int ValueSlider::value_at(Gfx::IntPoint const& position) const
{
if (position.x() < bar_rect().left())
return min();
if (position.x() > bar_rect().right())
return max();
float relative_offset = (float)(position.x() - bar_rect().left()) / (float)bar_rect().width();
int range = max() - min();
return min() + (int)(relative_offset * (float)range);
}
void ValueSlider::set_value(int value, AllowCallback allow_callback, DoClamp do_clamp)
{
AbstractSlider::set_value(value, allow_callback, do_clamp);
m_textbox->set_text(formatted_value());
}
void ValueSlider::leave_event(Core::Event&)
{
if (!m_hovered)
return;
m_hovered = false;
update(knob_rect());
}
void ValueSlider::mousewheel_event(MouseEvent& event)
{
if (event.wheel_delta_y() < 0)
increase_slider_by(1);
else
decrease_slider_by(1);
}
void ValueSlider::mousemove_event(MouseEvent& event)
{
bool is_hovered = knob_rect().contains(event.position());
if (is_hovered != m_hovered) {
m_hovered = is_hovered;
update(knob_rect());
}
if (!m_dragging)
return;
set_value(value_at(event.position()));
}
void ValueSlider::mousedown_event(MouseEvent& event)
{
if (event.button() != MouseButton::Primary)
return;
m_textbox->set_focus(true);
if (bar_rect().contains(event.position())) {
m_dragging = true;
set_value(value_at(event.position()));
}
}
void ValueSlider::mouseup_event(MouseEvent& event)
{
if (event.button() != MouseButton::Primary)
return;
m_dragging = false;
}
}