From 772f35fee7234517079156ba3d26eef5c8a75f7f Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Tue, 8 Apr 2025 21:36:18 +0300 Subject: [PATCH] add checkbox to toggle rulers --- src/app.zig | 1 + src/assets.zig | 44 +++------ src/components/systems/view_controls.zig | 1 + src/components/view.zig | 5 +- src/constants.zig | 1 - src/screens/main_screen.zig | 72 +++++++------- src/ui.zig | 114 +++++++++++++++++------ 7 files changed, 137 insertions(+), 101 deletions(-) diff --git a/src/app.zig b/src/app.zig index 81998a7..c7699a7 100644 --- a/src/app.zig +++ b/src/app.zig @@ -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) = .{}, diff --git a/src/assets.zig b/src/assets.zig index 43f31ca..304977b 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -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 { diff --git a/src/components/systems/view_controls.zig b/src/components/systems/view_controls.zig index c4d38b2..0c49b1b 100644 --- a/src/components/systems/view_controls.zig +++ b/src/components/systems/view_controls.zig @@ -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{ diff --git a/src/components/view.zig b/src/components/view.zig index dea8c02..0bfc1cc 100644 --- a/src/components/view.zig +++ b/src/components/view.zig @@ -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 { diff --git a/src/constants.zig b/src/constants.zig index 16d6035..f9ccd69 100644 --- a/src/constants.zig +++ b/src/constants.zig @@ -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; \ No newline at end of file diff --git a/src/screens/main_screen.zig b/src/screens/main_screen.zig index 4391c50..26b4960 100644 --- a/src/screens/main_screen.zig +++ b/src/screens/main_screen.zig @@ -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| { diff --git a/src/ui.zig b/src/ui.zig index 0f9935a..b5c9df2 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -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.*; + } } \ No newline at end of file