316 lines
9.3 KiB
Zig
316 lines
9.3 KiB
Zig
const std = @import("std");
|
|
const App = @import("../../app.zig");
|
|
const UI = @import("../../ui.zig");
|
|
const RangeF64 = @import("../../range.zig").RangeF64;
|
|
const constants = @import("../../constants.zig");
|
|
|
|
const Id = App.Id;
|
|
const System = @This();
|
|
|
|
pub const ViewAxisPosition = struct {
|
|
view_id: Id,
|
|
axis: UI.Axis,
|
|
position: f64,
|
|
|
|
fn set(optional_self: *?ViewAxisPosition, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
|
if (optional_self.*) |self| {
|
|
if (self.axis != axis or !self.view_id.eql(view_id)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (position != null) {
|
|
optional_self.* = .{
|
|
.axis = axis,
|
|
.position = position.?,
|
|
.view_id = view_id
|
|
};
|
|
} else {
|
|
optional_self.* = null;
|
|
}
|
|
}
|
|
|
|
fn get(optional_self: *?ViewAxisPosition, project: *App.Project, view_id: Id, axis: UI.Axis) ?f64 {
|
|
const self = optional_self.* orelse return null;
|
|
|
|
if (self.axis != axis) return null;
|
|
|
|
const view = project.views.get(view_id) orelse return null;
|
|
if (view.sync_controls) {
|
|
const owner_view = project.views.get(self.view_id).?;
|
|
if (!owner_view.sync_controls) {
|
|
return null;
|
|
}
|
|
} else {
|
|
if (!self.view_id.eql(view_id)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return self.position;
|
|
}
|
|
};
|
|
|
|
pub const Command = union(enum) {
|
|
breakpoint,
|
|
move_and_zoom: struct {
|
|
view_id: Id,
|
|
before_x: RangeF64,
|
|
before_y: RangeF64,
|
|
x: RangeF64,
|
|
y: RangeF64
|
|
},
|
|
|
|
fn apply(self: *const Command, system: *System) void {
|
|
const project = system.project;
|
|
|
|
switch (self.*) {
|
|
.breakpoint => {},
|
|
.move_and_zoom => |move_and_zoom| {
|
|
const view = project.views.get(move_and_zoom.view_id) orelse return;
|
|
const view_rect = &view.graph_opts;
|
|
view_rect.x_range = move_and_zoom.x;
|
|
view_rect.y_range = move_and_zoom.y;
|
|
view.follow = false; // TODO: Undo follow
|
|
}
|
|
}
|
|
}
|
|
|
|
fn undo(self: *const Command, system: *System) void {
|
|
const project = system.project;
|
|
|
|
switch (self.*) {
|
|
.breakpoint => {},
|
|
.move_and_zoom => |move_and_zoom| {
|
|
const view = project.views.get(move_and_zoom.view_id) orelse return;
|
|
const view_rect = &view.graph_opts;
|
|
view_rect.x_range = move_and_zoom.before_x;
|
|
view_rect.y_range = move_and_zoom.before_y;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const CommandFrame = struct {
|
|
updated_at_ns: i128,
|
|
commands: std.BoundedArray(Command, constants.max_views) = .{},
|
|
|
|
fn findCommandByView(self: *CommandFrame, view_id: Id) ?*Command {
|
|
const commands: []Command = self.commands.slice();
|
|
for (commands) |*command| {
|
|
if (command.* == .move_and_zoom and command.move_and_zoom.view_id.eql(view_id)) {
|
|
return command;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn apply(self: *const CommandFrame, system: *System) void {
|
|
const commands: []const Command = self.commands.constSlice();
|
|
for (commands) |*command| {
|
|
command.apply(system);
|
|
}
|
|
}
|
|
|
|
fn undo(self: *const CommandFrame, system: *System) void {
|
|
const commands: []const Command = self.commands.constSlice();
|
|
for (0..commands.len) |i| {
|
|
const command = commands[commands.len - 1 - i];
|
|
command.undo(system);
|
|
}
|
|
}
|
|
|
|
fn hasBreakpoint(self: *const CommandFrame) bool {
|
|
const commands: []const Command = self.commands.constSlice();
|
|
for (commands) |*command| {
|
|
if (command.* == .breakpoint) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
pub const CommandFrameArray = std.BoundedArray(CommandFrame, 64);
|
|
|
|
project: *App.Project,
|
|
|
|
// TODO: Redo
|
|
undo_stack: CommandFrameArray = .{},
|
|
last_applied_command: usize = 0,
|
|
zoom_start: ?ViewAxisPosition = null,
|
|
cursor: ?ViewAxisPosition = null,
|
|
view_settings: ?Id = null, // View id
|
|
view_fullscreen: ?Id = null, // View id
|
|
view_protocol_modal: ?Id = null, // View id
|
|
show_ruler: bool = false,
|
|
|
|
pub fn init(project: *App.Project) System {
|
|
return System{
|
|
.project = project
|
|
};
|
|
}
|
|
|
|
fn pushCommandFrame(self: *System) *CommandFrame {
|
|
if (self.undo_stack.unusedCapacitySlice().len == 0) {
|
|
_ = self.undo_stack.orderedRemove(0);
|
|
}
|
|
|
|
var frame = self.undo_stack.addOneAssumeCapacity();
|
|
frame.updated_at_ns = std.time.nanoTimestamp();
|
|
frame.commands.len = 0;
|
|
return frame;
|
|
}
|
|
|
|
fn lastCommandFrame(self: *System) ?*CommandFrame {
|
|
if (self.undo_stack.len > 0) {
|
|
return &self.undo_stack.buffer[self.undo_stack.len - 1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn pushBreakpoint(self: *System) void {
|
|
if (self.lastCommandFrame()) |last_frame| {
|
|
// No need to have 2 break points in a row
|
|
if (last_frame.hasBreakpoint()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const frame = self.pushCommandFrame();
|
|
frame.commands.appendAssumeCapacity(.{ .breakpoint = {} });
|
|
}
|
|
|
|
pub fn pushViewMove(self: *System, view_id: Id, x_range: RangeF64, y_range: RangeF64) void {
|
|
var frame: *CommandFrame = undefined;
|
|
{
|
|
var push_new_command = true;
|
|
if (self.lastCommandFrame()) |last_frame| {
|
|
frame = last_frame;
|
|
|
|
const now_ns = std.time.nanoTimestamp();
|
|
if (now_ns - last_frame.updated_at_ns < std.time.ns_per_ms * 250) {
|
|
last_frame.updated_at_ns = now_ns;
|
|
push_new_command = false;
|
|
|
|
if (self.last_applied_command == self.undo_stack.len) {
|
|
self.last_applied_command -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (push_new_command) {
|
|
frame = self.pushCommandFrame();
|
|
}
|
|
}
|
|
|
|
var sync_controls = false;
|
|
if (self.project.views.get(view_id)) |view| {
|
|
sync_controls = view.sync_controls;
|
|
}
|
|
|
|
var view_ids: std.BoundedArray(Id, constants.max_views) = .{};
|
|
if (sync_controls) {
|
|
var iter = self.project.views.idIterator();
|
|
while (iter.next()) |id| {
|
|
if (self.project.views.get(id).?.sync_controls) {
|
|
view_ids.appendAssumeCapacity(id);
|
|
}
|
|
}
|
|
} else {
|
|
view_ids.appendAssumeCapacity(view_id);
|
|
}
|
|
|
|
for (view_ids.constSlice()) |id| {
|
|
var command: *Command = undefined;
|
|
if (frame.findCommandByView(id)) |prev_command| {
|
|
command = prev_command;
|
|
|
|
command.move_and_zoom.x = x_range;
|
|
command.move_and_zoom.y = y_range;
|
|
} else {
|
|
const view = self.project.views.get(view_id) orelse continue;
|
|
const view_rect = &view.graph_opts;
|
|
|
|
command = frame.commands.addOneAssumeCapacity();
|
|
|
|
command.* = Command{
|
|
.move_and_zoom = .{
|
|
.view_id = id,
|
|
.before_x = view_rect.x_range,
|
|
.before_y = view_rect.y_range,
|
|
.x = x_range,
|
|
.y = y_range
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pub fn pushViewMoveAxis(self: *System, view_id: Id, axis: UI.Axis, view_range: RangeF64) void {
|
|
const view = self.project.views.get(view_id) orelse return;
|
|
const view_rect = &view.graph_opts;
|
|
|
|
if (axis == .X) {
|
|
self.pushViewMove(view_id, view_range, view_rect.y_range);
|
|
} else {
|
|
self.pushViewMove(view_id, view_rect.x_range, view_range);
|
|
}
|
|
}
|
|
|
|
pub fn undoLastMove(self: *System) void {
|
|
while (self.undo_stack.popOrNull()) |frame| {
|
|
frame.undo(self);
|
|
|
|
self.last_applied_command = @min(self.last_applied_command, self.undo_stack.len);
|
|
|
|
if (!frame.hasBreakpoint()) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn applyCommands(self: *System) void {
|
|
while (self.last_applied_command < self.undo_stack.len) : (self.last_applied_command += 1) {
|
|
const frame = self.undo_stack.buffer[self.last_applied_command];
|
|
frame.apply(self);
|
|
}
|
|
}
|
|
|
|
pub fn toggleViewSettings(self: *System, view_id: Id) void {
|
|
if (self.isViewSettingsOpen(view_id)) {
|
|
self.view_settings = null;
|
|
} else {
|
|
self.view_settings = view_id;
|
|
}
|
|
}
|
|
|
|
pub fn isViewSettingsOpen(self: *System, view_id: Id) bool {
|
|
return self.view_settings != null and self.view_settings.?.eql(view_id);
|
|
}
|
|
|
|
pub fn toggleFullscreenView(self: *System, view_id: Id) void {
|
|
if (self.view_fullscreen == null or !self.view_fullscreen.?.eql(view_id)) {
|
|
self.view_fullscreen = view_id;
|
|
} else {
|
|
self.view_fullscreen = null;
|
|
}
|
|
}
|
|
|
|
pub fn setCursor(self: *System, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
|
return ViewAxisPosition.set(&self.cursor, view_id, axis, position);
|
|
}
|
|
|
|
pub fn getCursor(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
|
return ViewAxisPosition.get(&self.cursor, self.project, view_id, axis);
|
|
}
|
|
|
|
pub fn setZoomStart(self: *System, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
|
return ViewAxisPosition.set(&self.zoom_start, view_id, axis, position);
|
|
}
|
|
|
|
pub fn getZoomStart(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
|
return ViewAxisPosition.get(&self.zoom_start, self.project,view_id, axis);
|
|
} |