show scientific numbers on ruler

This commit is contained in:
Rokas Puzonas 2025-03-14 00:45:33 +02:00
parent 778c0f4cb9
commit 6e332df183
4 changed files with 595 additions and 274 deletions

View File

@ -103,9 +103,9 @@ device_channels: [max_channels]?DeviceChannel = .{ null } ** max_channels,
started_collecting: bool = false, started_collecting: bool = false,
graph_controls: struct { graph_controls: struct {
graph_start_sample: ?struct { drag_start: ?struct {
value: f64, index: f64,
axis: UI.Axis value: f64
} = null, } = null,
} = .{}, } = .{},
@ -452,7 +452,7 @@ fn showChannelViewGraph(self: *App, channel_view: *ChannelView) !void {
.size_x = UI.Sizing.initGrowFull(), .size_x = UI.Sizing.initGrowFull(),
.size_y = UI.Sizing.initGrowFull(), .size_y = UI.Sizing.initGrowFull(),
.background = srcery.black, .background = srcery.black,
.flags = &.{ .clickable, .draggable }, .flags = &.{ .clickable, .draggable, .scrollable },
}); });
graph_box.beginChildren(); graph_box.beginChildren();
defer graph_box.endChildren(); defer graph_box.endChildren();
@ -462,209 +462,417 @@ fn showChannelViewGraph(self: *App, channel_view: *ChannelView) !void {
const signal = self.ui.signal(graph_box); const signal = self.ui.signal(graph_box);
var axis = UI.Axis.X; var sample_value_under_mouse: ?f64 = null;
var zooming: bool = false; var sample_index_under_mouse: ?f64 = null;
var start_sample: ?f64 = null;
var stop_sample: ?f64 = null;
var controls = &self.graph_controls;
if (signal.hot) { if (signal.hot) {
if (signal.shift_modifier) { sample_index_under_mouse = channel_rect_opts.mapSampleXToIndex(0, graph_rect.width, signal.relative_mouse.x);
axis = UI.Axis.Y; sample_value_under_mouse = channel_rect_opts.mapSampleYToValue(0, graph_rect.height, signal.relative_mouse.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 (signal.dragged()) {
if (axis == .X) { var x_offset: f64 = 0;
graph_box.active_cursor = .mouse_cursor_resize_ew; var y_offset: f64 = 0;
} else {
graph_box.active_cursor = .mouse_cursor_resize_ns;
}
if (signal.flags.contains(.right_released)) { y_offset = remap(
controls.graph_start_sample = null; 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) { x_offset = remap(
const lower_sample: f64 = @min(start_sample.?, stop_sample.?); f64,
const higher_sample: f64 = @max(start_sample.?, stop_sample.?); 0, graph_rect.width,
0, channel_rect_opts.to - channel_rect_opts.from,
signal.drag.x
);
if (axis == .X) { channel_rect_opts.from -= @floatCast(x_offset);
if (higher_sample - lower_sample > 1) { channel_rect_opts.to -= @floatCast(x_offset);
channel_rect_opts.from = @floatCast(lower_sample); channel_rect_opts.max_value += @floatCast(y_offset);
channel_rect_opts.to = @floatCast(higher_sample); channel_rect_opts.min_value += @floatCast(y_offset);
} 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);
}
} }
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); Graph.drawCached(&channel_view.view_cache, graph_box.persistent.size, channel_rect_opts.*, samples);
if (channel_view.view_cache.texture) |texture| { if (channel_view.view_cache.texture) |texture| {
graph_box.texture = 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 { fn showChannelView(self: *App, channel_view: *ChannelView, height: UI.Sizing) !void {
const zone2 = P.begin(@src(), "showChannelView"); const zone2 = P.begin(@src(), "showChannelView");
defer zone2.end(); defer zone2.end();
// const source = self.getChannelSource(channel_view) orelse return;
var ui = &self.ui; var ui = &self.ui;
const channel_rect_opts: *Graph.ViewOptions = &channel_view.view_rect;
const channel_view_box = ui.createBox(.{ const channel_view_box = ui.createBox(.{
.key = UI.Key.initPtr(channel_view), .key = UI.Key.initPtr(channel_view),
.layout_direction = .top_to_bottom, .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_y = ruler_size,
.size_x = UI.Sizing.initGrowFull(), .size_x = UI.Sizing.initGrowFull(),
.background = srcery.hard_black, .background = srcery.hard_black,
.flags = &.{ .clickable }, .flags = &.{ .clickable, .clip_view },
.hot_cursor = .mouse_cursor_pointing_hand .hot_cursor = .mouse_cursor_pointing_hand
}); });
} }
{ // {
const zone = P.begin(@src(), "Y markers"); // const zone = P.begin(@src(), "Y markers");
defer zone.end(); // defer zone.end();
y_markers.beginChildren(); // y_markers.beginChildren();
defer y_markers.endChildren(); // 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; // var axis_marker_size = min_gap_between_markers / y_axis_rect.height * y_range;
axis_marker_size = @ceil(axis_marker_size); // 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); // 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) { // while (marker < @min(channel_rect_opts.max_value, channel_view.default_max_value)) : (marker += axis_marker_size) {
const marker_box = ui.createBox(.{ // const marker_box = ui.createBox(.{
.background = rl.Color.yellow, // .background = rl.Color.yellow,
}); // });
marker_box.setFloatRect(.{ // marker_box.setFloatRect(.{
.width = y_axis_rect.width/5, // .width = y_axis_rect.width/5,
.height = 1, // .height = 1,
.x = y_axis_rect.x + y_axis_rect.width/5*4, // .x = y_axis_rect.x + y_axis_rect.width/5*4,
.y = @floatCast(remap( // .y = @floatCast(remap(
f64, // f64,
channel_rect_opts.max_value, channel_rect_opts.min_value, // channel_rect_opts.max_value, channel_rect_opts.min_value,
y_axis_rect.y, y_axis_rect.y + y_axis_rect.height, // y_axis_rect.y, y_axis_rect.y + y_axis_rect.height,
marker // marker
)) // ))
}); // });
} // }
} // }
{ {
const zone = P.begin(@src(), "X markers"); 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(); x_markers.beginChildren();
defer x_markers.endChildren(); defer x_markers.endChildren();
const y_axis_rect = x_markers.rect(); self.showChannelViewRuler(channel_view, .X);
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,
});
}
} }
} }

View File

@ -2,10 +2,73 @@ const std = @import("std");
const rl = @import("raylib"); const rl = @import("raylib");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const FontFace = @This();
font: rl.Font, font: rl.Font,
spacing: ?f32 = null, spacing: ?f32 = null,
line_height: f32 = 1.4, 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 { pub fn getSpacing(self: @This()) f32 {
if (self.spacing) |spacing| { if (self.spacing) |spacing| {
return spacing; return spacing;
@ -52,40 +115,16 @@ pub fn measureTextLines(self: @This(), lines: []const []const u8) rl.Vector2 {
return text_size; 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; if (self.font.texture.id == 0) return;
const font_size = self.getSize(); var ctx = DrawTextContext{
const spacing = self.getSpacing(); .tint = tint,
.font_face = self,
.origin = position
};
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 }; ctx.drawText(text);
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);
} }
pub fn drawTextAlloc(self: @This(), allocator: Allocator, comptime fmt: []const u8, args: anytype, position: rl.Vector2, tint: rl.Color) !void { pub fn drawTextAlloc(self: @This(), allocator: Allocator, comptime fmt: []const u8, args: anytype, position: rl.Vector2, tint: rl.Color) !void {

View File

@ -6,6 +6,7 @@ const srcery = @import("./srcery.zig");
const remap = @import("./utils.zig").remap; const remap = @import("./utils.zig").remap;
const assert = std.debug.assert; const assert = std.debug.assert;
const Vec2 = rl.Vector2; const Vec2 = rl.Vector2;
const Rect = rl.Rectangle;
const clamp = std.math.clamp; const clamp = std.math.clamp;
const disable_caching = false; 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 { pub const Cache = struct {
texture: ?rl.RenderTexture2D = null, texture: ?rl.RenderTexture2D = null,
options: ?ViewOptions = null, options: ?ViewOptions = null,

View File

@ -6,6 +6,11 @@ const rect_utils = @import("./rect-utils.zig");
const utils = @import("./utils.zig"); const utils = @import("./utils.zig");
const builtin = @import("builtin"); const builtin = @import("builtin");
const P = @import("profiler"); 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; const assert = std.debug.assert;
pub const Vec2 = rl.Vector2; pub const Vec2 = rl.Vector2;
@ -17,7 +22,7 @@ const Vec2Zero = Vec2{ .x = 0, .y = 0 };
const UI = @This(); const UI = @This();
const max_boxes = 512; const max_boxes = 512;
const max_events = 256; const max_events = 1024;
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 };
@ -69,7 +74,9 @@ pub const Event = union(enum) {
mouse_pressed: rl.MouseButton, mouse_pressed: rl.MouseButton,
mouse_released: rl.MouseButton, mouse_released: rl.MouseButton,
mouse_move: Vec2, mouse_move: Vec2,
mouse_scroll: Vec2 mouse_scroll: Vec2,
key_pressed: u32,
key_released: u32
}; };
pub const Signal = struct { pub const Signal = struct {
@ -461,6 +468,8 @@ pub const Box = struct {
view_offset: Vec2 = Vec2Zero, view_offset: Vec2 = Vec2Zero,
texture: ?rl.Texture2D = null, texture: ?rl.Texture2D = null,
texture_size: ?Vec2 = null, texture_size: ?Vec2 = null,
scientific_number: ?f64 = null,
scientific_precision: u32 = 1,
// 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,
@ -622,6 +631,9 @@ pub const BoxOptions = struct {
view_offset: ?Vec2 = null, view_offset: ?Vec2 = null,
texture: ?rl.Texture2D = null, texture: ?rl.Texture2D = null,
texture_size: ?Vec2 = 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$"); 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(); const mouse_wheel = rl.getMouseWheelMoveV();
if (mouse_wheel.x != 0 or mouse_wheel.y != 0) { if (mouse_wheel.x != 0 or mouse_wheel.y != 0) {
self.events.appendAssumeCapacity(Event{ .mouse_scroll = mouse_wheel }); 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, .view_offset = opts.view_offset orelse Vec2Zero,
.texture = opts.texture, .texture = opts.texture,
.texture_size = opts.texture_size, .texture_size = opts.texture_size,
.scientific_number = opts.scientific_number,
.scientific_precision = opts.scientific_precision orelse 1,
.last_used_frame = self.frame_index, .last_used_frame = self.frame_index,
.key = key, .key = key,
@ -1325,6 +1353,10 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
box.setFloatY(y); box.setFloatY(y);
} }
if (opts.float_rect) |rect| {
box.setFloatRect(rect);
}
if (self.parentBox()) |parent| { if (self.parentBox()) |parent| {
box.tree.parent_index = parent.tree.index; 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| { if (box.text) |text| {
const font_face = Assets.font(box.font); const font_face = Assets.font(box.font);
var text_position = box.persistent.position; var text_position = box.persistent.position;
@ -1472,22 +1507,22 @@ fn drawBox(self: *UI, box: *Box) void {
if (lines.len == 0) { if (lines.len == 0) {
const text_size = font_face.measureText(text); const text_size = font_face.measureText(text);
text_position.x += @max(available_width - text_size.x, 0) * box.alignment.x.getCoefficient(); text_position.x += (available_width - text_size.x) * alignment_x_coeff;
text_position.y += @max(available_height - text_size.y, 0) * box.alignment.y.getCoefficient(); text_position.y += (available_height - text_size.y) * alignment_y_coeff;
font_face.drawText(text, text_position, box.text_color); font_face.drawText(text, text_position, box.text_color);
} else { } else {
// TODO: Don't call `measureTextLines`, // TODO: Don't call `measureTextLines`,
// Because in the end `measureText` will be called twice for each line // Because in the end `measureText` will be called twice for each line
const text_size = font_face.measureTextLines(lines); const text_size = font_face.measureTextLines(lines);
text_position.x += @max(available_width - text_size.x, 0) * box.alignment.x.getCoefficient(); text_position.x += (available_width - text_size.x) * alignment_x_coeff;
text_position.y += @max(available_height - text_size.y, 0) * box.alignment.y.getCoefficient(); text_position.y += (available_height - text_size.y) * alignment_y_coeff;
var offset_y: f32 = 0; var offset_y: f32 = 0;
for (lines) |line| { for (lines) |line| {
const line_size = font_face.measureText(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( font_face.drawText(
line, 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 (draw_debug) {
if (self.isKeyActive(box.key)) { if (self.isKeyActive(box.key)) {
rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red); rl.drawRectangleLinesEx(box_rect, 3, rl.Color.red);