textEntryNumber
Parameters
- src:std.builtin.SourceLocation
- std.builtin.SourceLocation
- T:type
- type
- init_opts:TextEntryNumberInitOptions(T)
- TextEntryNumberInitOptions(T)
- opts:Options
- Options
Source
Implementation
pub fn textEntryNumber(src: std.builtin.SourceLocation, comptime T: type, init_opts: TextEntryNumberInitOptions(T), opts: Options) TextEntryNumberResult(T) {
const default_opts: Options = .{
.role = .number_input,
};
const base_filter = "1234567890";
const filter = switch (@typeInfo(T)) {
.int => |int| switch (int.signedness) {
.signed => base_filter ++ "+-",
.unsigned => base_filter ++ "+",
},
.float => base_filter ++ "+-.e",
else => unreachable,
};
// @typeName is needed so that the id changes with the type for `data...` functions
// https://github.com/david-vanderson/dvui/issues/502
const id = dvui.parentGet().extendId(src, opts.idExtra()).update(@typeName(T));
const backing_buffer: [30]u8 = @splat(0);
const text_limit = init_opts.text_limit orelse 30;
const buffer = dataGetSliceDefault(null, id, "_buffer", []u8, &backing_buffer)[0..@min(text_limit, backing_buffer.len)];
// always initialize with value so we do the dataGet
if (init_opts.value) |num| {
const old_value = dataGet(null, id, "value", T);
if (old_value == null or old_value.? != num.*) {
dataSet(null, id, "value", num.*);
@memset(buffer, 0); // clear out anything that was there before
_ = std.fmt.bufPrint(buffer, "{d}", .{num.*}) catch unreachable;
}
}
// display min/max
var minmax_buffer: [64]u8 = undefined;
var minmax_text: []const u8 = "";
if (init_opts.show_min_max) {
if (init_opts.min != null and init_opts.max != null) {
minmax_text = std.fmt.bufPrint(&minmax_buffer, "(min: {d}, max: {d})", .{ init_opts.min.?, init_opts.max.? }) catch unreachable;
} else if (init_opts.min != null) {
minmax_text = std.fmt.bufPrint(&minmax_buffer, "(min: {d})", .{init_opts.min.?}) catch unreachable;
} else if (init_opts.max != null) {
minmax_text = std.fmt.bufPrint(&minmax_buffer, "(max: {d})", .{init_opts.max.?}) catch unreachable;
}
}
const font = default_opts.override(opts).fontGet();
var limit_size: ?Size = null;
if (init_opts.text_limit) |limit| {
limit_size = font.sizeM(limit, 1);
}
const options = default_opts.override(.{ .min_size_content = limit_size }).override(opts);
var te: TextEntryWidget = undefined;
te.init(src, .{
.text = .{ .buffer = buffer },
.placeholder = init_opts.placeholder orelse if (init_opts.show_min_max) minmax_text else null,
}, options);
// if text was given, act like the user deleted everything and typed this
if (init_opts.text) |text| {
te.textSet(text, false);
}
te.processEvents();
// filter before drawing
te.filterIn(filter);
var result: TextEntryNumberResult(T) = .{ .enter_pressed = te.enter_pressed };
// validation
const text = te.getText();
const num = switch (@typeInfo(T)) {
.int => std.fmt.parseInt(T, text, 10) catch null,
.float => std.fmt.parseFloat(T, text) catch null,
else => unreachable,
};
//determine error if any
if (text.len == 0 and num == null) {
result.value = .Empty;
} else if (num == null) {
result.value = .Invalid;
} else if (num != null and init_opts.min != null and num.? < init_opts.min.?) {
result.value = .TooSmall;
} else if (num != null and init_opts.max != null and num.? > init_opts.max.?) {
result.value = .TooBig;
} else {
result.value = .{ .Valid = num.? };
if (init_opts.value) |value_ptr| {
if ((te.enter_pressed or te.text_changed) and value_ptr.* != num.?) {
dataSet(null, id, "value", num.?);
value_ptr.* = num.?;
result.changed = true;
}
}
}
te.draw();
if (result.value != .Valid and (init_opts.value != null or result.value != .Empty)) {
const rs = te.data().borderRectScale();
rs.r.outsetAll(1).stroke(te.data().options.cornersGet().scale(rs.s, CornerRect.Physical), .{ .thickness = 3 * rs.s, .color = dvui.themeGet().err.fill orelse .red, .after = true });
}
if (te.data().accesskit_node()) |ak_node| {
AccessKit.nodeClearValue(ak_node); // Only set a numberic value
if (@typeInfo(T) == .float) {
if (init_opts.min) |min| AccessKit.nodeSetMinNumericValue(ak_node, @floatCast(min));
if (init_opts.max) |max| AccessKit.nodeSetMinNumericValue(ak_node, @floatCast(max));
if (num) |value|
AccessKit.nodeSetNumericValue(ak_node, @floatCast(value));
} else {
if (init_opts.min) |min| AccessKit.nodeSetMinNumericValue(ak_node, @floatFromInt(min));
if (init_opts.max) |max| AccessKit.nodeSetMinNumericValue(ak_node, @floatFromInt(max));
if (num) |value|
AccessKit.nodeSetNumericValue(ak_node, @floatFromInt(value));
}
if (result.value == .Valid) {
AccessKit.nodeClearInvalid(ak_node);
} else {
AccessKit.nodeSetInvalid(ak_node, AccessKit.Invalid.ak_true);
AccessKit.nodeSetNumericValue(ak_node, 0);
}
}
te.deinit();
return result;
}