ladybird/LibGUI/GScrollBar.cpp
Andreas Kling b4da451c9a WindowServer+LibGUI: Implement automatic cursor tracking.
When a mouse button is pressed inside a window, put that window into an
automatic mouse tracking state where all mouse events are sent to that
window until all mouse buttons are released.

This might feel even better if it only cared about the mouse buttons you
actually pressed while *inside* the windows to get released, I don't know.
I'll have to use it for a while and see how it's like.
2019-03-24 15:01:56 +01:00

249 lines
6.9 KiB
C++

#include <LibGUI/GScrollBar.h>
#include <LibGUI/GStyle.h>
#include <SharedGraphics/CharacterBitmap.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/Painter.h>
static const char* s_up_arrow_bitmap_data = {
" "
" # "
" ### "
" ##### "
" ####### "
" ### "
" ### "
" ### "
" "
};
static const char* s_down_arrow_bitmap_data = {
" "
" ### "
" ### "
" ### "
" ####### "
" ##### "
" ### "
" # "
" "
};
static const char* s_left_arrow_bitmap_data = {
" "
" # "
" ## "
" ###### "
" ####### "
" ###### "
" ## "
" # "
" "
};
static const char* s_right_arrow_bitmap_data = {
" "
" # "
" ## "
" ###### "
" ####### "
" ###### "
" ## "
" # "
" "
};
static CharacterBitmap* s_up_arrow_bitmap;
static CharacterBitmap* s_down_arrow_bitmap;
static CharacterBitmap* s_left_arrow_bitmap;
static CharacterBitmap* s_right_arrow_bitmap;
GScrollBar::GScrollBar(Orientation orientation, GWidget* parent)
: GWidget(parent)
, m_orientation(orientation)
{
if (!s_up_arrow_bitmap)
s_up_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_up_arrow_bitmap_data, 9, 9).leak_ref();
if (!s_down_arrow_bitmap)
s_down_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_down_arrow_bitmap_data, 9, 9).leak_ref();
if (!s_left_arrow_bitmap)
s_left_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_left_arrow_bitmap_data, 9, 9).leak_ref();
if (!s_right_arrow_bitmap)
s_right_arrow_bitmap = &CharacterBitmap::create_from_ascii(s_right_arrow_bitmap_data, 9, 9).leak_ref();
if (m_orientation == Orientation::Vertical) {
set_preferred_size({ 15, 0 });
} else {
set_preferred_size({ 0, 15 });
}
}
GScrollBar::~GScrollBar()
{
}
void GScrollBar::set_range(int min, int max)
{
ASSERT(min <= max);
if (m_min == min && m_max == max)
return;
m_min = min;
m_max = max;
int old_value = m_value;
if (m_value < m_min)
m_value = m_min;
if (m_value > m_max)
m_value = m_max;
if (on_change && m_value != old_value)
on_change(m_value);
update();
}
void GScrollBar::set_value(int value)
{
if (value < m_min)
value = m_min;
if (value > m_max)
value = m_max;
if (value == m_value)
return;
m_value = value;
if (on_change)
on_change(value);
update();
}
Rect GScrollBar::up_button_rect() const
{
return { 0, 0, button_size(), button_size() };
}
Rect GScrollBar::down_button_rect() const
{
if (orientation() == Orientation::Vertical)
return { 0, height() - button_size(), button_size(), button_size() };
else
return { width() - button_size(), 0, button_size(), button_size() };
}
Rect GScrollBar::upper_gutter_rect() const
{
if (orientation() == Orientation::Vertical)
return { 0, button_size(), button_size(), scrubber_rect().top() - button_size() };
else
return { button_size(), 0, scrubber_rect().x() - button_size(), button_size() };
}
Rect GScrollBar::lower_gutter_rect() const
{
auto scrubber_rect = this->scrubber_rect();
if (orientation() == Orientation::Vertical)
return { 0, scrubber_rect.bottom() + 1, button_size(), height() - button_size() - scrubber_rect.bottom() - 1};
else
return { scrubber_rect.right() + 1, 0, width() - button_size() - scrubber_rect.right() - 1, button_size() };
}
int GScrollBar::scrubbable_range_in_pixels() const
{
if (orientation() == Orientation::Vertical)
return height() - button_size() * 3;
else
return width() - button_size() * 3;
}
bool GScrollBar::has_scrubber() const
{
return m_max != m_min;
}
Rect GScrollBar::scrubber_rect() const
{
if (!has_scrubber())
return { };
float x_or_y;
if (m_value == m_min)
x_or_y = button_size() - 1;
else if (m_value == m_max)
x_or_y = ((orientation() == Orientation::Vertical ? height() : width()) - (button_size() * 2)) + 1;
else {
float range_size = m_max - m_min;
float available = scrubbable_range_in_pixels();
float step = available / range_size;
x_or_y = (button_size() + (step * m_value));
}
if (orientation() == Orientation::Vertical)
return { 0, (int)x_or_y, button_size(), button_size() };
else
return { (int)x_or_y, 0, button_size(), button_size() };
}
void GScrollBar::paint_event(GPaintEvent& event)
{
Painter painter(*this);
painter.set_clip_rect(event.rect());
painter.fill_rect(rect(), Color(164, 164, 164));
GStyle::the().paint_button(painter, up_button_rect(), GButtonStyle::Normal, false);
painter.draw_bitmap(up_button_rect().location().translated(3, 3), orientation() == Orientation::Vertical ? *s_up_arrow_bitmap : *s_left_arrow_bitmap, has_scrubber() ? Color::Black : Color::MidGray);
GStyle::the().paint_button(painter, down_button_rect(), GButtonStyle::Normal, false);
painter.draw_bitmap(down_button_rect().location().translated(3, 3), orientation() == Orientation::Vertical ? *s_down_arrow_bitmap : *s_right_arrow_bitmap, has_scrubber() ? Color::Black : Color::MidGray);
if (has_scrubber())
GStyle::the().paint_button(painter, scrubber_rect(), GButtonStyle::Normal, false);
}
void GScrollBar::mousedown_event(GMouseEvent& event)
{
if (event.button() != GMouseButton::Left)
return;
if (up_button_rect().contains(event.position())) {
set_value(value() - m_step);
return;
}
if (down_button_rect().contains(event.position())) {
set_value(value() + m_step);
return;
}
if (has_scrubber() && upper_gutter_rect().contains(event.position())) {
set_value(value() - m_big_step);
return;
}
if (has_scrubber() && lower_gutter_rect().contains(event.position())) {
set_value(value() + m_big_step);
return;
}
if (has_scrubber() && scrubber_rect().contains(event.position())) {
m_scrubbing = true;
m_scrub_start_value = value();
m_scrub_origin = event.position();
update();
return;
}
}
void GScrollBar::mouseup_event(GMouseEvent& event)
{
if (event.button() != GMouseButton::Left)
return;
if (!m_scrubbing)
return;
m_scrubbing = false;
update();
}
void GScrollBar::mousemove_event(GMouseEvent& event)
{
if (!m_scrubbing)
return;
float delta = orientation() == Orientation::Vertical ? (event.y() - m_scrub_origin.y()) : (event.x() - m_scrub_origin.x());
float scrubbable_range = scrubbable_range_in_pixels();
float value_steps_per_scrubbed_pixel = (m_max - m_min) / scrubbable_range;
float new_value = m_scrub_start_value + (value_steps_per_scrubbed_pixel * delta);
set_value(new_value);
}