Keep activation borders above message label backplates
This commit is contained in:
@@ -1621,17 +1621,9 @@ impl Scene {
|
||||
.iter()
|
||||
.find(|line| {
|
||||
!line.label.is_empty()
|
||||
&& (Rectangle::new(
|
||||
line.label_at,
|
||||
Size::new(
|
||||
(line.label.chars().count() as f32 * 6.8 + 8.).min(225.),
|
||||
25.,
|
||||
),
|
||||
)
|
||||
.contains(point)
|
||||
|| line.points.windows(2).any(|p| {
|
||||
crate::diagram::distance_to_segment(point, p[0], p[1]) < 1.5
|
||||
}))
|
||||
&& line.points.windows(2).any(|p| {
|
||||
crate::diagram::distance_to_segment(point, p[0], p[1]) < 1.5
|
||||
})
|
||||
})
|
||||
.map(|line| line.id)
|
||||
})
|
||||
@@ -1642,6 +1634,22 @@ impl Scene {
|
||||
.find(|b| b.rect.contains(point))
|
||||
.map(|b| b.call)
|
||||
})
|
||||
.or_else(|| {
|
||||
self.lines
|
||||
.iter()
|
||||
.find(|line| {
|
||||
!line.label.is_empty()
|
||||
&& Rectangle::new(
|
||||
line.label_at,
|
||||
Size::new(
|
||||
(line.label.chars().count() as f32 * 6.8 + 8.).min(225.),
|
||||
25.,
|
||||
),
|
||||
)
|
||||
.contains(point)
|
||||
})
|
||||
.map(|line| line.id)
|
||||
})
|
||||
// The generous message tolerance must not steal the exposed
|
||||
// shoulder of another call's execution when the view is zoomed out.
|
||||
.or_else(|| {
|
||||
@@ -1973,6 +1981,18 @@ impl canvas::Program<Message> for Scene {
|
||||
.with_width(if guide.id == self.selected { 2.5 } else { 1.2 }),
|
||||
);
|
||||
}
|
||||
// Label backplates clear guides, but must not erase execution borders
|
||||
// or message geometry. Their text remains in the foreground below.
|
||||
for line in self.lines.iter().filter(|line| !line.label.is_empty()) {
|
||||
f.fill_rectangle(
|
||||
line.label_at,
|
||||
Size::new(
|
||||
(line.label.chars().count() as f32 * 6.8 + 8.).min(225.),
|
||||
22.,
|
||||
),
|
||||
Color::WHITE,
|
||||
);
|
||||
}
|
||||
for activation in &self.activations {
|
||||
let path = Path::rectangle(activation.rect.position(), activation.rect.size());
|
||||
f.fill(&path, Color::from_rgb8(243, 248, 248));
|
||||
@@ -2054,11 +2074,6 @@ impl canvas::Program<Message> for Scene {
|
||||
}),
|
||||
Stroke::default().with_color(color),
|
||||
);
|
||||
f.fill_rectangle(
|
||||
l.label_at,
|
||||
Size::new((l.label.chars().count() as f32 * 6.8 + 8.).min(225.), 22.),
|
||||
Color::WHITE,
|
||||
);
|
||||
f.fill_text(Text {
|
||||
content: short(&l.label, 32),
|
||||
position: l.label_at,
|
||||
|
||||
@@ -118,6 +118,99 @@ fn scene(f: &Fixture) -> Scene {
|
||||
fn bar(scene: &Scene, call: Id) -> &Segment {
|
||||
scene.activations.iter().find(|a| a.call == call).unwrap()
|
||||
}
|
||||
|
||||
fn long_labels(f: &mut Fixture) {
|
||||
for occurrence in [f.call, f.inner, f.inner_reply, f.reply] {
|
||||
f.project.elements.get_mut(&occurrence).unwrap().name =
|
||||
"Pressure verification with a descriptive operation label".into();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn activation_shoulders_under_label_backplates_select_their_call() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = fixture(layer, true);
|
||||
long_labels(&mut f);
|
||||
let scene = scene(&f);
|
||||
let outer = bar(&scene, f.call);
|
||||
for occurrence in [f.inner, f.inner_reply, f.reply] {
|
||||
let message = line(&scene, occurrence);
|
||||
let shoulder = Point::new(outer.rect.x + 1., message.label_at.y + 11.);
|
||||
assert!(outer.rect.contains(shoulder));
|
||||
assert!(Rectangle::new(message.label_at, Size::new(225., 22.)).contains(shoulder));
|
||||
assert!(message.label.chars().count() > 32);
|
||||
assert!(message.points.windows(2).all(|p| {
|
||||
crate::diagram::distance_to_segment(shoulder, p[0], p[1]) > 8. / scene.scale
|
||||
}));
|
||||
assert_eq!(scene.hit(shoulder), Some(f.call));
|
||||
assert_eq!(
|
||||
scene.hit(message.label_at + iced::Vector::new(4., 4.)),
|
||||
Some(occurrence),
|
||||
"Label text outside activations remains selectable"
|
||||
);
|
||||
}
|
||||
for occurrence in [f.call, f.inner, f.inner_reply, f.reply] {
|
||||
let message = line(&scene, occurrence);
|
||||
for endpoint in [message.points[0], *message.points.last().unwrap()] {
|
||||
assert_eq!(scene.hit(endpoint), Some(occurrence));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn long_message_backplates_do_not_erase_selected_activation_border_pixels() {
|
||||
use iced::advanced::renderer::Headless;
|
||||
use iced_runtime::user_interface::{Cache, UserInterface};
|
||||
|
||||
let mut mounted = crate::behavior::canvas_workspace::tests::Mounted::unshown().await;
|
||||
let mut f = fixture(Layer::Oa, true);
|
||||
long_labels(&mut f);
|
||||
let mut scene = scene(&f);
|
||||
scene.selected = f.call;
|
||||
let border_x = bar(&scene, f.call).rect.x;
|
||||
let label_spans: Vec<_> = [f.inner, f.inner_reply, f.reply]
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
let message = line(&scene, id);
|
||||
assert!(
|
||||
Rectangle::new(message.label_at, Size::new(225., 22.))
|
||||
.contains(Point::new(border_x, message.label_at.y + 11.))
|
||||
);
|
||||
(message.label_at.y + 3.) as usize..(message.label_at.y + 20.) as usize
|
||||
})
|
||||
.collect();
|
||||
let dimensions = Size::new(800., 700.);
|
||||
let content: Element<'_, Message> = canvas::Canvas::new(scene)
|
||||
.width(dimensions.width)
|
||||
.height(dimensions.height)
|
||||
.into();
|
||||
let mut ui = UserInterface::build(content, dimensions, Cache::new(), &mut mounted.renderer);
|
||||
ui.draw(
|
||||
&mut mounted.renderer,
|
||||
&style::theme(),
|
||||
&iced::advanced::renderer::Style {
|
||||
text_color: style::INK,
|
||||
},
|
||||
mouse::Cursor::Unavailable,
|
||||
);
|
||||
let pixels = Headless::screenshot(&mut mounted.renderer, Size::new(800, 700), 1., Color::WHITE);
|
||||
let expected = [style::TEAL.r, style::TEAL.g, style::TEAL.b].map(|v| v * 255.);
|
||||
for span in label_spans {
|
||||
for y in span {
|
||||
assert!(
|
||||
((border_x as usize - 2)..=(border_x as usize + 2)).any(|x| {
|
||||
let offset = (y * 800 + x) * 4;
|
||||
pixels[offset..offset + 3]
|
||||
.iter()
|
||||
.zip(expected)
|
||||
.all(|(actual, target)| (*actual as f32 - target).abs() < 30.)
|
||||
}),
|
||||
"Selected activation border was erased at y={y}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn line(scene: &Scene, occurrence: Id) -> &super::super::Line {
|
||||
scene.lines.iter().find(|a| a.id == occurrence).unwrap()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user