add checkbox to toggle rulers

This commit is contained in:
Rokas Puzonas 2025-04-08 21:36:18 +03:00
parent 36706ff348
commit 772f35fee7
7 changed files with 137 additions and 101 deletions

View File

@ -284,6 +284,7 @@ pub const Project = struct {
save_location: ?[]u8 = null,
sample_rate: ?f64 = null,
show_rulers: bool = true,
channels: GenerationalArray(Channel) = .{},
files: GenerationalArray(File) = .{},

View File

@ -45,10 +45,9 @@ pub var grab_texture: struct {
} = undefined;
pub var dropdown_arrow: rl.Texture2D = undefined;
pub var fullscreen: rl.Texture2D = undefined;
pub var output_generation: rl.Texture2D = undefined;
pub var checkbox_mark: rl.Texture2D = undefined;
pub fn font(font_id: FontId) FontFace {
var found_font: ?LoadedFont = null;
@ -120,38 +119,23 @@ pub fn init(allocator: std.mem.Allocator) !void {
};
}
{
const dropdown_arrow_ase = try Aseprite.init(allocator, @embedFile("./assets/dropdown-arrow.ase"));
defer dropdown_arrow_ase.deinit();
fullscreen = try loadTextureFromAseprite(allocator, @embedFile("./assets/dropdown-arrow.ase"));
fullscreen = try loadTextureFromAseprite(allocator, @embedFile("./assets/fullscreen-icon.ase"));
output_generation = try loadTextureFromAseprite(allocator, @embedFile("./assets/output-generation-icon.ase"));
checkbox_mark = try loadTextureFromAseprite(allocator, @embedFile("./assets/checkbox-mark.ase"));
}
const dropdown_image = dropdown_arrow_ase.getFrameImage(0);
defer dropdown_image.unload();
fn loadTextureFromAseprite(allocator: std.mem.Allocator, memory: []const u8) !rl.Texture {
const ase = try Aseprite.init(allocator, memory);
defer ase.deinit();
dropdown_arrow = rl.loadTextureFromImage(dropdown_image);
assert(rl.isTextureReady(dropdown_arrow));
}
const image = ase.getFrameImage(0);
defer image.unload();
{
const fullscreen_ase = try Aseprite.init(allocator, @embedFile("./assets/fullscreen-icon.ase"));
defer fullscreen_ase.deinit();
const texture = rl.loadTextureFromImage(image);
assert(rl.isTextureReady(texture));
const fullscreen_image = fullscreen_ase.getFrameImage(0);
defer fullscreen_image.unload();
fullscreen = rl.loadTextureFromImage(fullscreen_image);
assert(rl.isTextureReady(fullscreen));
}
{
const output_generation_ase = try Aseprite.init(allocator, @embedFile("./assets/output-generation-icon.ase"));
defer output_generation_ase.deinit();
const output_generation_image = output_generation_ase.getFrameImage(0);
defer output_generation_image.unload();
output_generation = rl.loadTextureFromImage(output_generation_image);
assert(rl.isTextureReady(output_generation));
}
return texture;
}
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {

View File

@ -125,6 +125,7 @@ cursor: ?ViewAxisPosition = null,
view_settings: ?Id = null, // View id
view_fullscreen: ?Id = null, // View id
view_protocol_modal: ?Id = null, // View id
show_ruler: bool = false,
pub fn init(project: *App.Project) System {
return System{

View File

@ -48,6 +48,9 @@ fn showGraph(ctx: Context, view_id: Id) *UI.Box {
.flags = &.{ .clickable, .draggable, .scrollable, .clip_view },
.align_x = .center,
.align_y = .center,
.borders = .{
.bottom = .{ .color = srcery.hard_black, .size = 4 }
}
});
graph_box.beginChildren();
defer graph_box.endChildren();
@ -237,7 +240,7 @@ pub fn show(ctx: Context, view_id: Id, height: UI.Sizing) !Result {
}
}
if (!constants.show_ruler) {
if (!ctx.app.project.show_rulers) {
_ = showGraph(ctx, view_id);
} else {

View File

@ -5,6 +5,5 @@ pub const max_channels = 32;
pub const max_views = 64;
// UI
pub const show_ruler = true;
pub const sync_view_controls = true;
pub const zoom_speed = 0.1;

View File

@ -20,22 +20,6 @@ const assert = std.debug.assert;
const remap = utils.remap;
const Id = App.Id;
const zoom_speed = 0.1;
const ruler_size = UI.Sizing.initFixed(.{ .pixels = 32 });
const show_ruler = true;
const sync_view_controls = true;
const ViewCommand = struct {
view_id: Id,
updated_at_ns: i128,
action: union(enum) {
move_and_zoom: struct {
before_x: RangeF64,
before_y: RangeF64,
}
}
};
app: *App,
view_controls: ViewControlsSystem,
@ -341,34 +325,43 @@ pub fn showSidePanel(self: *MainScreen) !void {
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
var placeholder: ?[]const u8 = null;
if (project.getDefaultSampleRate()) |default_sample_rate| {
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
}
{ // Sample rate
var placeholder: ?[]const u8 = null;
if (project.getDefaultSampleRate()) |default_sample_rate| {
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
}
var initial: ?[]const u8 = null;
if (project.sample_rate) |selected_sample_rate| {
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ selected_sample_rate });
}
_ = ui.label("Sample rate", .{});
self.parsed_sample_rate = try ui.numberInput(f64, .{
.key = ui.keyFromString("Sample rate input"),
.storage = &self.sample_rate_input,
.placeholder = placeholder,
.initial = initial,
.invalid = self.parsed_sample_rate != project.sample_rate,
.editable = !self.app.isCollectionInProgress()
});
project.sample_rate = self.parsed_sample_rate;
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
var initial: ?[]const u8 = null;
if (project.sample_rate) |selected_sample_rate| {
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
project.sample_rate = null;
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ selected_sample_rate });
}
_ = ui.label("Sample rate", .{});
self.parsed_sample_rate = try ui.numberInput(f64, .{
.key = ui.keyFromString("Sample rate input"),
.storage = &self.sample_rate_input,
.placeholder = placeholder,
.initial = initial,
.invalid = self.parsed_sample_rate != project.sample_rate,
.editable = !self.app.isCollectionInProgress()
});
project.sample_rate = self.parsed_sample_rate;
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
if (project.sample_rate) |selected_sample_rate| {
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
project.sample_rate = null;
}
}
}
}
{ // Show ruler checkbox
_ = ui.checkbox(.{
.value = &project.show_rulers,
.label = "Ruler"
});
}
}
}
@ -481,7 +474,6 @@ pub fn tick(self: *MainScreen) !void {
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
defer ui.endScrollbar();
scroll_area.layout_direction = .top_to_bottom;
scroll_area.layout_gap = 4;
var view_iter = self.app.project.views.idIterator();
while (view_iter.next()) |view_id| {

View File

@ -513,6 +513,8 @@ pub const Box = struct {
scientific_precision: u32 = 1,
float_relative_to: ?*Box = null,
draw: ?Draw = null,
visual_hot: bool = false,
visual_active: bool = false,
// Variables that you probably shouldn't be touching
last_used_frame: u64 = 0,
@ -731,7 +733,9 @@ pub const BoxOptions = struct {
float_relative_to: ?*Box = null,
parent: ?*UI.Box = null,
texture_color: ?rl.Color = null,
draw: ?Box.Draw = null
draw: ?Box.Draw = null,
visual_hot: ?bool = null,
visual_active: ?bool = null
};
pub const root_box_key = Key.initString(0, "$root$");
@ -951,11 +955,14 @@ pub fn end(self: *UI) void {
const box: *Box = _box;
if (box.key.isNil()) continue;
const is_hot: f32 = @floatFromInt(@intFromBool(self.isKeyHot(box.key)));
const is_active: f32 = @floatFromInt(@intFromBool(self.isKeyActive(box.key)));
const is_hot = self.isKeyHot(box.key) or box.visual_hot;
const is_active = self.isKeyActive(box.key) or box.visual_active;
box.persistent.hot += fast_rate * (is_hot - box.persistent.hot );
box.persistent.active += fast_rate * (is_active - box.persistent.active);
const is_hot_f32: f32 = @floatFromInt(@intFromBool(is_hot));
const is_active_f32: f32 = @floatFromInt(@intFromBool(is_active));
box.persistent.hot += fast_rate * (is_hot_f32 - box.persistent.hot );
box.persistent.active += fast_rate * (is_active_f32 - box.persistent.active);
}
}
@ -1539,6 +1546,8 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
.float_relative_to = opts.float_relative_to,
.texture_color = opts.texture_color,
.draw = opts.draw,
.visual_hot = opts.visual_hot orelse false,
.visual_active = opts.visual_active orelse false,
.last_used_frame = self.frame_index,
.key = key,
@ -1636,9 +1645,9 @@ fn drawBox(self: *UI, box: *Box) void {
defer if (do_scissor) self.endScissor();
var value_shift: f32 = 0;
if (box.flags.contains(.draw_active) and self.isKeyActive(box.key)) {
if (box.flags.contains(.draw_active) and box.persistent.active > 0.01) {
value_shift = -0.5 * box.persistent.active;
} else if (box.flags.contains(.draw_hot) and self.isKeyHot(box.key)) {
} else if (box.flags.contains(.draw_hot) and box.persistent.hot > 0.01) {
value_shift = 0.6 * box.persistent.hot;
}
@ -1646,6 +1655,28 @@ fn drawBox(self: *UI, box: *Box) void {
rl.drawRectangleRec(box_rect, utils.shiftColorInHSV(bg, value_shift));
}
if (box.texture) |texture| {
const source = rl.Rectangle{
.x = 0,
.y = 0,
.width = @floatFromInt(texture.width),
.height = @floatFromInt(texture.height)
};
var destination = box_rect;
if (box.texture_size) |texture_size| {
destination = rect_utils.initCentered(destination, texture_size.x, texture_size.y);
}
rl.drawTexturePro(
texture,
source,
destination,
rl.Vector2.zero(),
0,
box.texture_color orelse rl.Color.white
);
}
const borders_with_coords = .{
.{ box.borders.left , rl.Vector2.init(0, 0), rl.Vector2.init(0, 1), rl.Vector2.init( 1, 0) },
.{ box.borders.right , rl.Vector2.init(1, 0), rl.Vector2.init(1, 1), rl.Vector2.init(-1, 0) },
@ -1669,28 +1700,6 @@ fn drawBox(self: *UI, box: *Box) void {
}
}
if (box.texture) |texture| {
const source = rl.Rectangle{
.x = 0,
.y = 0,
.width = @floatFromInt(texture.width),
.height = @floatFromInt(texture.height)
};
var destination = box_rect;
if (box.texture_size) |texture_size| {
destination = rect_utils.initCentered(destination, texture_size.x, texture_size.y);
}
rl.drawTexturePro(
texture,
source,
destination,
rl.Vector2.zero(),
0,
box.texture_color orelse rl.Color.white
);
}
if (box.draw) |box_draw| {
box_draw.do(box_draw.ctx, box);
}
@ -2237,6 +2246,11 @@ pub const NumberInputOptions = struct {
invalid_color: rl.Color = srcery.red
};
pub const CheckboxOptions = struct {
value: *bool,
label: ?[]const u8 = null
};
pub fn mouseTooltip(self: *UI) *Box {
const tooltip = self.getBoxByKey(mouse_tooltip_box_key).?;
@ -2688,4 +2702,46 @@ pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
} else |_| {
return null;
}
}
pub fn checkbox(self: *UI, opts: CheckboxOptions) void {
const container = self.createBox(.{
.key = UI.Key.initPtr(opts.value),
.size_x = UI.Sizing.initFitChildren(),
.size_y = UI.Sizing.initFitChildren(),
.flags = &.{ .draw_hot, .draw_active, .clickable },
.hot_cursor = .mouse_cursor_pointing_hand,
.layout_direction = .left_to_right,
.layout_gap = self.rem(0.25)
});
container.beginChildren();
defer container.endChildren();
const container_signal = self.signal(container);
const marker = self.createBox(.{
.key = self.keyFromString("checkbox marker"),
.size_x = UI.Sizing.initFixedPixels(self.rem(1)),
.size_y = UI.Sizing.initFixedPixels(self.rem(1)),
.background = srcery.bright_white,
.visual_hot = container_signal.hot,
.visual_active = container_signal.active,
.flags = &.{ .draw_hot, .draw_active },
});
if (opts.label) |text| {
_ = self.createBox(.{
.size_x = Sizing.initFixed(.text),
.size_y = Sizing.initFixed(.text),
.text = text
});
}
if (opts.value.*) {
marker.texture = Assets.checkbox_mark;
}
if (container_signal.clicked()) {
opts.value.* = !opts.value.*;
}
}