From cf85b00084ebd95c5883d48ffe37c6c23c51faeb Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Thu, 20 Feb 2025 01:30:22 +0200 Subject: [PATCH] add crappy dropdowns --- src/app.zig | 331 ++++++++++++++++++++++++++++------ src/assets.zig | 53 ++++-- src/assets/dropdown-arrow.ase | Bin 0 -> 567 bytes src/ni-daq/root.zig | 58 +++++- src/ui.zig | 85 +++++++-- 5 files changed, 430 insertions(+), 97 deletions(-) create mode 100644 src/assets/dropdown-arrow.ase diff --git a/src/app.zig b/src/app.zig index f83fdf2..b984ebc 100644 --- a/src/app.zig +++ b/src/app.zig @@ -102,7 +102,7 @@ task_pool: TaskPool, shown_window: enum { channels, add_from_device -} = .channels, +} = .add_from_device, shown_modal: ?union(enum) { no_library_error, @@ -111,10 +111,13 @@ shown_modal: ?union(enum) { } = null, device_filter: NIDaq.BoundedDeviceName = .{}, -show_voltage_analog_inputs: bool = true, -show_voltage_analog_outputs: bool = true, +channel_type_filter: ?NIDaq.ChannelType = null, selected_channels: std.BoundedArray([:0]u8, max_channels) = .{}, +last_hot_channel: ?[:0]const u8 = null, +show_device_filter_dropdown: bool = false, +show_channel_type_filter_dropdown: bool = false, + pub fn init(self: *App, allocator: std.mem.Allocator) !void { self.* = App{ .allocator = allocator, @@ -349,6 +352,40 @@ fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) // ------------------------------- GUI -------------------------------------------- // +const Row = struct { + name: []const u8, + value: []const u8 +}; + +fn showLabelRows(self: *App, rows: []const Row) void { + { + const name_column = self.ui.newBoxFromString("Names"); + name_column.layout_axis = .Y; + name_column.size.y = UI.Size.childrenSum(1); + name_column.size.x = UI.Size.childrenSum(1); + self.ui.pushParent(name_column); + defer self.ui.popParent(); + + for (rows) |row| { + _ = self.ui.label(.text, row.name); + } + } + + { + const value_column = self.ui.newBoxFromString("Values"); + value_column.layout_axis = .Y; + value_column.size.y = UI.Size.childrenSum(1); + value_column.size.x = UI.Size.percent(1, 0); + self.ui.pushParent(value_column); + defer self.ui.popParent(); + + for (rows) |row| { + const label = self.ui.label(.text, row.value); + label.flags.insert(.text_wrapping); + } + } +} + fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count: f32) void { const min_visible_samples = 1; // sample_count*0.02; @@ -554,7 +591,7 @@ fn showChannelsWindow(self: *App) !void { { const prompt_box = self.ui.newBoxFromString("Add prompt"); prompt_box.size.x = UI.Size.percent(1, 0); - prompt_box.size.y = UI.Size.percent(1, 1); + prompt_box.size.y = UI.Size.pixels(200, 1); self.ui.pushParent(prompt_box); defer self.ui.popParent(); @@ -572,14 +609,102 @@ fn showChannelsWindow(self: *App) !void { const from_device_button = self.ui.button(.text, "Add from device"); from_device_button.background = srcery.green; if (self.ui.signalFromBox(from_device_button).clicked()) { - log.debug("TODO: Not implemented", .{}); + self.shown_window = .add_from_device; } } } +fn showChannelInfoPanel(self: *App, hot_channel: ?[:0]const u8) !void { + const ni_daq = &(self.ni_daq orelse return); + + var device_buff: NIDaq.BoundedDeviceName = .{}; + var hot_device: ?[:0]const u8 = null; + if (hot_channel) |channel| { + if (NIDaq.getDeviceNameFromChannel(channel)) |device| { + device_buff.appendSliceAssumeCapacity(device); + device_buff.buffer[device_buff.len] = 0; + hot_device = device_buff.buffer[0..device_buff.len :0]; + } + } + + const info_box = self.ui.newBoxFromString("Info box"); + info_box.layout_axis = .Y; + info_box.size.y = UI.Size.percent(1, 0); + info_box.size.x = UI.Size.percent(1, 0); + self.ui.pushParent(info_box); + defer self.ui.popParent(); + + if (hot_channel) |channel| { + _ = self.ui.label(.text, "Channel properties"); + + const channel_info = self.ui.newBoxFromString("Channel info"); + channel_info.layout_axis = .X; + channel_info.size.y = UI.Size.childrenSum(1); + channel_info.size.x = UI.Size.percent(1, 0); + self.ui.pushParent(channel_info); + defer self.ui.popParent(); + + var rows: std.BoundedArray(Row, 16) = .{}; + + rows.appendAssumeCapacity(Row{ + .name = "Name", + .value = channel + }); + + var channel_type_name: []const u8 = "unknown"; + if (NIDaq.getChannelType(channel)) |channel_type| { + channel_type_name = channel_type.name(); + // rows.appendAssumeCapacity(Row{ + // .name = "Type", + // .value = channel_type_name + // }); + } + + rows.appendAssumeCapacity(Row{ + .name = "Type", + .value = channel_type_name + }); + + self.showLabelRows(rows.constSlice()); + } + + self.ui.spacer(.{ .y = UI.Size.pixels(16, 0) }); + + if (hot_device) |device| { + _ = self.ui.label(.text, "Device properties"); + + const device_info = self.ui.newBoxFromString("Device info"); + device_info.layout_axis = .X; + device_info.size.y = UI.Size.childrenSum(1); + device_info.size.x = UI.Size.percent(1, 0); + self.ui.pushParent(device_info); + defer self.ui.popParent(); + + var rows: std.BoundedArray(Row, 16) = .{}; + + if (ni_daq.listDeviceAIMeasurementTypes(device)) |measurement_types| { + rows.appendAssumeCapacity(Row{ + .name = "Measurement types", + .value = try std.fmt.allocPrint(device_info.allocator, "{} types", .{measurement_types.len}) + }); + } else |e| { + log.err("ni_daq.listDeviceAIMeasurementTypes(): {}", .{ e }); + } + + rows.appendAssumeCapacity(Row{ + .name = "Foo", + .value = "bar" + }); + + self.showLabelRows(rows.constSlice()); + } +} + fn showAddFromDeviceWindow(self: *App) !void { const ni_daq = &(self.ni_daq orelse return); + const device_names = try ni_daq.listDeviceNames(); + const window = self.ui.newBoxFromString("Device window"); window.size.x = UI.Size.percent(1, 0); window.size.y = UI.Size.percent(1, 0); @@ -589,72 +714,151 @@ fn showAddFromDeviceWindow(self: *App) !void { { const filters_box = self.ui.newBoxFromString("Filters box"); - filters_box.size.x = UI.Size.percent(0.5, 1); + filters_box.size.x = UI.Size.percent(0.5, 0); filters_box.size.y = UI.Size.percent(1, 0); filters_box.layout_axis = .Y; self.ui.pushParent(filters_box); defer self.ui.popParent(); - for (try ni_daq.listDeviceNames()) |device| { - const device_box = self.ui.button(.text, device); - device_box.size.x = UI.Size.text(2, 1); - device_box.size.y = UI.Size.text(2, 1); + const device_name_filter = self.ui.clickableBox("Device name filter"); + const channel_type_filter = self.ui.clickableBox("Channel type filter"); - const signal = self.ui.signalFromBox(device_box); - if (signal.clicked()) { - self.device_filter = try NIDaq.BoundedDeviceName.fromSlice(device); - } - } + if (self.show_device_filter_dropdown) { + const dropdown = self.ui.clickableBox("Device name dropdown"); + dropdown.size.x = UI.Size.percent(1, 1); + dropdown.size.y = UI.Size.childrenSum(1); + dropdown.layout_axis = .Y; + dropdown.background = srcery.xgray2; + self.ui.pushParent(dropdown); + defer self.ui.popParent(); - { - const toggle_inputs_box = self.ui.button(.text, "Toggle inputs"); - toggle_inputs_box.size.x = UI.Size.text(2, 1); - toggle_inputs_box.size.y = UI.Size.text(2, 1); - toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs"); + dropdown.setFixedPosition( + device_name_filter.persistent.position.add(.{ .x = 0, .y = device_name_filter.persistent.size.y }) + ); - if (self.ui.signalFromBox(toggle_inputs_box).clicked()) { - self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs; - } - } + { + const device_box = self.ui.button(.text, "All"); + device_box.size.x = UI.Size.percent(1, 1); + device_box.size.y = UI.Size.text(0.5, 1); + device_box.flags.insert(.text_left_align); - { - const toggle_outputs_box = self.ui.button(.text, "Toggle outputs"); - toggle_outputs_box.size.x = UI.Size.text(2, 1); - toggle_outputs_box.size.y = UI.Size.text(2, 1); - toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs"); - - if (self.ui.signalFromBox(toggle_outputs_box).clicked()) { - self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs; - } - } - - { - const add_button = self.ui.button(.text, "Add selected"); - add_button.size.x = UI.Size.text(2, 1); - add_button.size.y = UI.Size.text(2, 1); - - if (self.ui.signalFromBox(add_button).clicked()) { - const selected_devices = self.selected_channels.constSlice(); - - for (selected_devices) |channel| { - try self.appendChannelFromDevice(channel); + if (self.ui.signalFromBox(device_box).clicked()) { + self.device_filter.len = 0; + self.show_device_filter_dropdown = false; } + } - for (selected_devices) |channel| { - self.allocator.free(channel); + for (device_names) |device_name| { + const device_box = self.ui.button(.text, device_name); + device_box.size.x = UI.Size.percent(1, 1); + device_box.size.y = UI.Size.text(0.5, 1); + device_box.flags.insert(.text_left_align); + + const signal = self.ui.signalFromBox(device_box); + if (signal.clicked()) { + self.device_filter = try NIDaq.BoundedDeviceName.fromSlice(device_name); + self.show_device_filter_dropdown = false; } - self.selected_channels.len = 0; + } + } - self.shown_window = .channels; + if (self.show_channel_type_filter_dropdown) { + const dropdown = self.ui.clickableBox("Channel type dropdown"); + dropdown.size.x = UI.Size.percent(1, 1); + dropdown.size.y = UI.Size.childrenSum(1); + dropdown.layout_axis = .Y; + dropdown.background = srcery.xgray2; + self.ui.pushParent(dropdown); + defer self.ui.popParent(); + + dropdown.setFixedPosition( + channel_type_filter.persistent.position.add(.{ .x = 0, .y = channel_type_filter.persistent.size.y }) + ); + + { + const device_box = self.ui.button(.text, "All"); + device_box.size.x = UI.Size.percent(1, 1); + device_box.size.y = UI.Size.text(0.5, 1); + device_box.flags.insert(.text_left_align); + + if (self.ui.signalFromBox(device_box).clicked()) { + self.channel_type_filter = null; + self.show_channel_type_filter_dropdown = false; + } + } + + for (&[_]NIDaq.ChannelType{ NIDaq.ChannelType.analog_input, NIDaq.ChannelType.analog_output }) |channel_type| { + const device_box = self.ui.button(.text, channel_type.name()); + device_box.size.x = UI.Size.percent(1, 1); + device_box.size.y = UI.Size.text(0.5, 1); + device_box.flags.insert(.text_left_align); + + if (self.ui.signalFromBox(device_box).clicked()) { + self.channel_type_filter = channel_type; + self.show_channel_type_filter_dropdown = false; + } + } + } + + { + device_name_filter.size.x = UI.Size.percent(1, 1); + device_name_filter.size.y = UI.Size.pixels(24, 1); + device_name_filter.layout_axis = .X; + self.ui.pushParent(device_name_filter); + defer self.ui.popParent(); + + { + self.ui.pushVerticalAlign(); + defer self.ui.popVerticalAlign(); + _ = self.ui.textureBox(Assets.dropdown_arrow, 1); + } + + if (self.device_filter.len > 0) { + _ = self.ui.label(.text, self.device_filter.constSlice()); + } else { + _ = self.ui.label(.text, "All"); + } + + if (self.ui.signalFromBox(device_name_filter).clicked()) { + self.show_device_filter_dropdown = !self.show_device_filter_dropdown; + } + + self.ui.spacer(.{ .x = UI.Size.percent(1, 0) }); + } + + { + channel_type_filter.size.x = UI.Size.percent(1, 1); + channel_type_filter.size.y = UI.Size.pixels(24, 1); + channel_type_filter.layout_axis = .X; + self.ui.pushParent(channel_type_filter); + defer self.ui.popParent(); + + { + self.ui.pushVerticalAlign(); + defer self.ui.popVerticalAlign(); + _ = self.ui.textureBox(Assets.dropdown_arrow, 1); + } + + if (self.channel_type_filter) |channeL_type| { + _ = self.ui.label(.text, channeL_type.name()); + } else { + _ = self.ui.label(.text, "All"); + } + + if (self.ui.signalFromBox(channel_type_filter).clicked()) { + self.show_channel_type_filter_dropdown = !self.show_channel_type_filter_dropdown; } } } + var hot_channel: ?[:0]const u8 = self.last_hot_channel; { const channels_box = self.ui.pushScrollbar(self.ui.newKeyFromString("Channels list")); defer self.ui.popScrollbar(); + const channels_box_container = self.ui.getParentOf(channels_box).?; channels_box.layout_axis = .Y; - channels_box.size.x = UI.Size.percent(1, 0); + //channels_box.size.x = UI.Size.childrenSum(1); + channels_box_container.size.x = UI.Size.percent(1, 0); var devices: []const [:0]const u8 = &.{}; if (self.device_filter.len > 0) { @@ -667,23 +871,25 @@ fn showAddFromDeviceWindow(self: *App) !void { for (devices) |device| { var ai_voltage_physical_channels: []const [:0]const u8 = &.{}; - if (self.show_voltage_analog_inputs) { - if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) { - ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device); - } + if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) { + ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device); } var ao_physical_channels: []const [:0]const u8 = &.{}; - if (self.show_voltage_analog_outputs) { - if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) { - ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device); - } + if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) { + ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device); } inline for (.{ ai_voltage_physical_channels, ao_physical_channels }) |channels| { for (channels) |channel| { const selected_channels_slice = self.selected_channels.constSlice(); + if (self.channel_type_filter) |channel_type_filter| { + if (NIDaq.getChannelType(channel) != channel_type_filter) { + continue; + } + } + const channel_box = self.ui.button(.text, channel); if (findChannelIndexByName(selected_channels_slice, channel) != null) { @@ -699,10 +905,19 @@ fn showAddFromDeviceWindow(self: *App) !void { } } + if (signal.hot) { + hot_channel = channel; + } } } } } + + try self.showChannelInfoPanel(hot_channel); + + if (hot_channel != null) { + self.last_hot_channel = hot_channel; + } } fn showToolbar(self: *App) void { diff --git a/src/assets.zig b/src/assets.zig index 290350a..cddbeea 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -21,6 +21,8 @@ pub var grab_texture: struct { active: rl.Texture2D, } = undefined; +pub var dropdown_arrow: rl.Texture2D = undefined; + pub fn font(font_id: FontId) FontFace { return fonts.get(font_id); } @@ -34,29 +36,42 @@ pub fn init(allocator: std.mem.Allocator) !void { .text = FontFace{ .font = default_font, .line_height = 1.2 } }); - const grab_ase = try Aseprite.init(allocator, @embedFile("./assets/grab-marker.ase")); - defer grab_ase.deinit(); + { + const grab_ase = try Aseprite.init(allocator, @embedFile("./assets/grab-marker.ase")); + defer grab_ase.deinit(); - const grab_normal_image = grab_ase.getTagImage(grab_ase.getTag("normal") orelse return error.TagNotFound); - defer grab_normal_image.unload(); - const grab_normal_texture = rl.loadTextureFromImage(grab_normal_image); - errdefer grab_normal_texture.unload(); + const grab_normal_image = grab_ase.getTagImage(grab_ase.getTag("normal") orelse return error.TagNotFound); + defer grab_normal_image.unload(); + const grab_normal_texture = rl.loadTextureFromImage(grab_normal_image); + errdefer grab_normal_texture.unload(); - const grab_hot_image = grab_ase.getTagImage(grab_ase.getTag("hot") orelse return error.TagNotFound); - defer grab_hot_image.unload(); - const grab_hot_texture = rl.loadTextureFromImage(grab_hot_image); - errdefer grab_hot_texture.unload(); + const grab_hot_image = grab_ase.getTagImage(grab_ase.getTag("hot") orelse return error.TagNotFound); + defer grab_hot_image.unload(); + const grab_hot_texture = rl.loadTextureFromImage(grab_hot_image); + errdefer grab_hot_texture.unload(); - const grab_active_image = grab_ase.getTagImage(grab_ase.getTag("active") orelse return error.TagNotFound); - defer grab_active_image.unload(); - const grab_active_texture = rl.loadTextureFromImage(grab_active_image); - errdefer grab_active_texture.unload(); + const grab_active_image = grab_ase.getTagImage(grab_ase.getTag("active") orelse return error.TagNotFound); + defer grab_active_image.unload(); + const grab_active_texture = rl.loadTextureFromImage(grab_active_image); + errdefer grab_active_texture.unload(); - grab_texture = .{ - .normal = grab_normal_texture, - .hot = grab_hot_texture, - .active = grab_active_texture - }; + grab_texture = .{ + .normal = grab_normal_texture, + .hot = grab_hot_texture, + .active = grab_active_texture + }; + } + + { + const dropdown_arrow_ase = try Aseprite.init(allocator, @embedFile("./assets/dropdown-arrow.ase")); + defer dropdown_arrow_ase.deinit(); + + const dropdown_image = dropdown_arrow_ase.getFrameImage(0); + defer dropdown_image.unload(); + + dropdown_arrow = rl.loadTextureFromImage(dropdown_image); + assert(rl.isTextureReady(dropdown_arrow)); + } } fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font { diff --git a/src/assets/dropdown-arrow.ase b/src/assets/dropdown-arrow.ase new file mode 100644 index 0000000000000000000000000000000000000000..a23455c87fecf3b99da5f880f345f9ab6ecc168f GIT binary patch literal 567 zcmcJMPbfoi9KgSuNi8{wUG_`TZnlNB$VwcTqREg0YpnJT^CyW&l3FdbixN|Iahvnx z;NmD3DUHLXxHu?sGWGL$`B(0~ulK3d;l#x~JimR1ni0ty<>BU962=V!j)g;Tu`300 zUN0<+MB(+y2~6+w!PJaf8|-2;zp%L+ORxd?v=dG>$@CISDVa19NF8Z(5k(O>W{cKT zkboaKX0$jFVngLLO{xNZ4+&Bw{gLJAdFAQmp;8Gc7Sa6v&#{YLmiEiqX|0=G*Q3(e fdi+|t9N2P%t|CLhT+ub0%+($^Rv#r>lkfcil{uP7 literal 0 HcmV?d00001 diff --git a/src/ni-daq/root.zig b/src/ni-daq/root.zig index e41ebb5..a75fed8 100644 --- a/src/ni-daq/root.zig +++ b/src/ni-daq/root.zig @@ -225,9 +225,46 @@ pub const AIMeasurementType = enum(i32) { TEDS_Sensor = c.DAQmx_Val_TEDS_Sensor, Charge = c.DAQmx_Val_Charge, Power = c.DAQmx_Val_Power, - _ + _, + + pub fn name(self: AIMeasurementType) []const u8 { + return switch (self) { + .Voltage => "Voltage", + .VoltageRMS => "Voltage RMS", + .Current => "Current", + .CurrentRMS => "Current RMS", + .Voltage_CustomWithExcitation => "Voltage (custom excitation)", + .Bridge => "Bridge", + .Freq_Voltage => "Frequency (frequency to voltage converter)", + .Resistance => "Resistance", + .Temp_TC => "Temperature (thermocouple)", + .Temp_Thrmstr => "Temperature (thermistor)", + .Temp_RTD => "Temperature (RTD)", + .Temp_BuiltInSensor => "Temperature (built-in)", + .Strain_Gage => "Strain", + .Rosette_Strain_Gage => "Strain (Rosette strain gage)", + .Position_LVDT => "Position (LVDT)", + .Position_RVDT => "Position (RVDT)", + .Position_EddyCurrentProximityProbe => "Position (eddy current proximity probe)", + .Accelerometer => "Acceleration", + .Acceleration_Charge => "Acceleration (charge-based)", + .Acceleration_4WireDCVoltage => "Acceleration (4 wire DC voltage based)", + .Velocity_IEPESensor => "Velocity (IEPE Sensor)", + .Force_Bridge => "Force (bridge-based)", + .Force_IEPESensor => "Force (IEPE Sensor)", + .Pressure_Bridge => "Pressure (bridge-based)", + .SoundPressure_Microphone => "Sound pressure (microphone)", + .Torque_Bridge => "Torque (bridge-based)", + .TEDS_Sensor => "TEDS", + .Charge => "Charge", + .Power => "Power source", + _ => "Unknown" + }; + } }; -const AIMeasurementTypeList = std.BoundedArray(AIMeasurementType, @typeInfo(AIMeasurementType).Enum.fields.len); + +pub const max_ai_measurement_type_list_len = @typeInfo(AIMeasurementType).Enum.fields.len; +pub const AIMeasurementTypeList = std.BoundedArray(AIMeasurementType, max_ai_measurement_type_list_len); pub const AOOutputType = enum(i32) { Voltage = c.DAQmx_Val_Voltage, @@ -632,17 +669,26 @@ pub fn listDeviceCIPhysicalChannels(self: *NIDaq, device: [:0]const u8) ![]const ); } -const ChannelType = enum { +pub const ChannelType = enum { analog_input, analog_output, counter_input, counter_output, + + pub fn name(self: ChannelType) []const u8 { + return switch (self) { + .analog_input => "Analog input", + .analog_output => "Analog output", + .counter_input => "Counter input", + .counter_output => "Counter output", + }; + } }; -fn getChannelType(device: [:0]const u8) ?ChannelType { - const slash = std.mem.indexOfScalar(u8, device, '/') orelse return null; +pub fn getChannelType(channel_name: []const u8) ?ChannelType { + const slash = std.mem.indexOfScalar(u8, channel_name, '/') orelse return null; - const afterSlash = device[(slash+1)..]; + const afterSlash = channel_name[(slash+1)..]; if (std.mem.startsWith(u8, afterSlash, "ai")) { return ChannelType.analog_input; } else if (std.mem.startsWith(u8, afterSlash, "ao")) { diff --git a/src/ui.zig b/src/ui.zig index d640a53..f824598 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -60,6 +60,7 @@ pub const Size = struct { pixels: f32, percent: f32, text: f32, + texture: f32, children_sum, }, strictness: f32 = 1, @@ -85,6 +86,13 @@ pub const Size = struct { }; } + pub fn texture(scale: f32, strictness: f32) Size { + return Size{ + .kind = .{ .texture = scale }, + .strictness = strictness + }; + } + pub fn childrenSum(strictness: f32) Size { return Size{ .kind = .children_sum, @@ -183,6 +191,7 @@ pub const Signal = struct { flags: std.EnumSet(Flag) = .{}, drag: Vec2 = .{ .x = 0, .y = 0 }, scroll: Vec2 = .{ .x = 0, .y = 0 }, + hot: bool = false, pub fn clicked(self: Signal) bool { return self.flags.contains(.left_clicked) or self.flags.contains(.right_clicked); @@ -413,7 +422,7 @@ pub const Box = struct { return self.flags.contains(flag); } - fn hasClipping(self: *Box) bool { + pub fn hasClipping(self: *Box) bool { return self.view_offset.equals(Vec2.zero()) == 0; } }; @@ -791,6 +800,18 @@ fn calcLayoutStandaloneSize(self: *UI, box: *Box, axis: Axis) void { computed_size.* = fixed_size; } else if (size.kind == .pixels) { computed_size.* = size.kind.pixels; + } else if (size.kind == .texture) { + if (box.texture) |texture| { + var texture_size: f32 = 0; + if (axis == .X) { + texture_size = @floatFromInt(texture.width); + } else if (axis == .Y) { + texture_size = @floatFromInt(texture.height); + } + computed_size.* = size.kind.texture * texture_size; + } else { + computed_size.* = 0; + } } else if (size.kind == .text) { if (box.text) |text| { const font = Assets.font(text.font); @@ -900,6 +921,8 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void { 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 (child_size.* > max_child_size) { @@ -1266,6 +1289,8 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal { } } + result.hot = self.isKeyHot(box.key); + return result; } @@ -1305,6 +1330,14 @@ fn getParentIndex(self: *UI) ?BoxIndex { } } +pub fn getParentOf(self: *UI, box: *Box) ?*Box { + if (box.parent_index) |index| { + return &self.boxes.buffer[index]; + } else { + return null; + } +} + pub fn getParent(self: *UI) ?*Box { if (self.getParentIndex()) |parent_index| { return &self.boxes.buffer[parent_index]; @@ -1448,18 +1481,27 @@ pub fn spacer(self: *UI, size: Vec2Size) void { box.size = size; } +pub fn label(self: *UI, font: Assets.FontId, text: []const u8) *Box { + const box = self.newBoxFromString(text); + box.size.x = UI.Size.text(1, 1); + box.size.y = UI.Size.text(0.5, 1); + box.setText(font, text); + + return box; +} + +pub fn textureBox(self: *UI, texture: rl.Texture2D, scale: f32) *Box { + const box = self.newBox(Key.initNil()); + box.size.x = UI.Size.texture(scale, 1); + box.size.y = UI.Size.texture(scale, 1); + box.texture = texture; + + return box; +} + pub fn pushCenterBox(self: *UI) *Box { self.pushHorizontalAlign(); - - const vertical_align = self.newBox(UI.Key.initNil()); - vertical_align.layout_axis = .Y; - vertical_align.size = .{ - .x = UI.Size.childrenSum(1), - .y = UI.Size.percent(1, 0), - }; - self.pushParent(vertical_align); - - self.spacer(.{ .y = UI.Size.percent(1, 0) }); + self.pushVerticalAlign(); const container = self.newBox(UI.Key.initNil()); container.size.x = UI.Size.childrenSum(1); @@ -1472,9 +1514,7 @@ pub fn pushCenterBox(self: *UI) *Box { pub fn popCenterBox(self: *UI) void { self.popParent(); - self.spacer(.{ .y = UI.Size.percent(1, 0) }); - self.popParent(); - + self.popVerticalAlign(); self.popHorizontalAlign(); } @@ -1493,4 +1533,21 @@ pub fn pushHorizontalAlign(self: *UI) void { pub fn popHorizontalAlign(self: *UI) void { self.spacer(.{ .x = UI.Size.percent(1, 0) }); self.popParent(); +} + +pub fn pushVerticalAlign(self: *UI) void { + const vertical_align = self.newBox(UI.Key.initNil()); + vertical_align.layout_axis = .Y; + vertical_align.size = .{ + .x = UI.Size.childrenSum(1), + .y = UI.Size.percent(1, 0), + }; + self.pushParent(vertical_align); + + self.spacer(.{ .y = UI.Size.percent(1, 0) }); +} + +pub fn popVerticalAlign(self: *UI) void { + self.spacer(.{ .y = UI.Size.percent(1, 0) }); + self.popParent(); } \ No newline at end of file