daq-view/src/screens/main_screen.zig

859 lines
28 KiB
Zig

const std = @import("std");
const rl = @import("raylib");
const UI = @import("../ui.zig");
const App = @import("../app.zig");
const srcery = @import("../srcery.zig");
const Platform = @import("../platform/root.zig");
const RangeF64 = @import("../range.zig").RangeF64;
const Graph = @import("../graph.zig");
const Assets = @import("../assets.zig");
const utils = @import("../utils.zig");
const NIDaq = @import("../ni-daq/root.zig");
const UIView = @import("../components/view.zig");
const ViewControlsSystem = @import("../components/systems/view_controls.zig");
const MainScreen = @This();
const log = std.log.scoped(.main_screen);
const assert = std.debug.assert;
const remap = utils.remap;
const Id = App.Id;
app: *App,
view_controls: ViewControlsSystem,
modal: ?union(enum){
view_protocol: Id, // View id
notes
} = null,
// Protocol modal
frequency_input: UI.TextInputStorage,
amplitude_input: UI.TextInputStorage,
protocol_error_message: ?[]const u8 = null,
protocol_graph_cache: Graph.RenderCache = .{},
preview_sample_list_id: App.Id,
preview_samples_y_range: RangeF64 = RangeF64.init(0, 0),
// Notes modal
notes_storage: UI.TextInputStorage,
// Project settings
sample_rate_input: UI.TextInputStorage,
parsed_sample_rate: ?f64 = null,
// View settings
transform_inputs: [App.View.max_transforms]UI.TextInputStorage,
channel_save_file_picker: ?Platform.FilePickerId = null,
file_save_file_picker: ?Platform.FilePickerId = null,
pub fn init(app: *App) !MainScreen {
const allocator = app.allocator;
var transform_inputs: [App.View.max_transforms]UI.TextInputStorage = undefined;
for (&transform_inputs) |*input| {
input.* = UI.TextInputStorage.init(allocator);
}
var self = MainScreen{
.app = app,
.frequency_input = UI.TextInputStorage.init(allocator),
.amplitude_input = UI.TextInputStorage.init(allocator),
.sample_rate_input = UI.TextInputStorage.init(allocator),
.notes_storage = UI.TextInputStorage.init(allocator),
.view_controls = ViewControlsSystem.init(&app.project),
.transform_inputs = transform_inputs,
.preview_sample_list_id = try app.project.addSampleList(allocator)
};
try self.frequency_input.setText("10");
try self.amplitude_input.setText("10");
return self;
}
pub fn deinit(self: *MainScreen) void {
self.frequency_input.deinit();
self.amplitude_input.deinit();
self.sample_rate_input.deinit();
self.notes_storage.deinit();
for (self.transform_inputs) |input| {
input.deinit();
}
self.app.project.removeSampleList(self.preview_sample_list_id);
self.clearProtocolErrorMessage();
}
pub fn showProtocolModal(self: *MainScreen, view_id: Id) !void {
var ui = &self.app.ui;
const allocator = self.app.allocator;
const view = self.app.getView(view_id) orelse return;
if (view.reference != .channel) return;
const channel_id = view.reference.channel;
const channel = self.app.getChannel(channel_id) orelse return;
const sample_rate = self.app.project.getSampleRate() orelse return;
const container = ui.createBox(.{
.key = ui.keyFromString("Protocol modal"),
.background = srcery.black,
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 300 }),
.layout_direction = .top_to_bottom,
.padding = UI.Padding.all(ui.rem(1.5)),
.flags = &.{ .clickable },
.layout_gap = ui.rem(0.5)
});
container.beginChildren();
defer container.endChildren();
{
const protocol_view = ui.createBox(.{
.key = ui.keyFromString("Protocol view"),
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixedPixels(ui.rem(4)),
.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 })
});
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
const view_rect = Graph.ViewOptions{
.x_range = RangeF64.init(0, @floatFromInt(sample_list.getLength())),
.y_range = self.preview_samples_y_range
};
Graph.drawCached(&self.protocol_graph_cache, protocol_view.persistent.size, view_rect, sample_list);
if (self.protocol_graph_cache.texture) |texture| {
protocol_view.texture = texture.texture;
}
}
}
const FormInput = struct {
name: []const u8,
storage: *UI.TextInputStorage,
value: *f32
};
var frequency: f32 = 0;
var amplitude: f32 = 0;
const form_inputs = &[_]FormInput{
.{
.name = "Frequency",
.storage = &self.frequency_input,
.value = &frequency
},
.{
.name = "Amplitude",
.storage = &self.amplitude_input,
.value = &amplitude
},
};
var any_input_modified = false;
for (form_inputs) |form_input| {
const label = form_input.name;
const text_input_storage = form_input.storage;
const row = ui.createBox(.{
.key = ui.keyFromString(label),
.layout_direction = .left_to_right,
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5))
});
row.beginChildren();
defer row.endChildren();
const label_box = ui.label("{s}", .{ label });
label_box.size.x = UI.Sizing.initFixed(.{ .font_size = 5 });
label_box.size.y = UI.Sizing.initGrowFull();
label_box.alignment.y = .center;
try ui.textInput(.{
.key = ui.keyFromString("Text input"),
.storage = text_input_storage
});
any_input_modified = any_input_modified or text_input_storage.modified;
}
if (any_input_modified) {
self.clearProtocolErrorMessage();
for (form_inputs) |form_input| {
const label = form_input.name;
const text_input_storage: *UI.TextInputStorage = form_input.storage;
const number = std.fmt.parseFloat(f32, text_input_storage.buffer.items) catch {
try self.setProtocolErrorMessage("ERROR: {s} must be a number", .{ label });
continue;
};
if (number <= 0) {
try self.setProtocolErrorMessage("ERROR: {s} must be positive", .{ label });
continue;
}
form_input.value.* = number;
}
}
if (self.protocol_error_message == null and any_input_modified) {
if (self.app.project.sample_lists.get(self.preview_sample_list_id)) |sample_list| {
var preview_samples: std.ArrayListUnmanaged(f64) = .{};
defer preview_samples.deinit(allocator);
try App.Channel.generateSine(&preview_samples, allocator, sample_rate, frequency, amplitude);
sample_list.clear(allocator);
try sample_list.append(preview_samples.items);
self.preview_samples_y_range = RangeF64.init(-amplitude*1.1, amplitude*1.1);
self.protocol_graph_cache.invalidate();
}
}
if (self.protocol_error_message) |message| {
_ = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixedText(),
.text_color = srcery.red,
.align_x = .start,
.align_y = .start,
.flags = &.{ .wrap_text },
.text = message
});
}
_ = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initGrowFull(),
});
{
const row = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFitChildren(),
.align_x = .end
});
row.beginChildren();
defer row.endChildren();
const btn = ui.textButton("Confirm");
btn.borders = UI.Borders.all(.{ .color = srcery.green, .size = 4 });
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_enter)) {
if (self.protocol_error_message == null) {
try App.Channel.generateSine(&channel.write_pattern, allocator, sample_rate, frequency, amplitude);
self.app.pushCommand(.{ .start_output = channel_id });
self.view_controls.view_protocol_modal = null;
}
}
}
_ = ui.signal(container);
}
fn showNotesModal(self: *MainScreen) !void {
var ui = &self.app.ui;
const container = ui.createBox(.{
.key = ui.keyFromString("Notes modal"),
.background = srcery.black,
.size_x = UI.Sizing.initGrowUpTo(.{ .pixels = 400 }),
.size_y = UI.Sizing.initGrowUpTo(.{ .pixels = 600 }),
.layout_direction = .top_to_bottom,
.padding = UI.Padding.all(ui.rem(1.5)),
.flags = &.{ .clickable },
.layout_gap = ui.rem(0.5)
});
container.beginChildren();
defer container.endChildren();
defer _ = ui.signal(container);
const label = ui.label("Notes", .{});
label.font = .{ .variant = .regular_italic, .size = ui.rem(2) };
label.alignment.x = .center;
label.size.x = UI.Sizing.initGrowFull();
_ = try ui.textInput(.{
.key = ui.keyFromString("Notes"),
.storage = &self.notes_storage,
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initGrowFull(),
.single_line = false
});
}
fn setProtocolErrorMessage(self: *MainScreen, comptime fmt: []const u8, args: anytype) !void {
self.clearProtocolErrorMessage();
const allocator = self.app.allocator;
self.protocol_error_message = try std.fmt.allocPrint(allocator, fmt, args);
}
fn clearProtocolErrorMessage(self: *MainScreen) void {
const allocator = self.app.allocator;
if (self.protocol_error_message) |msg| {
allocator.free(msg);
}
self.protocol_error_message = null;
}
fn showProjectSettings(self: *MainScreen) !void {
var ui = &self.app.ui;
const frame_allocator = ui.frameAllocator();
const project = &self.app.project;
{
const label = ui.label("Project", .{});
label.borders.bottom = .{
.color = srcery.bright_white,
.size = 1
};
}
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
{ // Sample rate
var placeholder: ?[]const u8 = null;
if (project.getDefaultSampleRate()) |default_sample_rate| {
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ default_sample_rate });
}
_ = ui.label("Sample rate", .{});
self.parsed_sample_rate = try ui.numberInput(f64, .{
.key = ui.keyFromString("Sample rate input"),
.storage = &self.sample_rate_input,
.placeholder = placeholder,
.initial = project.sample_rate,
.invalid = self.parsed_sample_rate != project.sample_rate,
.editable = !self.app.isCollectionInProgress()
});
project.sample_rate = self.parsed_sample_rate;
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
if (project.sample_rate) |selected_sample_rate| {
if (!allowed_sample_rates.hasInclusive(selected_sample_rate)) {
project.sample_rate = null;
}
}
}
}
_ = ui.checkbox(.{
.value = &project.show_rulers,
.label = "Ruler"
});
if (ui.signal(ui.textButton("Open notes")).clicked()) {
self.modal = .notes;
}
}
fn showViewSettings(self: *MainScreen, view_id: Id) !void {
var ui = &self.app.ui;
const project = &self.app.project;
const sample_rate = project.getSampleRate();
const view = project.views.get(view_id) orelse return;
{
const label = ui.label("Settings", .{});
label.borders.bottom = .{
.color = srcery.bright_white,
.size = 1
};
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
}
_ = ui.checkbox(.{
.value = &view.sync_controls,
.label = "Sync controls"
});
switch (view.reference) {
.channel => |channel_id| {
const channel = project.channels.get(channel_id).?;
const channel_name = utils.getBoundedStringZ(&channel.name);
const channel_type = NIDaq.getChannelType(channel_name);
_ = ui.label("Channel: {s}", .{ channel_name });
if (channel_type != null) {
_ = ui.label("Type: {s}", .{ channel_type.?.name() });
} else {
_ = ui.label("Type: unknown", .{ });
}
if (ui.fileInput(.{
.key = ui.keyFromString("Save location"),
.allocator = self.app.allocator,
.file_picker = &self.channel_save_file_picker,
.path = channel.saved_collected_samples,
.open_dialog = false
})) |path| {
if (channel.saved_collected_samples) |current_path| {
self.app.allocator.free(current_path);
}
channel.saved_collected_samples = path;
}
},
.file => |file_id| {
const file = project.files.get(file_id).?;
if (ui.fileInput(.{
.key = ui.keyFromString("Filename"),
.allocator = self.app.allocator,
.file_picker = &self.file_save_file_picker,
.path = file.path
})) |path| {
self.app.allocator.free(file.path);
file.path = path;
self.app.pushCommand(.{ .reload_file = file_id });
}
}
}
const sample_list_id = project.getViewSampleListId(view_id);
const sample_list = project.sample_lists.get(sample_list_id).?;
const sample_count = sample_list.getLength();
_ = ui.label("Samples: {d}", .{ sample_count });
var duration_str: ?[]const u8 = null;
if (sample_rate != null) {
const duration = @as(f64, @floatFromInt(sample_count)) / sample_rate.?;
if (utils.formatDuration(ui.frameAllocator(), duration)) |str| {
duration_str = str;
} else |_| {}
}
if (duration_str == null) {
duration_str = std.fmt.allocPrint(ui.frameAllocator(), "{d}", .{ sample_count }) catch null;
}
_ = ui.label("Duration: {s}", .{ duration_str orelse "-" });
var deferred_remove: std.BoundedArray(usize, App.View.max_transforms) = .{};
for (0.., view.transforms.slice()) |i, *_transform| {
const transform: *App.Transform = _transform;
const row = ui.createBox(.{
.key = UI.Key.initPtr(transform),
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixedPixels(ui.rem(1.5)),
.layout_direction = .left_to_right
});
row.beginChildren();
defer row.endChildren();
if (ui.signal(ui.textButton("Remove")).clicked()) {
deferred_remove.appendAssumeCapacity(i);
}
{
const options = .{
.{ .multiply, "Multiply" },
.{ .addition, "Addition" },
.{ .sliding_window, "Sliding window" },
};
const select = ui.button(ui.keyFromString("Transform select"));
select.setFmtText("{s}", .{switch (transform.*) {
.sliding_window => "Sliding window",
.addition => "Addition",
.multiply => "Multiply"
}});
select.size.y = UI.Sizing.initGrowFull();
select.alignment.y = .center;
if (ui.signal(select).clicked()) {
select.persistent.open = !select.persistent.open;
}
if (select.persistent.open) {
const popup = ui.createBox(.{
.key = ui.keyFromString("Select popup"),
.size_x = UI.Sizing.initFixedPixels(ui.rem(10)),
.size_y = UI.Sizing.initFitChildren(),
.flags = &.{ .clickable, .scrollable },
.layout_direction = .top_to_bottom,
.float_relative_to = select,
.background = srcery.black,
.borders = UI.Borders.all(.{ .color = srcery.bright_black, .size = 4 }),
.draw_on_top = true
});
popup.setFloatPosition(0, select.persistent.size.y);
popup.beginChildren();
defer popup.endChildren();
inline for (options) |option| {
const select_option = ui.textButton(option[1]);
select_option.alignment.x = .start;
select_option.size.x = UI.Sizing.initGrowFull();
select_option.borders = UI.Borders.all(.{ .color = srcery.hard_black, .size = 2 });
select_option.background = srcery.black;
const signal = ui.signal(select_option);
if (signal.clicked()) {
select.persistent.open = false;
transform.* = switch (option[0]) {
.sliding_window => App.Transform{ .sliding_window = sample_rate orelse 0 },
.addition => App.Transform{ .addition = 0 },
.multiply => App.Transform{ .multiply = 1 },
else => unreachable
};
}
}
_ = ui.signal(popup);
}
}
var input_opts = UI.NumberInputOptions{
.key = ui.keyFromString("Sliding window"),
.storage = &self.transform_inputs[i],
.width = ui.rem(4)
// .postfix = if (sample_rate != null) " s" else null,
// .display_scalar = sample_rate,
};
_ = ui.createBox(.{ .size_x = UI.Sizing.initGrowFull() });
if (transform.* == .sliding_window and sample_rate != null) {
input_opts.postfix = " s";
input_opts.display_scalar = sample_rate;
}
const current_value = switch (transform.*) {
.sliding_window => |*v| v,
.addition => |*v| v,
.multiply => |*v| v
};
input_opts.initial = current_value.*;
const new_value = try ui.numberInput(f64, input_opts);
if (new_value != null) {
current_value.* = new_value.?;
}
}
for (0..deferred_remove.len) |i| {
const transform_index = deferred_remove.get(deferred_remove.len - 1 - i);
_ = view.transforms.orderedRemove(transform_index);
}
if (view.transforms.unusedCapacitySlice().len > 0) {
const btn = ui.textButton("Add transform");
if (ui.signal(btn).clicked()) {
view.transforms.appendAssumeCapacity(.{ .addition = 0 });
}
}
}
fn showMarkedRange(self: *MainScreen, view_id: Id, index: usize) void {
var ui = &self.app.ui;
const view = self.app.getView(view_id) orelse return;
const marked_range = view.marked_ranges.get(index);
{
const label = ui.label("Selected range", .{});
label.borders.bottom = .{
.color = srcery.blue,
.size = 1
};
_ = ui.createBox(.{ .size_y = UI.Sizing.initFixedPixels(ui.rem(1)) });
}
const sample_rate = self.app.project.getSampleRate();
if (marked_range.axis == .X and sample_rate != null) {
_ = ui.label("From: {d:.3}s", .{ marked_range.range.lower / sample_rate.? });
_ = ui.label("To: {d:.3}s", .{ marked_range.range.upper / sample_rate.? });
_ = ui.label("Size: {d:.3}s", .{ marked_range.range.size() / sample_rate.? });
} else {
_ = ui.label("From: {d:.2}", .{ marked_range.range.lower });
_ = ui.label("To: {d:.2}", .{ marked_range.range.upper });
_ = ui.label("Size: {d:.2}", .{ marked_range.range.size() });
}
_ = ui.label("Samples: {d:.2}", .{ marked_range.range.size() });
if (marked_range.axis == .X) {
if (marked_range.min) |min| {
_ = ui.label("Minimum: {d:.3}", .{ min });
} else{
_ = ui.label("Minimum: <unknown>", .{});
}
if (marked_range.max) |max| {
_ = ui.label("Maximum: {d:.3}", .{ max });
} else{
_ = ui.label("Maximum: <unknown>", .{});
}
if (marked_range.average) |average| {
_ = ui.label("Average: {d:.3}", .{ average });
} else{
_ = ui.label("Average: <unknown>", .{});
}
if (marked_range.standard_deviation) |standard_deviation| {
_ = ui.label("Standard deviation: {d:.3}", .{ standard_deviation });
} else{
_ = ui.label("Standard deviation: <unknown>", .{});
}
}
_ = ui.createBox(.{ .size_y = UI.Sizing.initGrowFull() });
{
const btn = ui.textButton("Remove");
btn.borders = UI.Borders.all(.{ .color = srcery.red, .size = 4 });
const signal = ui.signal(btn);
if (signal.clicked() or ui.isKeyboardPressed(.key_backspace)) {
self.view_controls.show_marked_range = null;
_ = view.marked_ranges.swapRemove(index);
}
}
}
fn showToolbar(self: *MainScreen) void {
var ui = &self.app.ui;
const toolbar = ui.createBox(.{
.background = srcery.black,
.layout_direction = .left_to_right,
.size_x = .{ .fixed = .{ .parent_percent = 1 } },
.size_y = .{ .fixed = .{ .font_size = 2 } },
.borders = .{
.bottom = .{ .color = srcery.hard_black, .size = 4 }
}
});
toolbar.beginChildren();
defer toolbar.endChildren();
{
var btn = ui.textButton("Start/Stop button");
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.red });
btn.background = srcery.black;
btn.size.y = UI.Sizing.initFixed(.{ .parent_percent = 1 });
btn.padding.top = 0;
btn.padding.bottom = 0;
if (ui.signal(btn).clicked()) {
if (self.app.isCollectionInProgress()) {
self.app.pushCommand(.stop_collection);
} else {
self.app.pushCommand(.start_collection);
}
}
if (self.app.isCollectionInProgress()) {
btn.setText("Stop");
} else {
btn.setText("Start");
}
}
{
var btn = ui.textButton("Save");
btn.borders = UI.Borders.all(.{ .size = 4, .color = srcery.green });
if (ui.signal(btn).clicked()) {
self.app.pushCommand(.save_project);
}
}
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(1)) });
{
var btn = ui.textButton("Move");
if (self.view_controls.selected_tool == .move) {
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
}
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_one)) {
self.view_controls.selected_tool = .move;
}
}
{
var btn = ui.textButton("Select");
if (self.view_controls.selected_tool == .select) {
btn.borders = UI.Borders.bottom(.{ .size = 4, .color = srcery.green });
}
if (ui.signal(btn).clicked() or ui.isKeyboardPressed(.key_two)) {
self.view_controls.selected_tool = .select;
}
}
}
pub fn showSidePanel(self: *MainScreen) !void {
var ui = &self.app.ui;
const container = ui.createBox(.{
.size_x = UI.Sizing.initFitChildren(),
.size_y = UI.Sizing.initGrowFull(),
.borders = .{
.right = .{ .color = srcery.hard_black, .size = 4 }
},
.layout_direction = .top_to_bottom,
.padding = UI.Padding.all(ui.rem(1)),
.layout_gap = ui.rem(0.2)
});
container.beginChildren();
defer container.endChildren();
_ = ui.createBox(.{ .size_x = UI.Sizing.initFixedPixels(ui.rem(12)) });
if (self.view_controls.show_marked_range) |show_marked_range| {
self.showMarkedRange(show_marked_range.view_id, show_marked_range.index);
} else if (self.view_controls.view_settings) |view_id| {
try self.showViewSettings(view_id);
} else {
try self.showProjectSettings();
}
}
pub fn tick(self: *MainScreen) !void {
var ui = &self.app.ui;
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
self.view_controls.undoLastMove();
}
const root = ui.parentBox().?;
root.layout_direction = .top_to_bottom;
if (self.view_controls.view_protocol_modal) |view_protocol_modal| {
if (self.modal == null) {
self.modal = .{ .view_protocol = view_protocol_modal };
}
self.view_controls.view_protocol_modal = null;
}
const was_modal_open = self.modal != null;
var maybe_modal_overlay: ?*UI.Box = null;
if (self.modal != null) {
const padding = UI.Padding.all(ui.rem(2));
const modal_overlay = ui.createBox(.{
.key = ui.keyFromString("Overlay"),
.float_rect = .{
.x = 0,
.y = 0,
// TODO: This is a hack, UI core should handle this
.width = root.persistent.size.x - padding.byAxis(.X),
.height = root.persistent.size.y - padding.byAxis(.Y)
},
.background = rl.Color.black.alpha(0.6),
.flags = &.{ .clickable, .scrollable },
.padding = padding,
.align_x = .center,
.align_y = .center,
});
modal_overlay.beginChildren();
defer modal_overlay.endChildren();
switch (self.modal.?) {
.view_protocol => |view_id| try self.showProtocolModal(view_id),
.notes => try self.showNotesModal()
}
if (ui.signal(modal_overlay).clicked()) {
self.modal = null;
}
maybe_modal_overlay = modal_overlay;
}
self.showToolbar();
const ui_view_ctx = UIView.Context{
.app = self.app,
.ui = &self.app.ui,
.view_controls = &self.view_controls
};
if (self.view_controls.view_fullscreen) |view_id| {
_ = try UIView.show(ui_view_ctx, view_id, UI.Sizing.initGrowFull());
} else {
const container = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initGrowFull(),
.layout_direction = .left_to_right
});
container.beginChildren();
defer container.endChildren();
try self.showSidePanel();
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
defer ui.endScrollbar();
scroll_area.layout_direction = .top_to_bottom;
var view_iter = self.app.project.views.idIterator();
while (view_iter.next()) |view_id| {
const view = self.app.getView(view_id);
_ = try UIView.show(ui_view_ctx, view_id, UI.Sizing.initFixed(.{ .pixels = view.?.height }));
}
{
const add_channel_view = ui.createBox(.{
.size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initFixed(.{ .pixels = 200 }),
.align_x = .center,
.align_y = .center,
.layout_gap = 32
});
add_channel_view.beginChildren();
defer add_channel_view.endChildren();
const add_from_file = ui.textButton("Add from file");
add_from_file.borders = UI.Borders.all(.{ .size = 2, .color = srcery.green });
if (ui.signal(add_from_file).clicked()) {
self.app.pushCommand(.add_file_from_picker);
}
const add_from_device = ui.textButton("Add from device");
add_from_device.borders = UI.Borders.all(.{ .size = 2, .color = srcery.green });
if (ui.signal(add_from_device).clicked()) {
self.app.screen = .add_channels;
}
}
}
self.view_controls.applyCommands();
if (maybe_modal_overlay) |modal_overlay| {
root.bringChildToTop(modal_overlay);
}
if (ui.isKeyboardPressed(.key_escape)) {
if (self.modal != null) {
self.modal = null;
} else if (self.view_controls.view_fullscreen != null) {
self.view_controls.view_fullscreen = null;
} else if (self.view_controls.view_settings != null) {
self.view_controls.view_settings = null;
} else if (self.view_controls.show_marked_range != null) {
self.view_controls.show_marked_range = null;
} else {
self.app.should_close = true;
}
}
const is_modal_open = self.modal != null;
if (!was_modal_open and is_modal_open) {
self.protocol_graph_cache.clear();
}
}