remove magic numbers
This commit is contained in:
parent
d442119d70
commit
b66b9391a5
2 changed files with 121 additions and 92 deletions
192
src/tui.zig
192
src/tui.zig
|
|
@ -358,103 +358,117 @@ pub const App = struct {
|
||||||
return ctx.consumeAndRedraw();
|
return ctx.consumeAndRedraw();
|
||||||
},
|
},
|
||||||
.left => {
|
.left => {
|
||||||
if (mouse.type == .press) {
|
if (mouse.type != .press) return;
|
||||||
if (mouse.row == 0) {
|
// Tab bar: click to switch tabs
|
||||||
var col: i16 = 0;
|
if (mouse.row == 0) {
|
||||||
for (tabs) |t| {
|
var col: i16 = 0;
|
||||||
const lbl_len: i16 = @intCast(t.label().len);
|
for (tabs) |t| {
|
||||||
if (mouse.col >= col and mouse.col < col + lbl_len) {
|
const lbl_len: i16 = @intCast(t.label().len);
|
||||||
if (t == .earnings and self.earnings_disabled) return;
|
if (mouse.col >= col and mouse.col < col + lbl_len) {
|
||||||
self.active_tab = t;
|
if (t == .earnings and self.earnings_disabled) return;
|
||||||
self.scroll_offset = 0;
|
self.active_tab = t;
|
||||||
self.loadTabData();
|
self.scroll_offset = 0;
|
||||||
return ctx.consumeAndRedraw();
|
self.loadTabData();
|
||||||
|
return ctx.consumeAndRedraw();
|
||||||
|
}
|
||||||
|
col += lbl_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Portfolio tab: click header to sort, click row to expand/collapse
|
||||||
|
if (self.active_tab == .portfolio and mouse.row > 0) {
|
||||||
|
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
||||||
|
// Click on column header row -> sort by that column
|
||||||
|
if (self.portfolio_header_lines > 0 and content_row == self.portfolio_header_lines - 1) {
|
||||||
|
const col = @as(usize, @intCast(mouse.col));
|
||||||
|
const new_field: ?PortfolioSortField =
|
||||||
|
if (col < portfolio_tab.col_end_symbol)
|
||||||
|
.symbol
|
||||||
|
else if (col < portfolio_tab.col_end_shares)
|
||||||
|
.shares
|
||||||
|
else if (col < portfolio_tab.col_end_avg_cost)
|
||||||
|
.avg_cost
|
||||||
|
else if (col < portfolio_tab.col_end_price)
|
||||||
|
.price
|
||||||
|
else if (col < portfolio_tab.col_end_market_value)
|
||||||
|
.market_value
|
||||||
|
else if (col < portfolio_tab.col_end_gain_loss)
|
||||||
|
.gain_loss
|
||||||
|
else if (col < portfolio_tab.col_end_weight)
|
||||||
|
.weight
|
||||||
|
else if (col < portfolio_tab.col_end_date)
|
||||||
|
null // Date (not sortable)
|
||||||
|
else
|
||||||
|
.account;
|
||||||
|
if (new_field) |nf| {
|
||||||
|
if (nf == self.portfolio_sort_field) {
|
||||||
|
self.portfolio_sort_dir = self.portfolio_sort_dir.flip();
|
||||||
|
} else {
|
||||||
|
self.portfolio_sort_field = nf;
|
||||||
|
self.portfolio_sort_dir = if (nf == .symbol or nf == .account) .asc else .desc;
|
||||||
}
|
}
|
||||||
col += lbl_len;
|
self.sortPortfolioAllocations();
|
||||||
|
self.rebuildPortfolioRows();
|
||||||
|
return ctx.consumeAndRedraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (self.active_tab == .portfolio and mouse.row > 0) {
|
if (content_row >= self.portfolio_header_lines and self.portfolio_rows.items.len > 0) {
|
||||||
|
const line_idx = content_row - self.portfolio_header_lines;
|
||||||
|
if (line_idx < self.portfolio_line_count and line_idx < self.portfolio_line_to_row.len) {
|
||||||
|
const row_idx = self.portfolio_line_to_row[line_idx];
|
||||||
|
if (row_idx < self.portfolio_rows.items.len) {
|
||||||
|
self.cursor = row_idx;
|
||||||
|
self.toggleExpand();
|
||||||
|
return ctx.consumeAndRedraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Quote tab: click on timeframe selector to switch timeframes
|
||||||
|
if (self.active_tab == .quote and mouse.row > 0) {
|
||||||
|
if (self.chart.timeframe_row) |tf_row| {
|
||||||
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
||||||
// Click on column header row -> sort by that column
|
if (content_row == tf_row) {
|
||||||
if (self.portfolio_header_lines > 0 and content_row == self.portfolio_header_lines - 1) {
|
// " Chart: [6M] YTD 1Y 3Y 5Y ([ ] to change)"
|
||||||
// Column boundaries derived from sym_col_width (sw).
|
// Prefix " Chart: " = 9 chars, then each TF takes label_len+2 (brackets/spaces) + 1 gap
|
||||||
// prefix(4) + Symbol(sw+1) + Shares(8+1) + AvgCost(10+1) + Price(10+1) + MV(16+1) + G/L(14+1) + Weight(8)
|
|
||||||
const sw = fmt.sym_col_width;
|
|
||||||
const col = @as(usize, @intCast(mouse.col));
|
const col = @as(usize, @intCast(mouse.col));
|
||||||
const new_field: ?PortfolioSortField =
|
const prefix_len: usize = 9; // " Chart: "
|
||||||
if (col < 4 + sw + 1) .symbol else if (col < 4 + sw + 10) .shares else if (col < 4 + sw + 21) .avg_cost else if (col < 4 + sw + 32) .price else if (col < 4 + sw + 49) .market_value else if (col < 4 + sw + 64) .gain_loss else if (col < 4 + sw + 73) .weight else if (col < 4 + sw + 87) null // Date (not sortable)
|
if (col >= prefix_len) {
|
||||||
else .account;
|
const timeframes = [_]chart_mod.Timeframe{ .@"6M", .ytd, .@"1Y", .@"3Y", .@"5Y" };
|
||||||
if (new_field) |nf| {
|
var x: usize = prefix_len;
|
||||||
if (nf == self.portfolio_sort_field) {
|
for (timeframes) |tf| {
|
||||||
self.portfolio_sort_dir = self.portfolio_sort_dir.flip();
|
const lbl_len = tf.label().len;
|
||||||
} else {
|
const slot_width = lbl_len + 2 + 1; // [XX] + space or XX + space
|
||||||
self.portfolio_sort_field = nf;
|
if (col >= x and col < x + slot_width) {
|
||||||
self.portfolio_sort_dir = if (nf == .symbol or nf == .account) .asc else .desc;
|
if (tf != self.chart.timeframe) {
|
||||||
|
self.chart.timeframe = tf;
|
||||||
|
self.setStatus(tf.label());
|
||||||
|
return ctx.consumeAndRedraw();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
x += slot_width;
|
||||||
}
|
}
|
||||||
self.sortPortfolioAllocations();
|
}
|
||||||
self.rebuildPortfolioRows();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Options tab: single-click to select and expand/collapse
|
||||||
|
if (self.active_tab == .options and mouse.row > 0) {
|
||||||
|
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
||||||
|
if (content_row >= self.options_header_lines and self.options_rows.items.len > 0) {
|
||||||
|
// Walk options_rows tracking styled line position to find which
|
||||||
|
// row was clicked. Each row = 1 styled line, except puts_header
|
||||||
|
// which emits an extra blank line before it.
|
||||||
|
const target_line = content_row - self.options_header_lines;
|
||||||
|
var current_line: usize = 0;
|
||||||
|
for (self.options_rows.items, 0..) |orow, oi| {
|
||||||
|
if (orow.kind == .puts_header) current_line += 1; // extra blank
|
||||||
|
if (current_line == target_line) {
|
||||||
|
self.options_cursor = oi;
|
||||||
|
self.toggleOptionsExpand();
|
||||||
return ctx.consumeAndRedraw();
|
return ctx.consumeAndRedraw();
|
||||||
}
|
}
|
||||||
}
|
current_line += 1;
|
||||||
if (content_row >= self.portfolio_header_lines and self.portfolio_rows.items.len > 0) {
|
|
||||||
const line_idx = content_row - self.portfolio_header_lines;
|
|
||||||
if (line_idx < self.portfolio_line_count and line_idx < self.portfolio_line_to_row.len) {
|
|
||||||
const row_idx = self.portfolio_line_to_row[line_idx];
|
|
||||||
if (row_idx < self.portfolio_rows.items.len) {
|
|
||||||
self.cursor = row_idx;
|
|
||||||
self.toggleExpand();
|
|
||||||
return ctx.consumeAndRedraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Quote tab: click on timeframe selector to switch timeframes
|
|
||||||
if (self.active_tab == .quote and mouse.row > 0) {
|
|
||||||
if (self.chart.timeframe_row) |tf_row| {
|
|
||||||
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
|
||||||
if (content_row == tf_row) {
|
|
||||||
// " Chart: [6M] YTD 1Y 3Y 5Y ([ ] to change)"
|
|
||||||
// Prefix " Chart: " = 9 chars, then each TF takes label_len+2 (brackets/spaces) + 1 gap
|
|
||||||
const col = @as(usize, @intCast(mouse.col));
|
|
||||||
const prefix_len: usize = 9; // " Chart: "
|
|
||||||
if (col >= prefix_len) {
|
|
||||||
const timeframes = [_]chart_mod.Timeframe{ .@"6M", .ytd, .@"1Y", .@"3Y", .@"5Y" };
|
|
||||||
var x: usize = prefix_len;
|
|
||||||
for (timeframes) |tf| {
|
|
||||||
const lbl_len = tf.label().len;
|
|
||||||
const slot_width = lbl_len + 2 + 1; // [XX] + space or XX + space
|
|
||||||
if (col >= x and col < x + slot_width) {
|
|
||||||
if (tf != self.chart.timeframe) {
|
|
||||||
self.chart.timeframe = tf;
|
|
||||||
self.setStatus(tf.label());
|
|
||||||
return ctx.consumeAndRedraw();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
x += slot_width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Options tab: single-click to select and expand/collapse
|
|
||||||
if (self.active_tab == .options and mouse.row > 0) {
|
|
||||||
const content_row = @as(usize, @intCast(mouse.row)) + self.scroll_offset;
|
|
||||||
if (content_row >= self.options_header_lines and self.options_rows.items.len > 0) {
|
|
||||||
// Walk options_rows tracking styled line position to find which
|
|
||||||
// row was clicked. Each row = 1 styled line, except puts_header
|
|
||||||
// which emits an extra blank line before it.
|
|
||||||
const target_line = content_row - self.options_header_lines;
|
|
||||||
var current_line: usize = 0;
|
|
||||||
for (self.options_rows.items, 0..) |orow, oi| {
|
|
||||||
if (orow.kind == .puts_header) current_line += 1; // extra blank
|
|
||||||
if (current_line == target_line) {
|
|
||||||
self.options_cursor = oi;
|
|
||||||
self.toggleOptionsExpand();
|
|
||||||
return ctx.consumeAndRedraw();
|
|
||||||
}
|
|
||||||
current_line += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,24 @@ const PortfolioSortField = tui.PortfolioSortField;
|
||||||
const colLabel = tui.colLabel;
|
const colLabel = tui.colLabel;
|
||||||
const glyph = tui.glyph;
|
const glyph = tui.glyph;
|
||||||
|
|
||||||
// Portfolio column layout: gain/loss column start position (display columns).
|
// Portfolio column layout (display columns).
|
||||||
// prefix(4) + sym(sym_col_width+1) + shares(9) + avgcost(11) + price(11) + mv(17) = 4 + sym_col_width + 49
|
// Each column width includes its trailing separator space.
|
||||||
const gl_col_start: usize = 4 + fmt.sym_col_width + 49;
|
// prefix(4) + sym(sw+1) + shares(8+1) + avgcost(10+1) + price(10+1) + mv(16+1) + gl(14+1) + weight(8) + date(13+1) + account
|
||||||
|
const prefix_cols: usize = 4;
|
||||||
|
const sw: usize = fmt.sym_col_width;
|
||||||
|
|
||||||
|
/// Cumulative column end positions for click-to-sort hit testing.
|
||||||
|
pub const col_end_symbol: usize = prefix_cols + sw + 1;
|
||||||
|
pub const col_end_shares: usize = col_end_symbol + 9;
|
||||||
|
pub const col_end_avg_cost: usize = col_end_shares + 11;
|
||||||
|
pub const col_end_price: usize = col_end_avg_cost + 11;
|
||||||
|
pub const col_end_market_value: usize = col_end_price + 17;
|
||||||
|
pub const col_end_gain_loss: usize = col_end_market_value + 15;
|
||||||
|
pub const col_end_weight: usize = col_end_gain_loss + 9;
|
||||||
|
pub const col_end_date: usize = col_end_weight + 14;
|
||||||
|
|
||||||
|
// Gain/loss column start position (used for alt-style coloring)
|
||||||
|
const gl_col_start: usize = col_end_market_value;
|
||||||
|
|
||||||
// ── Data loading ──────────────────────────────────────────────
|
// ── Data loading ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue