Bound Create labels within lifeline capsules and preserve Unicode test inputs

This commit is contained in:
2026-09-11 06:56:17 -07:00
parent fb77b857d2
commit cb611a71df
16 changed files with 1815 additions and 12 deletions
+2 -1
View File
@@ -2164,7 +2164,8 @@ impl canvas::Program<Message> for Scene {
..Default::default()
});
}
self.lifecycle.draw_markers(&mut f, self.selected);
self.lifecycle
.draw_markers(&mut f, self.selected, self.scale);
if let Some((context, tool, editable)) = self.interaction {
let active = editable && state.context == Some(context) && state.tool == tool;
let point = cursor
@@ -277,7 +277,7 @@ impl Rendering {
}
}
pub(in crate::behavior) fn draw_markers(&self, frame: &mut Frame, selected: Id) {
pub(in crate::behavior) fn draw_markers(&self, frame: &mut Frame, selected: Id, scale: f32) {
for marker in &self.markers {
let color = if selected == marker.event.occurrence || selected == marker.lifeline {
style::TEAL
@@ -302,13 +302,20 @@ impl Rendering {
},
),
);
frame.fill_text(Text {
content: short(&marker.name, 18),
position: marker.capsule.position() + iced::Vector::new(8., 6.),
size: 12.into(),
color,
..Default::default()
});
if let Some(clip) = birth_label_clip(marker.capsule, scale, frame.size()) {
// WGPU clipped frames start with an identity transform;
// neither the parent's scale nor its clip is inherited.
frame.with_clip(clip, |label| {
label.scale(scale);
label.fill_text(Text {
content: short(&marker.name, 18),
position: marker.capsule.position() + iced::Vector::new(8., 6.),
size: 12.into(),
color,
..Default::default()
});
});
}
}
MarkerKind::Death => {
for (a, b) in death_strokes(marker.center) {
@@ -323,6 +330,17 @@ impl Rendering {
}
}
fn birth_label_clip(capsule: Rectangle, scale: f32, viewport: Size) -> Option<Rectangle> {
if !scale.is_finite() || scale <= 0. {
return None;
}
Rectangle::new(
Point::new((capsule.x + 8.) * scale, (capsule.y + 4.) * scale),
Size::new((capsule.width - 16.) * scale, (capsule.height - 8.) * scale),
)
.intersection(&Rectangle::new(Point::ORIGIN, viewport))
}
fn marker_distance(marker: &EventMarker, point: Point) -> f32 {
match marker.event.kind {
MarkerKind::Birth => {
@@ -172,6 +172,67 @@ fn intervals(scene: &Scene, lifeline: Id, scope: Id) -> Vec<(f32, f32, Life)> {
.collect()
}
#[test]
fn birth_label_clip_keeps_padded_world_bounds_at_each_scene_scale() {
let capsule = Rectangle::new(Point::new(100., 200.), Size::new(140., 28.));
for (scale, expected) in [
(
0.05,
Rectangle::new(Point::new(5.4, 10.2), Size::new(6.2, 1.)),
),
(
0.5,
Rectangle::new(Point::new(54., 102.), Size::new(62., 10.)),
),
(
1.,
Rectangle::new(Point::new(108., 204.), Size::new(124., 20.)),
),
(
2.,
Rectangle::new(Point::new(216., 408.), Size::new(248., 40.)),
),
] {
let clip = birth_label_clip(capsule, scale, Size::new(1000., 1000.)).unwrap();
for (actual, expected) in [
(clip.x, expected.x),
(clip.y, expected.y),
(clip.width, expected.width),
(clip.height, expected.height),
] {
assert!((actual - expected).abs() < 0.0001);
}
assert!(capsule.contains(Point::new(clip.x / scale, clip.y / scale)));
assert!(capsule.contains(Point::new(
(clip.x + clip.width) / scale,
(clip.y + clip.height) / scale,
)));
}
}
#[test]
fn birth_label_clip_preserves_parent_viewport_bounds() {
let capsule = Rectangle::new(Point::new(100., 200.), Size::new(140., 28.));
assert_eq!(
birth_label_clip(capsule, 2., Size::new(300., 420.)),
Some(Rectangle::new(Point::new(216., 408.), Size::new(84., 12.)))
);
let crossing_origin = Rectangle::new(Point::new(-20., -10.), Size::new(140., 28.));
assert_eq!(
birth_label_clip(crossing_origin, 2., Size::new(300., 420.)),
Some(Rectangle::new(Point::ORIGIN, Size::new(224., 28.)))
);
for viewport in [Size::new(200., 420.), Size::new(300., 400.), Size::ZERO] {
assert_eq!(birth_label_clip(capsule, 2., viewport), None);
}
for scale in [0., -1., f32::NAN, f32::INFINITY] {
assert_eq!(
birth_label_clip(capsule, scale, Size::new(1000., 1000.)),
None
);
}
}
#[test]
fn real_scene_empty_and_create_only_use_visible_root_extents_not_insertion_bounds() {
for layer in [Layer::Oa, Layer::Sa] {