In the Brush Preset picker's cards the tip thumbnail grew to 44.8 pt in a 42 pt cell while the size number was anchored to the cell's bottom, so the number of any medium or large brush (Hard Round's 30, Soft Round's 45) was drawn under the tip and couldn't be read. card_tip_rect now keeps a band for the number at the bottom of the cell and fits the tip above it, still scaled by the preset's thumb_scale so big brushes read bigger than small ones. Compact cards (no number) are unchanged. Test: card_tips_never_cover_their_size_number checks every footer scale that shows numbers, in all three card kinds. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2ca12e17b5
commit
e4d281eb20
|
|
@ -475,6 +475,22 @@ fn darker(c: Color32, f: f32) -> Color32 {
|
|||
Color32::from_rgba_unmultiplied((r as f32 * f).round() as u8, (g as f32 * f).round() as u8, (b as f32 * f).round() as u8, a)
|
||||
}
|
||||
|
||||
/// Where a card's tip thumbnail goes in its `cell`. `number_h` is the height of the size number
|
||||
/// under it, `None` on compact cards (no number: the tip takes the cell whole). Otherwise the
|
||||
/// number keeps a band at the bottom of the cell and the tip is centred in what's left above it,
|
||||
/// at [`CARD_TIP_BASE`] × `scale` or that room's side, whichever is smaller, times the preset's
|
||||
/// [`crate::brush_sections::thumb_scale`]: big brushes still read bigger than small ones, and
|
||||
/// none of them reaches down over the number.
|
||||
fn card_tip_rect(cell: egui::Rect, number_h: Option<f32>, size: f32, scale: f32) -> egui::Rect {
|
||||
let Some(number_h) = number_h else {
|
||||
return egui::Rect::from_center_size(cell.center(), egui::Vec2::splat((cell.width().min(cell.height()) - 2.0).max(1.0)));
|
||||
};
|
||||
// The number sits 1 pt off the cell's bottom, with a 1 pt gap above it.
|
||||
let room = egui::Rect::from_min_max(cell.min, pos2(cell.right(), (cell.bottom() - number_h - 2.0).max(cell.top())));
|
||||
let side = (CARD_TIP_BASE * scale).min(room.width().min(room.height())).max(0.0) * crate::brush_sections::thumb_scale(size);
|
||||
egui::Rect::from_center_size(room.center(), egui::Vec2::splat(if side.is_finite() { side.max(1.0) } else { 1.0 }))
|
||||
}
|
||||
|
||||
/// One preset in the Brush Preset picker: a 2 × 2 grid whose bottom row (the name) spans both
|
||||
/// columns — tip at the top left, stroke preview at the top right. A part that's off loses its
|
||||
/// cell (the other takes the room): with only the tip and the name on, the name moves into the
|
||||
|
|
@ -545,18 +561,13 @@ fn preset_card(
|
|||
let tip_w = if tip_only { top.width() } else { (CARD_TIP_W * scale).min(top.width() * 0.5) };
|
||||
let cell = egui::Rect::from_min_size(top.left_top(), vec2(tip_w, top_h));
|
||||
let tip = brush_preview::tip_texture(ui.ctx(), &format!("brushes-tip:{}", p.name), &pb.tip, (pb.hardness, pb.angle, pb.roundness), 36, t.text);
|
||||
let side = if compact { (cell.width().min(cell.height()) - 2.0).max(1.0) } else { CARD_TIP_BASE * scale * crate::brush_sections::thumb_scale(pb.size) };
|
||||
let center = if compact { cell.center() } else { pos2(cell.center().x, cell.center().y - 2.0 * scale_h) };
|
||||
let tr = egui::Rect::from_center_size(center, vec2(side, side));
|
||||
let number =
|
||||
(!compact).then(|| ui.painter().layout_no_wrap(format!("{}", pb.size.round() as i64), egui::FontId::proportional(CARD_NUM_FONT), t.text_faint));
|
||||
let tr = card_tip_rect(cell, number.as_ref().map(|g| g.size().y), pb.size, scale);
|
||||
ui.painter().image(tip.id(), tr, full_uv(), Color32::WHITE);
|
||||
if !compact {
|
||||
ui.painter().text(
|
||||
pos2(cell.center().x, cell.bottom() - 1.0),
|
||||
egui::Align2::CENTER_BOTTOM,
|
||||
format!("{}", pb.size.round() as i64),
|
||||
egui::FontId::proportional(CARD_NUM_FONT),
|
||||
t.text_faint,
|
||||
);
|
||||
if let Some(g) = number {
|
||||
let at = pos2(cell.center().x - g.size().x / 2.0, cell.bottom() - 1.0 - g.size().y);
|
||||
ui.painter().galley(at, g, t.text_faint);
|
||||
}
|
||||
x = cell.right();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,3 +265,56 @@ fn the_brushes_tab_grid_is_centred_past_its_indent() {
|
|||
h.run_steps(6);
|
||||
rows_are_centred(&h, PANEL_LIST.indent, 2);
|
||||
}
|
||||
|
||||
/// #4454: a card's size number sat under the tip thumbnail of any medium or large brush (the
|
||||
/// tip took up to 44.8 pt of a 42 pt cell, the number anchored to the cell's bottom), so the
|
||||
/// "30" of Hard Round and the "45" of Soft Round couldn't be read. At every footer scale that
|
||||
/// shows numbers, in each card kind, the tip now stays above the number, and bigger brushes still
|
||||
/// get bigger tips.
|
||||
#[test]
|
||||
fn card_tips_never_cover_their_size_number() {
|
||||
let number_h = std::rc::Rc::new(std::cell::Cell::new(0.0_f32));
|
||||
let seen = number_h.clone();
|
||||
let mut h = Harness::new_ui(move |ui| {
|
||||
let g = ui.painter().layout_no_wrap("45".into(), egui::FontId::proportional(CARD_NUM_FONT), Color32::WHITE);
|
||||
seen.set(g.size().y);
|
||||
});
|
||||
PhotocraftApp::setup_context(&h.ctx, Default::default());
|
||||
h.run();
|
||||
let number_h = number_h.get();
|
||||
assert!(number_h > 8.0, "the number has a real height: {number_h}");
|
||||
let mut scale = CARD_COMPACT_SCALE + 0.01;
|
||||
while scale <= *crate::brush_picker::SCALE_RANGE.end() + 1e-3 {
|
||||
let scale_h = scale.max(CARD_MIN_H_SCALE);
|
||||
// The tip cell of a full card (name row under it), a tip-and-name card and a tip-only one,
|
||||
// as [`preset_card`] sizes them in a wide column.
|
||||
let cells = [
|
||||
vec2(CARD_TIP_W * scale, (CARD_MAX_H - CARD_PAD * 2.0 - CARD_NAME_H) * scale_h),
|
||||
vec2(CARD_TIP_W * scale, (CARD_TIP_NAME_H - CARD_PAD * 2.0) * scale_h),
|
||||
vec2((CARD_TIP_ONLY_W - 16.0) * scale, (CARD_TIP_ONLY_H - CARD_PAD * 2.0) * scale_h),
|
||||
];
|
||||
for size in cells {
|
||||
let cell = Rect::from_min_size(egui::pos2(10.0, 20.0), size);
|
||||
let number_top = cell.bottom() - 1.0 - number_h;
|
||||
let mut last = 0.0;
|
||||
for brush in [1.0, 5.0, 13.0, 30.0, 45.0, 100.0, 2500.0] {
|
||||
let tip = card_tip_rect(cell, Some(number_h), brush, scale);
|
||||
assert!(tip.bottom() <= number_top + 1e-3, "scale {scale}, cell {size:?}, {brush} px: tip {tip:?} reaches the number at {number_top}");
|
||||
assert!(cell.expand(1e-3).contains_rect(tip), "scale {scale}, {brush} px: tip {tip:?} leaves its cell {cell:?}");
|
||||
assert!(tip.width() >= last, "scale {scale}, {brush} px: a bigger brush gets a smaller tip");
|
||||
last = tip.width();
|
||||
}
|
||||
}
|
||||
scale += 0.05;
|
||||
}
|
||||
// The tip-only grid's cells are tall enough for the number: a big brush's tip still spans
|
||||
// the cell's width.
|
||||
let cell = Rect::from_min_size(egui::pos2(0.0, 0.0), vec2(CARD_TIP_ONLY_W - 16.0, CARD_TIP_ONLY_H - CARD_PAD * 2.0));
|
||||
assert!((card_tip_rect(cell, Some(number_h), 45.0, 1.0).width() - cell.width()).abs() < 1e-3);
|
||||
// Compact cards: no number, the tip fills the cell; nonsense never makes a non-finite rect.
|
||||
assert_eq!(card_tip_rect(cell, None, 45.0, 0.5).width(), cell.width() - 2.0);
|
||||
for (n, size, scale) in [(f32::NAN, 30.0, 1.0), (500.0, f32::NAN, 1.0), (12.0, 30.0, f32::INFINITY), (12.0, -5.0, 0.0)] {
|
||||
let r = card_tip_rect(cell, Some(n), size, scale);
|
||||
assert!(r.min.is_finite() && r.max.is_finite(), "{n} {size} {scale}: {r:?}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue