show message if NI Daq fails to be loaded
This commit is contained in:
parent
f563ff931b
commit
7301c68b7e
24
README.md
24
README.md
@ -4,6 +4,28 @@
|
|||||||
zig build run
|
zig build run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
* https://www.ni.com/docs/en-US/bundle/ni-daqmx-c-api-ref/page/cdaqmx/help_file_title.html
|
||||||
|
* https://www.ni.com/en/support/documentation/supplemental/06/getting-started-with-ni-daqmx--main-page.html
|
||||||
|
* https://knowledge.ni.com/KnowledgeArticleDetails?id=kA03q000000YGfDCAW&l=en-LT
|
||||||
|
* https://ziglang.org/learn/build-system/
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail.
|
* Use downsampling for faster rendering of samples. When viewing many samples use dowsampled versions of data for rendering. Because you either way, you won't be able to see the detail.
|
||||||
|
|
||||||
|
* Export .xcf files at build time
|
||||||
|
```
|
||||||
|
(let* (
|
||||||
|
(image (car (gimp-file-load RUN-NONINTERACTIVE "./icon.xcf" "./icon.xcf")))
|
||||||
|
(merged-layer (car (gimp-image-merge-visible-layers image CLIP-TO-BOTTOM-LAYER)))
|
||||||
|
)
|
||||||
|
(file-png-save RUN-NONINTERACTIVE image merged-layer "./icon.png" "./icon.png" 0 9 0 0 0 0 0)
|
||||||
|
(gimp-image-delete image)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
gimp-console-2.10.exe -i -b <batch> -b "(gimp-quit 0)"
|
||||||
|
```
|
@ -82,6 +82,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const known_folders = b.dependency("known-folders", .{}).module("known-folders");
|
||||||
|
const ini = b.dependency("ini", .{}).module("ini");
|
||||||
|
|
||||||
const raylib_dep = b.dependency("raylib-zig", .{
|
const raylib_dep = b.dependency("raylib-zig", .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
@ -97,8 +100,6 @@ pub fn build(b: *std.Build) !void {
|
|||||||
});
|
});
|
||||||
png_to_icon_tool.root_module.addImport("stb_image", stb_image_lib);
|
png_to_icon_tool.root_module.addImport("stb_image", stb_image_lib);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "daq-view",
|
.name = "daq-view",
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
@ -110,6 +111,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
exe.root_module.addImport("raylib", raylib_dep.module("raylib"));
|
||||||
exe.root_module.addImport("stb_image", stb_image_lib);
|
exe.root_module.addImport("stb_image", stb_image_lib);
|
||||||
exe.root_module.addImport("cute_aseprite", cute_aseprite_lib);
|
exe.root_module.addImport("cute_aseprite", cute_aseprite_lib);
|
||||||
|
exe.root_module.addImport("known-folders", known_folders);
|
||||||
|
exe.root_module.addImport("ini", ini);
|
||||||
|
|
||||||
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
const external_compiler_support_dir = try std.process.getEnvVarOwned(b.allocator, "NIEXTCCOMPILERSUPP");
|
||||||
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
exe.addSystemIncludePath(.{ .cwd_relative = try std.fs.path.join(b.allocator, &.{ external_compiler_support_dir, "include" }) });
|
||||||
|
@ -7,6 +7,14 @@
|
|||||||
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
.url = "https://github.com/Not-Nik/raylib-zig/archive/43d15b05c2b97cab30103fa2b46cff26e91619ec.tar.gz",
|
||||||
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
.hash = "12204a223b19043e17b79300413d02f60fc8004c0d9629b8d8072831e352a78bf212"
|
||||||
},
|
},
|
||||||
|
.@"known-folders" = .{
|
||||||
|
.url = "git+https://github.com/ziglibs/known-folders.git#1cceeb70e77dec941a4178160ff6c8d05a74de6f",
|
||||||
|
.hash = "12205f5e7505c96573f6fc5144592ec38942fb0a326d692f9cddc0c7dd38f9028f29",
|
||||||
|
},
|
||||||
|
.@"ini" = .{
|
||||||
|
.url = "https://github.com/ziglibs/ini/archive/e18d36665905c1e7ba0c1ce3e8780076b33e3002.tar.gz",
|
||||||
|
.hash = "1220b0979ea9891fa4aeb85748fc42bc4b24039d9c99a4d65d893fb1c83e921efad8",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
|
446
src/app.zig
446
src/app.zig
@ -8,7 +8,7 @@ const Graph = @import("./graph.zig");
|
|||||||
const NIDaq = @import("ni-daq/root.zig");
|
const NIDaq = @import("ni-daq/root.zig");
|
||||||
const rect_utils = @import("./rect-utils.zig");
|
const rect_utils = @import("./rect-utils.zig");
|
||||||
const remap = @import("./utils.zig").remap;
|
const remap = @import("./utils.zig").remap;
|
||||||
const TaskPool = @import("./task-pool.zig");
|
const TaskPool = @import("ni-daq/task-pool.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -92,47 +92,79 @@ allocator: std.mem.Allocator,
|
|||||||
|
|
||||||
ui: UI,
|
ui: UI,
|
||||||
channel_views: std.BoundedArray(ChannelView, max_channels) = .{},
|
channel_views: std.BoundedArray(ChannelView, max_channels) = .{},
|
||||||
ni_daq: NIDaq,
|
|
||||||
task_pool: TaskPool,
|
|
||||||
loaded_files: [max_channels]?FileChannel = .{ null } ** max_channels,
|
loaded_files: [max_channels]?FileChannel = .{ null } ** max_channels,
|
||||||
device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
|
device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
|
||||||
|
|
||||||
|
ni_daq_api: ?NIDaq.Api = null,
|
||||||
|
ni_daq: ?NIDaq = null,
|
||||||
|
task_pool: TaskPool,
|
||||||
|
|
||||||
shown_window: enum {
|
shown_window: enum {
|
||||||
channels,
|
channels,
|
||||||
add_from_device
|
add_from_device
|
||||||
} = .channels,
|
} = .channels,
|
||||||
|
|
||||||
|
shown_modal: ?union(enum) {
|
||||||
|
no_library_error,
|
||||||
|
library_version_error: std.SemanticVersion,
|
||||||
|
library_version_warning: std.SemanticVersion
|
||||||
|
} = null,
|
||||||
|
|
||||||
device_filter: NIDaq.BoundedDeviceName = .{},
|
device_filter: NIDaq.BoundedDeviceName = .{},
|
||||||
show_voltage_analog_inputs: bool = true,
|
show_voltage_analog_inputs: bool = true,
|
||||||
show_voltage_analog_outputs: bool = true,
|
show_voltage_analog_outputs: bool = true,
|
||||||
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
|
selected_channels: std.BoundedArray([:0]u8, max_channels) = .{},
|
||||||
|
|
||||||
pub fn init(self: *App, allocator: std.mem.Allocator) !void {
|
pub fn init(self: *App, allocator: std.mem.Allocator) !void {
|
||||||
var ni_daq = try NIDaq.init(allocator, .{
|
|
||||||
.max_devices = 4,
|
|
||||||
.max_analog_inputs = 32,
|
|
||||||
.max_analog_outputs = 8,
|
|
||||||
.max_counter_outputs = 8,
|
|
||||||
.max_counter_inputs = 8,
|
|
||||||
.max_analog_input_voltage_ranges = 4,
|
|
||||||
.max_analog_output_voltage_ranges = 4
|
|
||||||
});
|
|
||||||
errdefer ni_daq.deinit(allocator);
|
|
||||||
|
|
||||||
self.* = App{
|
self.* = App{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.ui = UI.init(allocator),
|
.ui = UI.init(allocator),
|
||||||
.ni_daq = ni_daq,
|
|
||||||
.task_pool = undefined
|
.task_pool = undefined
|
||||||
};
|
};
|
||||||
|
errdefer if (self.ni_daq_api != null) self.ni_daq_api.?.deinit();
|
||||||
|
errdefer if (self.ni_daq != null) self.ni_daq.?.deinit(allocator);
|
||||||
|
|
||||||
try TaskPool.init(&self.task_pool, allocator, &self.ni_daq);
|
if (NIDaq.Api.init()) |ni_daq_api| {
|
||||||
|
self.ni_daq_api = ni_daq_api;
|
||||||
|
|
||||||
|
const ni_daq = try NIDaq.init(allocator, &self.ni_daq_api.?, .{
|
||||||
|
.max_devices = 4,
|
||||||
|
.max_analog_inputs = 32,
|
||||||
|
.max_analog_outputs = 8,
|
||||||
|
.max_counter_outputs = 8,
|
||||||
|
.max_counter_inputs = 8,
|
||||||
|
.max_analog_input_voltage_ranges = 4,
|
||||||
|
.max_analog_output_voltage_ranges = 4
|
||||||
|
});
|
||||||
|
self.ni_daq = ni_daq;
|
||||||
|
|
||||||
|
const installed_version = try ni_daq.version();
|
||||||
|
if (installed_version.order(NIDaq.Api.min_version) == .lt) {
|
||||||
|
self.shown_modal = .{ .library_version_warning = installed_version };
|
||||||
|
}
|
||||||
|
|
||||||
|
} else |e| {
|
||||||
|
log.err("Failed to load NI-Daq library: {any}", .{e});
|
||||||
|
|
||||||
|
switch (e) {
|
||||||
|
error.LibraryNotFound => {
|
||||||
|
self.shown_modal = .no_library_error;
|
||||||
|
},
|
||||||
|
error.SymbolNotFound => {
|
||||||
|
if (NIDaq.Api.version()) |version| {
|
||||||
|
self.shown_modal = .{ .library_version_error = version };
|
||||||
|
} else |_| {
|
||||||
|
self.shown_modal = .no_library_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try TaskPool.init(&self.task_pool, allocator);
|
||||||
errdefer self.task_pool.deinit();
|
errdefer self.task_pool.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App) void {
|
pub fn deinit(self: *App) void {
|
||||||
self.task_pool.deinit();
|
|
||||||
|
|
||||||
for (self.channel_views.slice()) |*channel| {
|
for (self.channel_views.slice()) |*channel| {
|
||||||
channel.view_cache.deinit();
|
channel.view_cache.deinit();
|
||||||
}
|
}
|
||||||
@ -157,39 +189,9 @@ pub fn deinit(self: *App) void {
|
|||||||
self.selected_channels.len = 0;
|
self.selected_channels.len = 0;
|
||||||
|
|
||||||
self.ui.deinit();
|
self.ui.deinit();
|
||||||
self.ni_daq.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showButton(self: *App, text: []const u8) UI.Interaction {
|
self.task_pool.deinit();
|
||||||
var button = self.ui.newWidget(self.ui.keyFromString(text));
|
if (self.ni_daq) |*ni_daq| ni_daq.deinit(self.allocator);
|
||||||
button.border = srcery.bright_blue;
|
|
||||||
button.padding.vertical(8);
|
|
||||||
button.padding.horizontal(16);
|
|
||||||
button.flags.insert(.clickable);
|
|
||||||
button.size = .{
|
|
||||||
.x = .{ .text = {} },
|
|
||||||
.y = .{ .text = {} },
|
|
||||||
};
|
|
||||||
|
|
||||||
const interaction = self.ui.getInteraction(button);
|
|
||||||
var text_color: rl.Color = undefined;
|
|
||||||
if (interaction.held_down) {
|
|
||||||
button.background = srcery.hard_black;
|
|
||||||
text_color = srcery.white;
|
|
||||||
} else if (interaction.hovering) {
|
|
||||||
button.background = srcery.bright_black;
|
|
||||||
text_color = srcery.bright_white;
|
|
||||||
} else {
|
|
||||||
button.background = srcery.blue;
|
|
||||||
text_color = srcery.bright_white;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.text = .{
|
|
||||||
.content = text,
|
|
||||||
.color = text_color
|
|
||||||
};
|
|
||||||
|
|
||||||
return interaction;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readSamplesFromFile(allocator: std.mem.Allocator, file: std.fs.File) ![]f64 {
|
fn readSamplesFromFile(allocator: std.mem.Allocator, file: std.fs.File) ![]f64 {
|
||||||
@ -272,6 +274,8 @@ pub fn appendChannelFromFile(self: *App, path: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
||||||
|
const ni_daq = &(self.ni_daq orelse return);
|
||||||
|
|
||||||
const device_channel_index = findFreeSlot(DeviceChannel, &self.device_channels) orelse return error.DeviceChannelLimitReached;
|
const device_channel_index = findFreeSlot(DeviceChannel, &self.device_channels) orelse return error.DeviceChannelLimitReached;
|
||||||
|
|
||||||
const name_buff = try DeviceChannel.Name.fromSlice(channel_name);
|
const name_buff = try DeviceChannel.Name.fromSlice(channel_name);
|
||||||
@ -283,17 +287,17 @@ pub fn appendChannelFromDevice(self: *App, channel_name: []const u8) !void {
|
|||||||
|
|
||||||
var min_value: f64 = 0;
|
var min_value: f64 = 0;
|
||||||
var max_value: f64 = 1;
|
var max_value: f64 = 1;
|
||||||
const voltage_ranges = try self.ni_daq.listDeviceAOVoltageRanges(device_z);
|
const voltage_ranges = try ni_daq.listDeviceAOVoltageRanges(device_z);
|
||||||
if (voltage_ranges.len > 0) {
|
if (voltage_ranges.len > 0) {
|
||||||
min_value = voltage_ranges[0].low;
|
min_value = voltage_ranges[0].low;
|
||||||
max_value = voltage_ranges[0].high;
|
max_value = voltage_ranges[0].high;
|
||||||
}
|
}
|
||||||
|
|
||||||
const max_sample_rate = try self.ni_daq.getMaxSampleRate(channel_name_z);
|
const max_sample_rate = try ni_daq.getMaxSampleRate(channel_name_z);
|
||||||
|
|
||||||
self.device_channels[device_channel_index] = DeviceChannel{
|
self.device_channels[device_channel_index] = DeviceChannel{
|
||||||
.name = name_buff,
|
.name = name_buff,
|
||||||
.min_sample_rate = self.ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
|
.min_sample_rate = ni_daq.getMinSampleRate(channel_name_z) catch max_sample_rate,
|
||||||
.max_sample_rate = max_sample_rate,
|
.max_sample_rate = max_sample_rate,
|
||||||
.min_value = min_value,
|
.min_value = min_value,
|
||||||
.max_value = max_value,
|
.max_value = max_value,
|
||||||
@ -334,6 +338,17 @@ fn getChannelSource(self: *App, channel_view: *ChannelView) ?ChannelView.SourceO
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize {
|
||||||
|
for (0.., haystack) |i, item| {
|
||||||
|
if (std.mem.eql(u8, item, needle)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- GUI -------------------------------------------- //
|
||||||
|
|
||||||
fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count: f32) void {
|
fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count: f32) void {
|
||||||
const min_visible_samples = 1; // sample_count*0.02;
|
const min_visible_samples = 1; // sample_count*0.02;
|
||||||
|
|
||||||
@ -347,26 +362,23 @@ fn showChannelViewSlider(self: *App, view_rect: *Graph.ViewOptions, sample_count
|
|||||||
|
|
||||||
const minimap_rect = minimap_box.computedRect();
|
const minimap_rect = minimap_box.computedRect();
|
||||||
|
|
||||||
const middle_box = self.ui.newBoxFromString("Middle knob");
|
const middle_box = self.ui.clickableBox("Middle knob");
|
||||||
{
|
{
|
||||||
middle_box.flags.insert(.clickable);
|
|
||||||
middle_box.flags.insert(.draggable_x);
|
middle_box.flags.insert(.draggable_x);
|
||||||
middle_box.background = rl.Color.black.alpha(0.5);
|
middle_box.background = rl.Color.black.alpha(0.5);
|
||||||
middle_box.size.y = UI.Size.pixels(32, 1);
|
middle_box.size.y = UI.Size.pixels(32, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const left_knob_box = self.ui.newBoxFromString("Left knob");
|
const left_knob_box = self.ui.clickableBox("Left knob");
|
||||||
{
|
{
|
||||||
left_knob_box.flags.insert(.clickable);
|
|
||||||
left_knob_box.flags.insert(.draggable_x);
|
left_knob_box.flags.insert(.draggable_x);
|
||||||
left_knob_box.background = rl.Color.black.alpha(0.5);
|
left_knob_box.background = rl.Color.black.alpha(0.5);
|
||||||
left_knob_box.size.x = UI.Size.pixels(8, 1);
|
left_knob_box.size.x = UI.Size.pixels(8, 1);
|
||||||
left_knob_box.size.y = UI.Size.pixels(32, 1);
|
left_knob_box.size.y = UI.Size.pixels(32, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const right_knob_box = self.ui.newBoxFromString("Right knob");
|
const right_knob_box = self.ui.clickableBox("Right knob");
|
||||||
{
|
{
|
||||||
right_knob_box.flags.insert(.clickable);
|
|
||||||
right_knob_box.flags.insert(.draggable_x);
|
right_knob_box.flags.insert(.draggable_x);
|
||||||
right_knob_box.background = rl.Color.black.alpha(0.5);
|
right_knob_box.background = rl.Color.black.alpha(0.5);
|
||||||
right_knob_box.size.x = UI.Size.pixels(8, 1);
|
right_knob_box.size.x = UI.Size.pixels(8, 1);
|
||||||
@ -460,15 +472,11 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
if (source == .device) {
|
if (source == .device) {
|
||||||
const device_channel = source.device;
|
const device_channel = source.device;
|
||||||
|
|
||||||
{
|
if (self.ni_daq) |*ni_daq| {
|
||||||
const record_button = self.ui.newBoxFromString("Record");
|
const record_button = self.ui.button(.text, "Record");
|
||||||
record_button.flags.insert(.clickable);
|
|
||||||
record_button.size.x = UI.Size.text(1, 0);
|
|
||||||
record_button.size.y = UI.Size.percent(1, 0);
|
record_button.size.y = UI.Size.percent(1, 0);
|
||||||
|
|
||||||
if (device_channel.active_task == null) {
|
if (device_channel.active_task != null) {
|
||||||
record_button.setText(.text, "Record");
|
|
||||||
} else {
|
|
||||||
record_button.setText(.text, "Stop");
|
record_button.setText(.text, "Stop");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,6 +488,7 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
} else {
|
} else {
|
||||||
const channel_name = device_channel.name.buffer[0..device_channel.name.len :0];
|
const channel_name = device_channel.name.buffer[0..device_channel.name.len :0];
|
||||||
device_channel.active_task = try self.task_pool.launchAIVoltageChannel(
|
device_channel.active_task = try self.task_pool.launchAIVoltageChannel(
|
||||||
|
ni_daq,
|
||||||
&device_channel.mutex,
|
&device_channel.mutex,
|
||||||
&device_channel.samples,
|
&device_channel.samples,
|
||||||
.{
|
.{
|
||||||
@ -499,11 +508,11 @@ fn showChannelView(self: *App, channel_view: *ChannelView) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const follow_button = self.ui.newBoxFromString("Follow");
|
const follow_button = self.ui.button(.text, "Follow");
|
||||||
follow_button.flags.insert(.clickable);
|
|
||||||
follow_button.size.x = UI.Size.text(1, 0);
|
|
||||||
follow_button.size.y = UI.Size.percent(1, 0);
|
follow_button.size.y = UI.Size.percent(1, 0);
|
||||||
follow_button.setText(.text, if (channel_view.follow) "Unfollow" else "Follow");
|
if (channel_view.follow) {
|
||||||
|
follow_button.setText(.text, "Unfollow");
|
||||||
|
}
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(follow_button);
|
const signal = self.ui.signalFromBox(follow_button);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -544,13 +553,15 @@ fn showChannelsWindow(self: *App) !void {
|
|||||||
|
|
||||||
{
|
{
|
||||||
const prompt_box = self.ui.newBoxFromString("Add prompt");
|
const prompt_box = self.ui.newBoxFromString("Add prompt");
|
||||||
prompt_box.layout_axis = .X;
|
|
||||||
prompt_box.size.x = UI.Size.percent(1, 0);
|
prompt_box.size.x = UI.Size.percent(1, 0);
|
||||||
prompt_box.size.y = UI.Size.pixels(150, 1);
|
prompt_box.size.y = UI.Size.percent(1, 1);
|
||||||
self.ui.pushParent(prompt_box);
|
self.ui.pushParent(prompt_box);
|
||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
|
const center_box = self.ui.pushCenterBox();
|
||||||
|
defer self.ui.popCenterBox();
|
||||||
|
center_box.layout_axis = .X;
|
||||||
|
center_box.layout_gap = 32;
|
||||||
|
|
||||||
const from_file_button = self.ui.button(.text, "Add from file");
|
const from_file_button = self.ui.button(.text, "Add from file");
|
||||||
from_file_button.background = srcery.green;
|
from_file_button.background = srcery.green;
|
||||||
@ -558,28 +569,17 @@ fn showChannelsWindow(self: *App) !void {
|
|||||||
log.debug("TODO: Not implemented", .{});
|
log.debug("TODO: Not implemented", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.pixels(32, 1) });
|
|
||||||
|
|
||||||
const from_device_button = self.ui.button(.text, "Add from device");
|
const from_device_button = self.ui.button(.text, "Add from device");
|
||||||
from_device_button.background = srcery.green;
|
from_device_button.background = srcery.green;
|
||||||
if (self.ui.signalFromBox(from_device_button).clicked()) {
|
if (self.ui.signalFromBox(from_device_button).clicked()) {
|
||||||
log.debug("TODO: Not implemented", .{});
|
log.debug("TODO: Not implemented", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ui.spacer(.{ .x = UI.Size.percent(1, 0) });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn findChannelIndexByName(haystack: []const [:0]const u8, needle: [:0]const u8) ?usize {
|
|
||||||
for (0.., haystack) |i, item| {
|
|
||||||
if (std.mem.eql(u8, item, needle)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn showAddFromDeviceWindow(self: *App) !void {
|
fn showAddFromDeviceWindow(self: *App) !void {
|
||||||
|
const ni_daq = &(self.ni_daq orelse return);
|
||||||
|
|
||||||
const window = self.ui.newBoxFromString("Device window");
|
const window = self.ui.newBoxFromString("Device window");
|
||||||
window.size.x = UI.Size.percent(1, 0);
|
window.size.x = UI.Size.percent(1, 0);
|
||||||
window.size.y = UI.Size.percent(1, 0);
|
window.size.y = UI.Size.percent(1, 0);
|
||||||
@ -595,12 +595,10 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
self.ui.pushParent(filters_box);
|
self.ui.pushParent(filters_box);
|
||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
for (try self.ni_daq.listDeviceNames()) |device| {
|
for (try ni_daq.listDeviceNames()) |device| {
|
||||||
const device_box = self.ui.newBoxFromString(device);
|
const device_box = self.ui.button(.text, device);
|
||||||
device_box.flags.insert(.clickable);
|
|
||||||
device_box.size.x = UI.Size.text(2, 1);
|
device_box.size.x = UI.Size.text(2, 1);
|
||||||
device_box.size.y = UI.Size.text(2, 1);
|
device_box.size.y = UI.Size.text(2, 1);
|
||||||
device_box.setText(.text, device);
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(device_box);
|
const signal = self.ui.signalFromBox(device_box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -609,37 +607,33 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const toggle_inputs_box = self.ui.newBoxFromString("Toggle inputs");
|
const toggle_inputs_box = self.ui.button(.text, "Toggle inputs");
|
||||||
toggle_inputs_box.flags.insert(.clickable);
|
|
||||||
toggle_inputs_box.size.x = UI.Size.text(2, 1);
|
toggle_inputs_box.size.x = UI.Size.text(2, 1);
|
||||||
toggle_inputs_box.size.y = 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");
|
toggle_inputs_box.setText(.text, if (self.show_voltage_analog_inputs) "Hide inputs" else "Show inputs");
|
||||||
const signal = self.ui.signalFromBox(toggle_inputs_box);
|
|
||||||
if (signal.clicked()) {
|
if (self.ui.signalFromBox(toggle_inputs_box).clicked()) {
|
||||||
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
|
self.show_voltage_analog_inputs = !self.show_voltage_analog_inputs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const toggle_outputs_box = self.ui.newBoxFromString("Toggle outputs");
|
const toggle_outputs_box = self.ui.button(.text, "Toggle outputs");
|
||||||
toggle_outputs_box.flags.insert(.clickable);
|
|
||||||
toggle_outputs_box.size.x = UI.Size.text(2, 1);
|
toggle_outputs_box.size.x = UI.Size.text(2, 1);
|
||||||
toggle_outputs_box.size.y = 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");
|
toggle_outputs_box.setText(.text, if (self.show_voltage_analog_outputs) "Hide outputs" else "Show outputs");
|
||||||
const signal = self.ui.signalFromBox(toggle_outputs_box);
|
|
||||||
if (signal.clicked()) {
|
if (self.ui.signalFromBox(toggle_outputs_box).clicked()) {
|
||||||
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
|
self.show_voltage_analog_outputs = !self.show_voltage_analog_outputs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const add_button = self.ui.newBoxFromString("Add");
|
const add_button = self.ui.button(.text, "Add selected");
|
||||||
add_button.flags.insert(.clickable);
|
|
||||||
add_button.size.x = UI.Size.text(2, 1);
|
add_button.size.x = UI.Size.text(2, 1);
|
||||||
add_button.size.y = UI.Size.text(2, 1);
|
add_button.size.y = UI.Size.text(2, 1);
|
||||||
add_button.setText(.text, "Add selected");
|
|
||||||
const signal = self.ui.signalFromBox(add_button);
|
if (self.ui.signalFromBox(add_button).clicked()) {
|
||||||
if (signal.clicked()) {
|
|
||||||
const selected_devices = self.selected_channels.constSlice();
|
const selected_devices = self.selected_channels.constSlice();
|
||||||
|
|
||||||
for (selected_devices) |channel| {
|
for (selected_devices) |channel| {
|
||||||
@ -668,21 +662,21 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
self.device_filter.buffer[0..self.device_filter.len :0]
|
self.device_filter.buffer[0..self.device_filter.len :0]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
devices = try self.ni_daq.listDeviceNames();
|
devices = try ni_daq.listDeviceNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (devices) |device| {
|
for (devices) |device| {
|
||||||
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
|
var ai_voltage_physical_channels: []const [:0]const u8 = &.{};
|
||||||
if (self.show_voltage_analog_inputs) {
|
if (self.show_voltage_analog_inputs) {
|
||||||
if (try self.ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
if (try ni_daq.checkDeviceAIMeasurementType(device, .Voltage)) {
|
||||||
ai_voltage_physical_channels = try self.ni_daq.listDeviceAIPhysicalChannels(device);
|
ai_voltage_physical_channels = try ni_daq.listDeviceAIPhysicalChannels(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ao_physical_channels: []const [:0]const u8 = &.{};
|
var ao_physical_channels: []const [:0]const u8 = &.{};
|
||||||
if (self.show_voltage_analog_outputs) {
|
if (self.show_voltage_analog_outputs) {
|
||||||
if (try self.ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
if (try ni_daq.checkDeviceAOOutputType(device, .Voltage)) {
|
||||||
ao_physical_channels = try self.ni_daq.listDeviceAOPhysicalChannels(device);
|
ao_physical_channels = try ni_daq.listDeviceAOPhysicalChannels(device);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,11 +684,7 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
for (channels) |channel| {
|
for (channels) |channel| {
|
||||||
const selected_channels_slice = self.selected_channels.constSlice();
|
const selected_channels_slice = self.selected_channels.constSlice();
|
||||||
|
|
||||||
const channel_box = self.ui.newBoxFromString(channel);
|
const channel_box = self.ui.button(.text, channel);
|
||||||
channel_box.flags.insert(.clickable);
|
|
||||||
channel_box.size.x = UI.Size.text(1, 1);
|
|
||||||
channel_box.size.y = UI.Size.text(0.5, 1);
|
|
||||||
channel_box.setText(.text, channel);
|
|
||||||
|
|
||||||
if (findChannelIndexByName(selected_channels_slice, channel) != null) {
|
if (findChannelIndexByName(selected_channels_slice, channel) != null) {
|
||||||
channel_box.background = srcery.xgray3;
|
channel_box.background = srcery.xgray3;
|
||||||
@ -717,7 +707,6 @@ fn showAddFromDeviceWindow(self: *App) !void {
|
|||||||
|
|
||||||
fn showToolbar(self: *App) void {
|
fn showToolbar(self: *App) void {
|
||||||
const toolbar = self.ui.newBoxFromString("Toolbar");
|
const toolbar = self.ui.newBoxFromString("Toolbar");
|
||||||
toolbar.flags.insert(.clickable);
|
|
||||||
toolbar.background = rl.Color.green;
|
toolbar.background = rl.Color.green;
|
||||||
toolbar.layout_axis = .X;
|
toolbar.layout_axis = .X;
|
||||||
toolbar.size = .{
|
toolbar.size = .{
|
||||||
@ -728,14 +717,9 @@ fn showToolbar(self: *App) void {
|
|||||||
defer self.ui.popParent();
|
defer self.ui.popParent();
|
||||||
|
|
||||||
{
|
{
|
||||||
const box = self.ui.newBoxFromString("Add from file");
|
const box = self.ui.button(.text, "Add from file");
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.background = rl.Color.red;
|
box.background = rl.Color.red;
|
||||||
box.size = .{
|
box.size.y = UI.Size.percent(1, 1);
|
||||||
.x = UI.Size.text(2, 1),
|
|
||||||
.y = UI.Size.percent(1, 1)
|
|
||||||
};
|
|
||||||
box.setText(.text, "Add from file",);
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(box);
|
const signal = self.ui.signalFromBox(box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -752,14 +736,9 @@ fn showToolbar(self: *App) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const box = self.ui.newBoxFromString("Add from device");
|
const box = self.ui.button(.text, "Add from device");
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.background = rl.Color.lime;
|
box.background = rl.Color.lime;
|
||||||
box.size = .{
|
box.size.y = UI.Size.percent(1, 1);
|
||||||
.x = UI.Size.text(2, 1),
|
|
||||||
.y = UI.Size.percent(1, 1)
|
|
||||||
};
|
|
||||||
box.setText(.text, "Add from device");
|
|
||||||
|
|
||||||
const signal = self.ui.signalFromBox(box);
|
const signal = self.ui.signalFromBox(box);
|
||||||
if (signal.clicked()) {
|
if (signal.clicked()) {
|
||||||
@ -772,6 +751,186 @@ fn showToolbar(self: *App) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn showModalNoLibraryError(self: *App) void {
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
const text = self.ui.newBoxFromString("Text");
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("PALA, PALA! Aš neradau būtinos bibliotekos ant kompiuterio. Programa vis dar veiks, bet ");
|
||||||
|
text.appendText("dauguma funkcijų bus paslėptos. Susirask iternete \"NI MAX\" ir instaliuok. Štai nuorada ");
|
||||||
|
text.appendText("į gidą.");
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const link = self.ui.newBoxFromString("Link");
|
||||||
|
link.flags.insert(.clickable);
|
||||||
|
link.flags.insert(.hover_mouse_hand);
|
||||||
|
link.flags.insert(.text_underline);
|
||||||
|
link.size.x = UI.Size.text(1, 1);
|
||||||
|
link.size.y = UI.Size.text(1, 1);
|
||||||
|
link.setText(
|
||||||
|
.text,
|
||||||
|
"Nuorada į gidą"
|
||||||
|
);
|
||||||
|
link.text.?.color = srcery.blue;
|
||||||
|
|
||||||
|
const signal = self.ui.signalFromBox(link);
|
||||||
|
if (signal.clicked()) {
|
||||||
|
rl.openURL("https://knowledge.ni.com/KnowledgeArticleDetails?id=kA03q000000YGQwCAO&l=en-LT");
|
||||||
|
}
|
||||||
|
if (self.ui.isBoxHot(link)) {
|
||||||
|
link.text.?.color = srcery.bright_blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModalLibraryVersionError(self: *App) void {
|
||||||
|
assert(self.shown_modal.? == .library_version_error);
|
||||||
|
|
||||||
|
const installed_version = self.shown_modal.?.library_version_error;
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.flags.insert(.text_left_align);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("Ooo ne! Reikalinga biblioteka surasta, bet nesurastos reikalingos funkcijos. ");
|
||||||
|
text.appendText("Susitikrink, kad turi pakankamai naują versiją NI MAX instaliuota.");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Instaliuota versija: {}", .{installed_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Rekomenduotina versija: {}", .{NIDaq.Api.min_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModalLibraryVersionWarning(self: *App) void {
|
||||||
|
assert(self.shown_modal.? == .library_version_warning);
|
||||||
|
|
||||||
|
const installed_version = self.shown_modal.?.library_version_warning;
|
||||||
|
const modal = self.ui.getParent().?;
|
||||||
|
|
||||||
|
modal.layout_axis = .Y;
|
||||||
|
modal.size = .{
|
||||||
|
.x = UI.Size.pixels(400, 1),
|
||||||
|
.y = UI.Size.pixels(320, 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.flags.insert(.text_wrapping);
|
||||||
|
text.flags.insert(.text_left_align);
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.appendText("Instaliuota NI MAX versija žemesnė negu rekomenduotina versija. ");
|
||||||
|
text.appendText("Daug kas turėtų veikti, bet negaliu garantuoti kad viskas veiks. ");
|
||||||
|
text.appendText("Jeigu susidursi su problemomis kur programa sustoja veikti pabandyk atsinaujinti ");
|
||||||
|
text.appendText("NI MAX.");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Instaliuota versija: {}", .{installed_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const text = self.ui.newBox(UI.Key.initNil());
|
||||||
|
text.size.x = UI.Size.text(2, 0);
|
||||||
|
text.size.y = UI.Size.text(1, 1);
|
||||||
|
text.setFmtText(.text, "Rekomenduotina versija: {}", .{NIDaq.Api.min_version});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
|
||||||
|
{
|
||||||
|
self.ui.pushHorizontalAlign();
|
||||||
|
defer self.ui.popHorizontalAlign();
|
||||||
|
|
||||||
|
const btn = self.ui.button(.text, "Supratau");
|
||||||
|
btn.background = srcery.green;
|
||||||
|
if (self.ui.signalFromBox(btn).clicked()) {
|
||||||
|
self.shown_modal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.ui.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showModal(self: *App) void {
|
||||||
|
assert(self.shown_modal != null);
|
||||||
|
|
||||||
|
switch (self.shown_modal.?) {
|
||||||
|
.no_library_error => self.showModalNoLibraryError(),
|
||||||
|
.library_version_error => self.showModalLibraryVersionError(),
|
||||||
|
.library_version_warning => self.showModalLibraryVersionWarning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn updateUI(self: *App) !void {
|
fn updateUI(self: *App) !void {
|
||||||
self.ui.begin();
|
self.ui.begin();
|
||||||
defer self.ui.end();
|
defer self.ui.end();
|
||||||
@ -779,6 +938,32 @@ fn updateUI(self: *App) !void {
|
|||||||
const root_box = self.ui.getParent().?;
|
const root_box = self.ui.getParent().?;
|
||||||
root_box.layout_axis = .Y;
|
root_box.layout_axis = .Y;
|
||||||
|
|
||||||
|
var maybe_modal_overlay: ?*UI.Box = null;
|
||||||
|
|
||||||
|
if (self.shown_modal != null) {
|
||||||
|
const modal_overlay = self.ui.newBoxNoAppend(self.ui.newKeyFromString("Modal overlay"));
|
||||||
|
maybe_modal_overlay = modal_overlay;
|
||||||
|
modal_overlay.flags.insert(.clickable);
|
||||||
|
modal_overlay.flags.insert(.scrollable);
|
||||||
|
modal_overlay.background = rl.Color.black.alpha(0.5);
|
||||||
|
modal_overlay.setFixedPosition(.{ .x = 0, .y = 0 });
|
||||||
|
modal_overlay.size = .{
|
||||||
|
.x = UI.Size.percent(1, 0),
|
||||||
|
.y = UI.Size.percent(1, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.ui.pushParent(modal_overlay);
|
||||||
|
defer self.ui.popParent();
|
||||||
|
|
||||||
|
const modal = self.ui.pushCenterBox();
|
||||||
|
defer self.ui.popCenterBox();
|
||||||
|
modal.background = srcery.hard_black;
|
||||||
|
|
||||||
|
self.showModal();
|
||||||
|
|
||||||
|
_ = self.ui.signalFromBox(modal_overlay);
|
||||||
|
}
|
||||||
|
|
||||||
self.showToolbar();
|
self.showToolbar();
|
||||||
|
|
||||||
if (self.shown_window == .channels) {
|
if (self.shown_window == .channels) {
|
||||||
@ -786,6 +971,10 @@ fn updateUI(self: *App) !void {
|
|||||||
} else if (self.shown_window == .add_from_device) {
|
} else if (self.shown_window == .add_from_device) {
|
||||||
try self.showAddFromDeviceWindow();
|
try self.showAddFromDeviceWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (maybe_modal_overlay) |box| {
|
||||||
|
self.ui.appendBox(box);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *App) !void {
|
pub fn tick(self: *App) !void {
|
||||||
@ -822,7 +1011,6 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// On the first frame, render the UI twice.
|
// On the first frame, render the UI twice.
|
||||||
// So that on the second pass widgets that depend on sizes from other widgets have settled
|
// So that on the second pass widgets that depend on sizes from other widgets have settled
|
||||||
if (self.ui.frame_index == 0) {
|
if (self.ui.frame_index == 0) {
|
||||||
|
@ -60,12 +60,18 @@ pub fn init(allocator: std.mem.Allocator) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
|
fn loadFont(ttf_data: []const u8, font_size: u32) !rl.Font {
|
||||||
var codepoints: [95]i32 = undefined;
|
var codepoints: std.BoundedArray(i32, 128) = .{};
|
||||||
for (0..codepoints.len) |i| {
|
for (0..95) |i| {
|
||||||
codepoints[i] = @as(i32, @intCast(i)) + 32;
|
codepoints.appendAssumeCapacity(@as(i32, @intCast(i)) + 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), &codepoints);
|
const lithuanina_characters = std.unicode.Utf8View.initComptime("ąčęėįšųūžĄČĘĖĮŠŲŪŽ");
|
||||||
|
var char_iter = lithuanina_characters.iterator();
|
||||||
|
while (char_iter.nextCodepoint()) |codepoint| {
|
||||||
|
codepoints.appendAssumeCapacity(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loaded_font = rl.loadFontFromMemory(".ttf", ttf_data, @intCast(font_size), codepoints.slice());
|
||||||
if (!loaded_font.isReady()) {
|
if (!loaded_font.isReady()) {
|
||||||
return error.LoadFontFromMemory;
|
return error.LoadFontFromMemory;
|
||||||
}
|
}
|
||||||
|
@ -134,6 +134,10 @@ pub fn measureText(self: @This(), text: []const u8) rl.Vector2 {
|
|||||||
return text_size;
|
return text_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn measureWidth(self: @This(), text: []const u8) f32 {
|
||||||
|
return self.measureText(text).x;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn drawTextCenter(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
pub fn drawTextCenter(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
||||||
const text_size = self.measureText(text);
|
const text_size = self.measureText(text);
|
||||||
const adjusted_position = rl.Vector2{
|
const adjusted_position = rl.Vector2{
|
||||||
|
@ -4,6 +4,7 @@ const builtin = @import("builtin");
|
|||||||
const Application = @import("./app.zig");
|
const Application = @import("./app.zig");
|
||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const Profiler = @import("./profiler.zig");
|
const Profiler = @import("./profiler.zig");
|
||||||
|
const Platform = @import("./platform.zig");
|
||||||
const raylib_h = @cImport({
|
const raylib_h = @cImport({
|
||||||
@cInclude("stdio.h");
|
@cInclude("stdio.h");
|
||||||
@cInclude("raylib.h");
|
@cInclude("raylib.h");
|
||||||
@ -64,6 +65,8 @@ fn raylibTraceLogCallback(logType: c_int, text: [*c]const u8, args: raylib_h.va_
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
Platform.init();
|
||||||
|
|
||||||
// TODO: Setup logging to a file
|
// TODO: Setup logging to a file
|
||||||
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
|
raylib_h.SetTraceLogCallback(raylibTraceLogCallback);
|
||||||
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
|
rl.setTraceLogLevel(toRaylibLogLevel(std.options.log_level));
|
||||||
|
@ -9,6 +9,17 @@ const Api = @This();
|
|||||||
|
|
||||||
const log = std.log.scoped(.ni_daq_api);
|
const log = std.log.scoped(.ni_daq_api);
|
||||||
|
|
||||||
|
pub const min_version = std.SemanticVersion{
|
||||||
|
.major = 24,
|
||||||
|
.minor = 8,
|
||||||
|
.patch = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Error = error {
|
||||||
|
LibraryNotFound,
|
||||||
|
SymbolNotFound
|
||||||
|
};
|
||||||
|
|
||||||
lib: std.DynLib, // This MUST be the first field of `Api` struct
|
lib: std.DynLib, // This MUST be the first field of `Api` struct
|
||||||
|
|
||||||
DAQmxGetSysNIDAQMajorVersion: *const @TypeOf(c.DAQmxGetSysNIDAQMajorVersion),
|
DAQmxGetSysNIDAQMajorVersion: *const @TypeOf(c.DAQmxGetSysNIDAQMajorVersion),
|
||||||
@ -36,10 +47,10 @@ DAQmxGetDevAIPhysicalChans: *const @TypeOf(c.DAQmxGetDevAIPhysicalChans),
|
|||||||
DAQmxGetDevAOPhysicalChans: *const @TypeOf(c.DAQmxGetDevAOPhysicalChans),
|
DAQmxGetDevAOPhysicalChans: *const @TypeOf(c.DAQmxGetDevAOPhysicalChans),
|
||||||
DAQmxReadAnalogF64: *const @TypeOf(c.DAQmxReadAnalogF64),
|
DAQmxReadAnalogF64: *const @TypeOf(c.DAQmxReadAnalogF64),
|
||||||
|
|
||||||
pub fn init() !Api {
|
pub fn init() Error!Api {
|
||||||
var api: Api = undefined;
|
var api: Api = undefined;
|
||||||
|
|
||||||
api.lib = try std.DynLib.open("nicaiu");
|
api.lib = std.DynLib.open("nicaiu") catch return error.LibraryNotFound;
|
||||||
errdefer api.lib.close();
|
errdefer api.lib.close();
|
||||||
|
|
||||||
inline for (@typeInfo(Api).Struct.fields[1..]) |field| {
|
inline for (@typeInfo(Api).Struct.fields[1..]) |field| {
|
||||||
@ -47,7 +58,7 @@ pub fn init() !Api {
|
|||||||
const name_z = name[0 .. (name.len - 1) :0];
|
const name_z = name[0 .. (name.len - 1) :0];
|
||||||
@field(api, field.name) = api.lib.lookup(field.type, name_z) orelse {
|
@field(api, field.name) = api.lib.lookup(field.type, name_z) orelse {
|
||||||
log.err("Symbol lookup failed for {s}", .{name});
|
log.err("Symbol lookup failed for {s}", .{name});
|
||||||
return error.SymbolLookup;
|
return error.SymbolNotFound;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,4 +67,34 @@ pub fn init() !Api {
|
|||||||
|
|
||||||
pub fn deinit(self: *Api) void {
|
pub fn deinit(self: *Api) void {
|
||||||
self.lib.close();
|
self.lib.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version() !std.SemanticVersion {
|
||||||
|
var lib = std.DynLib.open("nicaiu") catch return error.LibraryNotFound;
|
||||||
|
defer lib.close();
|
||||||
|
|
||||||
|
const getMajorVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQMajorVersion), "DAQmxGetSysNIDAQMajorVersion") orelse return error.SymbolNotFound;
|
||||||
|
const getMinorVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQMinorVersion), "DAQmxGetSysNIDAQMinorVersion") orelse return error.SymbolNotFound;
|
||||||
|
const getUpdateVersion = lib.lookup(*const @TypeOf(c.DAQmxGetSysNIDAQUpdateVersion), "DAQmxGetSysNIDAQUpdateVersion") orelse return error.SymbolNotFound;
|
||||||
|
|
||||||
|
var major: u32 = 0;
|
||||||
|
if (getMajorVersion(&major) < 0) {
|
||||||
|
return error.GetMajorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
var minor: u32 = 0;
|
||||||
|
if (getMinorVersion(&minor) < 0) {
|
||||||
|
return error.GetMinorVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
var update: u32 = 0;
|
||||||
|
if (getUpdateVersion(&update) < 0) {
|
||||||
|
return error.GetUpdateVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std.SemanticVersion{
|
||||||
|
.major = major,
|
||||||
|
.minor = minor,
|
||||||
|
.patch = update
|
||||||
|
};
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Api = @import("./api.zig");
|
pub const Api = @import("./api.zig");
|
||||||
pub const c = Api.c;
|
pub const c = Api.c;
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -373,16 +373,13 @@ const DeviceBuffers = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
options: Options,
|
options: Options,
|
||||||
api: Api,
|
api: *Api,
|
||||||
device_names_buffer: []u8,
|
device_names_buffer: []u8,
|
||||||
device_names: StringArrayListUnmanaged,
|
device_names: StringArrayListUnmanaged,
|
||||||
|
|
||||||
device_buffers: []DeviceBuffers,
|
device_buffers: []DeviceBuffers,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, options: Options) !NIDaq {
|
pub fn init(allocator: std.mem.Allocator, api: *Api, options: Options) !NIDaq {
|
||||||
var api = try Api.init();
|
|
||||||
errdefer api.deinit();
|
|
||||||
|
|
||||||
const device_names_buffer_size = options.max_devices * (max_device_name_size + 2);
|
const device_names_buffer_size = options.max_devices * (max_device_name_size + 2);
|
||||||
const device_names_buffer = try allocator.alloc(u8, device_names_buffer_size);
|
const device_names_buffer = try allocator.alloc(u8, device_names_buffer_size);
|
||||||
errdefer allocator.free(device_names_buffer);
|
errdefer allocator.free(device_names_buffer);
|
||||||
@ -408,8 +405,6 @@ pub fn init(allocator: std.mem.Allocator, options: Options) !NIDaq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *NIDaq, allocator: std.mem.Allocator) void {
|
pub fn deinit(self: *NIDaq, allocator: std.mem.Allocator) void {
|
||||||
self.api.deinit();
|
|
||||||
|
|
||||||
self.device_names.deinit(allocator);
|
self.device_names.deinit(allocator);
|
||||||
allocator.free(self.device_names_buffer);
|
allocator.free(self.device_names_buffer);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const NIDaq = @import("./ni-daq/root.zig");
|
const NIDaq = @import("./root.zig");
|
||||||
|
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const log = std.log.scoped(.task_pool);
|
const log = std.log.scoped(.task_pool);
|
||||||
@ -43,12 +43,10 @@ pub const Entry = struct {
|
|||||||
|
|
||||||
running: bool = false,
|
running: bool = false,
|
||||||
read_thread: std.Thread,
|
read_thread: std.Thread,
|
||||||
ni_daq: *NIDaq,
|
|
||||||
entries: [max_tasks]Entry = undefined,
|
entries: [max_tasks]Entry = undefined,
|
||||||
|
|
||||||
pub fn init(self: *TaskPool, allocator: std.mem.Allocator, ni_daq: *NIDaq) !void {
|
pub fn init(self: *TaskPool, allocator: std.mem.Allocator) !void {
|
||||||
self.* = TaskPool{
|
self.* = TaskPool{
|
||||||
.ni_daq = ni_daq,
|
|
||||||
.read_thread = undefined
|
.read_thread = undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,12 +130,13 @@ fn findFreeEntry(self: *TaskPool) ?*Entry {
|
|||||||
|
|
||||||
pub fn launchAIVoltageChannel(
|
pub fn launchAIVoltageChannel(
|
||||||
self: *TaskPool,
|
self: *TaskPool,
|
||||||
|
ni_daq: *NIDaq,
|
||||||
mutex: *std.Thread.Mutex,
|
mutex: *std.Thread.Mutex,
|
||||||
samples: *std.ArrayList(f64),
|
samples: *std.ArrayList(f64),
|
||||||
sampling: Sampling,
|
sampling: Sampling,
|
||||||
options: NIDaq.Task.AIVoltageChannelOptions
|
options: NIDaq.Task.AIVoltageChannelOptions
|
||||||
) !*Entry {
|
) !*Entry {
|
||||||
const task = try self.ni_daq.createTask(null);
|
const task = try ni_daq.createTask(null);
|
||||||
errdefer task.clear();
|
errdefer task.clear();
|
||||||
|
|
||||||
const entry = self.findFreeEntry() orelse return error.NotEnoughSpace;
|
const entry = self.findFreeEntry() orelse return error.NotEnoughSpace;
|
@ -132,4 +132,10 @@ pub fn openFilePicker() !std.fs.File {
|
|||||||
// TODO: Use the `openFileAbsoluteW` function.
|
// TODO: Use the `openFileAbsoluteW` function.
|
||||||
// Could not get it to work, because it always threw OBJECT_PATH_SYNTAX_BAD error
|
// Could not get it to work, because it always threw OBJECT_PATH_SYNTAX_BAD error
|
||||||
return try std.fs.openFileAbsolute(filename, .{ });
|
return try std.fs.openFileAbsolute(filename, .{ });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() void {
|
||||||
|
if (builtin.os.tag == .windows) {
|
||||||
|
_ = windows_h.SetConsoleOutputCP(65001);
|
||||||
|
}
|
||||||
}
|
}
|
496
src/ui.zig
496
src/ui.zig
@ -3,6 +3,7 @@ const rl = @import("raylib");
|
|||||||
const Assets = @import("./assets.zig");
|
const Assets = @import("./assets.zig");
|
||||||
const rect_utils = @import("./rect-utils.zig");
|
const rect_utils = @import("./rect-utils.zig");
|
||||||
const srcery = @import("./srcery.zig");
|
const srcery = @import("./srcery.zig");
|
||||||
|
const FontFace = @import("./font-face.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.ui);
|
const log = std.log.scoped(.ui);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -242,6 +243,9 @@ pub const Box = struct {
|
|||||||
pub const Flag = enum {
|
pub const Flag = enum {
|
||||||
clickable,
|
clickable,
|
||||||
|
|
||||||
|
highlight_hot,
|
||||||
|
highlight_active,
|
||||||
|
|
||||||
draggable_x,
|
draggable_x,
|
||||||
draggable_y,
|
draggable_y,
|
||||||
|
|
||||||
@ -250,7 +254,15 @@ pub const Box = struct {
|
|||||||
fixed_x,
|
fixed_x,
|
||||||
fixed_y,
|
fixed_y,
|
||||||
fixed_width,
|
fixed_width,
|
||||||
fixed_height
|
fixed_height,
|
||||||
|
|
||||||
|
hover_mouse_hand,
|
||||||
|
|
||||||
|
skip_draw,
|
||||||
|
|
||||||
|
text_underline,
|
||||||
|
text_wrapping,
|
||||||
|
text_left_align
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Flags = std.EnumSet(Flag);
|
pub const Flags = std.EnumSet(Flag);
|
||||||
@ -305,7 +317,19 @@ pub const Box = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setAllocText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
|
pub fn appendText(self: *Box, text: []const u8) void {
|
||||||
|
if (self.text == null) {
|
||||||
|
self.text = .{
|
||||||
|
.content = self.allocator.alloc(u8, 0) catch return,
|
||||||
|
.font = .text
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const new_content = std.mem.concat(self.allocator, u8, &.{ self.text.?.content, text }) catch return;
|
||||||
|
self.text.?.content = new_content;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setFmtText(self: *Box, font: Assets.FontId, comptime fmt: []const u8, args: anytype) void {
|
||||||
self.text = .{
|
self.text = .{
|
||||||
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
|
.content = std.fmt.allocPrint(self.allocator, fmt, args) catch return,
|
||||||
.font = font
|
.font = font
|
||||||
@ -543,7 +567,7 @@ pub fn end(self: *UI) void {
|
|||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ew));
|
||||||
} else if (active_box_flags.contains(.draggable_y)) {
|
} else if (active_box_flags.contains(.draggable_y)) {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_resize_ns));
|
||||||
} else if (hover_box_flags.contains(.clickable)) {
|
} else if (hover_box_flags.contains(.hover_mouse_hand)) {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_pointing_hand));
|
||||||
} else {
|
} else {
|
||||||
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_default));
|
rl.setMouseCursor(@intFromEnum(rl.MouseCursor.mouse_cursor_default));
|
||||||
@ -645,6 +669,10 @@ pub fn draw(self: *UI) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drawBox(self: *UI, box: *Box) void {
|
fn drawBox(self: *UI, box: *Box) void {
|
||||||
|
if (box.flags.contains(.skip_draw)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const box_rect = box.computedRect();
|
const box_rect = box.computedRect();
|
||||||
|
|
||||||
const do_scissor = box.hasClipping();
|
const do_scissor = box.hasClipping();
|
||||||
@ -662,10 +690,14 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
rl.drawRectangleRec(box_rect, background);
|
rl.drawRectangleRec(box_rect, background);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.isBoxActive(box.key)) {
|
if (self.isKeyActive(box.key)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
|
if (box.flags.contains(.highlight_active)) {
|
||||||
} else if (self.isBoxHot(box.key)) {
|
rl.drawRectangleLinesEx(box_rect, 2, rl.Color.orange);
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
|
}
|
||||||
|
} else if (self.isKeyHot(box.key)) {
|
||||||
|
if (box.flags.contains(.highlight_hot)) {
|
||||||
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.blue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (box.texture) |texture| {
|
if (box.texture) |texture| {
|
||||||
@ -686,7 +718,38 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
|
|
||||||
if (box.text) |text| {
|
if (box.text) |text| {
|
||||||
const font = Assets.font(text.font);
|
const font = Assets.font(text.font);
|
||||||
font.drawTextCenter(text.content, rect_utils.center(box_rect), text.color);
|
const text_size = font.measureText(text.content);
|
||||||
|
var text_rect = Rect{
|
||||||
|
.x = box_rect.x,
|
||||||
|
.y = box_rect.y,
|
||||||
|
.width = text_size.x,
|
||||||
|
.height = text_size.y
|
||||||
|
};
|
||||||
|
|
||||||
|
if (box.flags.contains(.text_left_align)) {
|
||||||
|
if (box.size.x.kind == .text) {
|
||||||
|
const padding = box.size.x.kind.text;
|
||||||
|
text_rect.x += padding * font.getSize() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (box.size.y.kind == .text) {
|
||||||
|
const padding = box.size.y.kind.text;
|
||||||
|
text_rect.y += padding * font.getSize() / 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_rect.x += (box_rect.width - text_size.x) / 2;
|
||||||
|
text_rect.y += (box_rect.height - text_size.y) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
font.drawText(text.content, .{ .x = text_rect.x, .y = text_rect.y }, text.color);
|
||||||
|
|
||||||
|
if (box.flags.contains(.text_underline)) {
|
||||||
|
rl.drawLineV(
|
||||||
|
rect_utils.bottomLeft(text_rect),
|
||||||
|
rect_utils.bottomRight(text_rect),
|
||||||
|
text.color
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
@ -695,9 +758,9 @@ fn drawBox(self: *UI, box: *Box) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
if (self.isBoxActive(box.key)) {
|
if (self.isKeyActive(box.key)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
||||||
} else if (self.isBoxHot(box.key)) {
|
} else if (self.isKeyHot(box.key)) {
|
||||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.orange);
|
||||||
} else {
|
} else {
|
||||||
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.pink);
|
rl.drawRectangleLinesEx(box_rect, 1, rl.Color.pink);
|
||||||
@ -790,6 +853,8 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
const computed_size = getVec2Axis(&box.persistent.size, axis);
|
const computed_size = getVec2Axis(&box.persistent.size, axis);
|
||||||
|
|
||||||
if (size.kind == .children_sum) {
|
if (size.kind == .children_sum) {
|
||||||
|
var first_child: bool = true;
|
||||||
|
|
||||||
var sum: f32 = 0;
|
var sum: f32 = 0;
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
while (child_iter.next()) |child| {
|
while (child_iter.next()) |child| {
|
||||||
@ -799,83 +864,36 @@ fn calcLayoutDownardsSize(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
|
|
||||||
if (box.layout_axis == axis) {
|
if (box.layout_axis == axis) {
|
||||||
sum += child_size;
|
sum += child_size;
|
||||||
|
if (!first_child) {
|
||||||
|
sum += box.layout_gap;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
sum = @max(sum, child_size);
|
sum = @max(sum, child_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
first_child = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
computed_size.* = sum;
|
computed_size.* = sum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
|
|
||||||
{
|
|
||||||
var layout_position: f32 = 0;
|
|
||||||
|
|
||||||
var child_iter = self.iterChildrenByParent(box);
|
|
||||||
while (child_iter.next()) |child| {
|
|
||||||
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
child_axis_position.* = parent_axis_position.*;
|
|
||||||
|
|
||||||
if (box.layout_axis == axis) {
|
|
||||||
child_axis_position.* += layout_position;
|
|
||||||
layout_position += child_axis_size.*;
|
|
||||||
layout_position += box.layout_gap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
child_axis_position.* -= getVec2Axis(&box.view_offset, axis).*;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (box.layout_axis == axis) {
|
|
||||||
var child_size_sum: f32 = 0;
|
|
||||||
var child_count: 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.*;
|
|
||||||
child_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child_count > 1) {
|
|
||||||
child_size_sum += (child_count - 1) * box.layout_gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
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| {
|
|
||||||
self.calcLayoutPositions(child, axis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
||||||
|
if (box.text != null and box.flags.contains(.text_wrapping)) {
|
||||||
|
const text = box.text.?;
|
||||||
|
const font = Assets.font(text.font);
|
||||||
|
const axis_size = box.size.getAxis(axis);
|
||||||
|
|
||||||
|
var max_width = box.persistent.size.x;
|
||||||
|
if (axis_size.kind == .text) {
|
||||||
|
max_width -= axis_size.kind.text * font.getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrapText(box.allocator, font, text.content, max_width) catch null) |wrapped_content| {
|
||||||
|
box.text.?.content = wrapped_content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Children can't be wider than the parent on the secondary axis
|
// Children can't be wider than the parent on the secondary axis
|
||||||
if (box.layout_axis != axis) {
|
if (box.layout_axis != axis) {
|
||||||
const max_child_size = getVec2Axis(&box.persistent.size, axis).*;
|
const max_child_size = getVec2Axis(&box.persistent.size, axis).*;
|
||||||
@ -949,12 +967,134 @@ fn calcLayoutEnforceConstraints(self: *UI, box: *Box, axis: Axis) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newKeyFromString(self: *UI, text: []const u8) Key {
|
fn calcLayoutPositions(self: *UI, box: *Box, axis: Axis) void {
|
||||||
var parent_hash: u64 = 0;
|
{
|
||||||
if (self.getParent()) |parent| {
|
var layout_position: f32 = 0;
|
||||||
parent_hash = parent.key.hash;
|
|
||||||
|
var child_iter = self.iterChildrenByParent(box);
|
||||||
|
while (child_iter.next()) |child| {
|
||||||
|
const child_axis_position = getVec2Axis(&child.persistent.position, axis);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
child_axis_position.* = parent_axis_position.*;
|
||||||
|
|
||||||
|
if (box.layout_axis == axis) {
|
||||||
|
child_axis_position.* += layout_position;
|
||||||
|
layout_position += child_axis_size.*;
|
||||||
|
layout_position += box.layout_gap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child_axis_position.* -= getVec2Axis(&box.view_offset, axis).*;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Key.initString(parent_hash, text);
|
|
||||||
|
if (box.layout_axis == axis) {
|
||||||
|
var child_size_sum: f32 = 0;
|
||||||
|
var child_count: 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.*;
|
||||||
|
child_count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (child_count > 1) {
|
||||||
|
child_size_sum += (child_count - 1) * box.layout_gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
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| {
|
||||||
|
self.calcLayoutPositions(child, axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrapText(allocator: std.mem.Allocator, font: FontFace, text: []const u8, max_width: f32) !?[]u8 {
|
||||||
|
// TODO: Implement word wrapping that doesn't include re-allocating the whole text
|
||||||
|
if (font.measureWidth(text) < max_width) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = try std.ArrayList(u8).initCapacity(allocator, text.len);
|
||||||
|
defer result.deinit();
|
||||||
|
|
||||||
|
var line_buffer = std.ArrayList(u8).init(allocator);
|
||||||
|
defer line_buffer.deinit();
|
||||||
|
|
||||||
|
var word_iter = std.mem.splitScalar(u8, text, ' ');
|
||||||
|
while (word_iter.next()) |word| {
|
||||||
|
try line_buffer.ensureUnusedCapacity(word.len + 1);
|
||||||
|
|
||||||
|
const current_line = line_buffer.items;
|
||||||
|
if (line_buffer.items.len > 0) {
|
||||||
|
try line_buffer.append(' ');
|
||||||
|
}
|
||||||
|
try line_buffer.appendSlice(word);
|
||||||
|
|
||||||
|
if (font.measureWidth(line_buffer.items) > max_width) {
|
||||||
|
if (result.items.len > 0) {
|
||||||
|
try result.append('\n');
|
||||||
|
}
|
||||||
|
try result.appendSlice(current_line);
|
||||||
|
|
||||||
|
line_buffer.clearRetainingCapacity();
|
||||||
|
try line_buffer.appendSlice(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line_buffer.items.len > 0) {
|
||||||
|
if (result.items.len > 0) {
|
||||||
|
try result.append('\n');
|
||||||
|
}
|
||||||
|
try result.appendSlice(line_buffer.items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return try result.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getKeySeed(self: *UI) u64 {
|
||||||
|
var maybe_current = self.getParent();
|
||||||
|
while (maybe_current) |current| {
|
||||||
|
if (!current.key.isNil()) {
|
||||||
|
return current.key.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_current = null;
|
||||||
|
if (current.parent_index) |parent_index| {
|
||||||
|
maybe_current = &self.boxes.buffer[parent_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newKeyFromString(self: *UI, text: []const u8) Key {
|
||||||
|
return Key.initString(self.getKeySeed(), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
|
pub fn newBoxFromString(self: *UI, text: []const u8) *Box {
|
||||||
@ -965,7 +1105,26 @@ pub fn newBoxFromPtr(self: *UI, ptr: anytype) *Box {
|
|||||||
return self.newBox(Key.initPtr(ptr));
|
return self.newBox(Key.initPtr(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn newBox(self: *UI, key: Key) *Box {
|
pub fn appendBox(self: *UI, box: *Box) void {
|
||||||
|
assert(box.parent_index == null);
|
||||||
|
|
||||||
|
if (self.getParentIndex()) |parent_index| {
|
||||||
|
const parent = &self.boxes.buffer[parent_index];
|
||||||
|
box.parent_index = parent_index;
|
||||||
|
|
||||||
|
if (parent.last_child_index) |last_child_index| {
|
||||||
|
const last_child = &self.boxes.buffer[last_child_index];
|
||||||
|
|
||||||
|
last_child.next_sibling_index = box.index;
|
||||||
|
parent.last_child_index = box.index;
|
||||||
|
} else {
|
||||||
|
parent.first_child_index = box.index;
|
||||||
|
parent.last_child_index = box.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn newBoxNoAppend(self: *UI, key: Key) *Box {
|
||||||
var box: *Box = undefined;
|
var box: *Box = undefined;
|
||||||
var box_index: ?BoxIndex = null;
|
var box_index: ?BoxIndex = null;
|
||||||
var persistent: Box.Persistent = .{};
|
var persistent: Box.Persistent = .{};
|
||||||
@ -995,21 +1154,12 @@ pub fn newBox(self: *UI, key: Key) *Box {
|
|||||||
.index = box_index.?
|
.index = box_index.?
|
||||||
};
|
};
|
||||||
|
|
||||||
if (self.getParentIndex()) |parent_index| {
|
return box;
|
||||||
const parent = &self.boxes.buffer[parent_index];
|
}
|
||||||
box.parent_index = parent_index;
|
|
||||||
|
|
||||||
if (parent.last_child_index) |last_child_index| {
|
|
||||||
const last_child = &self.boxes.buffer[last_child_index];
|
|
||||||
|
|
||||||
last_child.next_sibling_index = box.index;
|
|
||||||
parent.last_child_index = box.index;
|
|
||||||
} else {
|
|
||||||
parent.first_child_index = box.index;
|
|
||||||
parent.last_child_index = box.index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn newBox(self: *UI, key: Key) *Box {
|
||||||
|
const box = self.newBoxNoAppend(key);
|
||||||
|
self.appendBox(box);
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1056,7 +1206,7 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
|
|||||||
while (event_index < self.events.len) {
|
while (event_index < self.events.len) {
|
||||||
var taken = false;
|
var taken = false;
|
||||||
const event: Event = self.events.buffer[event_index];
|
const event: Event = self.events.buffer[event_index];
|
||||||
const is_active = self.isBoxActive(key);
|
const is_active = self.isKeyActive(key);
|
||||||
|
|
||||||
if (event == .mouse_pressed and clickable and is_mouse_inside) {
|
if (event == .mouse_pressed and clickable and is_mouse_inside) {
|
||||||
const mouse_button = event.mouse_pressed;
|
const mouse_button = event.mouse_pressed;
|
||||||
@ -1119,7 +1269,11 @@ pub fn signalFromBox(self: *UI, box: *Box) Signal {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isBoxHot(self: *UI, key: Key) bool {
|
pub fn isBoxHot(self: *UI, box: *Box) bool {
|
||||||
|
return self.isKeyHot(box.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isKeyHot(self: *UI, key: Key) bool {
|
||||||
if (self.hot_box_key) |hot_box_key| {
|
if (self.hot_box_key) |hot_box_key| {
|
||||||
return hot_box_key.eql(key);
|
return hot_box_key.eql(key);
|
||||||
} else {
|
} else {
|
||||||
@ -1127,7 +1281,7 @@ fn isBoxHot(self: *UI, key: Key) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isBoxActive(self: *UI, key: Key) bool {
|
pub fn isKeyActive(self: *UI, key: Key) bool {
|
||||||
inline for (std.meta.fields(rl.MouseButton)) |mouse_button_field| {
|
inline for (std.meta.fields(rl.MouseButton)) |mouse_button_field| {
|
||||||
const mouse_button: rl.MouseButton = @enumFromInt(mouse_button_field.value);
|
const mouse_button: rl.MouseButton = @enumFromInt(mouse_button_field.value);
|
||||||
|
|
||||||
@ -1195,6 +1349,7 @@ pub fn pushScrollbar(self: *UI, key: UI.Key) *Box {
|
|||||||
self.pushParent(container);
|
self.pushParent(container);
|
||||||
|
|
||||||
const content_area = self.newBoxFromString("Scroll area");
|
const content_area = self.newBoxFromString("Scroll area");
|
||||||
|
content_area.flags.insert(.skip_draw);
|
||||||
content_area.flags.insert(.scrollable);
|
content_area.flags.insert(.scrollable);
|
||||||
content_area.size = .{
|
content_area.size = .{
|
||||||
.x = UI.Size.percent(1, 0),
|
.x = UI.Size.percent(1, 0),
|
||||||
@ -1215,58 +1370,62 @@ pub fn popScrollbar(self: *UI) void {
|
|||||||
const content_area = self.getParent().?;
|
const content_area = self.getParent().?;
|
||||||
self.popParent(); // pop scroll area
|
self.popParent(); // pop scroll area
|
||||||
|
|
||||||
const scrollbar_area = self.newBoxFromString("Scrollbar area");
|
|
||||||
scrollbar_area.background = rl.Color.gold;
|
|
||||||
scrollbar_area.flags.insert(.scrollable);
|
|
||||||
scrollbar_area.size = .{
|
|
||||||
.x = UI.Size.pixels(24, 1),
|
|
||||||
.y = UI.Size.percent(1, 0),
|
|
||||||
};
|
|
||||||
self.pushParent(scrollbar_area);
|
|
||||||
defer self.popParent();
|
|
||||||
|
|
||||||
const visible_content_size = content_area.persistent.size.y;
|
const visible_content_size = content_area.persistent.size.y;
|
||||||
const used_content_size = content_area.persistent.children_size.y;
|
const used_content_size = content_area.persistent.children_size.y;
|
||||||
const visible_percent = clamp(visible_content_size / used_content_size, 0, 1);
|
const visible_percent = clamp(visible_content_size / used_content_size, 0, 1);
|
||||||
|
if (used_content_size != 0) {
|
||||||
const draggable = self.newBoxFromString("Scrollbar button");
|
content_area.flags.remove(.skip_draw);
|
||||||
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(visible_percent, 1),
|
|
||||||
};
|
|
||||||
|
|
||||||
const sroll_offset = &content_area.persistent.sroll_offset;
|
|
||||||
const scrollbar_height = scrollbar_area.persistent.size.y;
|
|
||||||
const max_offset = scrollbar_height * (1 - visible_percent);
|
|
||||||
draggable.setFixedY(scrollbar_area.persistent.position.y + sroll_offset.* * max_offset);
|
|
||||||
|
|
||||||
const draggable_signal = self.signalFromBox(draggable);
|
|
||||||
if (draggable_signal.dragged()) {
|
|
||||||
sroll_offset.* += draggable_signal.drag.y / max_offset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scroll_speed = 16;
|
if (!content_area.flags.contains(.skip_draw)) {
|
||||||
const scrollbar_signal = self.signalFromBox(scrollbar_area);
|
const scrollbar_area = self.newBoxFromString("Scrollbar area");
|
||||||
if (scrollbar_signal.scrolled()) {
|
scrollbar_area.background = rl.Color.gold;
|
||||||
sroll_offset.* -= scrollbar_signal.scroll.y / max_offset * scroll_speed;
|
scrollbar_area.flags.insert(.scrollable);
|
||||||
}
|
scrollbar_area.size = .{
|
||||||
|
.x = UI.Size.pixels(24, 1),
|
||||||
|
.y = UI.Size.percent(1, 0),
|
||||||
|
};
|
||||||
|
self.pushParent(scrollbar_area);
|
||||||
|
defer self.popParent();
|
||||||
|
|
||||||
const content_area_signal = self.signalFromBox(content_area);
|
const draggable = self.newBoxFromString("Scrollbar button");
|
||||||
if (content_area_signal.scrolled()) {
|
draggable.background = rl.Color.dark_brown;
|
||||||
sroll_offset.* -= content_area_signal.scroll.y / max_offset * scroll_speed;
|
draggable.flags.insert(.clickable);
|
||||||
}
|
draggable.flags.insert(.draggable_y);
|
||||||
|
draggable.size = .{
|
||||||
|
.x = UI.Size.percent(1, 1),
|
||||||
|
.y = UI.Size.percent(visible_percent, 1),
|
||||||
|
};
|
||||||
|
|
||||||
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
|
const sroll_offset = &content_area.persistent.sroll_offset;
|
||||||
|
const scrollbar_height = content_area.persistent.size.y;
|
||||||
|
const max_offset = scrollbar_height * (1 - visible_percent);
|
||||||
|
draggable.setFixedY(content_area.persistent.position.y + sroll_offset.* * max_offset);
|
||||||
|
|
||||||
|
const draggable_signal = self.signalFromBox(draggable);
|
||||||
|
if (draggable_signal.dragged()) {
|
||||||
|
sroll_offset.* += draggable_signal.drag.y / max_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scroll_speed = 16;
|
||||||
|
const scrollbar_signal = self.signalFromBox(scrollbar_area);
|
||||||
|
if (scrollbar_signal.scrolled()) {
|
||||||
|
sroll_offset.* -= scrollbar_signal.scroll.y / max_offset * scroll_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content_area_signal = self.signalFromBox(content_area);
|
||||||
|
if (content_area_signal.scrolled()) {
|
||||||
|
sroll_offset.* -= content_area_signal.scroll.y / max_offset * scroll_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
sroll_offset.* = clamp(sroll_offset.*, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
self.popParent(); // pop container
|
self.popParent(); // pop container
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
||||||
const box = self.newBoxFromString(text);
|
const box = self.clickableBox(text);
|
||||||
box.flags.insert(.clickable);
|
|
||||||
box.size.x = UI.Size.text(1, 1);
|
box.size.x = UI.Size.text(1, 1);
|
||||||
box.size.y = UI.Size.text(0.5, 1);
|
box.size.y = UI.Size.text(0.5, 1);
|
||||||
box.setText(font, text);
|
box.setText(font, text);
|
||||||
@ -1274,7 +1433,64 @@ pub fn button(self: *UI, font: Assets.FontId, text: []const u8) *Box {
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clickableBox(self: *UI, key: []const u8) *Box {
|
||||||
|
const box = self.newBoxFromString(key);
|
||||||
|
box.flags.insert(.clickable);
|
||||||
|
box.flags.insert(.highlight_active);
|
||||||
|
box.flags.insert(.highlight_hot);
|
||||||
|
box.flags.insert(.hover_mouse_hand);
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spacer(self: *UI, size: Vec2Size) void {
|
pub fn spacer(self: *UI, size: Vec2Size) void {
|
||||||
const box = self.newBox(UI.Key.initNil());
|
const box = self.newBox(UI.Key.initNil());
|
||||||
box.size = size;
|
box.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) });
|
||||||
|
|
||||||
|
const container = self.newBox(UI.Key.initNil());
|
||||||
|
container.size.x = UI.Size.childrenSum(1);
|
||||||
|
container.size.y = UI.Size.childrenSum(1);
|
||||||
|
self.pushParent(container);
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popCenterBox(self: *UI) void {
|
||||||
|
self.popParent();
|
||||||
|
|
||||||
|
self.spacer(.{ .y = UI.Size.percent(1, 0) });
|
||||||
|
self.popParent();
|
||||||
|
|
||||||
|
self.popHorizontalAlign();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushHorizontalAlign(self: *UI) void {
|
||||||
|
const horizontal_align = self.newBox(UI.Key.initNil());
|
||||||
|
horizontal_align.layout_axis = .X;
|
||||||
|
horizontal_align.size = .{
|
||||||
|
.x = UI.Size.percent(1, 0),
|
||||||
|
.y = UI.Size.childrenSum(1),
|
||||||
|
};
|
||||||
|
self.pushParent(horizontal_align);
|
||||||
|
|
||||||
|
self.spacer(.{ .x = UI.Size.percent(1, 0) });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popHorizontalAlign(self: *UI) void {
|
||||||
|
self.spacer(.{ .x = UI.Size.percent(1, 0) });
|
||||||
|
self.popParent();
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user