Add a scrollbar to the list view

This commit is contained in:
Magnus Norddahl 2024-01-03 02:39:20 +01:00 committed by Christoph Oelckers
parent e27cbe5bd9
commit 9745942130
7 changed files with 178 additions and 94 deletions

View File

@ -46,6 +46,15 @@ public:
static Rect xywh(double x, double y, double width, double height) { return Rect(x, y, width, height); }
static Rect ltrb(double left, double top, double right, double bottom) { return Rect(left, top, right - left, bottom - top); }
static Rect shrink(Rect box, double left, double top, double right, double bottom)
{
box.x += left;
box.y += top;
box.width = std::max(box.width - left - right, 0.0);
box.height = std::max(box.height - bottom - top, 0.0);
return box;
}
bool contains(const Point& p) const { return (p.x >= x && p.x < x + width) && (p.y >= y && p.y < y + height); }
double x = 0;

View File

@ -5,6 +5,8 @@
#include <vector>
#include <functional>
class Scrollbar;
class ListView : public Widget
{
public:
@ -12,6 +14,7 @@ public:
void AddItem(const std::string& text);
int GetSelectedItem() const { return selectedItem; }
void ScrollToItem(int index);
void Activate();
@ -22,7 +25,12 @@ protected:
void OnPaintFrame(Canvas* canvas) override;
void OnMouseDown(const Point& pos, int key) override;
void OnMouseDoubleclick(const Point& pos, int key) override;
void OnMouseWheel(const Point& pos, EInputKey key) override;
void OnKeyDown(EInputKey key) override;
void OnGeometryChanged() override;
void OnScrollbarScroll();
Scrollbar* scrollbar = nullptr;
std::vector<std::string> items;
int selectedItem = 0;

View File

@ -13,24 +13,27 @@ public:
bool IsVertical() const;
bool IsHorizontal() const;
int GetMin() const;
int GetMax() const;
int GetLineStep() const;
int GetPageStep() const;
int GetPosition() const;
double GetMin() const;
double GetMax() const;
double GetLineStep() const;
double GetPageStep() const;
double GetPosition() const;
void SetVertical();
void SetHorizontal();
void SetMin(int scroll_min);
void SetMax(int scroll_max);
void SetLineStep(int step);
void SetPageStep(int step);
void SetMin(double scroll_min);
void SetMax(double scroll_max);
void SetLineStep(double step);
void SetPageStep(double step);
void SetRanges(int scroll_min, int scroll_max, int line_step, int page_step);
void SetRanges(int view_size, int total_size);
void SetRanges(double scroll_min, double scroll_max, double line_step, double page_step);
void SetRanges(double view_size, double total_size);
void SetPosition(int pos);
void SetPosition(double pos);
double GetPreferredWidth() const { return 16.0; }
double GetPreferredHeight() const { return 16.0; }
std::function<void()> FuncScroll;
std::function<void()> FuncScrollMin;
@ -54,18 +57,20 @@ protected:
private:
bool UpdatePartPositions();
int CalculateThumbSize(int track_size);
int CalculateThumbPosition(int thumb_size, int track_size);
Rect CreateRect(int start, int end);
double CalculateThumbSize(double track_size);
double CalculateThumbPosition(double thumb_size, double track_size);
Rect CreateRect(double start, double end);
void InvokeScrollEvent(std::function<void()>* event_ptr);
void OnTimerExpired();
bool vertical = false;
int scroll_min = 0;
int scroll_max = 1;
int line_step = 1;
int page_step = 10;
int position = 0;
bool vertical = true;
double scroll_min = 0.0;
double scroll_max = 1.0;
double line_step = 1.0;
double page_step = 10.0;
double position = 0.0;
bool showbuttons = false;
enum MouseDownMode
{
@ -77,12 +82,12 @@ private:
mouse_down_thumb_drag
} mouse_down_mode = mouse_down_none;
int thumb_start_position = 0;
double thumb_start_position = 0.0;
Point mouse_drag_start_pos;
int thumb_start_pixel_position = 0;
double thumb_start_pixel_position = 0.0;
Timer* mouse_down_timer = nullptr;
int last_step_size = 0;
double last_step_size = 0.0;
Rect rect_button_decrement;
Rect rect_track_decrement;
@ -91,7 +96,4 @@ private:
Rect rect_button_increment;
std::function<void()>* FuncScrollOnMouseDown = nullptr;
static const int decr_height = 16;
static const int incr_height = 16;
};

View File

@ -419,7 +419,7 @@ Widget* Widget::ChildAt(const Point& pos)
{
if (cur->FrameGeometry.contains(pos))
{
Widget* cur2 = cur->ChildAt(pos - cur->FrameGeometry.topLeft());
Widget* cur2 = cur->ChildAt(pos - cur->ContentGeometry.topLeft());
return cur2 ? cur2 : cur;
}
}

View File

@ -1,9 +1,13 @@
#include "widgets/listview/listview.h"
#include "widgets/scrollbar/scrollbar.h"
ListView::ListView(Widget* parent) : Widget(parent)
{
SetNoncontentSizes(10.0, 5.0, 10.0, 5.0);
SetNoncontentSizes(10.0, 10.0, 3.0, 10.0);
scrollbar = new Scrollbar(this);
scrollbar->FuncScroll = [=]() { OnScrollbarScroll(); };
}
void ListView::AddItem(const std::string& text)
@ -18,21 +22,53 @@ void ListView::Activate()
OnActivated();
}
void ListView::ScrollToItem(int index)
{
double itemHeight = 20.0;
double y = itemHeight * index;
if (y < scrollbar->GetPosition())
{
scrollbar->SetPosition(y);
}
else if (y + itemHeight > scrollbar->GetPosition() + GetHeight())
{
scrollbar->SetPosition(std::max(y + itemHeight - GetHeight(), 0.0));
}
}
void ListView::OnScrollbarScroll()
{
Update();
}
void ListView::OnGeometryChanged()
{
double w = GetWidth();
double h = GetHeight();
double sw = scrollbar->GetPreferredWidth();
scrollbar->SetFrameGeometry(Rect::xywh(w - sw, 0.0, sw, h));
scrollbar->SetRanges(h, items.size() * 20.0);
}
void ListView::OnPaint(Canvas* canvas)
{
double y = 20.0;
double y = -scrollbar->GetPosition();
double x = 2.0;
double w = GetFrameGeometry().width;
double w = GetWidth() - scrollbar->GetPreferredWidth() - 2.0;
double h = 20.0;
int index = 0;
for (const std::string& item : items)
{
if (index == selectedItem)
double itemY = y;
if (itemY + h >= 0.0 && itemY < GetHeight())
{
canvas->fillRect(Rect::xywh(x - 2.0, y + 5.0 - h, w, h), Colorf::fromRgba8(100, 100, 100));
if (index == selectedItem)
{
canvas->fillRect(Rect::xywh(x - 2.0, itemY, w, h), Colorf::fromRgba8(100, 100, 100));
}
canvas->drawText(Point(x, y + 15.0), Colorf::fromRgba8(255, 255, 255), item);
}
canvas->drawText(Point(x, y), Colorf::fromRgba8(255, 255, 255), item);
y += h;
index++;
}
@ -56,11 +92,12 @@ void ListView::OnMouseDown(const Point& pos, int key)
if (key == IK_LeftMouse)
{
int index = (int)((pos.y - 5.0) / 20.0);
int index = (int)((pos.y - 5.0 + scrollbar->GetPosition()) / 20.0);
if (index >= 0 && (size_t)index < items.size())
{
selectedItem = index;
Update();
ScrollToItem(selectedItem);
}
}
}
@ -73,6 +110,18 @@ void ListView::OnMouseDoubleclick(const Point& pos, int key)
}
}
void ListView::OnMouseWheel(const Point& pos, EInputKey key)
{
if (key == IK_MouseWheelUp)
{
scrollbar->SetPosition(std::max(scrollbar->GetPosition() - 20.0, 0.0));
}
else if (key == IK_MouseWheelDown)
{
scrollbar->SetPosition(std::max(scrollbar->GetPosition() + 20.0, scrollbar->GetMax()));
}
}
void ListView::OnKeyDown(EInputKey key)
{
if (key == IK_Down)
@ -82,6 +131,7 @@ void ListView::OnKeyDown(EInputKey key)
selectedItem++;
Update();
}
ScrollToItem(selectedItem);
}
else if (key == IK_Up)
{
@ -90,6 +140,7 @@ void ListView::OnKeyDown(EInputKey key)
selectedItem--;
Update();
}
ScrollToItem(selectedItem);
}
else if (key == IK_Enter)
{

View File

@ -25,27 +25,27 @@ bool Scrollbar::IsHorizontal() const
return !vertical;
}
int Scrollbar::GetMin() const
double Scrollbar::GetMin() const
{
return scroll_min;
}
int Scrollbar::GetMax() const
double Scrollbar::GetMax() const
{
return scroll_max;
}
int Scrollbar::GetLineStep() const
double Scrollbar::GetLineStep() const
{
return line_step;
}
int Scrollbar::GetPageStep() const
double Scrollbar::GetPageStep() const
{
return page_step;
}
int Scrollbar::GetPosition() const
double Scrollbar::GetPosition() const
{
return position;
}
@ -64,61 +64,61 @@ void Scrollbar::SetHorizontal()
Update();
}
void Scrollbar::SetMin(int new_scroll_min)
void Scrollbar::SetMin(double new_scroll_min)
{
SetRanges(new_scroll_min, scroll_max, line_step, page_step);
}
void Scrollbar::SetMax(int new_scroll_max)
void Scrollbar::SetMax(double new_scroll_max)
{
SetRanges(scroll_min, new_scroll_max, line_step, page_step);
}
void Scrollbar::SetLineStep(int step)
void Scrollbar::SetLineStep(double step)
{
SetRanges(scroll_min, scroll_max, step, page_step);
}
void Scrollbar::SetPageStep(int step)
void Scrollbar::SetPageStep(double step)
{
SetRanges(scroll_min, scroll_max, line_step, step);
}
void Scrollbar::SetRanges(int scroll_min, int scroll_max, int line_step, int page_step)
void Scrollbar::SetRanges(double new_scroll_min, double new_scroll_max, double new_line_step, double new_page_step)
{
if (scroll_min >= scroll_max || line_step <= 0 || page_step <= 0)
if (new_scroll_min >= new_scroll_max || new_line_step <= 0.0 || new_page_step <= 0.0)
throw std::runtime_error("Scrollbar ranges out of bounds!");
scroll_min = scroll_min;
scroll_max = scroll_max;
line_step = line_step;
page_step = page_step;
scroll_min = new_scroll_min;
scroll_max = new_scroll_max;
line_step = new_line_step;
page_step = new_page_step;
if (position >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (position < scroll_min)
position = scroll_min;
if (UpdatePartPositions())
Update();
}
void Scrollbar::SetRanges(int view_size, int total_size)
void Scrollbar::SetRanges(double view_size, double total_size)
{
if (view_size <= 0 || total_size <= 0)
if (view_size <= 0.0 || total_size <= 0.0)
{
SetRanges(0, 1, 1, 1);
SetRanges(0.0, 1.0, 1.0, 1.0);
}
else
{
int scroll_max = std::max(1, total_size - view_size + 1);
int page_step = std::max(1, view_size);
SetRanges(0, scroll_max, 1, page_step);
double scroll_max = std::max(1.0, total_size - view_size + 1.0);
double page_step = std::max(1.0, view_size);
SetRanges(0.0, scroll_max, 10, page_step);
}
}
void Scrollbar::SetPosition(int pos)
void Scrollbar::SetPosition(double pos)
{
position = pos;
if (pos >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (pos < scroll_min)
position = scroll_min;
@ -130,24 +130,24 @@ void Scrollbar::OnMouseMove(const Point& pos)
{
if (mouse_down_mode == mouse_down_thumb_drag)
{
int last_position = position;
double last_position = position;
if (pos.x < -100 || pos.x > GetWidth() + 100 || pos.y < -100 || pos.y > GetHeight() + 100)
if (pos.x < -100.0 || pos.x > GetWidth() + 100.0 || pos.y < -100.0 || pos.y > GetHeight() + 100.0)
{
position = thumb_start_position;
}
else
{
int delta = (int)(vertical ? (pos.y - mouse_drag_start_pos.y) : (pos.x - mouse_drag_start_pos.x));
int position_pixels = thumb_start_pixel_position + delta;
double delta = vertical ? (pos.y - mouse_drag_start_pos.y) : (pos.x - mouse_drag_start_pos.x);
double position_pixels = thumb_start_pixel_position + delta;
int track_height = 0;
double track_height = 0;
if (vertical)
track_height = (int)(rect_track_decrement.height + rect_track_increment.height);
track_height = rect_track_decrement.height + rect_track_increment.height;
else
track_height = (int)(rect_track_decrement.width + rect_track_increment.width);
track_height = rect_track_decrement.width + rect_track_increment.width;
if (track_height != 0)
if (track_height != 0.0)
position = scroll_min + position_pixels * (scroll_max - scroll_min) / track_height;
else
position = 0;
@ -178,12 +178,12 @@ void Scrollbar::OnMouseDown(const Point& pos, int key)
mouse_down_mode = mouse_down_button_decr;
FuncScrollOnMouseDown = &FuncScrollLineDecrement;
int last_position = position;
double last_position = position;
position -= line_step;
last_step_size = -line_step;
if (position >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (position < scroll_min)
position = scroll_min;
@ -195,12 +195,12 @@ void Scrollbar::OnMouseDown(const Point& pos, int key)
mouse_down_mode = mouse_down_button_incr;
FuncScrollOnMouseDown = &FuncScrollLineIncrement;
int last_position = position;
double last_position = position;
position += line_step;
last_step_size = line_step;
if (position >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (position < scroll_min)
position = scroll_min;
@ -211,19 +211,19 @@ void Scrollbar::OnMouseDown(const Point& pos, int key)
{
mouse_down_mode = mouse_down_thumb_drag;
thumb_start_position = position;
thumb_start_pixel_position = (int)(vertical ? (rect_thumb.y - rect_track_decrement.y) : (rect_thumb.x - rect_track_decrement.x));
thumb_start_pixel_position = vertical ? (rect_thumb.y - rect_track_decrement.y) : (rect_thumb.x - rect_track_decrement.x);
}
else if (rect_track_decrement.contains(pos))
{
mouse_down_mode = mouse_down_track_decr;
FuncScrollOnMouseDown = &FuncScrollPageDecrement;
int last_position = position;
double last_position = position;
position -= page_step;
last_step_size = -page_step;
if (position >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (position < scroll_min)
position = scroll_min;
@ -235,12 +235,12 @@ void Scrollbar::OnMouseDown(const Point& pos, int key)
mouse_down_mode = mouse_down_track_incr;
FuncScrollOnMouseDown = &FuncScrollPageIncrement;
int last_position = position;
double last_position = position;
position += page_step;
last_step_size = page_step;
if (position >= scroll_max)
position = scroll_max - 1;
position = scroll_max - 1.0;
if (position < scroll_min)
position = scroll_min;
@ -291,20 +291,26 @@ void Scrollbar::OnPaint(Canvas* canvas)
part_track_increment.render_box(canvas, rect_track_increment);
part_button_increment.render_box(canvas, rect_button_increment);
*/
canvas->fillRect(Rect::shrink(Rect::xywh(0.0, 0.0, GetWidth(), GetHeight()), 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(33, 33, 33));
canvas->fillRect(Rect::shrink(rect_thumb, 4.0, 0.0, 4.0, 0.0), Colorf::fromRgba8(58, 58, 58));
}
// Calculates positions of all parts. Returns true if thumb position was changed compared to previously, false otherwise.
bool Scrollbar::UpdatePartPositions()
{
int total_height = (int)(vertical ? GetHeight() : GetWidth());
int track_height = std::max(0, total_height - decr_height - incr_height);
int thumb_height = CalculateThumbSize(track_height);
double decr_height = showbuttons ? 16.0 : 0.0;
double incr_height = showbuttons ? 16.0 : 0.0;
int thumb_offset = decr_height + CalculateThumbPosition(thumb_height, track_height);
double total_height = vertical ? GetHeight() : GetWidth();
double track_height = std::max(0.0, total_height - decr_height - incr_height);
double thumb_height = CalculateThumbSize(track_height);
double thumb_offset = decr_height + CalculateThumbPosition(thumb_height, track_height);
Rect previous_rect_thumb = rect_thumb;
rect_button_decrement = CreateRect(0, decr_height);
rect_button_decrement = CreateRect(0.0, decr_height);
rect_track_decrement = CreateRect(decr_height, thumb_offset);
rect_thumb = CreateRect(thumb_offset, thumb_offset + thumb_height);
rect_track_increment = CreateRect(thumb_offset + thumb_height, decr_height + track_height);
@ -313,12 +319,12 @@ bool Scrollbar::UpdatePartPositions()
return (previous_rect_thumb != rect_thumb);
}
int Scrollbar::CalculateThumbSize(int track_size)
double Scrollbar::CalculateThumbSize(double track_size)
{
int minimum_thumb_size = 20;
int range = scroll_max - scroll_min;
int length = range + page_step - 1;
int thumb_size = page_step * track_size / length;
double minimum_thumb_size = 20.0;
double range = scroll_max - scroll_min;
double length = range + page_step - 1;
double thumb_size = page_step * track_size / length;
if (thumb_size < minimum_thumb_size)
thumb_size = minimum_thumb_size;
if (thumb_size > track_size)
@ -326,13 +332,13 @@ int Scrollbar::CalculateThumbSize(int track_size)
return thumb_size;
}
int Scrollbar::CalculateThumbPosition(int thumb_size, int track_size)
double Scrollbar::CalculateThumbPosition(double thumb_size, double track_size)
{
int relative_pos = position - scroll_min;
int range = scroll_max - scroll_min - 1;
double relative_pos = position - scroll_min;
double range = scroll_max - scroll_min - 1;
if (range != 0)
{
int available_area = std::max(0, track_size - thumb_size);
double available_area = std::max(0.0, track_size - thumb_size);
return relative_pos * available_area / range;
}
else
@ -341,7 +347,7 @@ int Scrollbar::CalculateThumbPosition(int thumb_size, int track_size)
}
}
Rect Scrollbar::CreateRect(int start, int end)
Rect Scrollbar::CreateRect(double start, double end)
{
if (vertical)
return Rect(0.0, start, GetWidth(), end - start);
@ -356,7 +362,7 @@ void Scrollbar::OnTimerExpired()
mouse_down_timer->Start(100, false);
int last_position = position;
double last_position = position;
position += last_step_size;
if (position >= scroll_max)
position = scroll_max - 1;
@ -395,6 +401,6 @@ void Scrollbar::InvokeScrollEvent(std::function<void()>* event_ptr)
if (FuncScroll)
FuncScroll();
if (event_ptr)
if (event_ptr && *event_ptr)
(*event_ptr)();
}

View File

@ -455,7 +455,15 @@ LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
else if (msg == WM_MOUSEWHEEL)
{
double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA;
WindowHost->OnWindowMouseWheel(GetLParamPos(lparam), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp);
// Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates.
double dpiscale = GetDpiScale();
POINT pos;
pos.x = GET_X_LPARAM(lparam);
pos.y = GET_Y_LPARAM(lparam);
ScreenToClient(WindowHandle, &pos);
WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp);
}
else if (msg == WM_CHAR)
{