fix(gui): clip long row text instead of drawing over amounts; drafts aren't payments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MtYEaR6M6Ybdhm6X98BWNu
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-12 17:32:50 -07:00
1 parent 01d41c0daf
commit 54d2775a33
6 files changed
+70 -19

No files matched your search

+10 -6
View File
@@ -270,20 +270,24 @@ pub fn show_account(ui: &mut Ui, b: &Books, id: u64, shell: &mut Shell<'_>) {
theme::regular(12.5), theme::regular(12.5),
p.muted, 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::pos2(r.left() + 90.0, y),
egui::Align2::LEFT_CENTER, &desc,
desc,
font.clone(), font.clone(),
p.text, p.text,
text_w,
); );
if !sub.is_empty() { if !sub.is_empty() {
painter.text( widgets::clipped_text(
&painter,
egui::pos2(r.left() + 90.0, r.center().y + 9.0), egui::pos2(r.left() + 90.0, r.center().y + 9.0),
egui::Align2::LEFT_CENTER, &sub,
sub,
theme::regular(11.5), theme::regular(11.5),
p.faint, p.faint,
text_w,
); );
} }
for (i, v) in [d, c].iter().enumerate() { for (i, v) in [d, c].iter().enumerate() {
+11 -5
View File
@@ -368,12 +368,13 @@ fn coming_up(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
theme::regular(12.5), theme::regular(12.5),
if late { p.negative } else { p.muted }, if late { p.negative } else { p.muted },
); );
ui.painter().text( widgets::clipped_text(
ui.painter(),
r.left_center() + vec2(70.0, 0.0), r.left_center() + vec2(70.0, 0.0),
egui::Align2::LEFT_CENTER,
&it.label, &it.label,
theme::regular(13.0), theme::regular(13.0),
p.text, p.text,
r.width() - 70.0 - 130.0,
); );
let (txt, c) = if it.amount > Decimal::ZERO { let (txt, c) = if it.amount > Decimal::ZERO {
(format!("+{}", fmt::money(it.amount)), p.positive) (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 let mut out: Vec<&crate::books::EntryInfo> = b
.entries .entries
.iter() .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(); .collect();
out.sort_by_key(|e| e.cash_effect); out.sort_by_key(|e| e.cash_effect);
out.truncate(8); out.truncate(8);
@@ -431,12 +436,13 @@ fn biggest_outflows(ui: &mut Ui, b: &Books, shell: &mut Shell<'_>) {
theme::regular(12.5), theme::regular(12.5),
p.muted, p.muted,
); );
ui.painter().text( widgets::clipped_text(
ui.painter(),
r.left_center() + vec2(70.0, 0.0), r.left_center() + vec2(70.0, 0.0),
egui::Align2::LEFT_CENTER,
&e.description, &e.description,
theme::regular(13.0), theme::regular(13.0),
p.text, p.text,
r.width() - 70.0 - 130.0,
); );
ui.painter().text( ui.painter().text(
r.right_center() - vec2(6.0, 0.0), r.right_center() - vec2(6.0, 0.0),
+18 -5
View File
@@ -267,20 +267,33 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
for &i in &e.events { for &i in &e.events {
let line = &b.journal[i]; let line = &b.journal[i];
let ev = super::journal::describe_any(b, line); let ev = super::journal::describe_any(b, line);
ui.horizontal(|ui| { ui.horizontal_top(|ui| {
ui.label(RichText::new(ev.icon).color(p.muted)); ui.label(RichText::new(ev.icon).color(p.muted));
let r = widgets::link(ui, RichText::new(&ev.text).font(theme::regular(12.5))); 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() { if r.clicked() {
shell.go(Route::JournalEvent(line.seq)); shell.go(Route::JournalEvent(line.seq));
} }
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label( ui.label(
RichText::new(format!("#{} · {}", line.seq, fmt::timestamp(&line.ts))) RichText::new(format!(
.font(theme::regular(11.5)) "event {} · {}",
line.seq,
fmt::timestamp(&line.ts)
))
.font(theme::regular(11.0))
.color(p.faint), .color(p.faint),
); );
}); });
}); });
ui.add_space(4.0);
} }
}); });
} }
+3 -2
View File
@@ -401,12 +401,13 @@ fn list(ui: &mut Ui, b: &Books, selected: Option<u64>, shell: &mut Shell<'_>) {
egui::FontId::proportional(15.0), egui::FontId::proportional(15.0),
p.muted, p.muted,
); );
painter.text( widgets::clipped_text(
&painter,
egui::pos2(rect.left() + 80.0, y), egui::pos2(rect.left() + 80.0, y),
egui::Align2::LEFT_CENTER,
&ev.text, &ev.text,
theme::regular(13.0), theme::regular(13.0),
p.text, p.text,
rect.width() - 80.0 - 200.0,
); );
painter.text( painter.text(
egui::pos2(rect.right() - 8.0, y), egui::pos2(rect.right() - 8.0, y),
+24
View File
@@ -289,3 +289,27 @@ pub fn segmented<T: PartialEq + Copy>(ui: &mut Ui, value: &mut T, options: &[(T,
}); });
changed 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);
}
+4 -1
View File
@@ -1632,5 +1632,8 @@ fn mcp_receipts_always_required_when_threshold_is_zero() {
"books.post_entry", "books.post_entry",
json!({"entry_id": id, "confirmed": true}), 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}"
);
} }