diff --git a/crates/ledger-gui/src/pages/accounts.rs b/crates/ledger-gui/src/pages/accounts.rs index 6ab5a0e..d4c2bf8 100644 --- a/crates/ledger-gui/src/pages/accounts.rs +++ b/crates/ledger-gui/src/pages/accounts.rs @@ -270,20 +270,24 @@ pub fn show_account(ui: &mut Ui, b: &Books, id: u64, shell: &mut Shell<'_>) { theme::regular(12.5), p.muted, ); - painter.text( + // Leave room for the debit, credit, and balance columns. + let text_w = r.width() - 90.0 - amt * 3.0 - 12.0; + widgets::clipped_text( + &painter, egui::pos2(r.left() + 90.0, y), - egui::Align2::LEFT_CENTER, - desc, + &desc, font.clone(), p.text, + text_w, ); if !sub.is_empty() { - painter.text( + widgets::clipped_text( + &painter, egui::pos2(r.left() + 90.0, r.center().y + 9.0), - egui::Align2::LEFT_CENTER, - sub, + &sub, theme::regular(11.5), p.faint, + text_w, ); } for (i, v) in [d, c].iter().enumerate() { diff --git a/crates/ledger-gui/src/pages/cash.rs b/crates/ledger-gui/src/pages/cash.rs index ec7988e..db7a19a 100644 --- a/crates/ledger-gui/src/pages/cash.rs +++ b/crates/ledger-gui/src/pages/cash.rs @@ -368,12 +368,13 @@ fn coming_up(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) { theme::regular(12.5), if late { p.negative } else { p.muted }, ); - ui.painter().text( + widgets::clipped_text( + ui.painter(), r.left_center() + vec2(70.0, 0.0), - egui::Align2::LEFT_CENTER, &it.label, theme::regular(13.0), p.text, + r.width() - 70.0 - 130.0, ); let (txt, c) = if it.amount > Decimal::ZERO { (format!("+{}", fmt::money(it.amount)), p.positive) @@ -405,7 +406,11 @@ fn biggest_outflows(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) { let mut out: Vec<&crate::books::EntryInfo> = b .entries .iter() - .filter(|e| e.date > since && e.cash_effect < Decimal::ZERO) + .filter(|e| { + e.status == ledger_core::EntryStatus::Posted + && e.date > since + && e.cash_effect < Decimal::ZERO + }) .collect(); out.sort_by_key(|e| e.cash_effect); out.truncate(8); @@ -431,12 +436,13 @@ fn biggest_outflows(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) { theme::regular(12.5), p.muted, ); - ui.painter().text( + widgets::clipped_text( + ui.painter(), r.left_center() + vec2(70.0, 0.0), - egui::Align2::LEFT_CENTER, &e.description, theme::regular(13.0), p.text, + r.width() - 70.0 - 130.0, ); ui.painter().text( r.right_center() - vec2(6.0, 0.0), diff --git a/crates/ledger-gui/src/pages/entry.rs b/crates/ledger-gui/src/pages/entry.rs index e46ef81..aeef7cc 100644 --- a/crates/ledger-gui/src/pages/entry.rs +++ b/crates/ledger-gui/src/pages/entry.rs @@ -267,20 +267,33 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) { for &i in &e.events { let line = &b.journal[i]; let ev = super::journal::describe_any(b, line); - ui.horizontal(|ui| { + ui.horizontal_top(|ui| { ui.label(RichText::new(ev.icon).color(p.muted)); - let r = widgets::link(ui, RichText::new(&ev.text).font(theme::regular(12.5))); - if r.clicked() { - shell.go(Route::JournalEvent(line.seq)); - } - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.vertical(|ui| { + ui.spacing_mut().item_spacing.y = 0.0; + let r = ui.add( + egui::Label::new(RichText::new(&ev.text).font(theme::regular(12.5))) + .wrap() + .sense(egui::Sense::click()), + ); + if r.hovered() { + ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand); + } + if r.clicked() { + shell.go(Route::JournalEvent(line.seq)); + } ui.label( - RichText::new(format!("#{} · {}", line.seq, fmt::timestamp(&line.ts))) - .font(theme::regular(11.5)) - .color(p.faint), + RichText::new(format!( + "event {} · {}", + line.seq, + fmt::timestamp(&line.ts) + )) + .font(theme::regular(11.0)) + .color(p.faint), ); }); }); + ui.add_space(4.0); } }); } diff --git a/crates/ledger-gui/src/pages/journal.rs b/crates/ledger-gui/src/pages/journal.rs index 5da4534..fbabf27 100644 --- a/crates/ledger-gui/src/pages/journal.rs +++ b/crates/ledger-gui/src/pages/journal.rs @@ -401,12 +401,13 @@ fn list(ui: &mut Ui, b: &Books, selected: Option, shell: &mut Shell<'_>) { egui::FontId::proportional(15.0), p.muted, ); - painter.text( + widgets::clipped_text( + &painter, egui::pos2(rect.left() + 80.0, y), - egui::Align2::LEFT_CENTER, &ev.text, theme::regular(13.0), p.text, + rect.width() - 80.0 - 200.0, ); painter.text( egui::pos2(rect.right() - 8.0, y), diff --git a/crates/ledger-gui/src/widgets.rs b/crates/ledger-gui/src/widgets.rs index 2f28a07..ebafbf1 100644 --- a/crates/ledger-gui/src/widgets.rs +++ b/crates/ledger-gui/src/widgets.rs @@ -289,3 +289,27 @@ pub fn segmented(ui: &mut Ui, value: &mut T, options: &[(T, }); changed } + +/// Left-aligned single-line text, vertically centered at `pos`, cut with +/// an ellipsis at `max_width` so it never runs into whatever is to its +/// right (an amount, a date). +pub fn clipped_text( + painter: &egui::Painter, + pos: egui::Pos2, + text: &str, + font: egui::FontId, + color: Color32, + max_width: f32, +) { + use egui::text::{LayoutJob, TextFormat, TextWrapping}; + let mut job = LayoutJob::single_section(text.to_string(), TextFormat::simple(font, color)); + job.wrap = TextWrapping { + max_width: max_width.max(10.0), + max_rows: 1, + break_anywhere: true, + overflow_character: Some('…'), + }; + let galley = painter.layout_job(job); + let y = pos.y - galley.size().y / 2.0; + painter.galley(egui::pos2(pos.x, y), galley, color); +} diff --git a/crates/ledger-mcp/tests/mcp_stdio.rs b/crates/ledger-mcp/tests/mcp_stdio.rs index fa6c316..7bcd6b9 100644 --- a/crates/ledger-mcp/tests/mcp_stdio.rs +++ b/crates/ledger-mcp/tests/mcp_stdio.rs @@ -1632,5 +1632,8 @@ fn mcp_receipts_always_required_when_threshold_is_zero() { "books.post_entry", json!({"entry_id": id, "confirmed": true}), ); - assert!(err.contains("receipt"), "a $5 entry needs a receipt too: {err}"); + assert!( + err.contains("receipt"), + "a $5 entry needs a receipt too: {err}" + ); }