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:
6 files changed
+70
-19
No files matched your search
@@ -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() {
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user