2020-01-18 09:38:21 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
2022-02-28 19:21:12 -07:00
|
|
|
* Copyright (c) 2022, the SerenityOS developers.
|
2020-01-18 09:38:21 +01:00
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 09:38:21 +01:00
|
|
|
*/
|
|
|
|
|
|
2020-02-16 09:17:49 +01:00
|
|
|
#include <LibCore/Timer.h>
|
2020-02-14 21:41:10 +01:00
|
|
|
#include <LibGUI/Painter.h>
|
2021-04-13 16:18:20 +02:00
|
|
|
#include <LibGUI/Scrollbar.h>
|
2020-02-14 21:41:10 +01:00
|
|
|
#include <LibGfx/CharacterBitmap.h>
|
2020-02-06 12:04:00 +01:00
|
|
|
#include <LibGfx/Palette.h>
|
|
|
|
|
#include <LibGfx/StylePainter.h>
|
2019-02-09 11:19:38 +01:00
|
|
|
|
2022-03-07 14:19:35 -05:00
|
|
|
static constexpr int ANIMATION_INTERVAL = 16; // Milliseconds
|
|
|
|
|
static constexpr double ANIMATION_TIME = 0.18; // Seconds
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
REGISTER_WIDGET(GUI, Scrollbar)
|
2021-01-02 16:30:13 -07:00
|
|
|
|
2020-02-02 15:07:41 +01:00
|
|
|
namespace GUI {
|
|
|
|
|
|
2022-02-28 19:21:12 -07:00
|
|
|
static constexpr Gfx::CharacterBitmap s_up_arrow_bitmap {
|
2021-08-31 00:35:57 +02:00
|
|
|
" "
|
|
|
|
|
" "
|
2019-02-10 11:57:19 +01:00
|
|
|
" "
|
|
|
|
|
" # "
|
|
|
|
|
" ### "
|
|
|
|
|
" ##### "
|
|
|
|
|
" ####### "
|
2021-08-31 00:35:57 +02:00
|
|
|
" "
|
2022-02-28 19:21:12 -07:00
|
|
|
" ",
|
|
|
|
|
9, 9
|
2019-02-10 08:23:03 +01:00
|
|
|
};
|
|
|
|
|
|
2022-02-28 19:21:12 -07:00
|
|
|
static constexpr Gfx::CharacterBitmap s_down_arrow_bitmap {
|
2019-02-10 11:57:19 +01:00
|
|
|
" "
|
2021-08-31 00:35:57 +02:00
|
|
|
" "
|
|
|
|
|
" "
|
2019-02-10 11:57:19 +01:00
|
|
|
" ####### "
|
|
|
|
|
" ##### "
|
|
|
|
|
" ### "
|
|
|
|
|
" # "
|
|
|
|
|
" "
|
2022-02-28 19:21:12 -07:00
|
|
|
" ",
|
|
|
|
|
9, 9
|
2019-02-10 08:23:03 +01:00
|
|
|
};
|
|
|
|
|
|
2022-02-28 19:21:12 -07:00
|
|
|
static constexpr Gfx::CharacterBitmap s_left_arrow_bitmap {
|
2019-02-10 12:26:58 +01:00
|
|
|
" "
|
2021-08-31 00:35:57 +02:00
|
|
|
" # "
|
|
|
|
|
" ## "
|
|
|
|
|
" ### "
|
|
|
|
|
" #### "
|
|
|
|
|
" ### "
|
|
|
|
|
" ## "
|
|
|
|
|
" # "
|
2022-02-28 19:21:12 -07:00
|
|
|
" ",
|
|
|
|
|
9, 9
|
2019-02-10 12:26:58 +01:00
|
|
|
};
|
|
|
|
|
|
2022-02-28 19:21:12 -07:00
|
|
|
static constexpr Gfx::CharacterBitmap s_right_arrow_bitmap {
|
2019-02-10 12:26:58 +01:00
|
|
|
" "
|
2021-08-31 00:35:57 +02:00
|
|
|
" # "
|
|
|
|
|
" ## "
|
|
|
|
|
" ### "
|
|
|
|
|
" #### "
|
|
|
|
|
" ### "
|
|
|
|
|
" ## "
|
|
|
|
|
" # "
|
2022-02-28 19:21:12 -07:00
|
|
|
" ",
|
|
|
|
|
9, 9
|
2019-02-10 12:26:58 +01:00
|
|
|
};
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
Scrollbar::Scrollbar(Orientation orientation)
|
2020-12-30 15:20:02 +01:00
|
|
|
: AbstractSlider(orientation)
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2020-02-23 10:31:26 +01:00
|
|
|
m_automatic_scrolling_timer = add<Core::Timer>();
|
2019-02-10 11:57:19 +01:00
|
|
|
|
2020-12-30 15:20:02 +01:00
|
|
|
if (orientation == Orientation::Vertical) {
|
2020-12-30 01:23:32 +01:00
|
|
|
set_fixed_width(16);
|
2020-12-30 01:55:25 +01:00
|
|
|
} else {
|
|
|
|
|
set_fixed_height(16);
|
2019-02-10 11:57:19 +01:00
|
|
|
}
|
2019-06-07 10:43:10 +02:00
|
|
|
|
2019-09-20 15:19:46 +02:00
|
|
|
m_automatic_scrolling_timer->set_interval(100);
|
|
|
|
|
m_automatic_scrolling_timer->on_timeout = [this] {
|
2019-06-07 10:43:10 +02:00
|
|
|
on_automatic_scrolling_timer_fired();
|
|
|
|
|
};
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
Gfx::IntRect Scrollbar::decrement_button_rect() const
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2019-04-11 13:16:43 +02:00
|
|
|
return { 0, 0, button_width(), button_height() };
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
Gfx::IntRect Scrollbar::increment_button_rect() const
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2019-02-10 12:26:58 +01:00
|
|
|
if (orientation() == Orientation::Vertical)
|
2019-04-11 13:16:43 +02:00
|
|
|
return { 0, height() - button_height(), button_width(), button_height() };
|
2019-02-10 12:26:58 +01:00
|
|
|
else
|
2019-04-11 13:16:43 +02:00
|
|
|
return { width() - button_width(), 0, button_width(), button_height() };
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
int Scrollbar::scrubbable_range_in_pixels() const
|
2019-02-10 06:51:01 +01:00
|
|
|
{
|
2019-02-10 12:26:58 +01:00
|
|
|
if (orientation() == Orientation::Vertical)
|
2020-08-11 21:32:40 -04:00
|
|
|
return height() - button_height() * 2 - visible_scrubber_size();
|
2019-02-10 12:26:58 +01:00
|
|
|
else
|
2020-08-11 21:32:40 -04:00
|
|
|
return width() - button_width() * 2 - visible_scrubber_size();
|
2019-02-10 06:51:01 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
bool Scrollbar::has_scrubber() const
|
2019-02-10 07:11:01 +01:00
|
|
|
{
|
2020-12-30 15:20:02 +01:00
|
|
|
return max() != min();
|
2019-02-10 07:11:01 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-08 21:50:42 +00:00
|
|
|
void Scrollbar::set_scroll_animation(Animation scroll_animation)
|
|
|
|
|
{
|
|
|
|
|
m_scroll_animation = scroll_animation;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-29 16:01:59 +03:00
|
|
|
void Scrollbar::set_value(int value, AllowCallback allow_callback, DoClamp do_clamp)
|
2022-03-07 14:19:35 -05:00
|
|
|
{
|
|
|
|
|
m_target_value = value;
|
|
|
|
|
if (!(m_animated_scrolling_timer.is_null()))
|
|
|
|
|
m_animated_scrolling_timer->stop();
|
|
|
|
|
|
2022-03-29 16:01:59 +03:00
|
|
|
AbstractSlider::set_value(value, allow_callback, do_clamp);
|
2022-03-07 14:19:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Scrollbar::set_target_value(int new_target_value)
|
|
|
|
|
{
|
2022-03-08 21:50:42 +00:00
|
|
|
if (m_scroll_animation == Animation::CoarseScroll)
|
|
|
|
|
return set_value(new_target_value);
|
|
|
|
|
|
2022-03-07 14:19:35 -05:00
|
|
|
new_target_value = clamp(new_target_value, min(), max());
|
|
|
|
|
|
|
|
|
|
// If we are already at or scrolling to the new target then don't touch anything
|
|
|
|
|
if (m_target_value == new_target_value)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_animation_time_elapsed = 0;
|
|
|
|
|
m_start_value = value();
|
|
|
|
|
m_target_value = new_target_value;
|
|
|
|
|
|
|
|
|
|
if (m_animated_scrolling_timer.is_null()) {
|
|
|
|
|
m_animated_scrolling_timer = add<Core::Timer>();
|
|
|
|
|
m_animated_scrolling_timer->set_interval(ANIMATION_INTERVAL);
|
|
|
|
|
m_animated_scrolling_timer->on_timeout = [this]() {
|
|
|
|
|
m_animation_time_elapsed += (double)ANIMATION_INTERVAL / 1'000; // ms -> sec
|
|
|
|
|
update_animated_scroll();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_animated_scrolling_timer->start();
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-21 17:51:56 -04:00
|
|
|
float Scrollbar::unclamped_scrubber_size() const
|
2019-03-29 02:51:19 +01:00
|
|
|
{
|
2021-09-21 17:51:56 -04:00
|
|
|
float pixel_range = length(orientation()) - button_size() * 2;
|
|
|
|
|
float value_range = max() - min();
|
2020-09-18 09:49:51 +02:00
|
|
|
|
2021-09-21 17:51:56 -04:00
|
|
|
float scrubber_size { 0 };
|
2020-07-08 22:12:24 -06:00
|
|
|
if (value_range > 0) {
|
|
|
|
|
// Scrubber size should be proportional to the visible portion
|
|
|
|
|
// (page) in relation to the content (value range + page)
|
2020-12-30 15:20:02 +01:00
|
|
|
scrubber_size = (page_step() * pixel_range) / (value_range + page_step());
|
2020-07-08 22:12:24 -06:00
|
|
|
}
|
2020-08-11 21:35:15 -04:00
|
|
|
return scrubber_size;
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
int Scrollbar::visible_scrubber_size() const
|
2020-08-11 21:35:15 -04:00
|
|
|
{
|
|
|
|
|
return ::max(unclamped_scrubber_size(), button_size());
|
2019-03-29 02:51:19 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
Gfx::IntRect Scrollbar::scrubber_rect() const
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2020-08-11 21:32:40 -04:00
|
|
|
if (!has_scrubber() || length(orientation()) <= (button_size() * 2) + visible_scrubber_size())
|
2019-06-07 11:46:02 +02:00
|
|
|
return {};
|
2019-02-10 12:26:58 +01:00
|
|
|
float x_or_y;
|
2020-12-30 15:20:02 +01:00
|
|
|
if (value() == min())
|
2019-03-27 20:48:23 +01:00
|
|
|
x_or_y = button_size();
|
2020-12-30 15:20:02 +01:00
|
|
|
else if (value() == max())
|
2021-05-03 22:22:53 +01:00
|
|
|
x_or_y = length(orientation()) - button_size() - visible_scrubber_size();
|
2019-02-10 12:26:58 +01:00
|
|
|
else {
|
2020-12-30 15:20:02 +01:00
|
|
|
float range_size = max() - min();
|
2019-02-10 12:26:58 +01:00
|
|
|
float available = scrubbable_range_in_pixels();
|
|
|
|
|
float step = available / range_size;
|
2020-12-30 15:20:02 +01:00
|
|
|
x_or_y = (button_size() + (step * value()));
|
2019-02-10 12:26:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (orientation() == Orientation::Vertical)
|
2020-08-11 21:32:40 -04:00
|
|
|
return { 0, (int)x_or_y, button_width(), visible_scrubber_size() };
|
2019-02-10 12:26:58 +01:00
|
|
|
else
|
2020-08-11 21:32:40 -04:00
|
|
|
return { (int)x_or_y, 0, visible_scrubber_size(), button_height() };
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::paint_event(PaintEvent& event)
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2020-02-02 15:07:41 +01:00
|
|
|
Painter painter(*this);
|
2019-03-29 15:01:54 +01:00
|
|
|
painter.add_clip_rect(event.rect());
|
2019-02-09 11:19:38 +01:00
|
|
|
|
LibGUI: Only paint ScrollBar hover states if a component is pressed
While left-mouse is pressed on any component (arrows, gutter, scrubber),
don't draw hover states for components other than the pressed component.
For example, while clicking the arrow-down button and then dragging
around, the arrow-up button and the scrubber now aren't highlighted.
This also means that during a gutter drag session, the scrubber
isn't highlighted while it's under the mouse cursor. That makes
sense, since we get the gutter drag behavior, not the scrubber
drag behavior, in this case.
The highlight is supposed to indicate "clickability", but if the
mouse is already down, they can't be clicked.
Now that I check for it, this seems to match the scrollbar behavior
on Windows.
2020-08-25 13:29:52 -04:00
|
|
|
Component hovered_component_for_painting = m_hovered_component;
|
2020-09-09 14:57:12 -04:00
|
|
|
if (!has_scrubber() || (m_pressed_component != Component::None && m_hovered_component != m_pressed_component))
|
LibGUI: Only paint ScrollBar hover states if a component is pressed
While left-mouse is pressed on any component (arrows, gutter, scrubber),
don't draw hover states for components other than the pressed component.
For example, while clicking the arrow-down button and then dragging
around, the arrow-up button and the scrubber now aren't highlighted.
This also means that during a gutter drag session, the scrubber
isn't highlighted while it's under the mouse cursor. That makes
sense, since we get the gutter drag behavior, not the scrubber
drag behavior, in this case.
The highlight is supposed to indicate "clickability", but if the
mouse is already down, they can't be clicked.
Now that I check for it, this seems to match the scrollbar behavior
on Windows.
2020-08-25 13:29:52 -04:00
|
|
|
hovered_component_for_painting = Component::None;
|
|
|
|
|
|
2020-05-10 01:00:21 +02:00
|
|
|
painter.fill_rect_with_dither_pattern(rect(), palette().button().lightened(1.3f), palette().button());
|
2022-03-08 21:48:19 +00:00
|
|
|
if (m_gutter_click_state != GutterClickState::NotPressed && has_scrubber() && hovered_component_for_painting == Component::Gutter) {
|
2021-11-02 12:01:18 +01:00
|
|
|
VERIFY(!scrubber_rect().is_null());
|
|
|
|
|
Gfx::IntRect rect_to_fill = rect();
|
|
|
|
|
if (orientation() == Orientation::Vertical) {
|
2022-03-08 21:48:19 +00:00
|
|
|
if (m_gutter_click_state == GutterClickState::BeforeScrubber) {
|
2021-11-02 12:01:18 +01:00
|
|
|
rect_to_fill.set_top(decrement_button_rect().bottom());
|
|
|
|
|
rect_to_fill.set_bottom(scrubber_rect().top());
|
|
|
|
|
} else {
|
2022-03-08 21:48:19 +00:00
|
|
|
VERIFY(m_gutter_click_state == GutterClickState::AfterScrubber);
|
2021-11-02 12:01:18 +01:00
|
|
|
rect_to_fill.set_top(scrubber_rect().bottom());
|
|
|
|
|
rect_to_fill.set_bottom(increment_button_rect().top());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2022-03-08 21:48:19 +00:00
|
|
|
if (m_gutter_click_state == GutterClickState::BeforeScrubber) {
|
2021-11-02 12:01:18 +01:00
|
|
|
rect_to_fill.set_left(decrement_button_rect().right());
|
|
|
|
|
rect_to_fill.set_right(scrubber_rect().left());
|
|
|
|
|
} else {
|
2022-03-08 21:48:19 +00:00
|
|
|
VERIFY(m_gutter_click_state == GutterClickState::AfterScrubber);
|
2021-11-02 12:01:18 +01:00
|
|
|
rect_to_fill.set_left(scrubber_rect().right());
|
|
|
|
|
rect_to_fill.set_right(increment_button_rect().left());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
painter.fill_rect_with_dither_pattern(rect_to_fill, palette().button(), palette().button().lightened(0.77f));
|
|
|
|
|
}
|
2019-02-09 11:19:38 +01:00
|
|
|
|
2021-04-10 22:06:19 +02:00
|
|
|
bool decrement_pressed = (m_pressed_component == Component::DecrementButton) && (m_pressed_component == m_hovered_component);
|
|
|
|
|
bool increment_pressed = (m_pressed_component == Component::IncrementButton) && (m_pressed_component == m_hovered_component);
|
2020-02-16 08:56:13 +01:00
|
|
|
|
2021-08-30 23:29:29 +02:00
|
|
|
Gfx::StylePainter::paint_button(painter, decrement_button_rect(), palette(), Gfx::ButtonStyle::ThickCap, decrement_pressed, hovered_component_for_painting == Component::DecrementButton);
|
|
|
|
|
Gfx::StylePainter::paint_button(painter, increment_button_rect(), palette(), Gfx::ButtonStyle::ThickCap, increment_pressed, hovered_component_for_painting == Component::IncrementButton);
|
2019-07-01 02:46:36 -05:00
|
|
|
|
|
|
|
|
if (length(orientation()) > default_button_size()) {
|
2020-02-16 08:56:13 +01:00
|
|
|
auto decrement_location = decrement_button_rect().location().translated(3, 3);
|
|
|
|
|
if (decrement_pressed)
|
2021-04-12 11:47:09 -07:00
|
|
|
decrement_location.translate_by(1, 1);
|
2020-12-30 16:08:04 +01:00
|
|
|
if (!has_scrubber() || !is_enabled())
|
2022-02-28 19:21:12 -07:00
|
|
|
painter.draw_bitmap(decrement_location.translated(1, 1), orientation() == Orientation::Vertical ? s_up_arrow_bitmap : s_left_arrow_bitmap, palette().threed_highlight());
|
|
|
|
|
painter.draw_bitmap(decrement_location, orientation() == Orientation::Vertical ? s_up_arrow_bitmap : s_left_arrow_bitmap, (has_scrubber() && is_enabled()) ? palette().button_text() : palette().threed_shadow1());
|
2020-02-16 08:56:13 +01:00
|
|
|
|
|
|
|
|
auto increment_location = increment_button_rect().location().translated(3, 3);
|
|
|
|
|
if (increment_pressed)
|
2021-04-12 11:47:09 -07:00
|
|
|
increment_location.translate_by(1, 1);
|
2020-12-30 16:08:04 +01:00
|
|
|
if (!has_scrubber() || !is_enabled())
|
2022-02-28 19:21:12 -07:00
|
|
|
painter.draw_bitmap(increment_location.translated(1, 1), orientation() == Orientation::Vertical ? s_down_arrow_bitmap : s_right_arrow_bitmap, palette().threed_highlight());
|
|
|
|
|
painter.draw_bitmap(increment_location, orientation() == Orientation::Vertical ? s_down_arrow_bitmap : s_right_arrow_bitmap, (has_scrubber() && is_enabled()) ? palette().button_text() : palette().threed_shadow1());
|
2019-07-01 02:46:36 -05:00
|
|
|
}
|
2019-02-09 11:19:38 +01:00
|
|
|
|
2021-09-01 16:54:12 -04:00
|
|
|
if (has_scrubber() && !scrubber_rect().is_null())
|
2021-08-30 23:29:29 +02:00
|
|
|
Gfx::StylePainter::paint_button(painter, scrubber_rect(), palette(), Gfx::ButtonStyle::ThickCap, false, hovered_component_for_painting == Component::Scrubber || m_pressed_component == Component::Scrubber);
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::on_automatic_scrolling_timer_fired()
|
2019-06-07 10:43:10 +02:00
|
|
|
{
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::DecrementButton && component_at_position(m_last_mouse_position) == Component::DecrementButton) {
|
2021-10-31 12:34:55 -06:00
|
|
|
decrease_slider_by_steps(1);
|
2019-06-07 10:43:10 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::IncrementButton && component_at_position(m_last_mouse_position) == Component::IncrementButton) {
|
2021-10-31 12:31:49 -06:00
|
|
|
increase_slider_by_steps(1);
|
2019-06-07 10:43:10 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::Gutter && component_at_position(m_last_mouse_position) == Component::Gutter) {
|
2020-08-25 11:14:45 -04:00
|
|
|
scroll_by_page(m_last_mouse_position);
|
2021-11-02 12:01:18 +01:00
|
|
|
if (m_hovered_component != component_at_position(m_last_mouse_position)) {
|
|
|
|
|
m_hovered_component = component_at_position(m_last_mouse_position);
|
|
|
|
|
if (m_hovered_component != Component::Gutter)
|
2022-03-08 21:48:19 +00:00
|
|
|
m_gutter_click_state = GutterClickState::NotPressed;
|
2021-11-02 12:01:18 +01:00
|
|
|
update();
|
|
|
|
|
}
|
2020-08-25 11:14:45 -04:00
|
|
|
return;
|
|
|
|
|
}
|
2022-03-08 21:48:19 +00:00
|
|
|
m_gutter_click_state = GutterClickState::NotPressed;
|
2019-06-07 10:43:10 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::mousedown_event(MouseEvent& event)
|
2019-02-09 11:19:38 +01:00
|
|
|
{
|
2021-10-27 13:20:27 +02:00
|
|
|
if (event.button() != MouseButton::Primary)
|
2019-02-09 11:19:38 +01:00
|
|
|
return;
|
2020-02-16 08:56:13 +01:00
|
|
|
if (!has_scrubber())
|
|
|
|
|
return;
|
|
|
|
|
|
2020-08-25 11:04:53 -04:00
|
|
|
m_last_mouse_position = event.position();
|
2020-08-25 12:54:40 -04:00
|
|
|
m_pressed_component = component_at_position(m_last_mouse_position);
|
2020-08-25 10:31:20 -04:00
|
|
|
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::DecrementButton) {
|
|
|
|
|
set_automatic_scrolling_active(true, Component::DecrementButton);
|
2020-02-16 08:56:13 +01:00
|
|
|
update();
|
2019-02-09 11:19:38 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::IncrementButton) {
|
|
|
|
|
set_automatic_scrolling_active(true, Component::IncrementButton);
|
2020-02-16 08:56:13 +01:00
|
|
|
update();
|
2019-02-09 11:19:38 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2020-08-25 08:16:51 -04:00
|
|
|
|
2020-08-25 10:31:20 -04:00
|
|
|
if (event.shift()) {
|
2020-08-25 08:16:51 -04:00
|
|
|
scroll_to_position(event.position());
|
2020-08-25 12:54:40 -04:00
|
|
|
m_pressed_component = component_at_position(event.position());
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(m_pressed_component == Component::Scrubber);
|
2020-08-25 10:31:20 -04:00
|
|
|
}
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component == Component::Scrubber) {
|
2019-02-10 06:51:01 +01:00
|
|
|
m_scrub_start_value = value();
|
|
|
|
|
m_scrub_origin = event.position();
|
2019-02-10 07:11:01 +01:00
|
|
|
update();
|
2019-02-10 06:51:01 +01:00
|
|
|
return;
|
|
|
|
|
}
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(!event.shift());
|
2019-06-07 10:56:30 +02:00
|
|
|
|
2021-02-23 20:42:32 +01:00
|
|
|
VERIFY(m_pressed_component == Component::Gutter);
|
2020-08-25 12:54:40 -04:00
|
|
|
set_automatic_scrolling_active(true, Component::Gutter);
|
2020-08-25 11:14:45 -04:00
|
|
|
update();
|
2019-02-10 06:51:01 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::mouseup_event(MouseEvent& event)
|
2019-02-10 06:51:01 +01:00
|
|
|
{
|
2021-10-27 13:20:27 +02:00
|
|
|
if (event.button() != MouseButton::Primary)
|
2019-02-10 06:51:01 +01:00
|
|
|
return;
|
2020-08-25 12:54:40 -04:00
|
|
|
set_automatic_scrolling_active(false, Component::None);
|
2019-02-10 07:11:01 +01:00
|
|
|
update();
|
2019-02-10 06:51:01 +01:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::mousewheel_event(MouseEvent& event)
|
2019-08-20 20:10:02 +02:00
|
|
|
{
|
|
|
|
|
if (!is_scrollable())
|
|
|
|
|
return;
|
2021-12-13 23:22:28 +01:00
|
|
|
increase_slider_by_steps(event.wheel_delta_y());
|
2020-02-02 15:07:41 +01:00
|
|
|
Widget::mousewheel_event(event);
|
2019-08-20 20:10:02 +02:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::set_automatic_scrolling_active(bool active, Component pressed_component)
|
2019-06-07 10:43:10 +02:00
|
|
|
{
|
2020-08-25 12:54:40 -04:00
|
|
|
m_pressed_component = pressed_component;
|
|
|
|
|
if (m_pressed_component == Component::Gutter)
|
2020-08-25 11:14:45 -04:00
|
|
|
m_automatic_scrolling_timer->set_interval(200);
|
|
|
|
|
else
|
|
|
|
|
m_automatic_scrolling_timer->set_interval(100);
|
2020-08-25 09:51:32 -04:00
|
|
|
|
2019-06-07 10:43:10 +02:00
|
|
|
if (active) {
|
|
|
|
|
on_automatic_scrolling_timer_fired();
|
2019-09-20 15:19:46 +02:00
|
|
|
m_automatic_scrolling_timer->start();
|
2019-06-07 10:43:10 +02:00
|
|
|
} else {
|
2019-09-20 15:19:46 +02:00
|
|
|
m_automatic_scrolling_timer->stop();
|
2022-03-08 21:48:19 +00:00
|
|
|
m_gutter_click_state = GutterClickState::NotPressed;
|
2019-06-07 10:43:10 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::scroll_by_page(const Gfx::IntPoint& click_position)
|
2020-08-11 21:35:15 -04:00
|
|
|
{
|
2020-12-30 15:20:02 +01:00
|
|
|
float range_size = max() - min();
|
2020-08-11 21:35:15 -04:00
|
|
|
float available = scrubbable_range_in_pixels();
|
|
|
|
|
float rel_scrubber_size = unclamped_scrubber_size() / available;
|
|
|
|
|
float page_increment = range_size * rel_scrubber_size;
|
|
|
|
|
|
2021-11-02 12:01:18 +01:00
|
|
|
if (click_position.primary_offset_for_orientation(orientation()) < scrubber_rect().primary_offset_for_orientation(orientation())) {
|
2022-03-08 21:48:19 +00:00
|
|
|
m_gutter_click_state = GutterClickState::BeforeScrubber;
|
2021-10-31 12:27:52 -06:00
|
|
|
decrease_slider_by(page_increment);
|
2021-11-02 12:01:18 +01:00
|
|
|
} else {
|
2022-03-08 21:48:19 +00:00
|
|
|
m_gutter_click_state = GutterClickState::AfterScrubber;
|
2021-12-28 21:47:26 -06:00
|
|
|
increase_slider_by(page_increment);
|
2021-11-02 12:01:18 +01:00
|
|
|
}
|
2020-08-11 21:35:15 -04:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::scroll_to_position(const Gfx::IntPoint& click_position)
|
2020-08-11 20:56:52 -04:00
|
|
|
{
|
2020-12-30 15:20:02 +01:00
|
|
|
float range_size = max() - min();
|
2020-08-11 20:56:52 -04:00
|
|
|
float available = scrubbable_range_in_pixels();
|
|
|
|
|
|
2020-08-11 21:33:03 -04:00
|
|
|
float x_or_y = ::max(0, click_position.primary_offset_for_orientation(orientation()) - button_width() - button_width() / 2);
|
|
|
|
|
float rel_x_or_y = x_or_y / available;
|
2022-03-07 14:19:35 -05:00
|
|
|
set_target_value(min() + rel_x_or_y * range_size);
|
2020-08-11 20:56:52 -04:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
Scrollbar::Component Scrollbar::component_at_position(const Gfx::IntPoint& position)
|
2020-08-25 10:31:20 -04:00
|
|
|
{
|
|
|
|
|
if (scrubber_rect().contains(position))
|
|
|
|
|
return Component::Scrubber;
|
|
|
|
|
if (decrement_button_rect().contains(position))
|
|
|
|
|
return Component::DecrementButton;
|
|
|
|
|
if (increment_button_rect().contains(position))
|
|
|
|
|
return Component::IncrementButton;
|
|
|
|
|
if (rect().contains(position))
|
|
|
|
|
return Component::Gutter;
|
2020-08-25 12:54:40 -04:00
|
|
|
return Component::None;
|
2020-08-25 10:31:20 -04:00
|
|
|
}
|
|
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::mousemove_event(MouseEvent& event)
|
2019-02-10 06:51:01 +01:00
|
|
|
{
|
2021-07-19 11:42:53 +02:00
|
|
|
if (!is_scrollable())
|
|
|
|
|
return;
|
|
|
|
|
|
2020-08-25 11:04:53 -04:00
|
|
|
m_last_mouse_position = event.position();
|
|
|
|
|
|
2019-04-06 13:55:56 +02:00
|
|
|
auto old_hovered_component = m_hovered_component;
|
2020-08-25 11:04:53 -04:00
|
|
|
m_hovered_component = component_at_position(m_last_mouse_position);
|
2019-06-07 10:43:10 +02:00
|
|
|
if (old_hovered_component != m_hovered_component) {
|
2021-07-07 23:57:21 +02:00
|
|
|
if (is_enabled())
|
|
|
|
|
update();
|
2019-06-07 10:43:10 +02:00
|
|
|
}
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_pressed_component != Component::Scrubber)
|
2019-02-10 06:51:01 +01:00
|
|
|
return;
|
2019-02-10 12:26:58 +01:00
|
|
|
float delta = orientation() == Orientation::Vertical ? (event.y() - m_scrub_origin.y()) : (event.x() - m_scrub_origin.x());
|
2019-02-10 06:51:01 +01:00
|
|
|
float scrubbable_range = scrubbable_range_in_pixels();
|
2020-12-30 15:20:02 +01:00
|
|
|
float value_steps_per_scrubbed_pixel = (max() - min()) / scrubbable_range;
|
2019-02-10 06:51:01 +01:00
|
|
|
float new_value = m_scrub_start_value + (value_steps_per_scrubbed_pixel * delta);
|
|
|
|
|
set_value(new_value);
|
2019-02-09 11:19:38 +01:00
|
|
|
}
|
2019-04-06 13:55:56 +02:00
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::leave_event(Core::Event&)
|
2019-04-06 13:55:56 +02:00
|
|
|
{
|
2020-08-25 12:54:40 -04:00
|
|
|
if (m_hovered_component != Component::None) {
|
|
|
|
|
m_hovered_component = Component::None;
|
2021-07-07 23:57:21 +02:00
|
|
|
if (is_enabled())
|
|
|
|
|
update();
|
2019-04-10 01:50:10 +02:00
|
|
|
}
|
2019-04-06 13:55:56 +02:00
|
|
|
}
|
2019-05-25 13:40:57 +02:00
|
|
|
|
2021-04-13 16:18:20 +02:00
|
|
|
void Scrollbar::change_event(Event& event)
|
2019-05-25 13:40:57 +02:00
|
|
|
{
|
2020-02-02 15:07:41 +01:00
|
|
|
if (event.type() == Event::Type::EnabledChange) {
|
2019-05-25 13:40:57 +02:00
|
|
|
if (!is_enabled())
|
2020-08-25 12:54:40 -04:00
|
|
|
set_automatic_scrolling_active(false, Component::None);
|
2019-05-25 13:40:57 +02:00
|
|
|
}
|
2020-02-02 15:07:41 +01:00
|
|
|
return Widget::change_event(event);
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 14:19:35 -05:00
|
|
|
void Scrollbar::update_animated_scroll()
|
|
|
|
|
{
|
|
|
|
|
if (value() == m_target_value) {
|
|
|
|
|
m_animated_scrolling_timer->stop();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double time_percent = m_animation_time_elapsed / ANIMATION_TIME;
|
|
|
|
|
double ease_percent = 1.0 - pow(1.0 - time_percent, 5.0); // Ease out quint
|
|
|
|
|
double initial_distance = m_target_value - m_start_value;
|
|
|
|
|
double new_distance = initial_distance * ease_percent;
|
|
|
|
|
int new_value = m_start_value + (int)round(new_distance);
|
|
|
|
|
AbstractSlider::set_value(new_value);
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-25 13:40:57 +02:00
|
|
|
}
|