diff --git a/src/app.zig b/src/app.zig index 61054a2..639a4a7 100644 --- a/src/app.zig +++ b/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); } } diff --git a/src/font-face.zig b/src/font-face.zig index a96d61e..dc797c5 100644 --- a/src/font-face.zig +++ b/src/font-face.zig @@ -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 { diff --git a/src/graph.zig b/src/graph.zig index c292a51..92690b2 100644 --- a/src/graph.zig +++ b/src/graph.zig @@ -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, diff --git a/src/ui.zig b/src/ui.zig index 1775b6a..602143e 100644 --- a/src/ui.zig +++ b/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);