mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
Open and import card: the step marks share one box, size and stroke
The current step's spinner was egui's, sized to the text and drawn as a small partial arc next to the tick and the ring. All three marks now share one box the label's height, centred on its capitals (the first glyph's ink, not the line box), at the same left edge: the tick, a turning three-quarter circle and the ring are the same diameter, the spinner with the tick's stroke, whole at 100% and 200% UI scale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5Lzv2n8fyKqD61NCHSF
This commit is contained in:
@@ -62,47 +62,76 @@ pub(crate) fn progress(ui: &mut egui::Ui, key: &str, fraction: f32) -> egui::Res
|
||||
}
|
||||
|
||||
/// Steps of a running operation, one row each: a tick for the ones done, a spinner for the one
|
||||
/// under way and a ring for the ones to come, each the height of the text and centred on it.
|
||||
/// `detail` (how far the current step is) goes at the end of its row. Pair it with `progress`
|
||||
/// for the current step. Tokens: `font_label, control_height, form_ink/dim/primary, space_2/8`.
|
||||
/// under way and a ring for the ones to come. The three marks share one box the height of the
|
||||
/// text, centred on its capitals, and one stroke. `detail` (how far the current step is) goes at
|
||||
/// the end of its row. Pair it with `progress` for the current step.
|
||||
/// Tokens: `font_label, control_height, form_ink/dim/primary, space_2/3/8`.
|
||||
pub(crate) fn steps(ui: &mut egui::Ui, key: &str, labels: &[&str], current: usize, detail: Option<&str>) {
|
||||
let t = Tokens::get();
|
||||
let mark = t.font_label;
|
||||
let ppp = ui.ctx().pixels_per_point().max(0.5);
|
||||
let snap = |v: f32| (v * ppp).round() / ppp;
|
||||
for (i, label) in labels.iter().enumerate() {
|
||||
let (row, response) =
|
||||
ui.allocate_exact_size(vec2(super::metrics::dimension(ui.available_width()), super::row_height()), egui::Sense::hover());
|
||||
let slot = egui::Rect::from_center_size(egui::pos2(row.left() + mark / 2.0, row.center().y), vec2(mark, mark));
|
||||
let painter = ui.painter();
|
||||
let (font, ink) = match i.cmp(¤t) {
|
||||
std::cmp::Ordering::Less => {
|
||||
super::checks::paint_check_glyph(painter, slot, false, t.form_ink);
|
||||
(egui::FontId::proportional(t.font_label), t.form_ink)
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
egui::Spinner::new().size(mark).color(t.form_primary).paint_at(ui, slot);
|
||||
(crate::fonts::semibold(t.font_label), t.form_ink)
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
painter.circle_stroke(slot.center(), mark / 2.0 - t.space_2, egui::Stroke::new(1.0, t.form_dim));
|
||||
(egui::FontId::proportional(t.font_label), t.form_dim)
|
||||
}
|
||||
let order = i.cmp(¤t);
|
||||
let (font, ink) = match order {
|
||||
std::cmp::Ordering::Equal => (crate::fonts::semibold(t.font_label), t.form_ink),
|
||||
std::cmp::Ordering::Less => (egui::FontId::proportional(t.font_label), t.form_ink),
|
||||
std::cmp::Ordering::Greater => (egui::FontId::proportional(t.font_label), t.form_dim),
|
||||
};
|
||||
let mut text = egui::Rect::from_min_max(egui::pos2(slot.right() + t.space_8, row.top()), row.max);
|
||||
let text_left = row.left() + mark + t.space_8;
|
||||
let mut text_right = row.right();
|
||||
if let Some(detail) = detail.filter(|_| i == current) {
|
||||
let galley = ui.painter().layout_no_wrap(detail.to_string(), egui::FontId::proportional(t.font_label), t.form_dim);
|
||||
let at = egui::pos2(row.right() - galley.size().x, row.center().y - galley.size().y / 2.0);
|
||||
if at.x > text.left() + t.space_8 {
|
||||
let at = egui::pos2(snap(row.right() - galley.size().x), snap(row.center().y - galley.size().y / 2.0));
|
||||
if at.x > text_left + t.space_8 {
|
||||
text_right = at.x - t.space_8;
|
||||
ui.painter().galley(at, galley, t.form_dim);
|
||||
text.max.x = at.x - t.space_8;
|
||||
}
|
||||
}
|
||||
super::text::paint_text(ui, text, label, font, ink, false);
|
||||
let state = match i.cmp(¤t) {
|
||||
let mut job = egui::text::LayoutJob::single_section(label.to_string(), egui::TextFormat::simple(font, ink));
|
||||
job.wrap.max_width = (text_right - text_left).max(0.0);
|
||||
job.wrap.max_rows = 1;
|
||||
job.wrap.overflow_character = Some('\u{2026}');
|
||||
let galley = ui.fonts_mut(|fonts| fonts.layout_job(job));
|
||||
let at = egui::pos2(snap(text_left), snap(row.center().y - galley.size().y / 2.0));
|
||||
// The mark is centred on the label's capitals (its first glyph's ink), not on the line box.
|
||||
let caps = galley
|
||||
.rows
|
||||
.first()
|
||||
.and_then(|r| r.row.glyphs.first().map(|g| r.pos.y + g.pos.y + g.uv_rect.offset.y + g.uv_rect.size.y / 2.0))
|
||||
.map_or(row.center().y, |y| at.y + y);
|
||||
let slot = egui::Rect::from_center_size(egui::pos2(row.left() + mark / 2.0, caps), vec2(mark, mark));
|
||||
let inner = slot.shrink(t.space_3);
|
||||
let stroke = egui::Stroke::new(t.space_2, ink);
|
||||
let painter = ui.painter();
|
||||
match order {
|
||||
std::cmp::Ordering::Less => super::checks::paint_check_glyph(painter, slot, false, t.form_ink),
|
||||
std::cmp::Ordering::Equal => {
|
||||
// Three quarters of a circle turning, the tick's size and stroke: a whole round, never clipped.
|
||||
// The tick's stroke is centred on the box's edges, and so is the circle's.
|
||||
let radius = inner.height() / 2.0;
|
||||
let start = ui.input(|input| input.time) as f32 * std::f32::consts::TAU * 0.8;
|
||||
let points: Vec<egui::Pos2> = (0..=24)
|
||||
.map(|k| start + k as f32 / 24.0 * std::f32::consts::TAU * 0.75)
|
||||
.map(|a| inner.center() + radius * vec2(a.cos(), a.sin()))
|
||||
.collect();
|
||||
painter.add(egui::Shape::line(points, egui::Stroke::new(stroke.width, t.form_primary)));
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
painter.circle_stroke(inner.center(), inner.height() / 2.0, egui::Stroke::new(1.0, t.form_dim));
|
||||
}
|
||||
}
|
||||
ui.painter().with_clip_rect(ui.clip_rect().intersect(row)).galley(at, galley, ink);
|
||||
let state = match order {
|
||||
std::cmp::Ordering::Less => "done",
|
||||
std::cmp::Ordering::Equal => "current",
|
||||
std::cmp::Ordering::Greater => "next",
|
||||
};
|
||||
response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Label, ui.is_enabled(), *label));
|
||||
crate::scenario::publish_rect(&format!("dialog:step:{key}:{i}:{state}"), row);
|
||||
crate::scenario::publish_rect(&format!("dialog:step_mark:{key}:{i}"), slot);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user