derive tab labels
This commit is contained in:
parent
2cc1c4d05e
commit
bb0bb64da1
10 changed files with 78 additions and 28 deletions
65
src/tui.zig
65
src/tui.zig
|
|
@ -86,31 +86,44 @@ pub fn glyph(ch: u8) []const u8 {
|
|||
return " ";
|
||||
}
|
||||
|
||||
pub const Tab = enum {
|
||||
portfolio,
|
||||
quote,
|
||||
performance,
|
||||
options,
|
||||
earnings,
|
||||
analysis,
|
||||
history,
|
||||
projections,
|
||||
|
||||
fn label(self: Tab) []const u8 {
|
||||
return switch (self) {
|
||||
.portfolio => " 1:Portfolio ",
|
||||
.quote => " 2:Quote ",
|
||||
.performance => " 3:Performance ",
|
||||
.options => " 4:Options ",
|
||||
.earnings => " 5:Earnings ",
|
||||
.analysis => " 6:Analysis ",
|
||||
.history => " 7:History ",
|
||||
.projections => " 8:Projections ",
|
||||
};
|
||||
/// Tab enum derived from `tab_modules` registry. Each variant
|
||||
/// matches a registry field name; variant order = registry order
|
||||
/// = tab-bar display order. Adding a tab requires no edit here —
|
||||
/// just append to `tab_modules` and the variant appears.
|
||||
pub const Tab = blk: {
|
||||
const reg_fields = std.meta.fields(@TypeOf(tab_modules));
|
||||
var names: [reg_fields.len][]const u8 = undefined;
|
||||
var values: [reg_fields.len]u8 = undefined;
|
||||
for (reg_fields, 0..) |f, i| {
|
||||
names[i] = f.name;
|
||||
values[i] = @intCast(i);
|
||||
}
|
||||
break :blk @Enum(u8, .exhaustive, &names, &values);
|
||||
};
|
||||
|
||||
const tabs = [_]Tab{ .portfolio, .quote, .performance, .options, .earnings, .analysis, .history, .projections };
|
||||
/// Comptime lookup table of tab-bar display labels, indexed by
|
||||
/// `@intFromEnum(tab)`. Each entry is `" {N}:{label} "` composed
|
||||
/// from the 1-indexed registry position + the tab module's
|
||||
/// `pub const label`. The format (number prefix, padding) is
|
||||
/// framework policy; the bare name is owned by the tab module.
|
||||
const tab_labels = blk: {
|
||||
const reg_fields = std.meta.fields(@TypeOf(tab_modules));
|
||||
var arr: [reg_fields.len][]const u8 = undefined;
|
||||
for (reg_fields, 0..) |f, i| {
|
||||
const Module = @field(tab_modules, f.name);
|
||||
arr[i] = std.fmt.comptimePrint(" {d}:{s} ", .{ i + 1, Module.tab.label });
|
||||
}
|
||||
break :blk arr;
|
||||
};
|
||||
|
||||
fn tabLabel(t: Tab) []const u8 {
|
||||
return tab_labels[@intFromEnum(t)];
|
||||
}
|
||||
|
||||
/// All tab variants in registry order. Used for tab-bar iteration
|
||||
/// (rendering, hit-testing, next/prev navigation). Equivalent to
|
||||
/// `std.enums.values(Tab)`; aliased for brevity at call sites.
|
||||
const tabs: []const Tab = std.enums.values(Tab);
|
||||
|
||||
pub const InputMode = enum {
|
||||
normal,
|
||||
|
|
@ -645,7 +658,7 @@ pub const App = struct {
|
|||
if (mouse.row == 0) {
|
||||
var col: i16 = 0;
|
||||
for (tabs) |t| {
|
||||
const lbl_len: i16 = @intCast(t.label().len);
|
||||
const lbl_len: i16 = @intCast(tabLabel(t).len);
|
||||
if (mouse.col >= col and mouse.col < col + lbl_len) {
|
||||
if (self.isDisabled(t)) return;
|
||||
self.active_tab = t;
|
||||
|
|
@ -1667,7 +1680,7 @@ pub const App = struct {
|
|||
|
||||
var col: usize = 0;
|
||||
for (tabs) |t| {
|
||||
const lbl = t.label();
|
||||
const lbl = tabLabel(t);
|
||||
const is_active = t == self.active_tab;
|
||||
const is_disabled = self.isDisabled(t);
|
||||
const tab_style: vaxis.Style = if (is_active) th.tabActiveStyle() else if (is_disabled) th.tabDisabledStyle() else inactive_style;
|
||||
|
|
@ -2425,6 +2438,6 @@ test "SortDirection flip and indicator" {
|
|||
}
|
||||
|
||||
test "Tab label" {
|
||||
try testing.expectEqualStrings(" 1:Portfolio ", Tab.portfolio.label());
|
||||
try testing.expectEqualStrings(" 6:Analysis ", Tab.analysis.label());
|
||||
try testing.expectEqualStrings(" 1:Portfolio ", tabLabel(.portfolio));
|
||||
try testing.expectEqualStrings(" 6:Analysis ", tabLabel(.analysis));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Analysis";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).initFill("");
|
||||
pub const status_hints: []const Action = &.{};
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Earnings";
|
||||
|
||||
/// No tab-local bindings — refresh is global. Empty placeholder.
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "History";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .metric_next, .key = .{ .codepoint = 'm' } },
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Options";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
.{ .action = .expand_collapse, .key = .{ .codepoint = vaxis.Key.enter } },
|
||||
.{ .action = .collapse_all_calls, .key = .{ .codepoint = 'c' } },
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Performance";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{};
|
||||
pub const action_labels = std.enums.EnumArray(Action, []const u8).initFill("");
|
||||
pub const status_hints: []const Action = &.{};
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar. Composed by the framework
|
||||
/// into `" {N}:{label} "` where N is the registry index.
|
||||
pub const label: []const u8 = "Portfolio";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
// These are dead until scoped keymaps land — the global
|
||||
// keymap matches first. Declared per-tab so the help
|
||||
|
|
|
|||
|
|
@ -132,6 +132,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Projections";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
// Today's keybinds are in the global keymap (sort_reverse,
|
||||
// toggle_chart, toggle_events, projections_as_of_input).
|
||||
|
|
@ -213,8 +216,8 @@ pub const tab = struct {
|
|||
freeLoaded(state, app);
|
||||
state.loaded = false;
|
||||
loadData(state, app);
|
||||
const label = if (state.events_enabled) "Events enabled" else "Events disabled";
|
||||
app.setStatus(label);
|
||||
const status_msg = if (state.events_enabled) "Events enabled" else "Events disabled";
|
||||
app.setStatus(status_msg);
|
||||
},
|
||||
.as_of_input => {
|
||||
app.mode = .date_input;
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ pub const tab = struct {
|
|||
pub const ActionT = Action;
|
||||
pub const StateT = State;
|
||||
|
||||
/// Display name for the tab bar.
|
||||
pub const label: []const u8 = "Quote";
|
||||
|
||||
pub const default_bindings: []const framework.TabBinding(Action) = &.{
|
||||
.{ .action = .chart_timeframe_next, .key = .{ .codepoint = ']' } },
|
||||
.{ .action = .chart_timeframe_prev, .key = .{ .codepoint = '[' } },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@
|
|||
//! pub const ActionT = Action;
|
||||
//! pub const StateT = State;
|
||||
//!
|
||||
//! /// Display name for the tab bar. The framework composes
|
||||
//! /// this into `" {N}:{label} "` where N is the tab's
|
||||
//! /// 1-indexed registry position.
|
||||
//! pub const label: []const u8 = "...";
|
||||
//!
|
||||
//! /// Default keybindings for this tab.
|
||||
//! pub const default_bindings: []const TabBinding(Action) = &.{ ... };
|
||||
//!
|
||||
|
|
@ -231,6 +236,13 @@ pub fn validateTabModule(comptime Module: type) void {
|
|||
}
|
||||
|
||||
// ── Binding / label / hint constants ───────────────────
|
||||
expectDeclWithType(
|
||||
mod_name,
|
||||
tab_decl,
|
||||
"label",
|
||||
[]const u8,
|
||||
"pub const label: []const u8 = \"...\";",
|
||||
);
|
||||
expectDeclWithType(
|
||||
mod_name,
|
||||
tab_decl,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue