show statistics in marked ranges
This commit is contained in:
parent
12620714ed
commit
e5bc57d7b5
245
src/app.zig
245
src/app.zig
@ -159,6 +159,10 @@ fn GenerationalArray(Item: type) type {
|
|||||||
pub fn isEmpty(self: *Self) bool {
|
pub fn isEmpty(self: *Self) bool {
|
||||||
return self.used.eql(UsedBitSet.initFull());
|
return self.used.eql(UsedBitSet.initFull());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.used = UsedBitSet.initFull();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +184,7 @@ pub const Channel = struct {
|
|||||||
output_task: ?NIDaq.Task = null,
|
output_task: ?NIDaq.Task = null,
|
||||||
graph_min_max_cache: Graph.MinMaxCache = .{},
|
graph_min_max_cache: Graph.MinMaxCache = .{},
|
||||||
last_sample_save_at: ?i128 = null,
|
last_sample_save_at: ?i128 = null,
|
||||||
|
processed_samples_up_to: usize = 0,
|
||||||
|
|
||||||
pub fn deinit(self: *Channel, allocator: Allocator) void {
|
pub fn deinit(self: *Channel, allocator: Allocator) void {
|
||||||
self.clear(allocator);
|
self.clear(allocator);
|
||||||
@ -242,6 +247,10 @@ pub const Channel = struct {
|
|||||||
pub fn invalidateSavedSamples(self: *Channel) void {
|
pub fn invalidateSavedSamples(self: *Channel) void {
|
||||||
self.last_sample_save_at = null;
|
self.last_sample_save_at = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hasNewCollectedSamples(self: *Channel) bool {
|
||||||
|
return self.processed_samples_up_to != self.collected_samples.items.len;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const File = struct {
|
pub const File = struct {
|
||||||
@ -271,6 +280,86 @@ pub const File = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const View = struct {
|
pub const View = struct {
|
||||||
|
pub const MarkedRange = struct {
|
||||||
|
// Persistent
|
||||||
|
axis: UI.Axis,
|
||||||
|
range: RangeF64,
|
||||||
|
|
||||||
|
// Runtime
|
||||||
|
min: ?f64 = null,
|
||||||
|
max: ?f64 = null,
|
||||||
|
average: ?f64 = null,
|
||||||
|
standard_deviation: ?f64 = null,
|
||||||
|
|
||||||
|
pub fn clear(self: *MarkedRange) void {
|
||||||
|
self.min = null;
|
||||||
|
self.max = null;
|
||||||
|
self.average = null;
|
||||||
|
self.standard_deviation = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(self: *MarkedRange, samples: []const f64) void {
|
||||||
|
self.clear();
|
||||||
|
|
||||||
|
if (self.axis == .X) {
|
||||||
|
const from = std.math.clamp(@as(usize, @intFromFloat(@floor(self.range.lower))), 0, samples.len);
|
||||||
|
const to = std.math.clamp(@as(usize, @intFromFloat(@ceil(self.range.upper))), 0, samples.len);
|
||||||
|
if (to - from == 0) return;
|
||||||
|
|
||||||
|
const samples_in_range = samples[from..to];
|
||||||
|
if (samples_in_range.len > 0) {
|
||||||
|
const sample_count: f64 = @floatFromInt(samples_in_range.len);
|
||||||
|
|
||||||
|
var sum: f64 = 0;
|
||||||
|
var min = samples_in_range[0];
|
||||||
|
var max = samples_in_range[0];
|
||||||
|
for (samples_in_range) |sample| {
|
||||||
|
min = @min(min, sample);
|
||||||
|
max = @max(max, sample);
|
||||||
|
sum += sample;
|
||||||
|
}
|
||||||
|
const average = sum / sample_count;
|
||||||
|
|
||||||
|
self.min = min;
|
||||||
|
self.max = max;
|
||||||
|
self.average = average;
|
||||||
|
|
||||||
|
if (sample_count > 1) {
|
||||||
|
var standard_deviation: f64 = 0;
|
||||||
|
for (samples_in_range) |sample| {
|
||||||
|
standard_deviation += (sample - average) * (sample - average);
|
||||||
|
}
|
||||||
|
standard_deviation /= (sample_count - 1);
|
||||||
|
standard_deviation = @sqrt(standard_deviation);
|
||||||
|
|
||||||
|
self.standard_deviation = standard_deviation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MarkedRangeIterator = struct {
|
||||||
|
view: *View,
|
||||||
|
axis: UI.Axis,
|
||||||
|
index: usize = 0,
|
||||||
|
next_index: usize = 0,
|
||||||
|
|
||||||
|
pub fn next(self: *MarkedRangeIterator) ?RangeF64 {
|
||||||
|
const marked_ranges = self.view.marked_ranges.constSlice();
|
||||||
|
while (self.next_index < marked_ranges.len) {
|
||||||
|
self.index = self.next_index;
|
||||||
|
const marked_range = marked_ranges[self.index];
|
||||||
|
self.next_index += 1;
|
||||||
|
|
||||||
|
if (marked_range.axis == self.axis) {
|
||||||
|
return marked_range.range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Reference = union(enum) {
|
pub const Reference = union(enum) {
|
||||||
file: Id,
|
file: Id,
|
||||||
channel: Id
|
channel: Id
|
||||||
@ -283,6 +372,7 @@ pub const View = struct {
|
|||||||
follow: bool = false,
|
follow: bool = false,
|
||||||
graph_opts: Graph.ViewOptions = .{},
|
graph_opts: Graph.ViewOptions = .{},
|
||||||
sync_controls: bool = false,
|
sync_controls: bool = false,
|
||||||
|
marked_ranges: std.BoundedArray(MarkedRange, 32) = .{},
|
||||||
|
|
||||||
// Runtime
|
// Runtime
|
||||||
graph_cache: Graph.RenderCache = .{},
|
graph_cache: Graph.RenderCache = .{},
|
||||||
@ -309,11 +399,31 @@ pub const View = struct {
|
|||||||
.Y => self.available_y_range
|
.Y => self.available_y_range
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn appendMarkedRange(self: *View, axis: UI.Axis, range: RangeF64) ?*MarkedRange {
|
||||||
|
if (self.marked_ranges.unusedCapacitySlice().len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const marked_range = self.marked_ranges.addOneAssumeCapacity();
|
||||||
|
marked_range.* = MarkedRange{
|
||||||
|
.axis = axis,
|
||||||
|
.range = range
|
||||||
|
};
|
||||||
|
return marked_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iterMarkedRanges(self: *View, axis: UI.Axis) MarkedRangeIterator {
|
||||||
|
return MarkedRangeIterator{
|
||||||
|
.view = self,
|
||||||
|
.axis = axis
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Project = struct {
|
pub const Project = struct {
|
||||||
const file_endian = std.builtin.Endian.big;
|
|
||||||
const file_format_version: u8 = 0;
|
const file_format_version: u8 = 0;
|
||||||
|
const file_endian = std.builtin.Endian.big;
|
||||||
|
|
||||||
save_location: ?[]u8 = null,
|
save_location: ?[]u8 = null,
|
||||||
sample_rate: ?f64 = null,
|
sample_rate: ?f64 = null,
|
||||||
@ -357,16 +467,39 @@ pub const Project = struct {
|
|||||||
return result_range;
|
return result_range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getViewSamples(self: *Project, view_id: Id) []const f64 {
|
||||||
|
const empty = &[0]f64{};
|
||||||
|
|
||||||
|
var result: []const f64 = empty;
|
||||||
|
|
||||||
|
if (self.views.get(view_id)) |view| {
|
||||||
|
switch (view.reference) {
|
||||||
|
.channel => |channel_id| if (self.channels.get(channel_id)) |channel| {
|
||||||
|
result = channel.collected_samples.items;
|
||||||
|
},
|
||||||
|
.file => |file_id| if (self.files.get(file_id)) |file| {
|
||||||
|
result = file.samples orelse empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Project, allocator: Allocator) void {
|
pub fn deinit(self: *Project, allocator: Allocator) void {
|
||||||
var file_iter = self.files.iterator();
|
var file_iter = self.files.iterator();
|
||||||
while (file_iter.next()) |file| {
|
while (file_iter.next()) |file| {
|
||||||
file.deinit(allocator);
|
file.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
self.files.clear();
|
||||||
|
|
||||||
var channel_iter = self.channels.iterator();
|
var channel_iter = self.channels.iterator();
|
||||||
while (channel_iter.next()) |channel| {
|
while (channel_iter.next()) |channel| {
|
||||||
channel.deinit(allocator);
|
channel.deinit(allocator);
|
||||||
}
|
}
|
||||||
|
self.channels.clear();
|
||||||
|
|
||||||
|
self.views.clear();
|
||||||
|
|
||||||
if (self.save_location) |str| {
|
if (self.save_location) |str| {
|
||||||
allocator.free(str);
|
allocator.free(str);
|
||||||
@ -376,8 +509,9 @@ pub const Project = struct {
|
|||||||
|
|
||||||
// ------------------- Serialization ------------------ //
|
// ------------------- Serialization ------------------ //
|
||||||
|
|
||||||
pub fn initFromFile(allocator: Allocator, save_location: []const u8) !Project {
|
pub fn initFromFile(self: *Project, allocator: Allocator, save_location: []const u8) !void {
|
||||||
var self = Project{};
|
self.* = .{};
|
||||||
|
errdefer self.deinit(allocator);
|
||||||
|
|
||||||
const f = try std.fs.cwd().openFile(save_location, .{});
|
const f = try std.fs.cwd().openFile(save_location, .{});
|
||||||
defer f.close();
|
defer f.close();
|
||||||
@ -469,13 +603,18 @@ pub const Project = struct {
|
|||||||
view.graph_opts.y_range = try readRangeF64(reader);
|
view.graph_opts.y_range = try readRangeF64(reader);
|
||||||
const sync_controls = try readInt(reader, u8);
|
const sync_controls = try readInt(reader, u8);
|
||||||
view.sync_controls = sync_controls == 1;
|
view.sync_controls = sync_controls == 1;
|
||||||
|
|
||||||
|
const marked_ranges_count = try readInt(reader, u32);
|
||||||
|
for (0..marked_ranges_count) |_| {
|
||||||
|
try view.marked_ranges.append(View.MarkedRange{
|
||||||
|
.axis = try readEnum(reader, u8, UI.Axis),
|
||||||
|
.range = try readRangeF64(reader)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.save_location = try allocator.dupe(u8, save_location);
|
self.save_location = try allocator.dupe(u8, save_location);
|
||||||
errdefer allocator.free(self.save_location);
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(self: *Project) !void {
|
pub fn save(self: *Project) !void {
|
||||||
@ -555,6 +694,12 @@ pub const Project = struct {
|
|||||||
try writeRangeF64(writer, view.graph_opts.x_range);
|
try writeRangeF64(writer, view.graph_opts.x_range);
|
||||||
try writeRangeF64(writer, view.graph_opts.y_range);
|
try writeRangeF64(writer, view.graph_opts.y_range);
|
||||||
try writeInt(writer, u8, @intFromBool(view.sync_controls));
|
try writeInt(writer, u8, @intFromBool(view.sync_controls));
|
||||||
|
|
||||||
|
try writeInt(writer, u32, view.marked_ranges.len);
|
||||||
|
for (view.marked_ranges.constSlice()) |marked_range| {
|
||||||
|
try writeEnum(writer, u8, UI.Axis, marked_range.axis);
|
||||||
|
try writeRangeF64(writer, marked_range.range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -567,11 +712,11 @@ pub const Project = struct {
|
|||||||
try writeFloat(writer, f64, range.upper);
|
try writeFloat(writer, f64, range.upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readRangeF64(writer: anytype) !RangeF64 {
|
fn readRangeF64(reader: anytype) !RangeF64 {
|
||||||
var range: RangeF64 = undefined;
|
var range: RangeF64 = undefined;
|
||||||
|
|
||||||
range.lower = try readFloat(writer, f64);
|
range.lower = try readFloat(reader, f64);
|
||||||
range.upper = try readFloat(writer, f64);
|
range.upper = try readFloat(reader, f64);
|
||||||
|
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
@ -617,6 +762,27 @@ pub const Project = struct {
|
|||||||
try reader.readNoEof(buff);
|
try reader.readNoEof(buff);
|
||||||
return buff;
|
return buff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assertBackingIntForEnum(BackingInt: type, Enum: type) void {
|
||||||
|
assert(@typeInfo(Enum) == .Enum);
|
||||||
|
const enum_backing_type = @typeInfo(Enum).Enum.tag_type;
|
||||||
|
assert(@typeInfo(enum_backing_type) == .Int);
|
||||||
|
assert(@typeInfo(BackingInt).Int.bits >= @typeInfo(enum_backing_type).Int.bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn readEnum(reader: anytype, BackingInt: type, Enum: type) !Enum {
|
||||||
|
assertBackingIntForEnum(BackingInt, Enum);
|
||||||
|
|
||||||
|
const value_int = try readInt(reader, BackingInt);
|
||||||
|
|
||||||
|
return try std.meta.intToEnum(Enum, value_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeEnum(writer: anytype, BackingInt: type, Enum: type, value: Enum) !void {
|
||||||
|
assertBackingIntForEnum(BackingInt, Enum);
|
||||||
|
|
||||||
|
try writer.writeInt(BackingInt, @intFromEnum(value), file_endian);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Command = union(enum) {
|
pub const Command = union(enum) {
|
||||||
@ -755,11 +921,15 @@ fn loadProject(self: *App) !void {
|
|||||||
|
|
||||||
log.info("Load project from: {s}", .{save_location});
|
log.info("Load project from: {s}", .{save_location});
|
||||||
|
|
||||||
const loaded = try Project.initFromFile(self.allocator, save_location);
|
var loaded = try self.allocator.create(Project);
|
||||||
|
defer self.allocator.destroy(loaded);
|
||||||
|
|
||||||
|
loaded.* = .{};
|
||||||
errdefer loaded.deinit(self.allocator);
|
errdefer loaded.deinit(self.allocator);
|
||||||
|
try loaded.initFromFile(self.allocator, save_location);
|
||||||
|
|
||||||
self.deinitProject();
|
self.deinitProject();
|
||||||
self.project = loaded;
|
self.project = loaded.*;
|
||||||
|
|
||||||
var file_iter = self.project.files.idIterator();
|
var file_iter = self.project.files.idIterator();
|
||||||
while (file_iter.next()) |file_id| {
|
while (file_iter.next()) |file_id| {
|
||||||
@ -842,11 +1012,39 @@ pub fn tick(self: *App) !void {
|
|||||||
self.collection_samples_mutex.lock();
|
self.collection_samples_mutex.lock();
|
||||||
defer self.collection_samples_mutex.unlock();
|
defer self.collection_samples_mutex.unlock();
|
||||||
|
|
||||||
|
{
|
||||||
|
var view_iter = self.project.views.idIterator();
|
||||||
|
while (view_iter.next()) |id| {
|
||||||
|
const view = self.getView(id) orelse continue;
|
||||||
|
if (view.reference != .channel) continue;
|
||||||
|
|
||||||
|
const channel_id = view.reference.channel;
|
||||||
|
const channel = self.getChannel(channel_id) orelse continue;
|
||||||
|
if (!channel.hasNewCollectedSamples()) continue;
|
||||||
|
|
||||||
|
const samples = channel.collected_samples.items;
|
||||||
|
|
||||||
|
const new_samples_range = RangeF64.init(@floatFromInt(channel.processed_samples_up_to), @floatFromInt(samples.len));
|
||||||
|
const marked_ranges: []View.MarkedRange = view.marked_ranges.slice();
|
||||||
|
for (marked_ranges) |*marked_range| {
|
||||||
|
if (marked_range.axis != .X) continue;
|
||||||
|
|
||||||
|
if (new_samples_range.intersectPositive(marked_range.range).isPositive()) {
|
||||||
|
marked_range.refresh(samples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update channel min max caches
|
// Update channel min max caches
|
||||||
{
|
{
|
||||||
var channel_iter = self.project.channels.iterator();
|
var channel_iter = self.project.channels.iterator();
|
||||||
while (channel_iter.next()) |channel| {
|
while (channel_iter.next()) |channel| {
|
||||||
try channel.graph_min_max_cache.updateLast(self.allocator, channel.collected_samples.items);
|
if (channel.hasNewCollectedSamples()) {
|
||||||
|
channel.invalidateSavedSamples();
|
||||||
|
try channel.graph_min_max_cache.updateLast(self.allocator, channel.collected_samples.items);
|
||||||
|
channel.processed_samples_up_to = channel.collected_samples.items.len;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -858,11 +1056,6 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (self.isCollectionInProgress()) {
|
if (self.isCollectionInProgress()) {
|
||||||
var channel_iter = self.project.channels.idIterator();
|
|
||||||
while (channel_iter.next()) |channel_id| {
|
|
||||||
self.refreshViewAvailableXYRanges(channel_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
var view_iter = self.project.views.iterator();
|
var view_iter = self.project.views.iterator();
|
||||||
while (view_iter.next()) |view| {
|
while (view_iter.next()) |view| {
|
||||||
if (view.reference != .channel) continue;
|
if (view.reference != .channel) continue;
|
||||||
@ -1156,7 +1349,6 @@ pub fn collectionThreadCallback(self: *App) void {
|
|||||||
log.err("Failed to append samples for channel: {}", .{e});
|
log.err("Failed to append samples for channel: {}", .{e});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
channel.invalidateSavedSamples();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1405,22 +1597,7 @@ pub fn addView(self: *App, reference: View.Reference) !Id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getViewSamples(self: *App, id: Id) []const f64 {
|
pub fn getViewSamples(self: *App, id: Id) []const f64 {
|
||||||
const empty = &[0]f64{};
|
return self.project.getViewSamples(id);
|
||||||
|
|
||||||
var result: []const f64 = empty;
|
|
||||||
|
|
||||||
if (self.getView(id)) |view| {
|
|
||||||
switch (view.reference) {
|
|
||||||
.channel => |channel_id| if (self.getChannel(channel_id)) |channel| {
|
|
||||||
result = channel.collected_samples.items;
|
|
||||||
},
|
|
||||||
.file => |file_id| if (self.getFile(file_id)) |file| {
|
|
||||||
result = file.samples orelse empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getViewMinMaxCache(self: *App, id: Id) Graph.MinMaxCache {
|
pub fn getViewMinMaxCache(self: *App, id: Id) Graph.MinMaxCache {
|
||||||
|
@ -92,6 +92,10 @@ pub const Command = union(enum) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const CommandFrame = struct {
|
pub const CommandFrame = struct {
|
||||||
|
// When a new command is pushed after this one, it will become marked as "frozen"
|
||||||
|
// So that movement commands wouldn't be able to update it.
|
||||||
|
frozen: bool = false,
|
||||||
|
|
||||||
updated_at_ns: i128,
|
updated_at_ns: i128,
|
||||||
commands: std.BoundedArray(Command, constants.max_views) = .{},
|
commands: std.BoundedArray(Command, constants.max_views) = .{},
|
||||||
|
|
||||||
@ -133,6 +137,28 @@ pub const CommandFrame = struct {
|
|||||||
|
|
||||||
pub const CommandFrameArray = std.BoundedArray(CommandFrame, 64);
|
pub const CommandFrameArray = std.BoundedArray(CommandFrame, 64);
|
||||||
|
|
||||||
|
const MarkedRangeIterator = struct {
|
||||||
|
system: *System,
|
||||||
|
view_id: Id,
|
||||||
|
axis: UI.Axis,
|
||||||
|
index: usize = 0,
|
||||||
|
next_index: usize = 0,
|
||||||
|
|
||||||
|
pub fn next(self: *MarkedRangeIterator) ?RangeF64 {
|
||||||
|
const selected_ranges = self.system.marked_ranges.constSlice();
|
||||||
|
while (self.next_index < selected_ranges.len) {
|
||||||
|
self.index = self.next_index;
|
||||||
|
const selected_range = selected_ranges[self.index];
|
||||||
|
self.next_index += 1;
|
||||||
|
|
||||||
|
if (selected_range.axis == self.axis and selected_range.view_id.eql(self.view_id)) {
|
||||||
|
return selected_range.range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
project: *App.Project,
|
project: *App.Project,
|
||||||
|
|
||||||
// TODO: Redo
|
// TODO: Redo
|
||||||
@ -144,6 +170,11 @@ view_settings: ?Id = null, // View id
|
|||||||
view_fullscreen: ?Id = null, // View id
|
view_fullscreen: ?Id = null, // View id
|
||||||
view_protocol_modal: ?Id = null, // View id
|
view_protocol_modal: ?Id = null, // View id
|
||||||
show_ruler: bool = false,
|
show_ruler: bool = false,
|
||||||
|
selected_tool: enum { move, select } = .move,
|
||||||
|
show_marked_range: ?struct {
|
||||||
|
view_id: Id,
|
||||||
|
index: usize,
|
||||||
|
} = null,
|
||||||
|
|
||||||
pub fn init(project: *App.Project) System {
|
pub fn init(project: *App.Project) System {
|
||||||
return System{
|
return System{
|
||||||
@ -156,6 +187,10 @@ fn pushCommandFrame(self: *System) *CommandFrame {
|
|||||||
_ = self.undo_stack.orderedRemove(0);
|
_ = self.undo_stack.orderedRemove(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.lastCommandFrame()) |frame| {
|
||||||
|
frame.frozen = true;
|
||||||
|
}
|
||||||
|
|
||||||
var frame = self.undo_stack.addOneAssumeCapacity();
|
var frame = self.undo_stack.addOneAssumeCapacity();
|
||||||
frame.updated_at_ns = std.time.nanoTimestamp();
|
frame.updated_at_ns = std.time.nanoTimestamp();
|
||||||
frame.commands.len = 0;
|
frame.commands.len = 0;
|
||||||
@ -190,7 +225,7 @@ pub fn pushViewMove(self: *System, view_id: Id, x_range: RangeF64, y_range: Rang
|
|||||||
frame = last_frame;
|
frame = last_frame;
|
||||||
|
|
||||||
const now_ns = std.time.nanoTimestamp();
|
const now_ns = std.time.nanoTimestamp();
|
||||||
if (now_ns - last_frame.updated_at_ns < std.time.ns_per_ms * 250) {
|
if (!last_frame.frozen and now_ns - last_frame.updated_at_ns < std.time.ns_per_ms * 250) {
|
||||||
last_frame.updated_at_ns = now_ns;
|
last_frame.updated_at_ns = now_ns;
|
||||||
push_new_command = false;
|
push_new_command = false;
|
||||||
|
|
||||||
@ -307,10 +342,24 @@ pub fn getCursor(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
|||||||
return ViewAxisPosition.get(&self.cursor, self.project, view_id, axis);
|
return ViewAxisPosition.get(&self.cursor, self.project, view_id, axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setZoomStart(self: *System, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
pub fn setCursorHoldStart(self: *System, view_id: Id, axis: UI.Axis, position: ?f64) void {
|
||||||
return ViewAxisPosition.set(&self.zoom_start, view_id, axis, position);
|
return ViewAxisPosition.set(&self.zoom_start, view_id, axis, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getZoomStart(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
pub fn getCursorHoldStart(self: *System, view_id: Id, axis: UI.Axis) ?f64 {
|
||||||
return ViewAxisPosition.get(&self.zoom_start, self.project,view_id, axis);
|
return ViewAxisPosition.get(&self.zoom_start, self.project,view_id, axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggleShownMarkedRange(self: *System, view_id: Id, index: usize) void {
|
||||||
|
if (self.show_marked_range) |show_marked_range| {
|
||||||
|
if (show_marked_range.view_id.eql(view_id) and show_marked_range.index == index) {
|
||||||
|
self.show_marked_range = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.show_marked_range = .{
|
||||||
|
.view_id = view_id,
|
||||||
|
.index = index,
|
||||||
|
};
|
||||||
}
|
}
|
@ -70,45 +70,51 @@ fn showGraph(ctx: Context, view_id: Id) *UI.Box {
|
|||||||
sample_value_under_mouse = mouse_y_range.remapTo(view_opts.y_range, signal.relative_mouse.y);
|
sample_value_under_mouse = mouse_y_range.remapTo(view_opts.y_range, signal.relative_mouse.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal.dragged()) {
|
if (ctx.view_controls.selected_tool == .move) {
|
||||||
const x_offset = mouse_x_range.remapTo(RangeF64.init(0, view_opts.x_range.size()), signal.drag.x);
|
if (signal.dragged()) {
|
||||||
const y_offset = mouse_y_range.remapTo(RangeF64.init(0, view_opts.y_range.size()), signal.drag.y);
|
const x_offset = mouse_x_range.remapTo(RangeF64.init(0, view_opts.x_range.size()), signal.drag.x);
|
||||||
|
const y_offset = mouse_y_range.remapTo(RangeF64.init(0, view_opts.y_range.size()), signal.drag.y);
|
||||||
|
|
||||||
ctx.view_controls.pushViewMove(
|
ctx.view_controls.pushViewMove(
|
||||||
view_id,
|
view_id,
|
||||||
view_opts.x_range.sub(x_offset),
|
view_opts.x_range.sub(x_offset),
|
||||||
view_opts.y_range.add(y_offset)
|
view_opts.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 -= constants.zoom_speed;
|
|
||||||
} else {
|
|
||||||
scale_factor += constants.zoom_speed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.view_controls.pushViewMove(
|
if (signal.scrolled() and sample_index_under_mouse != null and sample_value_under_mouse != null) {
|
||||||
view_id,
|
var scale_factor: f64 = 1;
|
||||||
view_opts.x_range.zoom(sample_index_under_mouse.?, scale_factor),
|
if (signal.scroll.y > 0) {
|
||||||
view_opts.y_range.zoom(sample_value_under_mouse.?, scale_factor)
|
scale_factor -= constants.zoom_speed;
|
||||||
);
|
} else {
|
||||||
|
scale_factor += constants.zoom_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.view_controls.pushViewMove(
|
||||||
|
view_id,
|
||||||
|
view_opts.x_range.zoom(sample_index_under_mouse.?, scale_factor),
|
||||||
|
view_opts.y_range.zoom(sample_value_under_mouse.?, scale_factor)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal.flags.contains(.middle_clicked)) {
|
||||||
|
ctx.view_controls.pushViewMove(view_id, view.available_x_range, view.available_y_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signal.flags.contains(.left_released)) {
|
||||||
|
ctx.view_controls.pushBreakpoint();
|
||||||
|
}
|
||||||
|
} else if (ctx.view_controls.selected_tool == .select) {
|
||||||
|
// TODO:
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal.flags.contains(.middle_clicked)) {
|
|
||||||
ctx.view_controls.pushViewMove(view_id, view.available_x_range, view.available_y_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal.flags.contains(.left_released)) {
|
{ // Render graph
|
||||||
ctx.view_controls.pushBreakpoint();
|
view.graph_cache.min_max_cache = app.getViewMinMaxCache(view_id);
|
||||||
}
|
Graph.drawCached(&view.graph_cache, graph_box.persistent.size, view_opts.*, samples);
|
||||||
|
if (view.graph_cache.texture) |texture| {
|
||||||
view.graph_cache.min_max_cache = app.getViewMinMaxCache(view_id);
|
graph_box.texture = texture.texture;
|
||||||
|
}
|
||||||
Graph.drawCached(&view.graph_cache, graph_box.persistent.size, view_opts.*, samples);
|
|
||||||
if (view.graph_cache.texture) |texture| {
|
|
||||||
graph_box.texture = texture.texture;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (view_opts.x_range.size() == 0 or view_opts.y_range.size() == 0) {
|
if (view_opts.x_range.size() == 0 or view_opts.y_range.size() == 0) {
|
||||||
|
@ -15,32 +15,42 @@ const assert = std.debug.assert;
|
|||||||
|
|
||||||
const ruler_size = UI.Sizing.initFixed(.{ .pixels = 32 });
|
const ruler_size = UI.Sizing.initFixed(.{ .pixels = 32 });
|
||||||
|
|
||||||
|
const Ruler = struct {
|
||||||
|
project: *App.Project,
|
||||||
|
box: *UI.Box,
|
||||||
|
graph_box: *UI.Box,
|
||||||
|
view_id: Id,
|
||||||
|
axis: UI.Axis,
|
||||||
|
|
||||||
|
fn getBoxDrawContext(self: *const Ruler) DrawContext {
|
||||||
|
var draw_ctx = DrawContext.init(self.axis, self.project, self.view_id);
|
||||||
|
draw_ctx.rect = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = self.box.persistent.size.x,
|
||||||
|
.height = self.box.persistent.size.y
|
||||||
|
};
|
||||||
|
return draw_ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getGraphDrawContext(self: *const Ruler) DrawContext {
|
||||||
|
var draw_ctx = DrawContext.init(self.axis, self.project, self.view_id);
|
||||||
|
draw_ctx.rect = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = self.graph_box.persistent.size.x,
|
||||||
|
.height = self.graph_box.persistent.size.y
|
||||||
|
};
|
||||||
|
return draw_ctx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Context = struct {
|
pub const Context = struct {
|
||||||
ui: *UI,
|
ui: *UI,
|
||||||
project: *App.Project,
|
project: *App.Project,
|
||||||
view_controls: *ViewControlsSystem
|
view_controls: *ViewControlsSystem
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn createBox(ctx: Context, key: UI.Key, axis: UI.Axis) *UI.Box {
|
|
||||||
var ui = ctx.ui;
|
|
||||||
|
|
||||||
var ruler = ui.createBox(.{
|
|
||||||
.key = key,
|
|
||||||
.background = srcery.hard_black,
|
|
||||||
.flags = &.{ .clickable, .scrollable, .clip_view },
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DrawContext = struct {
|
const DrawContext = struct {
|
||||||
one_unit: ?f64,
|
one_unit: ?f64,
|
||||||
render_range: RangeF64,
|
render_range: RangeF64,
|
||||||
@ -62,7 +72,7 @@ const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getPoint(self: *DrawContext, along_axis_pos: f64, cross_axis_pos: f64) rl.Vector2 {
|
fn getPoint(self: *const DrawContext, along_axis_pos: f64, cross_axis_pos: f64) rl.Vector2 {
|
||||||
const rect_width: f64 = @floatCast(self.rect.width);
|
const rect_width: f64 = @floatCast(self.rect.width);
|
||||||
const rect_height: f64 = @floatCast(self.rect.height);
|
const rect_height: f64 = @floatCast(self.rect.height);
|
||||||
|
|
||||||
@ -85,7 +95,7 @@ const DrawContext = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getLine(self: *DrawContext, along_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
fn getLine(self: *const DrawContext, along_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||||
const along_axis_size = self.render_range.size() / switch(self.axis) {
|
const along_axis_size = self.render_range.size() / switch(self.axis) {
|
||||||
.X => @as(f64, @floatCast(self.rect.width)),
|
.X => @as(f64, @floatCast(self.rect.width)),
|
||||||
.Y => @as(f64, @floatCast(self.rect.height))
|
.Y => @as(f64, @floatCast(self.rect.height))
|
||||||
@ -99,7 +109,7 @@ const DrawContext = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getRect(self: *DrawContext, along_axis_pos: f64, along_axis_size: f64, cross_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
fn getRect(self: *const DrawContext, along_axis_pos: f64, along_axis_size: f64, cross_axis_pos: f64, cross_axis_size: f64) rl.Rectangle {
|
||||||
const pos = self.getPoint(along_axis_pos, cross_axis_pos);
|
const pos = self.getPoint(along_axis_pos, cross_axis_pos);
|
||||||
const corner = self.getPoint(along_axis_pos + along_axis_size, cross_axis_pos + cross_axis_size);
|
const corner = self.getPoint(along_axis_pos + along_axis_size, cross_axis_pos + cross_axis_size);
|
||||||
var rect = rl.Rectangle{
|
var rect = rl.Rectangle{
|
||||||
@ -122,7 +132,7 @@ const DrawContext = struct {
|
|||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawLine(self: *DrawContext, along_axis_pos: f64, cross_axis_size: f64, color: rl.Color) void {
|
fn drawLine(self: *const DrawContext, along_axis_pos: f64, cross_axis_size: f64, color: rl.Color) void {
|
||||||
rl.drawLineV(
|
rl.drawLineV(
|
||||||
self.getPoint(along_axis_pos, 0),
|
self.getPoint(along_axis_pos, 0),
|
||||||
self.getPoint(along_axis_pos, cross_axis_size),
|
self.getPoint(along_axis_pos, cross_axis_size),
|
||||||
@ -130,7 +140,7 @@ const DrawContext = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawTicks(self: *DrawContext, from: f64, to: f64, step: f64, line_size: f64, color: rl.Color) void {
|
fn drawTicks(self: *const DrawContext, from: f64, to: f64, step: f64, line_size: f64, color: rl.Color) void {
|
||||||
var position = from;
|
var position = from;
|
||||||
while (position < to) : (position += step) {
|
while (position < to) : (position += step) {
|
||||||
self.drawLine(position, line_size, color);
|
self.drawLine(position, line_size, color);
|
||||||
@ -138,6 +148,26 @@ const DrawContext = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn createBox(ctx: Context, key: UI.Key, axis: UI.Axis) *UI.Box {
|
||||||
|
var ui = ctx.ui;
|
||||||
|
|
||||||
|
var ruler = ui.createBox(.{
|
||||||
|
.key = key,
|
||||||
|
.background = srcery.hard_black,
|
||||||
|
.flags = &.{ .clickable, .scrollable, .clip_view },
|
||||||
|
.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 drawRulerTicks(_ctx: ?*anyopaque, box: *UI.Box) void {
|
fn drawRulerTicks(_ctx: ?*anyopaque, box: *UI.Box) void {
|
||||||
const ctx: *DrawContext = @ptrCast(@alignCast(_ctx));
|
const ctx: *DrawContext = @ptrCast(@alignCast(_ctx));
|
||||||
ctx.rect = box.rect();
|
ctx.rect = box.rect();
|
||||||
@ -192,27 +222,46 @@ fn showMouseTooltip(ctx: Context, axis: UI.Axis, view_id: Id, position: f64) voi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn showMarkerLine(ui: *UI, ruler: Ruler, position: f64, color: rl.Color) void {
|
||||||
|
_ = ui.createBox(.{
|
||||||
|
.background = color,
|
||||||
|
.float_rect = ruler.getBoxDrawContext().getLine(position, 1),
|
||||||
|
.float_relative_to = ruler.box,
|
||||||
|
.parent = ruler.box
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = ui.createBox(.{
|
||||||
|
.background = color,
|
||||||
|
.float_rect = ruler.getGraphDrawContext().getLine(position, 1),
|
||||||
|
.float_relative_to = ruler.graph_box,
|
||||||
|
.parent = ruler.graph_box
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showMarkerRect(ui: *UI, ruler: Ruler, range: RangeF64, color: rl.Color, key: ?UI.Key) *UI.Box {
|
||||||
|
return ui.createBox(.{
|
||||||
|
.key = key,
|
||||||
|
.background = color,
|
||||||
|
.float_relative_to = ruler.box,
|
||||||
|
.parent = ruler.box,
|
||||||
|
.float_rect = ruler.getBoxDrawContext().getRect(@min(range.lower, range.upper), range.size(), 0, 1),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) !void {
|
pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: UI.Axis) !void {
|
||||||
|
const ruler = Ruler{
|
||||||
|
.project = ctx.project,
|
||||||
|
.box = box,
|
||||||
|
.graph_box = graph_box,
|
||||||
|
.view_id = view_id,
|
||||||
|
.axis = axis
|
||||||
|
};
|
||||||
|
|
||||||
var ui = ctx.ui;
|
var ui = ctx.ui;
|
||||||
const project = ctx.project;
|
const project = ctx.project;
|
||||||
|
|
||||||
const view = project.views.get(view_id) orelse return;
|
const view = project.views.get(view_id) orelse return;
|
||||||
|
|
||||||
var ruler_ctx = DrawContext.init(axis, project, view_id);
|
|
||||||
var graph_ctx = ruler_ctx;
|
|
||||||
ruler_ctx.rect = .{
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = box.persistent.size.x,
|
|
||||||
.height = box.persistent.size.y
|
|
||||||
};
|
|
||||||
graph_ctx.rect = .{
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = graph_box.persistent.size.x,
|
|
||||||
.height = graph_box.persistent.size.y
|
|
||||||
};
|
|
||||||
|
|
||||||
box.beginChildren();
|
box.beginChildren();
|
||||||
defer box.endChildren();
|
defer box.endChildren();
|
||||||
|
|
||||||
@ -229,44 +278,68 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
|||||||
{ // Visuals
|
{ // Visuals
|
||||||
const cursor = ctx.view_controls.getCursor(view_id, axis);
|
const cursor = ctx.view_controls.getCursor(view_id, axis);
|
||||||
|
|
||||||
var zoom_start: ?f64 = null;
|
var hold_start: ?f64 = null;
|
||||||
if (ctx.view_controls.getZoomStart(view_id, axis)) |zoom_start_position| {
|
if (ctx.view_controls.getCursorHoldStart(view_id, axis)) |hold_start_position| {
|
||||||
zoom_start = zoom_start_position;
|
hold_start = hold_start_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
var markers: std.BoundedArray(f64, 2) = .{};
|
var markers: std.BoundedArray(f64, 2) = .{};
|
||||||
|
|
||||||
if (zoom_start != null) {
|
if (hold_start != null) {
|
||||||
markers.appendAssumeCapacity(zoom_start.?);
|
markers.appendAssumeCapacity(hold_start.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
markers.appendAssumeCapacity(cursor.?);
|
markers.appendAssumeCapacity(cursor.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (markers.constSlice()) |marker_position| {
|
const marker_color = srcery.green;
|
||||||
_ = ui.createBox(.{
|
|
||||||
.background = srcery.green,
|
|
||||||
.float_rect = ruler_ctx.getLine(marker_position, 1),
|
|
||||||
.float_relative_to = box,
|
|
||||||
});
|
|
||||||
|
|
||||||
_ = ui.createBox(.{
|
for (markers.constSlice()) |marker_position| {
|
||||||
.background = srcery.green,
|
showMarkerLine(ui, ruler, marker_position, marker_color);
|
||||||
.float_rect = graph_ctx.getLine(marker_position, 1),
|
|
||||||
.float_relative_to = graph_box,
|
|
||||||
.parent = graph_box
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zoom_start != null and cursor != null) {
|
if (hold_start != null and cursor != null) {
|
||||||
const zoom_end = cursor.?;
|
_ = showMarkerRect(ui, ruler, RangeF64.init(hold_start.?, cursor.?), marker_color.alpha(0.5), null);
|
||||||
|
}
|
||||||
|
|
||||||
_ = ui.createBox(.{
|
{
|
||||||
.background = srcery.green.alpha(0.5),
|
|
||||||
.float_relative_to = box,
|
var selected_range_iter = view.iterMarkedRanges(axis);
|
||||||
.float_rect = ruler_ctx.getRect(zoom_start.?, zoom_end - zoom_start.?, 0, 1),
|
while (selected_range_iter.next()) |selected_range| {
|
||||||
});
|
var color = srcery.blue;
|
||||||
|
const index = selected_range_iter.index;
|
||||||
|
|
||||||
|
if (ctx.view_controls.show_marked_range) |show_marked_range| {
|
||||||
|
if (show_marked_range.view_id.eql(view_id) and show_marked_range.index == index) {
|
||||||
|
if (@mod(rl.getTime(), 0.5) < 0.25) {
|
||||||
|
color = utils.shiftColorInHSV(color, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showMarkerLine(ui, ruler, selected_range.lower, color);
|
||||||
|
showMarkerLine(ui, ruler, selected_range.upper, color);
|
||||||
|
|
||||||
|
var hasher = UI.Key.CombineHasher.init();
|
||||||
|
hasher.update(std.mem.asBytes(&view_id));
|
||||||
|
hasher.update(std.mem.asBytes(&axis));
|
||||||
|
hasher.update(std.mem.asBytes(&index));
|
||||||
|
const range_box_key = UI.Key.init(hasher.final());
|
||||||
|
|
||||||
|
var range_box = showMarkerRect(ui, ruler, selected_range, color.alpha(0.5), range_box_key);
|
||||||
|
range_box.flags.insert(.clickable);
|
||||||
|
range_box.flags.insert(.draw_hot);
|
||||||
|
range_box.flags.insert(.draw_active);
|
||||||
|
|
||||||
|
range_box.hot_cursor = .mouse_cursor_pointing_hand;
|
||||||
|
if (ctx.view_controls.selected_tool == .select) {
|
||||||
|
const signal = ui.signal(range_box);
|
||||||
|
if (signal.clicked()) {
|
||||||
|
ctx.view_controls.toggleShownMarkedRange(view_id, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,42 +367,67 @@ pub fn show(ctx: Context, box: *UI.Box, graph_box: *UI.Box, view_id: Id, axis: U
|
|||||||
const cursor = ctx.view_controls.getCursor(view_id, axis);
|
const cursor = ctx.view_controls.getCursor(view_id, axis);
|
||||||
|
|
||||||
if (signal.flags.contains(.left_pressed)) {
|
if (signal.flags.contains(.left_pressed)) {
|
||||||
ctx.view_controls.setZoomStart(view_id, axis, cursor);
|
ctx.view_controls.setCursorHoldStart(view_id, axis, cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal.scrolled() and cursor != null) {
|
if (ctx.view_controls.selected_tool == .move) {
|
||||||
var scale_factor: f64 = 1;
|
if (signal.scrolled() and cursor != null) {
|
||||||
if (signal.scroll.y > 0) {
|
var scale_factor: f64 = 1;
|
||||||
scale_factor -= constants.zoom_speed;
|
if (signal.scroll.y > 0) {
|
||||||
} else {
|
scale_factor -= constants.zoom_speed;
|
||||||
scale_factor += constants.zoom_speed;
|
} else {
|
||||||
|
scale_factor += constants.zoom_speed;
|
||||||
|
}
|
||||||
|
const new_view_range = view_range.zoom(cursor.?, scale_factor);
|
||||||
|
ctx.view_controls.pushViewMoveAxis(view_id, axis, new_view_range);
|
||||||
}
|
}
|
||||||
const new_view_range = view_range.zoom(cursor.?, scale_factor);
|
|
||||||
ctx.view_controls.pushViewMoveAxis(view_id, axis, new_view_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.view_controls.getZoomStart(view_id, axis)) |zoom_start| {
|
if (ctx.view_controls.getCursorHoldStart(view_id, axis)) |zoom_start| {
|
||||||
if (signal.flags.contains(.left_released)) {
|
if (signal.flags.contains(.left_released)) {
|
||||||
const zoom_end = cursor;
|
const zoom_end = cursor;
|
||||||
|
|
||||||
if (zoom_end != null) {
|
if (zoom_end != null) {
|
||||||
const zoom_start_mouse = view_range.remapTo(mouse_range, zoom_start);
|
const zoom_start_mouse = view_range.remapTo(mouse_range, zoom_start);
|
||||||
const zoom_end_mouse = view_range.remapTo(mouse_range, zoom_end.?);
|
const zoom_end_mouse = view_range.remapTo(mouse_range, zoom_end.?);
|
||||||
const mouse_move_distance = @abs(zoom_end_mouse - zoom_start_mouse);
|
const mouse_move_distance = @abs(zoom_end_mouse - zoom_start_mouse);
|
||||||
if (mouse_move_distance > 5) {
|
if (mouse_move_distance > 5) {
|
||||||
var new_view_range = RangeF64.init(
|
var new_view_range = RangeF64.init(
|
||||||
@min(zoom_start, zoom_end.?),
|
@min(zoom_start, zoom_end.?),
|
||||||
@max(zoom_start, zoom_end.?)
|
@max(zoom_start, zoom_end.?)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (axis == .Y) {
|
if (axis == .Y) {
|
||||||
new_view_range = new_view_range.flip();
|
new_view_range = new_view_range.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.view_controls.pushViewMoveAxis(view_id, axis, new_view_range);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.view_controls.pushViewMoveAxis(view_id, axis, new_view_range);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.view_controls.zoom_start = null;
|
}
|
||||||
|
} else if (ctx.view_controls.selected_tool == .select) {
|
||||||
|
// TODO:
|
||||||
|
|
||||||
|
if (cursor != null) {
|
||||||
|
if (ctx.view_controls.getCursorHoldStart(view_id, axis)) |hold_start| {
|
||||||
|
const range = RangeF64.init(
|
||||||
|
@min(hold_start, cursor.?),
|
||||||
|
@max(hold_start, cursor.?),
|
||||||
|
);
|
||||||
|
const hold_start_mouse = view_range.remapTo(mouse_range, range.lower);
|
||||||
|
const hold_end_mouse = view_range.remapTo(mouse_range, range.upper);
|
||||||
|
const mouse_move_distance = @abs(hold_end_mouse - hold_start_mouse);
|
||||||
|
if (signal.flags.contains(.left_released) and mouse_move_distance > 5) {
|
||||||
|
if (view.appendMarkedRange(axis, range)) |marked_range| {
|
||||||
|
marked_range.refresh(ctx.project.getViewSamples(view_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (signal.flags.contains(.left_released)) {
|
||||||
|
ctx.view_controls.setCursorHoldStart(view_id, axis, null);
|
||||||
|
}
|
||||||
}
|
}
|
17
src/main.zig
17
src/main.zig
@ -135,23 +135,6 @@ pub fn main() !void {
|
|||||||
_ = try app.addView(.{
|
_ = try app.addView(.{
|
||||||
.file = try app.addFile("./samples-18m.bin")
|
.file = try app.addFile("./samples-18m.bin")
|
||||||
});
|
});
|
||||||
|
|
||||||
// _ = try app.addView(.{
|
|
||||||
// .channel = try app.addChannel("Dev1/ai0")
|
|
||||||
// });
|
|
||||||
// _ = try app.addView(.{
|
|
||||||
// .channel = try app.addChannel("Dev3/ao0")
|
|
||||||
// });
|
|
||||||
// _ = try app.addView(.{
|
|
||||||
// .file = try app.addFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin")
|
|
||||||
// });
|
|
||||||
|
|
||||||
// try app.appendChannelFromDevice("Dev1/ai0");
|
|
||||||
// try app.appendChannelFromDevice("Dev3/ao0");
|
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_I.bin");
|
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
|
||||||
// try app.appendChannelFromFile("samples/HeLa Cx37_ 40nM GFX + 35uM Propofol_18-Sep-2024_0003_IjStim.bin");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var profiler: ?Profiler = null;
|
var profiler: ?Profiler = null;
|
||||||
|
@ -384,11 +384,149 @@ fn showViewSettings(self: *MainScreen, view_id: Id) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() });
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
pub fn showSidePanel(self: *MainScreen) !void {
|
||||||
var ui = &self.app.ui;
|
var ui = &self.app.ui;
|
||||||
|
|
||||||
const container = ui.createBox(.{
|
const container = ui.createBox(.{
|
||||||
.size_x = UI.Sizing.initGrowUpTo(.{ .parent_percent = 0.2 }),
|
.size_x = UI.Sizing.initFitChildren(),
|
||||||
.size_y = UI.Sizing.initGrowFull(),
|
.size_y = UI.Sizing.initGrowFull(),
|
||||||
.borders = .{
|
.borders = .{
|
||||||
.right = .{ .color = srcery.hard_black, .size = 4 }
|
.right = .{ .color = srcery.hard_black, .size = 4 }
|
||||||
@ -400,7 +538,11 @@ pub fn showSidePanel(self: *MainScreen) !void {
|
|||||||
container.beginChildren();
|
container.beginChildren();
|
||||||
defer container.endChildren();
|
defer container.endChildren();
|
||||||
|
|
||||||
if (self.view_controls.view_settings) |view_id| {
|
_ = 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);
|
try self.showViewSettings(view_id);
|
||||||
} else {
|
} else {
|
||||||
try self.showProjectSettings();
|
try self.showProjectSettings();
|
||||||
@ -449,49 +591,7 @@ pub fn tick(self: *MainScreen) !void {
|
|||||||
maybe_modal_overlay = modal_overlay;
|
maybe_modal_overlay = modal_overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
self.showToolbar();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ui_view_ctx = UIView.Context{
|
const ui_view_ctx = UIView.Context{
|
||||||
.app = self.app,
|
.app = self.app,
|
||||||
@ -561,6 +661,8 @@ pub fn tick(self: *MainScreen) !void {
|
|||||||
self.view_controls.view_fullscreen = null;
|
self.view_controls.view_fullscreen = null;
|
||||||
} else if (self.view_controls.view_settings != null) {
|
} else if (self.view_controls.view_settings != null) {
|
||||||
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 {
|
} else {
|
||||||
self.app.should_close = true;
|
self.app.should_close = true;
|
||||||
}
|
}
|
||||||
|
46
src/ui.zig
46
src/ui.zig
@ -23,13 +23,22 @@ const Vec2Zero = Vec2{ .x = 0, .y = 0 };
|
|||||||
const UI = @This();
|
const UI = @This();
|
||||||
|
|
||||||
const max_boxes = 512;
|
const max_boxes = 512;
|
||||||
const max_events = 1024;
|
const max_events = 256;
|
||||||
const draw_debug = false; //builtin.mode == .Debug;
|
const draw_debug = false; //builtin.mode == .Debug;
|
||||||
const default_font = Assets.FontId{ .variant = .regular, .size = 16 };
|
const default_font = Assets.FontId{ .variant = .regular, .size = 16 };
|
||||||
|
|
||||||
pub const Key = struct {
|
pub const Key = struct {
|
||||||
|
const StringHasher = std.hash.XxHash3;
|
||||||
|
pub const CombineHasher = std.hash.Fnv1a_64;
|
||||||
|
|
||||||
hash: u64 = 0,
|
hash: u64 = 0,
|
||||||
|
|
||||||
|
pub fn init(hash: u64) Key {
|
||||||
|
return Key{
|
||||||
|
.hash = hash
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn initPtr(ptr: anytype) Key {
|
pub fn initPtr(ptr: anytype) Key {
|
||||||
return Key.initUsize(@intFromPtr(ptr));
|
return Key.initUsize(@intFromPtr(ptr));
|
||||||
}
|
}
|
||||||
@ -42,7 +51,17 @@ pub const Key = struct {
|
|||||||
|
|
||||||
pub fn initString(seed: u64, text: []const u8) Key {
|
pub fn initString(seed: u64, text: []const u8) Key {
|
||||||
return Key{
|
return Key{
|
||||||
.hash = std.hash.XxHash3.hash(seed, text)
|
.hash = StringHasher.hash(seed, text)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn combine(self: Key, other: Key) Key {
|
||||||
|
var hasher = CombineHasher.init();
|
||||||
|
hasher.update(std.mem.asBytes(&self.hash));
|
||||||
|
hasher.update(std.mem.asBytes(&other.hash));
|
||||||
|
|
||||||
|
return Key{
|
||||||
|
.hash = hasher.final()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -517,6 +536,8 @@ pub const Box = struct {
|
|||||||
visual_hot: bool = false,
|
visual_hot: bool = false,
|
||||||
visual_active: bool = false,
|
visual_active: bool = false,
|
||||||
tooltip: ?[]const u8 = null,
|
tooltip: ?[]const u8 = null,
|
||||||
|
float_x: ?f32 = null,
|
||||||
|
float_y: ?f32 = null,
|
||||||
|
|
||||||
// Variables that you probably shouldn't be touching
|
// Variables that you probably shouldn't be touching
|
||||||
last_used_frame: u64 = 0,
|
last_used_frame: u64 = 0,
|
||||||
@ -590,13 +611,11 @@ pub const Box = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn setFloatX(self: *Box, x: f32) void {
|
pub fn setFloatX(self: *Box, x: f32) void {
|
||||||
self.persistent.position.x = x;
|
self.float_x = x;
|
||||||
self.flags.insert(.float_x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setFloatY(self: *Box, y: f32) void {
|
pub fn setFloatY(self: *Box, y: f32) void {
|
||||||
self.persistent.position.y = y;
|
self.float_y = y;
|
||||||
self.flags.insert(.float_y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setFloatRect(self: *Box, float_rect: Rect) void {
|
pub fn setFloatRect(self: *Box, float_rect: Rect) void {
|
||||||
@ -640,8 +659,8 @@ pub const Box = struct {
|
|||||||
|
|
||||||
fn isFloating(self: *const Box, axis: Axis) bool {
|
fn isFloating(self: *const Box, axis: Axis) bool {
|
||||||
return switch (axis) {
|
return switch (axis) {
|
||||||
.X => self.flags.contains(.float_x),
|
.X => self.float_x != null,
|
||||||
.Y => self.flags.contains(.float_y),
|
.Y => self.float_y != null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1017,11 +1036,11 @@ pub fn end(self: *UI) void {
|
|||||||
// Reset sizes and positions, because it will be recalculated in layout pass
|
// Reset sizes and positions, because it will be recalculated in layout pass
|
||||||
for (self.boxes.slice()) |*box| {
|
for (self.boxes.slice()) |*box| {
|
||||||
var position = Vec2{ .x = 0, .y = 0 };
|
var position = Vec2{ .x = 0, .y = 0 };
|
||||||
if (box.isFloating(.X)) {
|
if (box.float_x) |x| {
|
||||||
position.x = box.persistent.position.x;
|
position.x = x;
|
||||||
}
|
}
|
||||||
if (box.isFloating(.Y)) {
|
if (box.float_y) |y| {
|
||||||
position.y = box.persistent.position.y;
|
position.y = y;
|
||||||
}
|
}
|
||||||
box.persistent.size = Vec2Zero;
|
box.persistent.size = Vec2Zero;
|
||||||
box.persistent.position = position;
|
box.persistent.position = position;
|
||||||
@ -2314,7 +2333,8 @@ pub fn textButton(self: *UI, text: []const u8) *Box {
|
|||||||
pub fn label(self: *UI, comptime fmt: []const u8, args: anytype) *Box {
|
pub fn label(self: *UI, comptime fmt: []const u8, args: anytype) *Box {
|
||||||
const box = self.createBox(.{
|
const box = self.createBox(.{
|
||||||
.size_x = Sizing.initFixed(.text),
|
.size_x = Sizing.initFixed(.text),
|
||||||
.size_y = Sizing.initFixed(.text)
|
.size_y = Sizing.initFixed(.text),
|
||||||
|
.flags = &.{ .wrap_text }
|
||||||
});
|
});
|
||||||
|
|
||||||
box.setFmtText(fmt, args);
|
box.setFmtText(fmt, args);
|
||||||
|
@ -39,7 +39,9 @@ pub fn shiftColorInHSV(color: rl.Color, value_shift: f32) rl.Color {
|
|||||||
|
|
||||||
var hsv = rl.colorToHSV(color);
|
var hsv = rl.colorToHSV(color);
|
||||||
hsv.z = std.math.clamp(hsv.z * (1 + value_shift), 0, 1);
|
hsv.z = std.math.clamp(hsv.z * (1 + value_shift), 0, 1);
|
||||||
return rl.colorFromHSV(hsv.x, hsv.y, hsv.z);
|
var new_color = rl.colorFromHSV(hsv.x, hsv.y, hsv.z);
|
||||||
|
new_color.a = color.a;
|
||||||
|
return new_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawUnderline(rect: rl.Rectangle, size: f32, color: rl.Color) void {
|
pub fn drawUnderline(rect: rl.Rectangle, size: f32, color: rl.Color) void {
|
||||||
|
Loading…
Reference in New Issue
Block a user