add scrollbar
This commit is contained in:
parent
43b6ca0ff2
commit
fa4fb2009f
38
src/app.zig
38
src/app.zig
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
261
src/ui.zig
261
src/ui.zig
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user