752 lines
24 KiB
Zig
752 lines
24 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.zig");
|
|
const RangeF64 = @import("../range.zig").RangeF64;
|
|
const Graph = @import("../graph.zig");
|
|
const Assets = @import("../assets.zig");
|
|
const utils = @import("../utils.zig");
|
|
|
|
const MainScreen = @This();
|
|
|
|
const log = std.log.scoped(.main_screen);
|
|
const ChannelView = App.ChannelView;
|
|
const assert = std.debug.assert;
|
|
const remap = utils.remap;
|
|
|
|
const zoom_speed = 0.1;
|
|
const ruler_size = UI.Sizing.initFixed(.{ .pixels = 32 });
|
|
|
|
const ChannelCommand = struct {
|
|
channel: *ChannelView,
|
|
updated_at_ns: i128,
|
|
action: union(enum) {
|
|
move_and_zoom: struct {
|
|
before_x: RangeF64,
|
|
before_y: RangeF64,
|
|
}
|
|
}
|
|
};
|
|
|
|
app: *App,
|
|
fullscreen_channel: ?*ChannelView = null,
|
|
|
|
axis_zoom: ?struct {
|
|
channel: *ChannelView,
|
|
axis: UI.Axis,
|
|
start: f64,
|
|
} = null,
|
|
|
|
// TODO: Redo
|
|
channel_undo_stack: std.BoundedArray(ChannelCommand, 100) = .{},
|
|
|
|
fn pushChannelMoveCommand(self: *MainScreen, channel_view: *ChannelView, x_range: RangeF64, y_range: RangeF64) void {
|
|
const now_ns = std.time.nanoTimestamp();
|
|
var undo_stack = &self.channel_undo_stack;
|
|
var push_new_command = true;
|
|
|
|
if (undo_stack.len > 0) {
|
|
const top_command = &undo_stack.buffer[undo_stack.len - 1];
|
|
if (now_ns - top_command.updated_at_ns < std.time.ns_per_ms * 250) {
|
|
top_command.updated_at_ns = now_ns;
|
|
push_new_command = false;
|
|
}
|
|
}
|
|
|
|
var view_rect = &channel_view.view_rect;
|
|
|
|
if (push_new_command) {
|
|
if (undo_stack.unusedCapacitySlice().len == 0) {
|
|
_ = undo_stack.orderedRemove(0);
|
|
}
|
|
|
|
undo_stack.appendAssumeCapacity(ChannelCommand{
|
|
.channel = channel_view,
|
|
.updated_at_ns = now_ns,
|
|
.action = .{
|
|
.move_and_zoom = .{
|
|
.before_x = view_rect.x_range,
|
|
.before_y = view_rect.y_range,
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
view_rect.x_range = x_range;
|
|
view_rect.y_range = y_range;
|
|
channel_view.follow = false;
|
|
}
|
|
|
|
fn pushChannelMoveCommandAxis(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis, view_range: RangeF64) void {
|
|
if (axis == .X) {
|
|
const view_rect = &channel_view.view_rect;
|
|
self.pushChannelMoveCommand(channel_view, view_range, view_rect.y_range);
|
|
} else {
|
|
const view_rect = &channel_view.view_rect;
|
|
self.pushChannelMoveCommand(channel_view, view_rect.x_range, view_range);
|
|
}
|
|
}
|
|
|
|
fn showChannelViewGraph(self: *MainScreen, channel_view: *ChannelView) *UI.Box {
|
|
var ui = &self.app.ui;
|
|
|
|
const samples = self.app.getChannelSamples(channel_view);
|
|
const view_rect: *Graph.ViewOptions = &channel_view.view_rect;
|
|
|
|
const graph_box = ui.createBox(.{
|
|
.key = ui.keyFromString("Graph"),
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
.background = srcery.black,
|
|
.flags = &.{ .clickable, .draggable, .scrollable },
|
|
.align_x = .center,
|
|
.align_y = .center,
|
|
});
|
|
graph_box.beginChildren();
|
|
defer graph_box.endChildren();
|
|
|
|
const graph_rect = graph_box.rect();
|
|
|
|
const signal = ui.signal(graph_box);
|
|
|
|
var sample_value_under_mouse: ?f64 = null;
|
|
var sample_index_under_mouse: ?f64 = null;
|
|
|
|
const mouse_x_range = RangeF64.init(0, graph_rect.width);
|
|
const mouse_y_range = RangeF64.init(0, graph_rect.height);
|
|
|
|
if (signal.hot) {
|
|
sample_index_under_mouse = mouse_x_range.remapTo(view_rect.x_range, signal.relative_mouse.x);
|
|
sample_value_under_mouse = mouse_y_range.remapTo(view_rect.y_range, signal.relative_mouse.y);
|
|
}
|
|
|
|
if (signal.dragged()) {
|
|
const x_offset = mouse_x_range.remapTo(RangeF64.init(0, view_rect.x_range.size()), signal.drag.x);
|
|
const y_offset = mouse_y_range.remapTo(RangeF64.init(0, view_rect.y_range.size()), signal.drag.y);
|
|
|
|
self.pushChannelMoveCommand(
|
|
channel_view,
|
|
view_rect.x_range.sub(x_offset),
|
|
view_rect.y_range.add(y_offset)
|
|
);
|
|
}
|
|
|
|
if (signal.scrolled() and sample_index_under_mouse != null and sample_value_under_mouse != null) {
|
|
var scale_factor: f64 = 1;
|
|
if (signal.scroll.y > 0) {
|
|
scale_factor -= zoom_speed;
|
|
} else {
|
|
scale_factor += zoom_speed;
|
|
}
|
|
|
|
self.pushChannelMoveCommand(
|
|
channel_view,
|
|
view_rect.x_range.zoom(sample_index_under_mouse.?, scale_factor),
|
|
view_rect.y_range.zoom(sample_value_under_mouse.?, scale_factor)
|
|
);
|
|
}
|
|
|
|
if (signal.flags.contains(.middle_clicked)) {
|
|
self.pushChannelMoveCommand(channel_view, channel_view.x_range, channel_view.y_range);
|
|
}
|
|
|
|
Graph.drawCached(&channel_view.view_cache, graph_box.persistent.size, view_rect.*, samples);
|
|
if (channel_view.view_cache.texture) |texture| {
|
|
graph_box.texture = texture.texture;
|
|
}
|
|
|
|
if (view_rect.x_range.size() == 0 or view_rect.y_range.size() == 0) {
|
|
graph_box.setText("<Empty>");
|
|
graph_box.text_color = srcery.hard_black;
|
|
graph_box.font = .{
|
|
.variant = .bold_italic,
|
|
.size = ui.rem(3)
|
|
};
|
|
}
|
|
|
|
return graph_box;
|
|
}
|
|
|
|
fn getLineOnRuler(
|
|
channel_view: *ChannelView,
|
|
ruler: *UI.Box,
|
|
axis: UI.Axis,
|
|
|
|
along_axis_pos: f64,
|
|
cross_axis_pos: f64,
|
|
cross_axis_size: f64
|
|
) rl.Rectangle {
|
|
|
|
const view_rect = channel_view.view_rect;
|
|
|
|
const along_axis_size = switch (axis) {
|
|
.X => view_rect.x_range.size()/ruler.persistent.size.x,
|
|
.Y => view_rect.y_range.size()/ruler.persistent.size.y,
|
|
};
|
|
|
|
return getRectOnRuler(
|
|
channel_view,
|
|
ruler,
|
|
axis,
|
|
|
|
along_axis_pos,
|
|
along_axis_size,
|
|
cross_axis_pos,
|
|
cross_axis_size
|
|
);
|
|
}
|
|
|
|
fn getRectOnRuler(
|
|
channel_view: *ChannelView,
|
|
ruler: *UI.Box,
|
|
axis: UI.Axis,
|
|
|
|
along_axis_pos: f64,
|
|
along_axis_size: f64,
|
|
cross_axis_pos: f64,
|
|
cross_axis_size: f64
|
|
) rl.Rectangle {
|
|
assert(0 <= cross_axis_size and cross_axis_size <= 1);
|
|
|
|
const rect = ruler.rect();
|
|
const rect_height: f64 = @floatCast(rect.height);
|
|
const rect_width: f64 = @floatCast(rect.width);
|
|
const view_range = channel_view.getViewRange(axis);
|
|
|
|
if (axis == .X) {
|
|
const width_range = RangeF64.init(0, rect.width);
|
|
var result = rl.Rectangle{
|
|
.width = @floatCast(along_axis_size / view_range.size() * rect_width),
|
|
.height = @floatCast(rect_height * cross_axis_size),
|
|
.x = @floatCast(view_range.remapTo(width_range, along_axis_pos)),
|
|
.y = @floatCast(rect_height * cross_axis_pos),
|
|
};
|
|
|
|
if (result.width < 0) {
|
|
result.x += result.width;
|
|
result.width *= -1;
|
|
}
|
|
|
|
return result;
|
|
} else {
|
|
const height_range = RangeF64.init(0, rect.height);
|
|
var result = rl.Rectangle{
|
|
.width = @floatCast(rect_width * cross_axis_size),
|
|
.height = @floatCast(along_axis_size / view_range.size() * rect_height),
|
|
.x = @floatCast(rect_width * (1 - cross_axis_pos - cross_axis_size)),
|
|
.y = @floatCast(view_range.remapTo(height_range, along_axis_pos + along_axis_size)),
|
|
};
|
|
|
|
if (result.height < 0) {
|
|
result.y += result.height;
|
|
result.height *= -1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
fn showRulerTicksRange(
|
|
self: *MainScreen,
|
|
channel_view: *ChannelView,
|
|
ruler: *UI.Box,
|
|
axis: UI.Axis,
|
|
|
|
from: f64,
|
|
to: f64,
|
|
step: f64,
|
|
|
|
marker_size: f64
|
|
) void {
|
|
var marker = from;
|
|
while (marker < to) : (marker += step) {
|
|
_ = self.app.ui.createBox(.{
|
|
.background = srcery.yellow,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, marker, 0, marker_size),
|
|
.float_relative_to = ruler
|
|
});
|
|
}
|
|
}
|
|
|
|
fn showRulerTicks(self: *MainScreen, channel_view: *ChannelView, axis: UI.Axis) void {
|
|
const view_range = channel_view.getViewRange(axis);
|
|
const full_range = channel_view.getSampleRange(axis);
|
|
|
|
var ui = &self.app.ui;
|
|
const ruler = ui.parentBox().?;
|
|
|
|
const ruler_rect = ruler.rect();
|
|
const ruler_rect_size_along_axis = switch (axis) {
|
|
.X => ruler_rect.width,
|
|
.Y => ruler_rect.height
|
|
};
|
|
|
|
if (ruler_rect_size_along_axis == 0) {
|
|
return;
|
|
}
|
|
|
|
if (view_range.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
if (full_range.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
const ideal_pixels_per_division = 150;
|
|
var subdivisions: f32 = 20;
|
|
subdivisions = 20;
|
|
while (true) {
|
|
assert(subdivisions > 0);
|
|
const step = full_range.size() / subdivisions;
|
|
const pixels_per_division = step / view_range.size() * ruler_rect_size_along_axis;
|
|
assert(pixels_per_division > 0);
|
|
|
|
if (pixels_per_division > ideal_pixels_per_division*2) {
|
|
subdivisions *= 2;
|
|
} else if (pixels_per_division < ideal_pixels_per_division/2) {
|
|
subdivisions /= 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const step = full_range.size() / subdivisions;
|
|
|
|
{
|
|
_ = self.app.ui.createBox(.{
|
|
.background = srcery.yellow,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, full_range.lower, 0, 0.75),
|
|
.float_relative_to = ruler
|
|
});
|
|
}
|
|
|
|
{
|
|
_ = self.app.ui.createBox(.{
|
|
.background = srcery.yellow,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, full_range.upper, 0, 0.75),
|
|
.float_relative_to = ruler
|
|
});
|
|
}
|
|
|
|
if (full_range.hasExclusive(0)) {
|
|
_ = ui.createBox(.{
|
|
.background = srcery.yellow,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, 0, 0, 0.75),
|
|
.float_relative_to = ruler
|
|
});
|
|
}
|
|
|
|
const ticks_range = view_range.grow(step).intersectPositive(full_range.*);
|
|
|
|
self.showRulerTicksRange(
|
|
channel_view,
|
|
ruler,
|
|
axis,
|
|
utils.roundNearestTowardZero(f64, ticks_range.lower, step) + step/2,
|
|
ticks_range.upper,
|
|
step,
|
|
0.5
|
|
);
|
|
|
|
self.showRulerTicksRange(
|
|
channel_view,
|
|
ruler,
|
|
axis,
|
|
utils.roundNearestTowardZero(f64, ticks_range.lower, step),
|
|
ticks_range.upper,
|
|
step,
|
|
0.25
|
|
);
|
|
}
|
|
|
|
fn addRulerPlaceholder(self: *MainScreen, key: UI.Key, axis: UI.Axis) *UI.Box {
|
|
var ui = &self.app.ui;
|
|
|
|
var ruler = ui.createBox(.{
|
|
.key = key,
|
|
.background = srcery.hard_black,
|
|
.flags = &.{ .clip_view, .clickable, .scrollable },
|
|
.hot_cursor = .mouse_cursor_pointing_hand
|
|
});
|
|
if (axis == .X) {
|
|
ruler.size.x = UI.Sizing.initGrowFull();
|
|
ruler.size.y = ruler_size;
|
|
} else {
|
|
ruler.size.x = ruler_size;
|
|
ruler.size.y = UI.Sizing.initGrowFull();
|
|
}
|
|
|
|
return ruler;
|
|
}
|
|
|
|
fn showRuler(self: *MainScreen, ruler: *UI.Box, graph_box: *UI.Box, channel_view: *ChannelView, axis: UI.Axis) void {
|
|
var ui = &self.app.ui;
|
|
|
|
ruler.beginChildren();
|
|
defer ruler.endChildren();
|
|
|
|
self.showRulerTicks(channel_view, axis);
|
|
|
|
const signal = ui.signal(ruler);
|
|
const mouse_position = switch (axis) {
|
|
.X => signal.relative_mouse.x,
|
|
.Y => signal.relative_mouse.y
|
|
};
|
|
const mouse_range = switch (axis) {
|
|
.X => RangeF64.init(0, ruler.persistent.size.x),
|
|
.Y => RangeF64.init(0, ruler.persistent.size.y)
|
|
};
|
|
const view_range = channel_view.getViewRange(axis);
|
|
const mouse_position_on_graph = mouse_range.remapTo(view_range.*, mouse_position);
|
|
|
|
var zoom_start: ?f64 = null;
|
|
var zoom_end: ?f64 = null;
|
|
|
|
var is_zooming: bool = false;
|
|
if (self.axis_zoom) |axis_zoom| {
|
|
is_zooming = axis_zoom.channel == channel_view and axis_zoom.axis == axis;
|
|
}
|
|
|
|
if (signal.hot and view_range.size() > 0) {
|
|
const mouse_tooltip = ui.mouseTooltip();
|
|
mouse_tooltip.beginChildren();
|
|
defer mouse_tooltip.endChildren();
|
|
|
|
if (channel_view.getSampleRange(axis).hasInclusive(mouse_position_on_graph)) {
|
|
if (axis == .Y and channel_view.unit != null) {
|
|
const unit_name = channel_view.unit.?.name() orelse "Unknown";
|
|
_ = ui.label("{s}: {d:.3}", .{unit_name, mouse_position_on_graph});
|
|
} else if (axis == .X and channel_view.sample_rate != null) {
|
|
const sample_rate = channel_view.sample_rate.?;
|
|
_ = ui.label("{d:.3}s", .{mouse_position_on_graph / sample_rate});
|
|
} else {
|
|
_ = ui.label("{d:.3}", .{mouse_position_on_graph});
|
|
}
|
|
}
|
|
|
|
zoom_start = mouse_position_on_graph;
|
|
}
|
|
|
|
if (signal.flags.contains(.left_pressed)) {
|
|
self.axis_zoom = .{
|
|
.axis = axis,
|
|
.start = mouse_position_on_graph,
|
|
.channel = channel_view
|
|
};
|
|
}
|
|
|
|
if (is_zooming) {
|
|
zoom_start = self.axis_zoom.?.start;
|
|
zoom_end = mouse_position_on_graph;
|
|
}
|
|
|
|
if (zoom_start != null) {
|
|
_ = ui.createBox(.{
|
|
.background = srcery.green,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, zoom_start.?, 0, 1),
|
|
.float_relative_to = ruler,
|
|
});
|
|
|
|
_ = ui.createBox(.{
|
|
.background = srcery.green,
|
|
.float_rect = getLineOnRuler(channel_view, graph_box, axis, zoom_start.?, 0, 1),
|
|
.float_relative_to = graph_box,
|
|
.parent = graph_box
|
|
});
|
|
}
|
|
|
|
if (zoom_end != null) {
|
|
_ = ui.createBox(.{
|
|
.background = srcery.green,
|
|
.float_rect = getLineOnRuler(channel_view, ruler, axis, zoom_end.?, 0, 1),
|
|
.float_relative_to = ruler,
|
|
});
|
|
|
|
_ = ui.createBox(.{
|
|
.background = srcery.green,
|
|
.float_rect = getLineOnRuler(channel_view, graph_box, axis, zoom_end.?, 0, 1),
|
|
.float_relative_to = graph_box,
|
|
.parent = graph_box
|
|
});
|
|
}
|
|
|
|
if (zoom_start != null and zoom_end != null) {
|
|
_ = ui.createBox(.{
|
|
.background = srcery.green.alpha(0.5),
|
|
.float_relative_to = ruler,
|
|
.float_rect = getRectOnRuler(
|
|
channel_view,
|
|
ruler,
|
|
axis,
|
|
zoom_start.?,
|
|
zoom_end.? - zoom_start.?,
|
|
0,
|
|
1
|
|
)
|
|
});
|
|
}
|
|
|
|
if (signal.scrolled()) {
|
|
var scale_factor: f64 = 1;
|
|
if (signal.scroll.y > 0) {
|
|
scale_factor -= zoom_speed;
|
|
} else {
|
|
scale_factor += zoom_speed;
|
|
}
|
|
const new_view_range = view_range.zoom(mouse_position_on_graph, scale_factor);
|
|
self.pushChannelMoveCommandAxis(channel_view, axis, new_view_range);
|
|
}
|
|
|
|
if (is_zooming and signal.flags.contains(.left_released)) {
|
|
if (zoom_start != null and zoom_end != null) {
|
|
const zoom_start_mouse = view_range.remapTo(mouse_range, zoom_start.?);
|
|
const zoom_end_mouse = view_range.remapTo(mouse_range, zoom_end.?);
|
|
const mouse_move_distance = @abs(zoom_end_mouse - zoom_start_mouse);
|
|
if (mouse_move_distance > 5) {
|
|
var new_view_range = RangeF64.init(
|
|
@min(zoom_start.?, zoom_end.?),
|
|
@max(zoom_start.?, zoom_end.?)
|
|
);
|
|
|
|
if (axis == .Y) {
|
|
new_view_range = new_view_range.flip();
|
|
}
|
|
|
|
self.pushChannelMoveCommandAxis(channel_view, axis, new_view_range);
|
|
}
|
|
}
|
|
self.axis_zoom = null;
|
|
}
|
|
}
|
|
|
|
fn showChannelView(self: *MainScreen, channel_view: *ChannelView, height: UI.Sizing) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
const show_ruler = true;
|
|
|
|
const channel_view_box = ui.createBox(.{
|
|
.key = UI.Key.initPtr(channel_view),
|
|
.layout_direction = .top_to_bottom,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = height
|
|
});
|
|
channel_view_box.beginChildren();
|
|
defer channel_view_box.endChildren();
|
|
|
|
const toolbar = ui.createBox(.{
|
|
.layout_direction = .left_to_right,
|
|
.layout_gap = 16,
|
|
.background = srcery.hard_black,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initFixed(.{ .pixels = ui.rem(2) })
|
|
});
|
|
{
|
|
toolbar.beginChildren();
|
|
defer toolbar.endChildren();
|
|
|
|
if (self.app.getChannelSourceDevice(channel_view)) |device_channel| {
|
|
_ = device_channel;
|
|
|
|
const follow = ui.textButton("Follow");
|
|
follow.background = srcery.hard_black;
|
|
if (channel_view.follow) {
|
|
follow.borders = UI.Borders.bottom(.{
|
|
.color = srcery.green,
|
|
.size = 4
|
|
});
|
|
}
|
|
if (ui.signal(follow).clicked()) {
|
|
channel_view.follow = !channel_view.follow;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!show_ruler) {
|
|
_ = self.showChannelViewGraph(channel_view);
|
|
|
|
} else {
|
|
var graph_box: *UI.Box = undefined;
|
|
var x_ruler: *UI.Box = undefined;
|
|
var y_ruler: *UI.Box = undefined;
|
|
|
|
{
|
|
const container = ui.createBox(.{
|
|
.layout_direction = .left_to_right,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = UI.Sizing.initGrowFull(),
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
|
|
y_ruler = self.addRulerPlaceholder(ui.keyFromString("Y ruler"), .Y);
|
|
|
|
graph_box = self.showChannelViewGraph(channel_view);
|
|
}
|
|
|
|
{
|
|
const container = ui.createBox(.{
|
|
.layout_direction = .left_to_right,
|
|
.size_x = UI.Sizing.initGrowFull(),
|
|
.size_y = ruler_size,
|
|
});
|
|
container.beginChildren();
|
|
defer container.endChildren();
|
|
|
|
const fullscreen = ui.createBox(.{
|
|
.key = ui.keyFromString("Fullscreen toggle"),
|
|
.size_x = ruler_size,
|
|
.size_y = ruler_size,
|
|
.background = srcery.hard_black,
|
|
.hot_cursor = .mouse_cursor_pointing_hand,
|
|
.flags = &.{ .draw_hot, .draw_active, .clickable },
|
|
.texture = Assets.fullscreen,
|
|
.texture_size = .{ .x = 28, .y = 28 }
|
|
});
|
|
if (ui.signal(fullscreen).clicked()) {
|
|
if (self.fullscreen_channel != null and self.fullscreen_channel.? == channel_view) {
|
|
self.fullscreen_channel = null;
|
|
} else {
|
|
self.fullscreen_channel = channel_view;
|
|
}
|
|
}
|
|
|
|
x_ruler = self.addRulerPlaceholder(ui.keyFromString("X ruler"), .X);
|
|
}
|
|
|
|
self.showRuler(x_ruler, graph_box, channel_view, .X);
|
|
self.showRuler(y_ruler, graph_box, channel_view, .Y);
|
|
|
|
}
|
|
}
|
|
|
|
pub fn tick(self: *MainScreen) !void {
|
|
var ui = &self.app.ui;
|
|
|
|
if (ui.isKeyboardPressed(.key_escape)) {
|
|
if (self.fullscreen_channel != null) {
|
|
self.fullscreen_channel = null;
|
|
} else {
|
|
self.app.should_close = true;
|
|
}
|
|
}
|
|
|
|
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
|
|
if (self.channel_undo_stack.popOrNull()) |command| {
|
|
switch (command.action) {
|
|
.move_and_zoom => |args| {
|
|
const view_rect = &command.channel.view_rect;
|
|
view_rect.x_range = args.before_x;
|
|
view_rect.y_range = args.before_y;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const root = ui.parentBox().?;
|
|
root.layout_direction = .top_to_bottom;
|
|
|
|
{
|
|
const toolbar = ui.createBox(.{
|
|
.background = srcery.black,
|
|
.layout_direction = .left_to_right,
|
|
.size_x = .{ .fixed = .{ .parent_percent = 1 } },
|
|
.size_y = .{ .fixed = .{ .font_size = 2 } }
|
|
});
|
|
toolbar.beginChildren();
|
|
defer toolbar.endChildren();
|
|
|
|
var start_all = ui.textButton("Start/Stop button");
|
|
start_all.borders = UI.Borders.all(.{ .size = 4, .color = srcery.red });
|
|
start_all.background = srcery.black;
|
|
start_all.size.y = UI.Sizing.initFixed(.{ .parent_percent = 1 });
|
|
start_all.padding.top = 0;
|
|
start_all.padding.bottom = 0;
|
|
if (ui.signal(start_all).clicked()) {
|
|
self.app.started_collecting = !self.app.started_collecting;
|
|
|
|
for (self.app.listChannelViews()) |*channel_view| {
|
|
if (self.app.started_collecting) {
|
|
self.app.startDeviceChannelReading(channel_view);
|
|
} else {
|
|
self.app.stopDeviceChannelReading(channel_view);
|
|
}
|
|
}
|
|
}
|
|
if (self.app.started_collecting) {
|
|
start_all.setText("Stop");
|
|
} else {
|
|
start_all.setText("Start");
|
|
}
|
|
}
|
|
|
|
if (self.app.started_collecting) {
|
|
for (self.app.listChannelViews()) |*channel_view| {
|
|
const device_channel = self.app.getChannelSourceDevice(channel_view) orelse continue;
|
|
if (!channel_view.follow) continue;
|
|
|
|
const sample_rate = device_channel.active_task.?.sampling.sample_rate;
|
|
const sample_count: f32 = @floatFromInt(device_channel.samples.items.len);
|
|
|
|
channel_view.view_rect.y_range = channel_view.y_range;
|
|
|
|
channel_view.view_rect.x_range.lower = 0;
|
|
if (sample_count > channel_view.view_rect.x_range.upper) {
|
|
channel_view.view_rect.x_range.upper = sample_count + @as(f32, @floatCast(sample_rate)) * 10;
|
|
}
|
|
// channel_view.view_cache.invalidate();
|
|
}
|
|
}
|
|
|
|
if (self.fullscreen_channel) |channel| {
|
|
try self.showChannelView(channel, UI.Sizing.initGrowFull());
|
|
|
|
} else {
|
|
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
|
|
defer ui.endScrollbar();
|
|
scroll_area.layout_direction = .top_to_bottom;
|
|
scroll_area.layout_gap = 4;
|
|
|
|
for (self.app.listChannelViews()) |*channel_view| {
|
|
try self.showChannelView(channel_view, UI.Sizing.initFixed(.{ .pixels = channel_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.channel_mutex.unlock();
|
|
defer self.app.channel_mutex.lock();
|
|
|
|
if (Platform.openFilePicker(self.app.allocator)) |filename| {
|
|
defer self.app.allocator.free(filename);
|
|
|
|
// TODO: Handle error
|
|
self.app.appendChannelFromFile(filename) catch @panic("Failed to append channel from file");
|
|
} else |err| {
|
|
// TODO: Show error message to user;
|
|
log.err("Failed to pick file: {}", .{ err });
|
|
}
|
|
}
|
|
|
|
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.current_screen = .channel_from_device;
|
|
}
|
|
}
|
|
}
|
|
} |