feat: improve scrollbar behavior
Clicking and dragging inside the scrollbar grip will now preserve the mouse click offset and not jump to the center position on click. Clicking outside the scrollbar grip will now jump as though the center of the grip was dragged to the click position. This is more inline with typical GUI scrollbar behaviors.
This commit is contained in:
parent
84225983b7
commit
8745cd05d2
1 changed files with 55 additions and 22 deletions
|
|
@ -13,6 +13,7 @@ plane: Plane,
|
||||||
pos_scrn: u32 = 0,
|
pos_scrn: u32 = 0,
|
||||||
view_scrn: u32 = 8,
|
view_scrn: u32 = 8,
|
||||||
size_scrn: u32 = 8,
|
size_scrn: u32 = 8,
|
||||||
|
mouse_pos_scrn_offset: u32 = 0,
|
||||||
|
|
||||||
pos_virt: u32 = 0,
|
pos_virt: u32 = 0,
|
||||||
view_virt: u32 = 1,
|
view_virt: u32 = 1,
|
||||||
|
|
@ -67,14 +68,16 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||||
|
|
||||||
if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) {
|
if (try m.match(.{ "B", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) {
|
||||||
self.active = true;
|
self.active = true;
|
||||||
self.move_to(y, ypx);
|
self.update_max_ypx(ypx);
|
||||||
|
self.click_at(y, ypx);
|
||||||
return true;
|
return true;
|
||||||
} else if (try m.match(.{ "B", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.more })) {
|
} else if (try m.match(.{ "B", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.more })) {
|
||||||
self.active = false;
|
self.active = false;
|
||||||
return true;
|
return true;
|
||||||
} else if (try m.match(.{ "D", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) {
|
} else if (try m.match(.{ "D", input.event.press, @intFromEnum(input.mouse.BUTTON1), tp.any, tp.any, tp.extract(&y), tp.any, tp.extract(&ypx) })) {
|
||||||
self.active = true;
|
self.active = true;
|
||||||
self.move_to(y, ypx);
|
self.update_max_ypx(ypx);
|
||||||
|
self.drag_to(y, ypx);
|
||||||
return true;
|
return true;
|
||||||
} else if (try m.match(.{ "B", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.more })) {
|
} else if (try m.match(.{ "B", input.event.release, @intFromEnum(input.mouse.BUTTON1), tp.more })) {
|
||||||
self.active = false;
|
self.active = false;
|
||||||
|
|
@ -89,33 +92,63 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_to(self: *Self, y_: i32, ypx_: i32) void {
|
fn update_max_ypx(self: *Self, ypx_: i32) void {
|
||||||
self.max_ypx = @max(self.max_ypx, ypx_);
|
self.max_ypx = @max(self.max_ypx, ypx_);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y_coord_to_pos_scrn(self: *const Self, y_: i32, ypx_: i32) u32 {
|
||||||
const max_ypx: f64 = @floatFromInt(self.max_ypx);
|
const max_ypx: f64 = @floatFromInt(self.max_ypx);
|
||||||
const y: f64 = @floatFromInt(y_);
|
const y: f64 = @floatFromInt(y_);
|
||||||
const ypx: f64 = @floatFromInt(ypx_);
|
const ypx: f64 = @floatFromInt(ypx_);
|
||||||
const plane_y: f64 = @floatFromInt(self.plane.abs_y());
|
const plane_y: f64 = @floatFromInt(self.plane.abs_y());
|
||||||
const size_scrn: f64 = @floatFromInt(self.size_scrn);
|
|
||||||
const view_scrn: f64 = @floatFromInt(self.view_scrn);
|
|
||||||
|
|
||||||
const ratio = max_ypx / eighths_c;
|
const ratio = max_ypx / eighths_c;
|
||||||
const pos_scrn: f64 = ((y - plane_y) * eighths_c) + (ypx / ratio) - (view_scrn / 2);
|
const pos_scrn_ = ((y - plane_y) * eighths_c) + (ypx / ratio);
|
||||||
const max_pos_scrn = size_scrn - view_scrn;
|
const pos_scrn: i32 = @intFromFloat(pos_scrn_);
|
||||||
const pos_scrn_clamped = @min(@max(0, pos_scrn), max_pos_scrn);
|
return @max(0, pos_scrn);
|
||||||
const pos_virt = self.pos_scrn_to_virt(@intFromFloat(pos_scrn_clamped));
|
|
||||||
|
|
||||||
self.set(self.size_virt, self.view_virt, pos_virt);
|
|
||||||
_ = self.event_sink.msg(.{ "scroll_to", pos_virt }) catch {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pos_scrn_to_virt(self: Self, pos_scrn_: u32) u32 {
|
fn clamp_pos_scrn(self: *const Self, pos_scrn: u32) u32 {
|
||||||
|
const max_pos_scrn = self.size_scrn -| self.view_scrn;
|
||||||
|
return @min(pos_scrn, max_pos_scrn);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pos_scrn_to_virt(self: *const Self, pos_scrn_: u32) u32 {
|
||||||
|
const pos_scrn: f64 = @floatFromInt(self.clamp_pos_scrn(pos_scrn_));
|
||||||
const size_virt: f64 = @floatFromInt(self.size_virt);
|
const size_virt: f64 = @floatFromInt(self.size_virt);
|
||||||
const size_scrn: f64 = @floatFromInt(self.plane.dim_y() * eighths_c);
|
const size_scrn: f64 = @floatFromInt(self.plane.dim_y() * eighths_c);
|
||||||
const pos_scrn: f64 = @floatFromInt(pos_scrn_);
|
|
||||||
const ratio = size_virt / size_scrn;
|
const ratio = size_virt / size_scrn;
|
||||||
return @intFromFloat(pos_scrn * ratio);
|
return @intFromFloat(pos_scrn * ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_pos_scrn_in_bar(self: *const Self, pos_scrn: u32) bool {
|
||||||
|
return pos_scrn > self.pos_scrn and pos_scrn <= self.pos_scrn + self.view_scrn;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn click_at(self: *Self, y: i32, ypx: i32) void {
|
||||||
|
const pos_scrn = self.y_coord_to_pos_scrn(y, ypx);
|
||||||
|
if (self.is_pos_scrn_in_bar(pos_scrn)) {
|
||||||
|
self.mouse_pos_scrn_offset = pos_scrn -| self.pos_scrn;
|
||||||
|
@import("std").log.debug("click: {d}:{d}", .{ pos_scrn, self.mouse_pos_scrn_offset });
|
||||||
|
} else {
|
||||||
|
self.mouse_pos_scrn_offset = self.view_scrn / 2;
|
||||||
|
@import("std").log.debug("click off: {d}:{d}", .{ pos_scrn, self.mouse_pos_scrn_offset });
|
||||||
|
self.move_to(self.pos_scrn_to_virt(pos_scrn -| self.mouse_pos_scrn_offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag_to(self: *Self, y: i32, ypx: i32) void {
|
||||||
|
const pos_scrn = self.y_coord_to_pos_scrn(y, ypx) -| self.mouse_pos_scrn_offset;
|
||||||
|
const pos_virt = self.pos_scrn_to_virt(pos_scrn);
|
||||||
|
@import("std").log.debug("drag_to: {d:}:{d}:{d}", .{ pos_scrn, pos_virt, self.mouse_pos_scrn_offset });
|
||||||
|
self.move_to(pos_virt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn move_to(self: *Self, pos_virt: u32) void {
|
||||||
|
self.set(self.size_virt, self.view_virt, pos_virt);
|
||||||
|
_ = self.event_sink.msg(.{ "scroll_to", pos_virt }) catch {};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||||
const style = if (self.style_factory) |f|
|
const style = if (self.style_factory) |f|
|
||||||
f(self, theme)
|
f(self, theme)
|
||||||
|
|
@ -130,7 +163,7 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
|
||||||
self.plane.set_base_style(style);
|
self.plane.set_base_style(style);
|
||||||
self.plane.erase();
|
self.plane.erase();
|
||||||
if (!(tui.config().scrollbar_auto_hide and self.size_scrn == self.view_scrn))
|
if (!(tui.config().scrollbar_auto_hide and self.size_scrn == self.view_scrn))
|
||||||
smooth_bar_at(&self.plane, @intCast(self.pos_scrn), @intCast(self.view_scrn)) catch {};
|
smooth_bar_at(&self.plane, self.pos_scrn, self.view_scrn) catch {};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,19 +189,19 @@ pub fn set(self: *Self, size_virt_: u32, view_virt_: u32, pos_virt_: u32) void {
|
||||||
|
|
||||||
const eighths_b = [_][]const u8{ "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁" };
|
const eighths_b = [_][]const u8{ "█", "▇", "▆", "▅", "▄", "▃", "▂", "▁" };
|
||||||
const eighths_t = [_][]const u8{ " ", "▔", "🮂", "🮃", "▀", "🮄", "🮅", "🮆" };
|
const eighths_t = [_][]const u8{ " ", "▔", "🮂", "🮃", "▀", "🮄", "🮅", "🮆" };
|
||||||
const eighths_c: i32 = @intCast(eighths_b.len);
|
const eighths_c: u32 = eighths_b.len;
|
||||||
|
|
||||||
fn smooth_bar_at(plane: *Plane, pos_: i32, size_: i32) !void {
|
fn smooth_bar_at(plane: *Plane, pos_: u32, size_: u32) !void {
|
||||||
const height: i32 = @intCast(plane.dim_y());
|
const height: u32 = plane.dim_y();
|
||||||
var size = @max(size_, 8);
|
var size = @max(size_, 8);
|
||||||
const pos: i32 = @min(height * eighths_c - size, pos_);
|
const pos = @min(height * eighths_c - size, pos_);
|
||||||
var pos_y = @as(c_int, @intCast(@divFloor(pos, eighths_c)));
|
var pos_y: c_int = @intCast(@divFloor(pos, eighths_c));
|
||||||
const blk = @mod(pos, eighths_c);
|
const blk = @mod(pos, eighths_c);
|
||||||
const b = eighths_b[@intCast(blk)];
|
const b = eighths_b[blk];
|
||||||
plane.erase();
|
plane.erase();
|
||||||
plane.cursor_move_yx(pos_y, 0) catch return;
|
plane.cursor_move_yx(pos_y, 0) catch return;
|
||||||
_ = try plane.putstr(@ptrCast(b));
|
_ = try plane.putstr(@ptrCast(b));
|
||||||
size -= @as(u16, @intCast(eighths_c)) - @as(u16, @intCast(blk));
|
size -= eighths_c - blk;
|
||||||
while (size >= 8) {
|
while (size >= 8) {
|
||||||
pos_y += 1;
|
pos_y += 1;
|
||||||
size -= 8;
|
size -= 8;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue