add sample rate input
This commit is contained in:
parent
293d220b34
commit
23c4b99455
74
src/app.zig
74
src/app.zig
@ -292,6 +292,14 @@ pub const Project = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDefaultSampleRate(self: *Project) ?f64 {
|
pub fn getDefaultSampleRate(self: *Project) ?f64 {
|
||||||
|
if (self.getAllowedSampleRates()) |range| {
|
||||||
|
return range.upper;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getAllowedSampleRates(self: *Project) ?RangeF64 {
|
||||||
var result_range: ?RangeF64 = null;
|
var result_range: ?RangeF64 = null;
|
||||||
|
|
||||||
var channel_iter = self.channels.iterator();
|
var channel_iter = self.channels.iterator();
|
||||||
@ -308,11 +316,7 @@ pub const Project = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result_range) |r| {
|
return result_range;
|
||||||
return r.upper;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Project, allocator: Allocator) void {
|
pub fn deinit(self: *Project, allocator: Allocator) void {
|
||||||
@ -561,6 +565,7 @@ pub const CollectionTask = struct {
|
|||||||
|
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
ui: UI,
|
ui: UI,
|
||||||
|
double_pass_ui: bool = true,
|
||||||
should_close: bool = false,
|
should_close: bool = false,
|
||||||
ni_daq_api: ?NIDaq.Api = null,
|
ni_daq_api: ?NIDaq.Api = null,
|
||||||
ni_daq: ?NIDaq = null,
|
ni_daq: ?NIDaq = null,
|
||||||
@ -586,7 +591,7 @@ pub fn init(self: *App, allocator: Allocator) !void {
|
|||||||
.main_screen = undefined,
|
.main_screen = undefined,
|
||||||
.collection_thread = undefined
|
.collection_thread = undefined
|
||||||
};
|
};
|
||||||
self.main_screen = try MainScreen.init(self);
|
try self.initUI();
|
||||||
|
|
||||||
if (NIDaq.Api.init()) |ni_daq_api| {
|
if (NIDaq.Api.init()) |ni_daq_api| {
|
||||||
self.ni_daq_api = ni_daq_api;
|
self.ni_daq_api = ni_daq_api;
|
||||||
@ -624,8 +629,7 @@ pub fn deinit(self: *App) void {
|
|||||||
self.collection_condition.signal();
|
self.collection_condition.signal();
|
||||||
self.collection_thread.join();
|
self.collection_thread.join();
|
||||||
|
|
||||||
self.ui.deinit();
|
self.deinitUI();
|
||||||
self.main_screen.deinit();
|
|
||||||
|
|
||||||
if (self.ni_daq) |*ni_daq| {
|
if (self.ni_daq) |*ni_daq| {
|
||||||
ni_daq.deinit(self.allocator);
|
ni_daq.deinit(self.allocator);
|
||||||
@ -636,12 +640,24 @@ pub fn deinit(self: *App) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinitProject(self: *App) void {
|
fn deinitProject(self: *App) void {
|
||||||
self.stopCollection();
|
self.stopCollection();
|
||||||
self.project.deinit(self.allocator);
|
self.project.deinit(self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadProject(self: *App) !void {
|
fn initUI(self: *App) !void {
|
||||||
|
self.ui = UI.init(self.allocator);
|
||||||
|
self.main_screen = try MainScreen.init(self);
|
||||||
|
self.screen = .main;
|
||||||
|
self.double_pass_ui = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinitUI(self: *App) void {
|
||||||
|
self.ui.deinit();
|
||||||
|
self.main_screen.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadProject(self: *App) !void {
|
||||||
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
|
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
|
||||||
|
|
||||||
log.info("Load project from: {s}", .{save_location});
|
log.info("Load project from: {s}", .{save_location});
|
||||||
@ -672,9 +688,12 @@ pub fn loadProject(self: *App) !void {
|
|||||||
log.err("Failed to load view: {}", .{ e });
|
log.err("Failed to load view: {}", .{ e });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.deinitUI();
|
||||||
|
self.initUI() catch @panic("Failed to initialize UI, can't recover");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn saveProject(self: *App) !void {
|
fn saveProject(self: *App) !void {
|
||||||
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
|
const save_location = self.project.save_location orelse return error.MissingSaveLocation;
|
||||||
|
|
||||||
log.info("Save project to: {s}", .{save_location});
|
log.info("Save project to: {s}", .{save_location});
|
||||||
@ -682,6 +701,20 @@ pub fn saveProject(self: *App) !void {
|
|||||||
try self.project.save();
|
try self.project.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn showUI(self: *App) !void {
|
||||||
|
var ui = &self.ui;
|
||||||
|
|
||||||
|
ui.begin();
|
||||||
|
defer ui.end();
|
||||||
|
|
||||||
|
switch (self.screen) {
|
||||||
|
.main => try self.main_screen.tick(),
|
||||||
|
.add_channels => {
|
||||||
|
self.screen = .main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tick(self: *App) !void {
|
pub fn tick(self: *App) !void {
|
||||||
var ui = &self.ui;
|
var ui = &self.ui;
|
||||||
self.command_queue.len = 0;
|
self.command_queue.len = 0;
|
||||||
@ -728,17 +761,17 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.begin();
|
if (rl.isWindowResized() or self.double_pass_ui) {
|
||||||
defer ui.end();
|
try self.showUI();
|
||||||
|
self.double_pass_ui = false;
|
||||||
switch (self.screen) {
|
|
||||||
.main => try self.main_screen.tick(),
|
|
||||||
.add_channels => {
|
|
||||||
self.screen = .main;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try self.showUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.clearBackground(srcery.black);
|
||||||
|
ui.draw();
|
||||||
|
|
||||||
for (self.command_queue.constSlice()) |command| {
|
for (self.command_queue.constSlice()) |command| {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
.start_collection => {
|
.start_collection => {
|
||||||
@ -775,9 +808,6 @@ pub fn tick(self: *App) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.clearBackground(srcery.black);
|
|
||||||
ui.draw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pushCommand(self: *App, command: Command) void {
|
pub fn pushCommand(self: *App, command: Command) void {
|
||||||
|
@ -46,6 +46,8 @@ view_undo_stack: std.BoundedArray(ViewCommand, 100) = .{},
|
|||||||
protocol_modal: ?Id = null,
|
protocol_modal: ?Id = null,
|
||||||
frequency_input: UI.TextInputStorage,
|
frequency_input: UI.TextInputStorage,
|
||||||
amplitude_input: UI.TextInputStorage,
|
amplitude_input: UI.TextInputStorage,
|
||||||
|
sample_rate_input: UI.TextInputStorage,
|
||||||
|
parsed_sample_rate: ?f64 = null,
|
||||||
protocol_error_message: ?[]const u8 = null,
|
protocol_error_message: ?[]const u8 = null,
|
||||||
protocol_graph_cache: Graph.Cache = .{},
|
protocol_graph_cache: Graph.Cache = .{},
|
||||||
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
preview_samples: std.ArrayListUnmanaged(f64) = .{},
|
||||||
@ -58,6 +60,7 @@ pub fn init(app: *App) !MainScreen {
|
|||||||
.app = app,
|
.app = app,
|
||||||
.frequency_input = UI.TextInputStorage.init(allocator),
|
.frequency_input = UI.TextInputStorage.init(allocator),
|
||||||
.amplitude_input = UI.TextInputStorage.init(allocator),
|
.amplitude_input = UI.TextInputStorage.init(allocator),
|
||||||
|
.sample_rate_input = UI.TextInputStorage.init(allocator)
|
||||||
};
|
};
|
||||||
|
|
||||||
try self.frequency_input.setText("10");
|
try self.frequency_input.setText("10");
|
||||||
@ -71,6 +74,7 @@ pub fn deinit(self: *MainScreen) void {
|
|||||||
|
|
||||||
self.frequency_input.deinit();
|
self.frequency_input.deinit();
|
||||||
self.amplitude_input.deinit();
|
self.amplitude_input.deinit();
|
||||||
|
self.sample_rate_input.deinit();
|
||||||
self.preview_samples.clearAndFree(allocator);
|
self.preview_samples.clearAndFree(allocator);
|
||||||
|
|
||||||
self.clearProtocolErrorMessage();
|
self.clearProtocolErrorMessage();
|
||||||
@ -828,7 +832,10 @@ pub fn showProtocolModal(self: *MainScreen, channel_id: Id) !void {
|
|||||||
label_box.size.y = UI.Sizing.initGrowFull();
|
label_box.size.y = UI.Sizing.initGrowFull();
|
||||||
label_box.alignment.y = .center;
|
label_box.alignment.y = .center;
|
||||||
|
|
||||||
try ui.textInput(ui.keyFromString("Text input"), text_input_storage);
|
try ui.textInput(.{
|
||||||
|
.key = ui.keyFromString("Text input"),
|
||||||
|
.storage = text_input_storage
|
||||||
|
});
|
||||||
|
|
||||||
any_input_modified = any_input_modified or text_input_storage.modified;
|
any_input_modified = any_input_modified or text_input_storage.modified;
|
||||||
}
|
}
|
||||||
@ -918,18 +925,57 @@ fn clearProtocolErrorMessage(self: *MainScreen) void {
|
|||||||
self.protocol_error_message = null;
|
self.protocol_error_message = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(self: *MainScreen) !void {
|
pub fn showSidePanel(self: *MainScreen) !void {
|
||||||
var ui = &self.app.ui;
|
var ui = &self.app.ui;
|
||||||
|
const frame_allocator = ui.frameAllocator();
|
||||||
|
|
||||||
if (ui.isKeyboardPressed(.key_escape)) {
|
const container = ui.createBox(.{
|
||||||
if (self.protocol_modal != null) {
|
.size_x = UI.Sizing.initGrowUpTo(.{ .parent_percent = 0.2 }),
|
||||||
self.closeModal();
|
.size_y = UI.Sizing.initGrowFull(),
|
||||||
} else if (self.fullscreen_view != null) {
|
.borders = .{
|
||||||
self.fullscreen_view = null;
|
.right = .{ .color = srcery.hard_black, .size = 4 }
|
||||||
} else {
|
},
|
||||||
self.app.should_close = true;
|
.layout_direction = .top_to_bottom,
|
||||||
|
.padding = UI.Padding.all(ui.rem(1))
|
||||||
|
});
|
||||||
|
container.beginChildren();
|
||||||
|
defer container.endChildren();
|
||||||
|
|
||||||
|
const project = &self.app.project;
|
||||||
|
|
||||||
|
{
|
||||||
|
var placeholder: ?[]const u8 = null;
|
||||||
|
if (project.getDefaultSampleRate()) |sample_rate| {
|
||||||
|
placeholder = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
||||||
|
}
|
||||||
|
|
||||||
|
var initial: ?[]const u8 = null;
|
||||||
|
if (project.sample_rate) |sample_rate| {
|
||||||
|
initial = try std.fmt.allocPrint(frame_allocator, "{d}", .{ sample_rate });
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = ui.label("Sample rate", .{});
|
||||||
|
self.parsed_sample_rate = try ui.numberInput(f64, .{
|
||||||
|
.key = ui.keyFromString("Sample rate input"),
|
||||||
|
.storage = &self.sample_rate_input,
|
||||||
|
.placeholder = placeholder,
|
||||||
|
.initial = initial,
|
||||||
|
.invalid = self.parsed_sample_rate != project.sample_rate
|
||||||
|
});
|
||||||
|
project.sample_rate = self.parsed_sample_rate;
|
||||||
|
|
||||||
|
if (project.getAllowedSampleRates()) |allowed_sample_rates| {
|
||||||
|
if (project.sample_rate) |sample_rate| {
|
||||||
|
if (!allowed_sample_rates.hasInclusive(sample_rate)) {
|
||||||
|
project.sample_rate = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick(self: *MainScreen) !void {
|
||||||
|
var ui = &self.app.ui;
|
||||||
|
|
||||||
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
|
if (ui.isCtrlDown() and ui.isKeyboardPressed(.key_z)) {
|
||||||
self.undoLastMoveCommand();
|
self.undoLastMoveCommand();
|
||||||
@ -1016,6 +1062,16 @@ pub fn tick(self: *MainScreen) !void {
|
|||||||
try self.showView(view_id, UI.Sizing.initGrowFull());
|
try self.showView(view_id, UI.Sizing.initGrowFull());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
const container = ui.createBox(.{
|
||||||
|
.size_x = UI.Sizing.initGrowFull(),
|
||||||
|
.size_y = UI.Sizing.initGrowFull(),
|
||||||
|
.layout_direction = .left_to_right
|
||||||
|
});
|
||||||
|
container.beginChildren();
|
||||||
|
defer container.endChildren();
|
||||||
|
|
||||||
|
try self.showSidePanel();
|
||||||
|
|
||||||
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
|
const scroll_area = ui.beginScrollbar(ui.keyFromString("Channels"));
|
||||||
defer ui.endScrollbar();
|
defer ui.endScrollbar();
|
||||||
scroll_area.layout_direction = .top_to_bottom;
|
scroll_area.layout_direction = .top_to_bottom;
|
||||||
@ -1055,4 +1111,14 @@ pub fn tick(self: *MainScreen) !void {
|
|||||||
if (maybe_modal_overlay) |modal_overlay| {
|
if (maybe_modal_overlay) |modal_overlay| {
|
||||||
root.bringChildToTop(modal_overlay);
|
root.bringChildToTop(modal_overlay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ui.isKeyboardPressed(.key_escape)) {
|
||||||
|
if (self.protocol_modal != null) {
|
||||||
|
self.closeModal();
|
||||||
|
} else if (self.fullscreen_view != null) {
|
||||||
|
self.fullscreen_view = null;
|
||||||
|
} else {
|
||||||
|
self.app.should_close = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
106
src/ui.zig
106
src/ui.zig
@ -113,6 +113,7 @@ pub const Signal = struct {
|
|||||||
active: bool = false,
|
active: bool = false,
|
||||||
shift_modifier: bool = false,
|
shift_modifier: bool = false,
|
||||||
ctrl_modifier: bool = false,
|
ctrl_modifier: bool = false,
|
||||||
|
clicked_outside: bool = false,
|
||||||
|
|
||||||
pub fn clicked(self: Signal) bool {
|
pub fn clicked(self: Signal) bool {
|
||||||
return self.flags.contains(.left_clicked) or self.flags.contains(.right_clicked) or self.flags.contains(.middle_clicked);
|
return self.flags.contains(.left_clicked) or self.flags.contains(.right_clicked) or self.flags.contains(.middle_clicked);
|
||||||
@ -479,6 +480,7 @@ pub const Box = struct {
|
|||||||
|
|
||||||
ui: *UI,
|
ui: *UI,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
created: bool = false,
|
||||||
|
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
background: ?rl.Color,
|
background: ?rl.Color,
|
||||||
@ -493,6 +495,7 @@ pub const Box = struct {
|
|||||||
padding: Padding,
|
padding: Padding,
|
||||||
font: Assets.FontId,
|
font: Assets.FontId,
|
||||||
text_color: rl.Color,
|
text_color: rl.Color,
|
||||||
|
// TODO: Add option to specify where the border is drawn: outside, inside, center.
|
||||||
borders: Borders,
|
borders: Borders,
|
||||||
text: ?[]u8,
|
text: ?[]u8,
|
||||||
hot_cursor: ?rl.MouseCursor,
|
hot_cursor: ?rl.MouseCursor,
|
||||||
@ -816,12 +819,12 @@ pub fn deinit(self: *UI) void {
|
|||||||
self.arenas[1].deinit();
|
self.arenas[1].deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame_arena(self: *UI) *std.heap.ArenaAllocator {
|
pub fn frameArena(self: *UI) *std.heap.ArenaAllocator {
|
||||||
return &self.arenas[@mod(self.frame_index, 2)];
|
return &self.arenas[@mod(self.frame_index, 2)];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frameAllocator(self: *UI) std.mem.Allocator {
|
pub fn frameAllocator(self: *UI) std.mem.Allocator {
|
||||||
return self.frame_arena().allocator();
|
return self.frameArena().allocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pullOsEvents(self: *UI) void {
|
pub fn pullOsEvents(self: *UI) void {
|
||||||
@ -902,7 +905,7 @@ pub fn begin(self: *UI) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.frame_index += 1;
|
self.frame_index += 1;
|
||||||
_ = self.frame_arena().reset(.retain_capacity);
|
_ = self.frameArena().reset(.retain_capacity);
|
||||||
|
|
||||||
self.pushFont(default_font);
|
self.pushFont(default_font);
|
||||||
|
|
||||||
@ -1452,6 +1455,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
|||||||
var box_index: ?BoxIndex = null;
|
var box_index: ?BoxIndex = null;
|
||||||
var key = opts.key orelse Key.initNil();
|
var key = opts.key orelse Key.initNil();
|
||||||
var persistent = Box.Persistent{};
|
var persistent = Box.Persistent{};
|
||||||
|
var created = false;
|
||||||
|
|
||||||
if (!key.isNil()) {
|
if (!key.isNil()) {
|
||||||
if (self.getBoxIndexByKey(key)) |last_frame_box_index| {
|
if (self.getBoxIndexByKey(key)) |last_frame_box_index| {
|
||||||
@ -1469,6 +1473,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
|||||||
if (box_index == null) {
|
if (box_index == null) {
|
||||||
box = self.boxes.addOneAssumeCapacity();
|
box = self.boxes.addOneAssumeCapacity();
|
||||||
box_index = self.boxes.len - 1;
|
box_index = self.boxes.len - 1;
|
||||||
|
created = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var size = Sizing2{
|
var size = Sizing2{
|
||||||
@ -1503,6 +1508,7 @@ pub fn createBox(self: *UI, opts: BoxOptions) *Box {
|
|||||||
box.* = Box{
|
box.* = Box{
|
||||||
.ui = self,
|
.ui = self,
|
||||||
.allocator = self.frameAllocator(),
|
.allocator = self.frameAllocator(),
|
||||||
|
.created = created,
|
||||||
|
|
||||||
.persistent = persistent,
|
.persistent = persistent,
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
@ -1897,6 +1903,10 @@ pub fn signal(self: *UI, box: *Box) Signal {
|
|||||||
taken = true;
|
taken = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event == .mouse_released and clickable and !is_mouse_inside) {
|
||||||
|
result.clicked_outside = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (event == .mouse_scroll and is_mouse_inside and scrollable) {
|
if (event == .mouse_scroll and is_mouse_inside and scrollable) {
|
||||||
result.scroll = event.mouse_scroll;
|
result.scroll = event.mouse_scroll;
|
||||||
result.flags.insert(.scrolled);
|
result.flags.insert(.scrolled);
|
||||||
@ -2193,8 +2203,24 @@ pub const TextInputStorage = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TextInputResult = struct {
|
pub const TextInputOptions = struct {
|
||||||
changed: bool = false
|
key: Key,
|
||||||
|
storage: *TextInputStorage,
|
||||||
|
|
||||||
|
initial: ?[]const u8 = null,
|
||||||
|
placeholder: ?[]const u8 = null,
|
||||||
|
text_color: rl.Color = srcery.black
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NumberInputOptions = struct {
|
||||||
|
key: Key,
|
||||||
|
storage: *TextInputStorage,
|
||||||
|
invalid: bool = false,
|
||||||
|
|
||||||
|
initial: ?[]const u8 = null,
|
||||||
|
placeholder: ?[]const u8 = null,
|
||||||
|
text_color: rl.Color = srcery.black,
|
||||||
|
invalid_color: rl.Color = srcery.red
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn mouseTooltip(self: *UI) *Box {
|
pub fn mouseTooltip(self: *UI) *Box {
|
||||||
@ -2320,11 +2346,11 @@ pub fn endScrollbar(self: *UI) void {
|
|||||||
wrapper.endChildren();
|
wrapper.endChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
pub fn textInput(self: *UI, opts: TextInputOptions) !void {
|
||||||
const now = std.time.nanoTimestamp();
|
const now = std.time.nanoTimestamp();
|
||||||
|
|
||||||
const container = self.createBox(.{
|
const container = self.createBox(.{
|
||||||
.key = key,
|
.key = opts.key,
|
||||||
.size_x = Sizing.initGrowUpTo(.{ .pixels = 200 }),
|
.size_x = Sizing.initGrowUpTo(.{ .pixels = 200 }),
|
||||||
.size_y = Sizing.initFixed(Unit.initPixels(self.rem(1))),
|
.size_y = Sizing.initFixed(Unit.initPixels(self.rem(1))),
|
||||||
.flags = &.{ .clickable, .clip_view, .draggable },
|
.flags = &.{ .clickable, .clip_view, .draggable },
|
||||||
@ -2336,13 +2362,26 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
defer container.endChildren();
|
defer container.endChildren();
|
||||||
|
|
||||||
const font = Assets.font(container.font);
|
const font = Assets.font(container.font);
|
||||||
const text = &storage.buffer;
|
const storage = opts.storage;
|
||||||
|
const storage_text = &storage.buffer;
|
||||||
|
|
||||||
|
if (opts.initial != null and container.created) {
|
||||||
|
storage_text.clearAndFree();
|
||||||
|
try storage_text.appendSlice(opts.initial.?);
|
||||||
|
}
|
||||||
|
|
||||||
const cursor_start_x = storage.getCharOffsetX(font, storage.cursor_start);
|
const cursor_start_x = storage.getCharOffsetX(font, storage.cursor_start);
|
||||||
const cursor_stop_x = storage.getCharOffsetX(font, storage.cursor_stop);
|
const cursor_stop_x = storage.getCharOffsetX(font, storage.cursor_stop);
|
||||||
|
|
||||||
{ // Text visuals
|
{ // Text visuals
|
||||||
const text_size = font.measureText(text.items);
|
var text_color = opts.text_color;
|
||||||
|
var text: []const u8 = storage_text.items;
|
||||||
|
if (opts.placeholder != null and text.len == 0) {
|
||||||
|
text = opts.placeholder.?;
|
||||||
|
text_color = text_color.alpha(0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
const text_size = font.measureText(text);
|
||||||
const visible_text_width = container.persistent.size.x - container.padding.byAxis(.X);
|
const visible_text_width = container.persistent.size.x - container.padding.byAxis(.X);
|
||||||
|
|
||||||
const shown_window_size = @min(visible_text_width, text_size.x);
|
const shown_window_size = @min(visible_text_width, text_size.x);
|
||||||
@ -2369,8 +2408,8 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ = self.createBox(.{
|
_ = self.createBox(.{
|
||||||
.text_color = srcery.black,
|
.text_color = text_color,
|
||||||
.text = text.items,
|
.text = text,
|
||||||
.float_relative_to = container,
|
.float_relative_to = container,
|
||||||
.float_rect = Rect{
|
.float_rect = Rect{
|
||||||
.x = container.padding.left - storage.shown_slice_start,
|
.x = container.padding.left - storage.shown_slice_start,
|
||||||
@ -2461,7 +2500,7 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
storage.cursor_stop = @intCast(std.math.clamp(
|
storage.cursor_stop = @intCast(std.math.clamp(
|
||||||
cursor_stop + move_cursor_dir,
|
cursor_stop + move_cursor_dir,
|
||||||
0,
|
0,
|
||||||
@as(isize, @intCast(text.items.len))
|
@as(isize, @intCast(storage_text.items.len))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2494,7 +2533,7 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
cursor = @intCast(std.math.clamp(
|
cursor = @intCast(std.math.clamp(
|
||||||
@as(isize, @intCast(cursor)) + move_cursor_dir,
|
@as(isize, @intCast(cursor)) + move_cursor_dir,
|
||||||
0,
|
0,
|
||||||
@as(isize, @intCast(text.items.len))
|
@as(isize, @intCast(storage_text.items.len))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2526,7 +2565,7 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deletion
|
// Deletion
|
||||||
if (self.isKeyboardPressedOrHeld(.key_backspace) and text.items.len > 0) {
|
if (self.isKeyboardPressedOrHeld(.key_backspace) and storage_text.items.len > 0) {
|
||||||
if (storage.cursor_start == storage.cursor_stop) {
|
if (storage.cursor_start == storage.cursor_stop) {
|
||||||
if (storage.cursor_start > 0) {
|
if (storage.cursor_start > 0) {
|
||||||
storage.deleteMany(storage.cursor_start-1, storage.cursor_start);
|
storage.deleteMany(storage.cursor_start-1, storage.cursor_start);
|
||||||
@ -2552,7 +2591,7 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
}
|
}
|
||||||
} else if (self.isKeyboardPressedOrHeld(.key_a)) {
|
} else if (self.isKeyboardPressedOrHeld(.key_a)) {
|
||||||
storage.cursor_start = 0;
|
storage.cursor_start = 0;
|
||||||
storage.cursor_stop = text.items.len;
|
storage.cursor_stop = storage_text.items.len;
|
||||||
|
|
||||||
} else if (self.isKeyboardPressedOrHeld(.key_c)) {
|
} else if (self.isKeyboardPressedOrHeld(.key_c)) {
|
||||||
if (storage.cursor_start != storage.cursor_stop) {
|
if (storage.cursor_start != storage.cursor_stop) {
|
||||||
@ -2593,5 +2632,42 @@ pub fn textInput(self: *UI, key: Key, storage: *TextInputStorage) !void {
|
|||||||
if (no_blinking) {
|
if (no_blinking) {
|
||||||
storage.last_pressed_at_ns = now;
|
storage.last_pressed_at_ns = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.isKeyboardPressed(.key_escape) or self.isKeyboardPressed(.key_enter)) {
|
||||||
|
storage.editing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container_signal.clicked_outside and !container_signal.is_mouse_inside) {
|
||||||
|
storage.editing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn numberInput(self: *UI, T: type, opts: NumberInputOptions) !?T {
|
||||||
|
const storage = opts.storage;
|
||||||
|
|
||||||
|
var text_opts = TextInputOptions{
|
||||||
|
.key = opts.key,
|
||||||
|
.storage = opts.storage,
|
||||||
|
.initial = opts.initial,
|
||||||
|
.text_color = opts.text_color,
|
||||||
|
.placeholder = opts.placeholder
|
||||||
|
};
|
||||||
|
|
||||||
|
var is_invalid = opts.invalid;
|
||||||
|
if (storage.buffer.items.len > 0 and std.meta.isError(std.fmt.parseFloat(T, storage.buffer.items))) {
|
||||||
|
is_invalid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_invalid) {
|
||||||
|
text_opts.text_color = opts.invalid_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.textInput(text_opts);
|
||||||
|
|
||||||
|
if (std.fmt.parseFloat(T, storage.buffer.items)) |new_value| {
|
||||||
|
return new_value;
|
||||||
|
} else |_| {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user