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),
|
||||
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() {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)));
|
||||
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.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
ui.label(
|
||||
RichText::new(format!("#{} · {}", line.seq, fmt::timestamp(&line.ts)))
|
||||
.font(theme::regular(11.5))
|
||||
RichText::new(format!(
|
||||
"event {} · {}",
|
||||
line.seq,
|
||||
fmt::timestamp(&line.ts)
|
||||
))
|
||||
.font(theme::regular(11.0))
|
||||
.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),
|
||||
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),
|
||||
|
||||
@@ -289,3 +289,27 @@ pub fn segmented<T: PartialEq + Copy>(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);
|
||||
}
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user