show scientific numbers on ruler
This commit is contained in:
parent
778c0f4cb9
commit
6e332df183
660
src/app.zig
660
src/app.zig
@ -103,9 +103,9 @@ device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
|
||||
started_collecting: bool = false,
|
||||
|
||||
graph_controls: struct {
|
||||
graph_start_sample: ?struct {
|
||||
value: f64,
|
||||
axis: UI.Axis
|
||||
drag_start: ?struct {
|
||||
index: f64,
|
||||
value: f64
|
||||
} = null,
|
||||
} = .{},
|
||||
|
||||
@ -452,7 +452,7 @@ fn showChannelViewGraph(self: *App, channel_view: *ChannelView) !void {
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.size_y = UI.Sizing.initGrowFull(),
|
||||
.background = srcery.black,
|
||||
.flags = &.{ .clickable, .draggable },
|
||||
.flags = &.{ .clickable, .draggable, .scrollable },
|
||||
});
|
||||
graph_box.beginChildren();
|
||||
defer graph_box.endChildren();
|
||||
@ -462,209 +462,417 @@ fn showChannelViewGraph(self: *App, channel_view: *ChannelView) !void {
|
||||
|
||||
const signal = self.ui.signal(graph_box);
|
||||
|
||||
var axis = UI.Axis.X;
|
||||
var zooming: bool = false;
|
||||
var start_sample: ?f64 = null;
|
||||
var stop_sample: ?f64 = null;
|
||||
|
||||
var controls = &self.graph_controls;
|
||||
var sample_value_under_mouse: ?f64 = null;
|
||||
var sample_index_under_mouse: ?f64 = null;
|
||||
|
||||
if (signal.hot) {
|
||||
if (signal.shift_modifier) {
|
||||
axis = UI.Axis.Y;
|
||||
} else {
|
||||
axis = UI.Axis.X;
|
||||
}
|
||||
|
||||
if (controls.graph_start_sample) |graph_start_sample| {
|
||||
axis = graph_start_sample.axis;
|
||||
zooming = true;
|
||||
}
|
||||
|
||||
// TODO: Don't use relative mouse movement, after a lock of sliding the point where you grabbed by drifts
|
||||
var mouse_sample: f64 = undefined;
|
||||
if (axis == .X) {
|
||||
const mouse_sample_index = channel_rect_opts.mapSampleXToIndex(0, graph_rect.width, signal.relative_mouse.x);
|
||||
mouse_sample = mouse_sample_index;
|
||||
} else if (axis == .Y) {
|
||||
const mouse_sample_value = channel_rect_opts.mapSampleYToValue(0, graph_rect.height, signal.relative_mouse.y);
|
||||
mouse_sample = mouse_sample_value;
|
||||
}
|
||||
|
||||
start_sample = mouse_sample;
|
||||
|
||||
if (signal.flags.contains(.right_pressed)) {
|
||||
controls.graph_start_sample = .{
|
||||
.value = mouse_sample,
|
||||
.axis = axis
|
||||
};
|
||||
}
|
||||
|
||||
if (controls.graph_start_sample) |graph_start_sample| {
|
||||
start_sample = graph_start_sample.value;
|
||||
stop_sample = mouse_sample;
|
||||
zooming = true;
|
||||
}
|
||||
sample_index_under_mouse = channel_rect_opts.mapSampleXToIndex(0, graph_rect.width, signal.relative_mouse.x);
|
||||
sample_value_under_mouse = channel_rect_opts.mapSampleYToValue(0, graph_rect.height, signal.relative_mouse.y);
|
||||
}
|
||||
|
||||
if (zooming) {
|
||||
if (axis == .X) {
|
||||
graph_box.active_cursor = .mouse_cursor_resize_ew;
|
||||
} else {
|
||||
graph_box.active_cursor = .mouse_cursor_resize_ns;
|
||||
}
|
||||
if (signal.dragged()) {
|
||||
var x_offset: f64 = 0;
|
||||
var y_offset: f64 = 0;
|
||||
|
||||
if (signal.flags.contains(.right_released)) {
|
||||
controls.graph_start_sample = null;
|
||||
y_offset = remap(
|
||||
f64,
|
||||
0, graph_rect.height,
|
||||
0, channel_rect_opts.max_value - channel_rect_opts.min_value,
|
||||
signal.drag.y
|
||||
);
|
||||
|
||||
if (start_sample != null and stop_sample != null) {
|
||||
const lower_sample: f64 = @min(start_sample.?, stop_sample.?);
|
||||
const higher_sample: f64 = @max(start_sample.?, stop_sample.?);
|
||||
x_offset = remap(
|
||||
f64,
|
||||
0, graph_rect.width,
|
||||
0, channel_rect_opts.to - channel_rect_opts.from,
|
||||
signal.drag.x
|
||||
);
|
||||
|
||||
if (axis == .X) {
|
||||
if (higher_sample - lower_sample > 1) {
|
||||
channel_rect_opts.from = @floatCast(lower_sample);
|
||||
channel_rect_opts.to = @floatCast(higher_sample);
|
||||
} else {
|
||||
// TODO: Show error message that selected range is too small
|
||||
}
|
||||
} else if (axis == .Y) {
|
||||
if (higher_sample - lower_sample > 0.001) {
|
||||
channel_rect_opts.min_value = lower_sample;
|
||||
channel_rect_opts.max_value = higher_sample;
|
||||
} else {
|
||||
// TODO: Show error message that selected range is too small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start_sample = null;
|
||||
stop_sample = null;
|
||||
}
|
||||
|
||||
if (start_sample != null and stop_sample != null) {
|
||||
|
||||
const fill = ui.createBox(.{
|
||||
.background = srcery.green.alpha(0.5),
|
||||
});
|
||||
|
||||
if (axis == .X) {
|
||||
const start_x = channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, start_sample.?);
|
||||
const stop_x = channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, stop_sample.?);
|
||||
|
||||
fill.setFloatRect(.{
|
||||
.x = @floatCast(@min(start_x, stop_x)),
|
||||
.y = graph_rect.y,
|
||||
.width = @floatCast(@abs(start_x - stop_x)),
|
||||
.height = graph_rect.height
|
||||
});
|
||||
} else if (axis == .Y) {
|
||||
const start_y = channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, start_sample.?);
|
||||
const stop_y = channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, stop_sample.?);
|
||||
|
||||
fill.setFloatRect(.{
|
||||
.x = graph_rect.x,
|
||||
.y = @floatCast(@min(start_y, stop_y)),
|
||||
.width = graph_rect.width,
|
||||
.height = @floatCast(@abs(start_y - stop_y)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (start_sample) |sample| {
|
||||
const marker = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
});
|
||||
|
||||
if (axis == .X) {
|
||||
const value = samples[@intFromFloat(sample)];
|
||||
marker.setFmtText("{d:0.2} | {d:0.6}", .{sample, value});
|
||||
marker.setFloatRect(UI.Rect{
|
||||
.x = @floatCast(channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, sample)),
|
||||
.y = graph_rect.y,
|
||||
.width = 1,
|
||||
.height = graph_rect.height
|
||||
});
|
||||
|
||||
} else if (axis == .Y) {
|
||||
marker.setFmtText("{d:0.2}", .{sample});
|
||||
marker.setFloatRect(UI.Rect{
|
||||
.x = graph_rect.x,
|
||||
.y = @floatCast(channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, sample)),
|
||||
.width = graph_rect.width,
|
||||
.height = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_sample) |sample| {
|
||||
const marker = ui.createBox(.{
|
||||
.background = srcery.green,
|
||||
});
|
||||
|
||||
marker.setFmtText("{d:0.2}", .{sample});
|
||||
if (axis == .X) {
|
||||
marker.setFloatRect(.{
|
||||
.x = @floatCast(channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, sample)),
|
||||
.y = graph_rect.y,
|
||||
.width = 1,
|
||||
.height = graph_rect.height
|
||||
});
|
||||
} else if (axis == .Y) {
|
||||
marker.setFloatRect(.{
|
||||
.x = graph_rect.x,
|
||||
.y = @floatCast(channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, sample)),
|
||||
.width = graph_rect.width,
|
||||
.height = 1
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (signal.dragged()) {
|
||||
const middle_mouse_drag = signal.flags.contains(.middle_dragging);
|
||||
var x_offset: f64 = 0;
|
||||
var y_offset: f64 = 0;
|
||||
|
||||
if (signal.shift_modifier or middle_mouse_drag) {
|
||||
y_offset = remap(
|
||||
f64,
|
||||
0, graph_rect.height,
|
||||
0, channel_rect_opts.max_value - channel_rect_opts.min_value,
|
||||
signal.drag.y
|
||||
);
|
||||
}
|
||||
|
||||
if (!signal.shift_modifier or middle_mouse_drag) {
|
||||
x_offset = remap(
|
||||
f64,
|
||||
0, graph_rect.width,
|
||||
0, channel_rect_opts.to - channel_rect_opts.from,
|
||||
signal.drag.x
|
||||
);
|
||||
}
|
||||
|
||||
channel_rect_opts.from -= @floatCast(x_offset);
|
||||
channel_rect_opts.to -= @floatCast(x_offset);
|
||||
channel_rect_opts.max_value += @floatCast(y_offset);
|
||||
channel_rect_opts.min_value += @floatCast(y_offset);
|
||||
}
|
||||
channel_rect_opts.from -= @floatCast(x_offset);
|
||||
channel_rect_opts.to -= @floatCast(x_offset);
|
||||
channel_rect_opts.max_value += @floatCast(y_offset);
|
||||
channel_rect_opts.min_value += @floatCast(y_offset);
|
||||
}
|
||||
|
||||
if (signal.scrolled() and sample_index_under_mouse != null and sample_value_under_mouse != null) {
|
||||
var scale_factor: f32 = 1;
|
||||
if (signal.scroll.y > 0) {
|
||||
scale_factor -= 0.1;
|
||||
} else {
|
||||
scale_factor += 0.1;
|
||||
}
|
||||
|
||||
const center_index: f32 = @floatCast(sample_index_under_mouse.?);
|
||||
channel_rect_opts.from = (channel_rect_opts.from - center_index) * scale_factor + center_index;
|
||||
channel_rect_opts.to = (channel_rect_opts.to - center_index) * scale_factor + center_index;
|
||||
|
||||
const center_value: f32 = @floatCast(sample_value_under_mouse.?);
|
||||
channel_rect_opts.min_value = (channel_rect_opts.min_value - center_value) * scale_factor + center_value;
|
||||
channel_rect_opts.max_value = (channel_rect_opts.max_value - center_value) * scale_factor + center_value;
|
||||
}
|
||||
|
||||
// var axis = UI.Axis.X;
|
||||
// var zooming: bool = false;
|
||||
// var start_sample: ?f64 = null;
|
||||
// var stop_sample: ?f64 = null;
|
||||
|
||||
// var controls = &self.graph_controls;
|
||||
|
||||
// if (signal.hot) {
|
||||
// if (signal.shift_modifier) {
|
||||
// axis = UI.Axis.Y;
|
||||
// } else {
|
||||
// axis = UI.Axis.X;
|
||||
// }
|
||||
|
||||
// if (controls.graph_start_sample) |graph_start_sample| {
|
||||
// axis = graph_start_sample.axis;
|
||||
// zooming = true;
|
||||
// }
|
||||
|
||||
// // TODO: Don't use relative mouse movement, after a lock of sliding the point where you grabbed by drifts
|
||||
// var mouse_sample: f64 = undefined;
|
||||
// if (axis == .X) {
|
||||
// const mouse_sample_index = channel_rect_opts.mapSampleXToIndex(0, graph_rect.width, signal.relative_mouse.x);
|
||||
// mouse_sample = mouse_sample_index;
|
||||
// } else if (axis == .Y) {
|
||||
// const mouse_sample_value = channel_rect_opts.mapSampleYToValue(0, graph_rect.height, signal.relative_mouse.y);
|
||||
// mouse_sample = mouse_sample_value;
|
||||
// }
|
||||
|
||||
// start_sample = mouse_sample;
|
||||
|
||||
// if (signal.flags.contains(.right_pressed)) {
|
||||
// controls.graph_start_sample = .{
|
||||
// .value = mouse_sample,
|
||||
// .axis = axis
|
||||
// };
|
||||
// }
|
||||
|
||||
// if (controls.graph_start_sample) |graph_start_sample| {
|
||||
// start_sample = graph_start_sample.value;
|
||||
// stop_sample = mouse_sample;
|
||||
// zooming = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (zooming) {
|
||||
// if (axis == .X) {
|
||||
// graph_box.active_cursor = .mouse_cursor_resize_ew;
|
||||
// } else {
|
||||
// graph_box.active_cursor = .mouse_cursor_resize_ns;
|
||||
// }
|
||||
|
||||
// if (signal.flags.contains(.right_released)) {
|
||||
// controls.graph_start_sample = null;
|
||||
|
||||
// if (start_sample != null and stop_sample != null) {
|
||||
// const lower_sample: f64 = @min(start_sample.?, stop_sample.?);
|
||||
// const higher_sample: f64 = @max(start_sample.?, stop_sample.?);
|
||||
|
||||
// if (axis == .X) {
|
||||
// if (higher_sample - lower_sample > 1) {
|
||||
// channel_rect_opts.from = @floatCast(lower_sample);
|
||||
// channel_rect_opts.to = @floatCast(higher_sample);
|
||||
// } else {
|
||||
// // TODO: Show error message that selected range is too small
|
||||
// }
|
||||
// } else if (axis == .Y) {
|
||||
// if (higher_sample - lower_sample > 0.001) {
|
||||
// channel_rect_opts.min_value = lower_sample;
|
||||
// channel_rect_opts.max_value = higher_sample;
|
||||
// } else {
|
||||
// // TODO: Show error message that selected range is too small
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// start_sample = null;
|
||||
// stop_sample = null;
|
||||
// }
|
||||
|
||||
// if (start_sample != null and stop_sample != null) {
|
||||
|
||||
// const fill = ui.createBox(.{
|
||||
// .background = srcery.green.alpha(0.5),
|
||||
// });
|
||||
|
||||
// if (axis == .X) {
|
||||
// const start_x = channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, start_sample.?);
|
||||
// const stop_x = channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, stop_sample.?);
|
||||
|
||||
// fill.setFloatRect(.{
|
||||
// .x = @floatCast(@min(start_x, stop_x)),
|
||||
// .y = graph_rect.y,
|
||||
// .width = @floatCast(@abs(start_x - stop_x)),
|
||||
// .height = graph_rect.height
|
||||
// });
|
||||
// } else if (axis == .Y) {
|
||||
// const start_y = channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, start_sample.?);
|
||||
// const stop_y = channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, stop_sample.?);
|
||||
|
||||
// fill.setFloatRect(.{
|
||||
// .x = graph_rect.x,
|
||||
// .y = @floatCast(@min(start_y, stop_y)),
|
||||
// .width = graph_rect.width,
|
||||
// .height = @floatCast(@abs(start_y - stop_y)),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (start_sample) |sample| {
|
||||
// const marker = ui.createBox(.{
|
||||
// .background = srcery.green,
|
||||
// });
|
||||
|
||||
// if (axis == .X) {
|
||||
// const value = samples[@intFromFloat(sample)];
|
||||
// marker.setFmtText("{d:0.2} | {d:0.6}", .{sample, value});
|
||||
// marker.setFloatRect(UI.Rect{
|
||||
// .x = @floatCast(channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, sample)),
|
||||
// .y = graph_rect.y,
|
||||
// .width = 1,
|
||||
// .height = graph_rect.height
|
||||
// });
|
||||
|
||||
// } else if (axis == .Y) {
|
||||
// marker.setFmtText("{d:0.2}", .{sample});
|
||||
// marker.setFloatRect(UI.Rect{
|
||||
// .x = graph_rect.x,
|
||||
// .y = @floatCast(channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, sample)),
|
||||
// .width = graph_rect.width,
|
||||
// .height = 1
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (stop_sample) |sample| {
|
||||
// const marker = ui.createBox(.{
|
||||
// .background = srcery.green,
|
||||
// });
|
||||
|
||||
// marker.setFmtText("{d:0.2}", .{sample});
|
||||
// if (axis == .X) {
|
||||
// marker.setFloatRect(.{
|
||||
// .x = @floatCast(channel_rect_opts.mapSampleIndexToX(graph_rect.x, graph_rect.width, sample)),
|
||||
// .y = graph_rect.y,
|
||||
// .width = 1,
|
||||
// .height = graph_rect.height
|
||||
// });
|
||||
// } else if (axis == .Y) {
|
||||
// marker.setFloatRect(.{
|
||||
// .x = graph_rect.x,
|
||||
// .y = @floatCast(channel_rect_opts.mapSampleValueToY(graph_rect.y, graph_rect.height, sample)),
|
||||
// .width = graph_rect.width,
|
||||
// .height = 1
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if (signal.dragged()) {
|
||||
// const middle_mouse_drag = signal.flags.contains(.middle_dragging);
|
||||
// var x_offset: f64 = 0;
|
||||
// var y_offset: f64 = 0;
|
||||
|
||||
// if (signal.shift_modifier or middle_mouse_drag) {
|
||||
// y_offset = remap(
|
||||
// f64,
|
||||
// 0, graph_rect.height,
|
||||
// 0, channel_rect_opts.max_value - channel_rect_opts.min_value,
|
||||
// signal.drag.y
|
||||
// );
|
||||
// }
|
||||
|
||||
// if (!signal.shift_modifier or middle_mouse_drag) {
|
||||
// x_offset = remap(
|
||||
// f64,
|
||||
// 0, graph_rect.width,
|
||||
// 0, channel_rect_opts.to - channel_rect_opts.from,
|
||||
// signal.drag.x
|
||||
// );
|
||||
// }
|
||||
|
||||
// channel_rect_opts.from -= @floatCast(x_offset);
|
||||
// channel_rect_opts.to -= @floatCast(x_offset);
|
||||
// channel_rect_opts.max_value += @floatCast(y_offset);
|
||||
// channel_rect_opts.min_value += @floatCast(y_offset);
|
||||
// }
|
||||
// }
|
||||
|
||||
Graph.drawCached(&channel_view.view_cache, graph_box.persistent.size, channel_rect_opts.*, samples);
|
||||
if (channel_view.view_cache.texture) |texture| {
|
||||
graph_box.texture = texture.texture;
|
||||
}
|
||||
}
|
||||
|
||||
fn showChannelViewRulerMarker(self: *App, channel_view: *ChannelView, rect: UI.Rect, axis: UI.Axis, index: f64, size: f32) void {
|
||||
assert(axis == .X);
|
||||
assert(0 <= size and size <= 1);
|
||||
|
||||
const view_with_rect = Graph.ViewOptionsWithRect{
|
||||
.view = channel_view.view_rect,
|
||||
.rect = rect,
|
||||
};
|
||||
|
||||
_ = self.ui.createBox(.{
|
||||
.background = srcery.yellow,
|
||||
.float_rect = .{
|
||||
.width = 1,
|
||||
.height = rect.height * size,
|
||||
.x = @floatCast(view_with_rect.mapSampleIndexToX(index)),
|
||||
.y = rect.y,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn showChannelViewRuler(self: *App, channel_view: *ChannelView, axis: UI.Axis) void {
|
||||
assert(axis == .X);
|
||||
|
||||
const channel_rect_opts: *Graph.ViewOptions = &channel_view.view_rect;
|
||||
|
||||
const source = self.getChannelSource(channel_view) orelse return;
|
||||
const samples = source.samples();
|
||||
source.lockSamples();
|
||||
defer source.unlockSamples();
|
||||
|
||||
const sample_count: f32 = @floatFromInt(samples.len);
|
||||
|
||||
var ui = &self.ui;
|
||||
const container = ui.parentBox().?;
|
||||
|
||||
const ruler_rect = container.rect();
|
||||
|
||||
const range_size = channel_view.default_to - channel_view.default_from;
|
||||
var subdivisions: f32 = 20;
|
||||
while (true) {
|
||||
assert(subdivisions > 0);
|
||||
const pixels_per_division = remap(
|
||||
f64,
|
||||
0, channel_rect_opts.to - channel_rect_opts.from,
|
||||
0, ruler_rect.width,
|
||||
range_size / subdivisions
|
||||
);
|
||||
|
||||
if (pixels_per_division < 50) {
|
||||
subdivisions /= 2;
|
||||
} else if (pixels_per_division > 200) {
|
||||
subdivisions *= 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const step = range_size / subdivisions;
|
||||
|
||||
const view_with_rect = Graph.ViewOptionsWithRect{
|
||||
.view = channel_view.view_rect,
|
||||
.rect = ruler_rect,
|
||||
};
|
||||
|
||||
const number_range = channel_rect_opts.to - channel_rect_opts.from;
|
||||
var precision: u32 = 1;
|
||||
if (number_range < 1_000) {
|
||||
precision = 5;
|
||||
} else if (number_range < 10_000) {
|
||||
precision = 5;
|
||||
} else if (number_range < 100_000) {
|
||||
precision = 3;
|
||||
} else if (number_range < 1_000_000) {
|
||||
precision = 2;
|
||||
}
|
||||
|
||||
{
|
||||
self.showChannelViewRulerMarker(
|
||||
channel_view,
|
||||
ruler_rect,
|
||||
axis,
|
||||
0,
|
||||
1
|
||||
);
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.float_rect = .{
|
||||
.width = 1,
|
||||
.height = ruler_rect.height/2,
|
||||
.x = @floatCast(view_with_rect.mapSampleIndexToX(0) - 4),
|
||||
.y = ruler_rect.y + ruler_rect.height/2,
|
||||
},
|
||||
.text = "0",
|
||||
.align_x = .end
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
self.showChannelViewRulerMarker(
|
||||
channel_view,
|
||||
ruler_rect,
|
||||
axis,
|
||||
sample_count,
|
||||
1
|
||||
);
|
||||
|
||||
const text = ui.createBox(.{
|
||||
.float_rect = .{
|
||||
.width = 1,
|
||||
.height = ruler_rect.height/2,
|
||||
.x = @floatCast(view_with_rect.mapSampleIndexToX(sample_count) + 4),
|
||||
.y = ruler_rect.y + ruler_rect.height/2,
|
||||
},
|
||||
.align_x = .start
|
||||
});
|
||||
text.setFmtText("{d:.0}", .{sample_count});
|
||||
}
|
||||
|
||||
{
|
||||
var marker = utils.roundNearestDown(f64, @max(channel_rect_opts.from, channel_view.default_from + step), step);
|
||||
while (marker < @min(channel_rect_opts.to, channel_view.default_to - step/2)) : (marker += step) {
|
||||
self.showChannelViewRulerMarker(
|
||||
channel_view,
|
||||
ruler_rect,
|
||||
axis,
|
||||
marker,
|
||||
0.5
|
||||
);
|
||||
|
||||
_ = ui.createBox(.{
|
||||
.float_rect = .{
|
||||
.width = 1,
|
||||
.height = ruler_rect.height/2,
|
||||
.x = @floatCast(view_with_rect.mapSampleIndexToX(marker)),
|
||||
.y = ruler_rect.y + ruler_rect.height/2,
|
||||
},
|
||||
.align_x = .center,
|
||||
.scientific_number = marker,
|
||||
.scientific_precision = precision
|
||||
});
|
||||
// if (marker >= 1_000_000) {
|
||||
// text.setFmtText("{d:.1}M", .{ marker / 1_000_000 });
|
||||
// } else {
|
||||
// text.setFmtText("{d:.0}K", .{ marker / 1_000 });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var marker = utils.roundNearestDown(f64, @max(channel_rect_opts.from, channel_view.default_from + step), step) - step/2;
|
||||
while (marker < @min(channel_rect_opts.to, channel_view.default_to)) : (marker += step) {
|
||||
self.showChannelViewRulerMarker(
|
||||
channel_view,
|
||||
ruler_rect,
|
||||
axis,
|
||||
marker,
|
||||
0.25
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn showChannelView(self: *App, channel_view: *ChannelView, height: UI.Sizing) !void {
|
||||
const zone2 = P.begin(@src(), "showChannelView");
|
||||
defer zone2.end();
|
||||
|
||||
// const source = self.getChannelSource(channel_view) orelse return;
|
||||
var ui = &self.ui;
|
||||
|
||||
const channel_rect_opts: *Graph.ViewOptions = &channel_view.view_rect;
|
||||
|
||||
const channel_view_box = ui.createBox(.{
|
||||
.key = UI.Key.initPtr(channel_view),
|
||||
.layout_direction = .top_to_bottom,
|
||||
@ -732,45 +940,45 @@ fn showChannelView(self: *App, channel_view: *ChannelView, height: UI.Sizing) !v
|
||||
.size_y = ruler_size,
|
||||
.size_x = UI.Sizing.initGrowFull(),
|
||||
.background = srcery.hard_black,
|
||||
.flags = &.{ .clickable },
|
||||
.flags = &.{ .clickable, .clip_view },
|
||||
.hot_cursor = .mouse_cursor_pointing_hand
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const zone = P.begin(@src(), "Y markers");
|
||||
defer zone.end();
|
||||
// {
|
||||
// const zone = P.begin(@src(), "Y markers");
|
||||
// defer zone.end();
|
||||
|
||||
y_markers.beginChildren();
|
||||
defer y_markers.endChildren();
|
||||
// y_markers.beginChildren();
|
||||
// defer y_markers.endChildren();
|
||||
|
||||
const y_axis_rect = y_markers.rect();
|
||||
// const y_axis_rect = y_markers.rect();
|
||||
|
||||
const min_gap_between_markers = 8;
|
||||
// const min_gap_between_markers = 8;
|
||||
|
||||
const y_range = channel_rect_opts.max_value - channel_rect_opts.min_value;
|
||||
// const y_range = channel_rect_opts.max_value - channel_rect_opts.min_value;
|
||||
|
||||
var axis_marker_size = min_gap_between_markers / y_axis_rect.height * y_range;
|
||||
axis_marker_size = @ceil(axis_marker_size);
|
||||
// var axis_marker_size = min_gap_between_markers / y_axis_rect.height * y_range;
|
||||
// axis_marker_size = @ceil(axis_marker_size);
|
||||
|
||||
var marker = utils.roundNearestUp(f64, @max(channel_rect_opts.min_value, channel_view.default_min_value), axis_marker_size);
|
||||
while (marker < @min(channel_rect_opts.max_value, channel_view.default_max_value)) : (marker += axis_marker_size) {
|
||||
const marker_box = ui.createBox(.{
|
||||
.background = rl.Color.yellow,
|
||||
});
|
||||
marker_box.setFloatRect(.{
|
||||
.width = y_axis_rect.width/5,
|
||||
.height = 1,
|
||||
.x = y_axis_rect.x + y_axis_rect.width/5*4,
|
||||
.y = @floatCast(remap(
|
||||
f64,
|
||||
channel_rect_opts.max_value, channel_rect_opts.min_value,
|
||||
y_axis_rect.y, y_axis_rect.y + y_axis_rect.height,
|
||||
marker
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
// var marker = utils.roundNearestUp(f64, @max(channel_rect_opts.min_value, channel_view.default_min_value), axis_marker_size);
|
||||
// while (marker < @min(channel_rect_opts.max_value, channel_view.default_max_value)) : (marker += axis_marker_size) {
|
||||
// const marker_box = ui.createBox(.{
|
||||
// .background = rl.Color.yellow,
|
||||
// });
|
||||
// marker_box.setFloatRect(.{
|
||||
// .width = y_axis_rect.width/5,
|
||||
// .height = 1,
|
||||
// .x = y_axis_rect.x + y_axis_rect.width/5*4,
|
||||
// .y = @floatCast(remap(
|
||||
// f64,
|
||||
// channel_rect_opts.max_value, channel_rect_opts.min_value,
|
||||
// y_axis_rect.y, y_axis_rect.y + y_axis_rect.height,
|
||||
// marker
|
||||
// ))
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
{
|
||||
const zone = P.begin(@src(), "X markers");
|
||||
@ -779,27 +987,7 @@ fn showChannelView(self: *App, channel_view: *ChannelView, height: UI.Sizing) !v
|
||||
x_markers.beginChildren();
|
||||
defer x_markers.endChildren();
|
||||
|
||||
const y_axis_rect = x_markers.rect();
|
||||
|
||||
const axis_marker_size = 100000;
|
||||
|
||||
var marker = utils.roundNearestUp(f64, @max(channel_rect_opts.from, channel_view.default_from), axis_marker_size);
|
||||
while (marker < @min(channel_rect_opts.to, channel_view.default_to)) : (marker += axis_marker_size) {
|
||||
const marker_box = ui.createBox(.{
|
||||
.background = srcery.yellow
|
||||
});
|
||||
marker_box.setFloatRect(.{
|
||||
.width = 1,
|
||||
.height = y_axis_rect.height/2,
|
||||
.x = @floatCast(remap(
|
||||
f64,
|
||||
channel_rect_opts.from, channel_rect_opts.to,
|
||||
y_axis_rect.x, y_axis_rect.x + y_axis_rect.width,
|
||||
marker
|
||||
)),
|
||||
.y = y_axis_rect.y,
|
||||
});
|
||||
}
|
||||
self.showChannelViewRuler(channel_view, .X);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,73 @@ const std = @import("std");
|
||||
const rl = @import("raylib");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const FontFace = @This();
|
||||
|
||||
font: rl.Font,
|
||||
spacing: ?f32 = null,
|
||||
line_height: f32 = 1.4,
|
||||
|
||||
pub const DrawTextContext = struct {
|
||||
font_face: FontFace,
|
||||
tint: rl.Color,
|
||||
origin: rl.Vector2,
|
||||
offset: rl.Vector2 = .{ .x = 0, .y = 0 },
|
||||
|
||||
pub fn moveToNextLine(self: *DrawTextContext) void {
|
||||
const font_size = self.font_face.getSize();
|
||||
|
||||
self.offset.x = 0;
|
||||
self.offset.y += font_size * self.font_face.line_height;
|
||||
}
|
||||
|
||||
pub fn advanceX(self: *DrawTextContext, ox: f32) void {
|
||||
const font_size = self.font_face.getSize();
|
||||
|
||||
self.offset.x += ox * font_size;
|
||||
}
|
||||
|
||||
pub fn advanceY(self: *DrawTextContext, oy: f32) void {
|
||||
const font_size = self.font_face.getSize();
|
||||
|
||||
self.offset.y += oy * font_size;
|
||||
}
|
||||
|
||||
pub fn drawCodepoint(self: *DrawTextContext, codepoint: u21) void {
|
||||
if (codepoint == '\n') {
|
||||
self.moveToNextLine();
|
||||
|
||||
} else {
|
||||
const font = self.font_face.font;
|
||||
const font_size = self.font_face.getSize();
|
||||
const spacing = self.font_face.getSpacing();
|
||||
|
||||
if (!(codepoint <= 255 and std.ascii.isWhitespace(@intCast(codepoint)))) {
|
||||
var codepoint_position = self.origin.add(self.offset);
|
||||
codepoint_position.x = @round(codepoint_position.x);
|
||||
codepoint_position.y = @round(codepoint_position.y);
|
||||
rl.drawTextCodepoint(font, codepoint, codepoint_position, font_size, self.tint);
|
||||
}
|
||||
|
||||
const index: u32 = @intCast(rl.getGlyphIndex(font, codepoint));
|
||||
if (font.glyphs[index].advanceX != 0) {
|
||||
self.offset.x += @floatFromInt(font.glyphs[index].advanceX);
|
||||
} else {
|
||||
self.offset.x += font.recs[index].width;
|
||||
self.offset.x += @floatFromInt(font.glyphs[index].offsetX);
|
||||
}
|
||||
|
||||
self.offset.x += spacing;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawText(self: *DrawTextContext, text: []const u8) void {
|
||||
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
|
||||
while (iter.nextCodepoint()) |codepoint| {
|
||||
self.drawCodepoint(codepoint);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn getSpacing(self: @This()) f32 {
|
||||
if (self.spacing) |spacing| {
|
||||
return spacing;
|
||||
@ -52,40 +115,16 @@ pub fn measureTextLines(self: @This(), lines: []const []const u8) rl.Vector2 {
|
||||
return text_size;
|
||||
}
|
||||
|
||||
pub fn drawTextEx(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color, offset: *rl.Vector2) void {
|
||||
pub fn drawText(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
||||
if (self.font.texture.id == 0) return;
|
||||
|
||||
const font_size = self.getSize();
|
||||
const spacing = self.getSpacing();
|
||||
var ctx = DrawTextContext{
|
||||
.tint = tint,
|
||||
.font_face = self,
|
||||
.origin = position
|
||||
};
|
||||
|
||||
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
|
||||
while (iter.nextCodepoint()) |codepoint| {
|
||||
if (codepoint == '\n') {
|
||||
offset.x = 0;
|
||||
offset.y += font_size * self.line_height;
|
||||
} else {
|
||||
if (!(codepoint <= 255 and std.ascii.isWhitespace(@intCast(codepoint)))) {
|
||||
var codepoint_position = position.add(offset.*);
|
||||
codepoint_position.x = @round(codepoint_position.x);
|
||||
codepoint_position.y = @round(codepoint_position.y);
|
||||
rl.drawTextCodepoint(self.font, codepoint, codepoint_position, font_size, tint);
|
||||
}
|
||||
|
||||
const index: u32 = @intCast(rl.getGlyphIndex(self.font, codepoint));
|
||||
if (self.font.glyphs[index].advanceX != 0) {
|
||||
offset.x += @floatFromInt(self.font.glyphs[index].advanceX);
|
||||
} else {
|
||||
offset.x += self.font.recs[index].width;
|
||||
offset.x += @floatFromInt(self.font.glyphs[index].offsetX);
|
||||
}
|
||||
offset.x += spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawText(self: @This(), text: []const u8, position: rl.Vector2, tint: rl.Color) void {
|
||||
var offset = rl.Vector2.init(0, 0);
|
||||
self.drawTextEx(text, position, tint, &offset);
|
||||
ctx.drawText(text);
|
||||
}
|
||||
|
||||
pub fn drawTextAlloc(self: @This(), allocator: Allocator, comptime fmt: []const u8, args: anytype, position: rl.Vector2, tint: rl.Color) !void {
|
||||
|
@ -6,6 +6,7 @@ const srcery = @import("./srcery.zig");
|
||||
const remap = @import("./utils.zig").remap;
|
||||
const assert = std.debug.assert;
|
||||
const Vec2 = rl.Vector2;
|
||||
const Rect = rl.Rectangle;
|
||||
const clamp = std.math.clamp;
|
||||
|
||||
const disable_caching = false;
|
||||
@ -69,6 +70,15 @@ pub const ViewOptions = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const ViewOptionsWithRect = struct {
|
||||
view: ViewOptions,
|
||||
rect: Rect,
|
||||
|
||||
pub fn mapSampleIndexToX(self: ViewOptionsWithRect, index: f64) f64 {
|
||||
return self.view.mapSampleIndexToX(self.rect.x, self.rect.width, index);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Cache = struct {
|
||||
texture: ?rl.RenderTexture2D = null,
|
||||
options: ?ViewOptions = null,
|
||||
|
98
src/ui.zig
98
src/ui.zig
@ -6,6 +6,11 @@ const rect_utils = @import("./rect-utils.zig");
|
||||
const utils = @import("./utils.zig");
|
||||
const builtin = @import("builtin");
|
||||
const P = @import("profiler");
|
||||
const FontFace = @import("./font-face.zig");
|
||||
const raylib_h = @cImport({
|
||||
@cInclude("stdio.h");
|
||||
@cInclude("raylib.h");
|
||||
});
|
||||
|
||||
const assert = std.debug.assert;
|
||||
pub const Vec2 = rl.Vector2;
|
||||
@ -17,7 +22,7 @@ const Vec2Zero = Vec2{ .x = 0, .y = 0 };
|
||||
const UI = @This();
|
||||
|
||||
const max_boxes = 512;
|
||||
const max_events = 256;
|
||||
const max_events = 1024;
|
||||
const draw_debug = false; //builtin.mode == .Debug;
|
||||
const default_font = Assets.FontId{ .variant = .regular, .size = 16 };
|
||||
|
||||
@ -69,7 +74,9 @@ pub const Event = union(enum) {
|
||||
mouse_pressed: rl.MouseButton,
|
||||
mouse_released: rl.MouseButton,
|
||||
mouse_move: Vec2,
|
||||
mouse_scroll: Vec2
|
||||
mouse_scroll: Vec2,
|
||||
key_pressed: u32,
|
||||
key_released: u32
|
||||
};
|
||||
|
||||
pub const Signal = struct {
|
||||
@ -461,6 +468,8 @@ pub const Box = struct {
|
||||
view_offset: Vec2 = Vec2Zero,
|
||||
texture: ?rl.Texture2D = null,
|
||||
texture_size: ?Vec2 = null,
|
||||
scientific_number: ?f64 = null,
|
||||
scientific_precision: u32 = 1,
|
||||
|
||||
// Variables that you probably shouldn't be touching
|
||||
last_used_frame: u64 = 0,
|
||||
@ -622,6 +631,9 @@ pub const BoxOptions = struct {
|
||||
view_offset: ?Vec2 = null,
|
||||
texture: ?rl.Texture2D = null,
|
||||
texture_size: ?Vec2 = null,
|
||||
float_rect: ?Rect = null,
|
||||
scientific_number: ?f64 = null,
|
||||
scientific_precision: ?u32 = null
|
||||
};
|
||||
|
||||
pub const root_box_key = Key.initString(0, "$root$");
|
||||
@ -745,6 +757,20 @@ pub fn begin(self: *UI) void {
|
||||
}
|
||||
}
|
||||
|
||||
for (0..512) |key| {
|
||||
if (raylib_h.IsKeyPressed(@intCast(key))) {
|
||||
self.events.appendAssumeCapacity(Event{
|
||||
.key_pressed = @intCast(key)
|
||||
});
|
||||
}
|
||||
|
||||
if (raylib_h.IsKeyReleased(@intCast(key))) {
|
||||
self.events.appendAssumeCapacity(Event{
|
||||
.key_released = @intCast(key)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const mouse_wheel = rl.getMouseWheelMoveV();
|
||||
if (mouse_wheel.x != 0 or mouse_wheel.y != 0) {
|
||||
self.events.appendAssumeCapacity(Event{ .mouse_scroll = mouse_wheel });
|
||||
@ -1297,6 +1323,8 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
||||
.view_offset = opts.view_offset orelse Vec2Zero,
|
||||
.texture = opts.texture,
|
||||
.texture_size = opts.texture_size,
|
||||
.scientific_number = opts.scientific_number,
|
||||
.scientific_precision = opts.scientific_precision orelse 1,
|
||||
|
||||
.last_used_frame = self.frame_index,
|
||||
.key = key,
|
||||
@ -1325,6 +1353,10 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
||||
box.setFloatY(y);
|
||||
}
|
||||
|
||||
if (opts.float_rect) |rect| {
|
||||
box.setFloatRect(rect);
|
||||
}
|
||||
|
||||
if (self.parentBox()) |parent| {
|
||||
box.tree.parent_index = parent.tree.index;
|
||||
|
||||
@ -1459,6 +1491,9 @@ fn drawBox(self: *UI, box: *Box) void {
|
||||
);
|
||||
}
|
||||
|
||||
const alignment_x_coeff = box.alignment.x.getCoefficient();
|
||||
const alignment_y_coeff = box.alignment.y.getCoefficient();
|
||||
|
||||
if (box.text) |text| {
|
||||
const font_face = Assets.font(box.font);
|
||||
var text_position = box.persistent.position;
|
||||
@ -1472,22 +1507,22 @@ fn drawBox(self: *UI, box: *Box) void {
|
||||
|
||||
if (lines.len == 0) {
|
||||
const text_size = font_face.measureText(text);
|
||||
text_position.x += @max(available_width - text_size.x, 0) * box.alignment.x.getCoefficient();
|
||||
text_position.y += @max(available_height - text_size.y, 0) * box.alignment.y.getCoefficient();
|
||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||
|
||||
font_face.drawText(text, text_position, box.text_color);
|
||||
} else {
|
||||
// TODO: Don't call `measureTextLines`,
|
||||
// Because in the end `measureText` will be called twice for each line
|
||||
const text_size = font_face.measureTextLines(lines);
|
||||
text_position.x += @max(available_width - text_size.x, 0) * box.alignment.x.getCoefficient();
|
||||
text_position.y += @max(available_height - text_size.y, 0) * box.alignment.y.getCoefficient();
|
||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||
|
||||
var offset_y: f32 = 0;
|
||||
|
||||
for (lines) |line| {
|
||||
const line_size = font_face.measureText(line);
|
||||
const offset_x = @max(text_size.x - line_size.x, 0) * box.alignment.x.getCoefficient();
|
||||
const offset_x = (text_size.x - line_size.x) * alignment_x_coeff;
|
||||
|
||||
font_face.drawText(
|
||||
line,
|
||||
@ -1500,6 +1535,55 @@ fn drawBox(self: *UI, box: *Box) void {
|
||||
}
|
||||
}
|
||||
|
||||
if (box.scientific_number) |scientific_number| {
|
||||
const regular = Assets.font(box.font);
|
||||
const superscript = Assets.font(.{
|
||||
.size = box.font.size * 0.8,
|
||||
.variant = box.font.variant
|
||||
});
|
||||
|
||||
var text_position = box.persistent.position;
|
||||
text_position.x += box.padding.left;
|
||||
text_position.y += box.padding.top;
|
||||
|
||||
const available_width = box.availableChildrenSize(.X);
|
||||
const available_height = box.availableChildrenSize(.Y);
|
||||
|
||||
const exponent = @floor(std.math.log10(scientific_number));
|
||||
const multiplier = std.math.pow(f64, 10, exponent);
|
||||
const coefficient = scientific_number / multiplier;
|
||||
|
||||
// const text = std.fmt.allocPrint(box.allocator, "{d:.2}x10{d}", .{coefficient, exponent}) catch "";
|
||||
|
||||
var coefficient_buff: [256]u8 = undefined;
|
||||
const coefficient_str = std.fmt.formatFloat(&coefficient_buff, coefficient, .{ .mode = .decimal, .precision = box.scientific_precision }) catch "";
|
||||
const exponent_str = std.fmt.allocPrint(box.allocator, "{d:.0}", .{exponent}) catch "";
|
||||
|
||||
var text_size = regular.measureText(coefficient_str);
|
||||
text_size.x += regular.measureWidth("x10");
|
||||
text_size.x += superscript.measureWidth(exponent_str);
|
||||
|
||||
text_position.x += (available_width - text_size.x) * alignment_x_coeff;
|
||||
text_position.y += (available_height - text_size.y) * alignment_y_coeff;
|
||||
|
||||
var ctx = FontFace.DrawTextContext{
|
||||
.font_face = regular,
|
||||
.origin = text_position,
|
||||
.tint = box.text_color
|
||||
};
|
||||
|
||||
ctx.drawText(coefficient_str);
|
||||
ctx.advanceY(-0.04);
|
||||
ctx.advanceX(0.1);
|
||||
ctx.drawText("x");
|
||||
ctx.advanceY(0.04);
|
||||
ctx.drawText("10");
|
||||
|
||||
ctx.font_face = superscript;
|
||||
ctx.advanceY(-0.2);
|
||||
ctx.drawText(exponent_str);
|
||||
}
|
||||
|
||||
if (draw_debug) {
|
||||
if (self.isKeyActive(box.key)) {
|
||||
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);
|
||||
|
Loading…
Reference in New Issue
Block a user