add scrollbar

This commit is contained in:
Rokas Puzonas 2025-02-03 00:21:07 +02:00
parent 43b6ca0ff2
commit fa4fb2009f
2 changed files with 242 additions and 57 deletions

View File

@ -212,14 +212,9 @@ pub fn tick(self: *App) !void {
}
{
const rows_container = self.ui.newBoxFromString("Channels");
rows_container.layout_axis = .Y;
rows_container.size = .{
.x = UI.Size.percent(1, 1),
.y = UI.Size.percent(1, 0),
};
self.ui.pushParent(rows_container);
defer self.ui.popParent();
const scroll_area = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels"));
scroll_area.layout_axis = .Y;
defer self.ui.popScrollbar();
for (self.channels.slice()) |*_channel| {
const channel: *Channel = _channel;
@ -260,7 +255,7 @@ pub fn tick(self: *App) !void {
{
const middle_box = self.ui.newBoxFromString("Middle knob");
middle_box.flags.insert(.clickable);
middle_box.flags.insert(.draggable);
middle_box.flags.insert(.draggable_x);
middle_box.background = rl.Color.ray_white;
middle_box.size.y = UI.Size.pixels(32, 1);
@ -275,14 +270,14 @@ pub fn tick(self: *App) !void {
channel.view_rect.to += samples_moved;
}
middle_box.override_x = minimap_rect.width * channel.view_rect.from / sample_count + 4;
middle_box.size.x = UI.Size.pixels(minimap_rect.width * (channel.view_rect.to - channel.view_rect.from) / sample_count - 8, 1);
middle_box.setFixedX(minimap_rect.width * channel.view_rect.from / sample_count + 4);
middle_box.setFixedWidth(minimap_rect.width * (channel.view_rect.to - channel.view_rect.from) / sample_count - 8);
}
{
const left_knob_box = self.ui.newBoxFromString("Left knob");
left_knob_box.flags.insert(.clickable);
left_knob_box.flags.insert(.draggable);
left_knob_box.flags.insert(.draggable_x);
left_knob_box.background = rl.Color.ray_white;
left_knob_box.size.x = UI.Size.pixels(8, 1);
left_knob_box.size.y = UI.Size.pixels(32, 1);
@ -299,13 +294,13 @@ pub fn tick(self: *App) !void {
channel.view_rect.from = clamp(channel.view_rect.from, 0, channel.view_rect.to-min_visible_samples);
}
left_knob_box.override_x = minimap_rect.width * channel.view_rect.from / sample_count - left_knob_box.persistent.size.x/2;
left_knob_box.setFixedX(minimap_rect.width * channel.view_rect.from / sample_count - left_knob_box.persistent.size.x/2);
}
{
const right_knob_box = self.ui.newBoxFromString("Right knobaaa");
right_knob_box.flags.insert(.clickable);
right_knob_box.flags.insert(.draggable);
right_knob_box.flags.insert(.draggable_x);
right_knob_box.background = rl.Color.ray_white;
right_knob_box.size.x = UI.Size.pixels(8, 1);
right_knob_box.size.y = UI.Size.pixels(32, 1);
@ -322,22 +317,9 @@ pub fn tick(self: *App) !void {
channel.view_rect.to = clamp(channel.view_rect.to, channel.view_rect.from+min_visible_samples, sample_count);
}
right_knob_box.override_x = minimap_rect.width * channel.view_rect.to / sample_count - right_knob_box.persistent.size.x/2;
right_knob_box.setFixedX(minimap_rect.width * channel.view_rect.to / sample_count - right_knob_box.persistent.size.x/2);
}
}
// const graph_widget = self.ui.newWidget(self.ui.keyFromString("samples-plot"));
// graph_widget.size.y = .{ .pixels = channel.height };
// graph_widget.size.x = .{ .percent = 1 };
// graph_widget.graph = .{
// .cache = &channel.view_cache,
// .options = channel.view_rect,
// .samples = channel.samples.owned
// };
// const minimap_widget = self.showChannelMinimap(channel);
// minimap_widget.size.y = .fit_children;
// minimap_widget.size.x = .{ .percent = 1 };
}
}
}

View File

@ -4,10 +4,11 @@ const Assets = @import("./assets.zig");
const rect_utils = @import("./rect-utils.zig");
const srcery = @import("./srcery.zig");
const log = std.lgo.scoped(.ui);
const log = std.log.scoped(.ui);
const assert = std.debug.assert;
const Vec2 = rl.Vector2;
const Rect = rl.Rectangle;
const clamp = std.math.clamp;
const UI = @This();
@ -225,11 +226,21 @@ pub const Box = struct {
pub const Persistent = struct {
size: Vec2 = .{ .x = 0, .y = 0 },
position: Vec2 = .{ .x = 0, .y = 0 },
children_size: Vec2 = .{ .x = 0, .y = 0 },
sroll_offset: f32 = 0
};
pub const Flag = enum {
clickable,
draggable
draggable_x,
draggable_y,
fixed_x,
fixed_y,
fixed_width,
fixed_height
};
pub const Flags = std.EnumSet(Flag);
@ -239,8 +250,7 @@ pub const Box = struct {
key: Key,
size: Vec2Size = Vec2Size.zero(),
flags: Flags = .{},
override_x: ?f32 = null,
override_y: ?f32 = null,
fixed_rect: Rect = .{ .x = 0, .y = 0, .width = 0, .height = 0 },
background: ?rl.Color = null,
rounded: bool = false,
layout_axis: Axis = .X,
@ -251,6 +261,7 @@ pub const Box = struct {
color: rl.Color = srcery.bright_white
} = null,
texture: ?rl.Texture2D = null,
view_offset: Vec2 = .{ .x = 0, .y = 0 },
persistent: Persistent = .{},
@ -282,6 +293,83 @@ pub const Box = struct {
.font = font
};
}
pub fn setFixedX(self: *Box, x: f32) void {
self.flags.insert(.fixed_x);
self.fixed_rect.x = x;
}
pub fn setFixedY(self: *Box, y: f32) void {
self.flags.insert(.fixed_y);
self.fixed_rect.y = y;
}
pub fn setFixedWidth(self: *Box, width: f32) void {
self.flags.insert(.fixed_width);
self.fixed_rect.width = width;
}
pub fn setFixedHeight(self: *Box, height: f32) void {
self.flags.insert(.fixed_height);
self.fixed_rect.height = height;
}
pub fn setFixedPosition(self: *Box, position: Vec2) void {
self.setFixedX(position.x);
self.setFixedY(position.y);
}
pub fn setFixedSize(self: *Box, size: Vec2) void {
self.setFixedWidth(size.x);
self.setFixedHeight(size.y);
}
pub fn setFixedRect(self: *Box, rect: Rect) void {
self.setFixedX(rect.x);
self.setFixedY(rect.y);
self.setFixedWidth(rect.width);
self.setFixedHeight(rect.height);
}
fn getFixedPositionAxis(self: *Box, axis: Axis) ?f32 {
if (!self.isPositionFixed(axis)) {
return null;
}
return switch (axis) {
.X => self.fixed_rect.x,
.Y => self.fixed_rect.y
};
}
fn getFixedSizeAxis(self: *Box, axis: Axis) ?f32 {
if (!self.isSizeFixed(axis)) {
return null;
}
return switch (axis) {
.X => self.fixed_rect.width,
.Y => self.fixed_rect.height
};
}
fn isPositionFixed(self: *Box, axis: Axis) bool {
const flag = switch (axis) {
.X => Flag.fixed_x,
.Y => Flag.fixed_y
};
return self.flags.contains(flag);
}
fn isSizeFixed(self: *Box, axis: Axis) bool {
const flag = switch (axis) {
.X => Flag.fixed_width,
.Y => Flag.fixed_height
};
return self.flags.contains(flag);
}
};
const BoxChildIterator = struct {
@ -351,13 +439,16 @@ pub fn begin(self: *UI) void {
self.mouse_delta = mouse.subtract(self.mouse);
const active_box_flags = self.getActiveBoxFlags();
if (active_box_flags.contains(.draggable)) {
const mouse_x = rl.getMouseX();
const mouse_y = rl.getMouseY();
if (active_box_flags.contains(.draggable_x)) {
rl.setMousePosition(
@mod(mouse_x, @as(i32, @intFromFloat(self.window_size.x))),
@mod(mouse_y, @as(i32, @intFromFloat(self.window_size.y)))
@mod(rl.getMouseX(), window_width),
rl.getMouseY()
);
}
if (active_box_flags.contains(.draggable_y)) {
rl.setMousePosition(
rl.getMouseX(),
@mod(rl.getMouseY(), window_height)
);
}
@ -420,8 +511,10 @@ pub fn end(self: *UI) void {
}
}
if (active_box_flags.contains(.draggable)) {
if (active_box_flags.contains(.draggable_x)) {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
} else if (active_box_flags.contains(.draggable_y)) {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
} else if (hover_box_flags.contains(.clickable)) {
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
} else {
@ -526,6 +619,17 @@ pub fn draw(self: *UI) void {
fn drawBox(self: *UI, box: *Box) void {
const box_rect = box.computedRect();
const do_scissor = box.view_offset.equals(Vec2.zero()) == 0;
if (do_scissor) {
rl.beginScissorMode(
@intFromFloat(box_rect.x),
@intFromFloat(box_rect.y),
@intFromFloat(box_rect.width),
@intFromFloat(box_rect.height)
);
}
defer if (do_scissor) rl.endScissorMode();
if (box.background) |background| {
rl.drawRectangleRec(box_rect, background);
}
@ -592,7 +696,9 @@ fn calcLayoutStandaloneSize(self: *UI, box: *Box, axis: Axis) void {
const size = box.size.getAxis(axis);
const computed_size = getVec2Axis(&box.persistent.size, axis);
if (size.kind == .pixels) {
if (box.getFixedSizeAxis(axis)) |fixed_size| {
computed_size.* = fixed_size;
} else if (size.kind == .pixels) {
computed_size.* = size.kind.pixels;
} else if (size.kind == .text) {
if (box.text) |text| {
@ -621,7 +727,11 @@ fn calcLayoutUpwardsSize(self: *UI, box: *Box, axis: Axis) void {
var parent_iter = self.iterUpwardByParent(box);
while (parent_iter.next()) |parent| {
const parent_size_kind = parent.size.getAxis(axis).kind;
if (parent_size_kind == .pixels or parent_size_kind == .percent or parent_size_kind == .text) {
if (parent.isSizeFixed(axis) or
parent_size_kind == .pixels or
parent_size_kind == .percent or
parent_size_kind == .text)
{
maybe_fixed_parent = parent;
break;
}
@ -655,6 +765,8 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
var sum: f32 = 0;
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
if (child.isPositionFixed(axis)) continue;
const child_size = getVec2Axis(&child.persistent.size, axis).*;
if (box.layout_axis == axis) {
@ -675,24 +787,51 @@ fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
const child_axis_size = getVec2Axis(&child.persistent.size, axis);
const parent_axis_position = getVec2Axis(&box.persistent.position, axis);
const child_override_position = switch (axis) {
.X => child.override_x,
.Y => child.override_y,
};
child_axis_position.* = parent_axis_position.*;
if (child.getFixedPositionAxis(axis)) |position| {
child_axis_position.* = position;
} else {
const child_axis_size = getVec2Axis(&child.persistent.size, axis);
const parent_axis_position = getVec2Axis(&box.persistent.position, axis);
if (child_override_position) |position| {
child_axis_position.* += position;
} else if (box.layout_axis == axis) {
child_axis_position.* += layout_position;
layout_position += child_axis_size.*;
child_axis_position.* = parent_axis_position.*;
if (box.layout_axis == axis) {
child_axis_position.* += layout_position;
layout_position += child_axis_size.*;
}
}
child_axis_position.* -= getVec2Axis(&box.view_offset, axis).*;
}
}
if (box.layout_axis == axis) {
var child_size_sum: f32 = 0;
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
if (child.isPositionFixed(axis)) continue;
const child_size = getVec2Axis(&child.persistent.size, axis);
child_size_sum += child_size.*;
}
getVec2Axis(&box.persistent.children_size, axis).* = child_size_sum;
} else {
var max_child_size: f32 = 0;
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
if (child.isPositionFixed(axis)) continue;
const child_size = getVec2Axis(&child.persistent.size, axis);
max_child_size = @max(max_child_size, child_size.*);
}
getVec2Axis(&box.persistent.children_size, axis).* = max_child_size;
}
{
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
@ -752,7 +891,6 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
}
}
{
var child_iter = self.iterChildrenByParent(box);
while (child_iter.next()) |child| {
@ -761,12 +899,16 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
}
}
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
pub fn newKeyFromString(self: *UI, text: []const u8) Key {
var parent_hash: u64 = 0;
if (self.getParent()) |parent| {
parent_hash = parent.key.hash;
}
return self.newBox(Key.initString(parent_hash, text));
return Key.initString(parent_hash, text);
}
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
return self.newBox(self.newKeyFromString(text));
}
pub fn newBoxFromPtr(self: *UI, ptr: anytype) *Box {
@ -832,7 +974,7 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
const rect = box.computedRect();
const is_mouse_inside = rect_utils.isInsideVec2(rect, self.mouse);
const clickable = box.flags.contains(.clickable);
const draggable = box.flags.contains(.draggable);
const draggable = box.flags.contains(.draggable_x) or box.flags.contains(.draggable_y);
var event_index: usize = 0;
while (event_index < self.events.len) {
@ -952,3 +1094,64 @@ pub fn iterUpwardByParent(self: *UI, box: *const Box) BoxParentIterator {
pub fn frameArena(self: *UI) *std.heap.ArenaAllocator {
return &self.arenas[@mod(self.frame_index, 2)];
}
pub fn pushScrollbar(self: *UI, key: UI.Key) *Box {
const container = self.newBox(key);
container.layout_axis = .X;
container.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.percent(1, 0),
};
self.pushParent(container);
const scroll_area = self.newBoxFromString("Scroll area");
scroll_area.size = .{
.x = UI.Size.percent(1, 0),
.y = UI.Size.percent(1, 0),
};
self.pushParent(scroll_area);
return scroll_area;
}
pub fn popScrollbar(self: *UI) void {
const scroll_area = self.getParent().?;
self.popParent(); // pop scroll area
const scrollbar_area = self.newBoxFromString("Scrollbar area");
scrollbar_area.background = rl.Color.gold;
scrollbar_area.size = .{
.x = UI.Size.pixels(24, 1),
.y = UI.Size.percent(1, 0),
};
self.pushParent(scrollbar_area);
defer self.popParent();
var scrollbar_size_percent = scroll_area.persistent.size.y / scroll_area.persistent.children_size.y;
scrollbar_size_percent = std.math.clamp(scrollbar_size_percent, 0, 1);
const draggable = self.newBoxFromString("Scrollbar button");
draggable.background = rl.Color.dark_brown;
draggable.flags.insert(.clickable);
draggable.flags.insert(.draggable_y);
draggable.size = .{
.x = UI.Size.percent(1, 1),
.y = UI.Size.percent(scrollbar_size_percent, 1),
};
const scrollbar_area_height = scrollbar_area.persistent.size.y;
const scrollbar_max_move = scrollbar_area_height * (1 - scrollbar_size_percent);
const sroll_offset = &draggable.persistent.sroll_offset;
const signal = self.signalFromBox(draggable);
if (signal.dragged()) {
sroll_offset.* += signal.drag.y / scrollbar_max_move;
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
}
draggable.setFixedY(scrollbar_area.persistent.position.y + sroll_offset.* * scrollbar_max_move);
scroll_area.view_offset.y = sroll_offset.* * (1 - scrollbar_size_percent) * scroll_area.persistent.children_size.y;
self.popParent(); // pop container
}