Combine source navigation and alias rename with canvas ownership guards

This commit is contained in:
2026-09-12 19:49:26 -07:00
parent 95e0e12716
commit a17ca5d066
72 changed files with 21764 additions and 189 deletions
+130 -45
View File
@@ -5,6 +5,7 @@ use iced::widget::{Column, text_input};
use std::collections::BTreeSet;
pub(crate) mod contextual;
mod direct;
mod usages;
mod view;
use direct::Input;
pub(crate) use direct::{
@@ -908,6 +909,13 @@ fn sequence_links(p: &Project, owner: Id) -> Vec<&Relation> {
})
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct PreviewLink {
relation: Id,
source: OccurrenceEndpoint,
target: OccurrenceEndpoint,
}
struct GraphNode {
id: Id,
rect: Rectangle,
@@ -916,26 +924,98 @@ struct GraphNode {
control: Option<ControlKind>,
source: Option<Id>,
preview: Vec<(OccurrenceEndpoint, String)>,
preview_links: Vec<(OccurrenceEndpoint, OccurrenceEndpoint)>,
preview_links: Vec<PreviewLink>,
preview_total: usize,
}
impl GraphNode {
fn preview_link_points(&self, link: &PreviewLink) -> Option<[Point; 2]> {
if ![&link.source, &link.target]
.into_iter()
.all(|endpoint| self.preview.iter().any(|(shown, _)| shown == endpoint))
{
return None;
}
let a = self.endpoint_rect(&link.source);
let b = self.endpoint_rect(&link.target);
Some([
Point::new(a.x + a.width, a.center_y()),
Point::new(b.x, b.center_y()),
])
}
fn preview_link_visible_segment(&self, link: &PreviewLink) -> Option<[Point; 2]> {
visible_preview_segment(
self.preview_link_points(link)?,
(0..self.preview.len()).map(|index| self.preview_rect(index)),
)
}
fn preview_rect(&self, index: usize) -> Rectangle {
Rectangle::new(
Point::new(
self.rect.x + 12. + (index % 2) as f32 * 125.,
self.rect.y + 118. + (index / 2) as f32 * 44.,
),
Size::new(113., 32.),
)
}
fn endpoint_rect(&self, endpoint: &OccurrenceEndpoint) -> Rectangle {
self.preview
.iter()
.position(|(candidate, _)| candidate == endpoint)
.map(|i| {
Rectangle::new(
Point::new(
self.rect.x + 12. + (i % 2) as f32 * 125.,
self.rect.y + 118. + (i / 2) as f32 * 44.,
),
Size::new(113., 32.),
)
})
.map(|index| self.preview_rect(index))
.unwrap_or(self.rect)
}
}
/// Preview tiles are painted after their arrows. Focus only a remaining line
/// segment, excluding opaque tile interiors and their one-pixel borders.
fn visible_preview_segment(
[a, b]: [Point; 2],
tiles: impl Iterator<Item = Rectangle>,
) -> Option<[Point; 2]> {
let delta = b - a;
let length = (delta.x * delta.x + delta.y * delta.y).sqrt();
if !length.is_finite() || length <= 0.5 {
return None;
}
let mut hidden = Vec::new();
for tile in tiles {
let mut low = 0_f32;
let mut high = 1_f32;
for (origin, step, min, max) in [
(a.x, delta.x, tile.x - 1., tile.x + tile.width + 1.),
(a.y, delta.y, tile.y - 1., tile.y + tile.height + 1.),
] {
if step.abs() <= f32::EPSILON {
if origin < min || origin > max {
high = -1.;
break;
}
} else {
let first = (min - origin) / step;
let last = (max - origin) / step;
low = low.max(first.min(last));
high = high.min(first.max(last));
}
}
if high > low {
hidden.push((low, high));
}
}
hidden.sort_by(|a, b| a.0.total_cmp(&b.0));
let mut cursor = 0_f32;
let mut best = (0_f32, 0_f32);
for (low, high) in hidden.into_iter().chain([(1., 1.)]) {
if low - cursor > best.1 - best.0 {
best = (cursor, low);
}
cursor = cursor.max(high);
}
((best.1 - best.0) * length > 0.5).then(|| [a + delta * best.0, a + delta * best.1])
}
struct GraphEdge {
id: Id,
source: Id,
@@ -984,6 +1064,8 @@ struct Graph<'a> {
ordered: bool,
size: Size,
selected: Id,
selected_preview: Option<OccurrenceEndpoint>,
selected_preview_link: Option<PreviewLink>,
scale: f32,
}
impl<'a> Graph<'a> {
@@ -1132,17 +1214,16 @@ impl<'a> Graph<'a> {
occurrence: r.target(),
})
})
.map(|r| {
(
OccurrenceEndpoint {
references: vec![e.id],
occurrence: r.source(),
},
OccurrenceEndpoint {
references: vec![e.id],
occurrence: r.target(),
},
)
.map(|r| PreviewLink {
relation: r.id,
source: OccurrenceEndpoint {
references: vec![e.id],
occurrence: r.source(),
},
target: OccurrenceEndpoint {
references: vec![e.id],
occurrence: r.target(),
},
})
.collect();
}
@@ -1317,6 +1398,8 @@ impl<'a> Graph<'a> {
ordered: !order.is_empty(),
size,
selected,
selected_preview: None,
selected_preview_link: None,
scale: 1.,
}
}
@@ -1546,39 +1629,41 @@ impl canvas::Program<Message> for Graph<'_> {
let mini: BTreeMap<_, _> = n
.preview
.iter()
.enumerate()
.map(|(i, (id, _))| {
(
id.clone(),
Rectangle::new(
Point::new(
n.rect.x + 12. + (i % 2) as f32 * 125.,
n.rect.y + 118. + (i / 2) as f32 * 44.,
),
Size::new(113., 32.),
),
)
})
.map(|(id, _)| (id.clone(), n.endpoint_rect(id)))
.collect();
for (a, b) in &n.preview_links {
let a = mini[a];
let b = mini[b];
for link in &n.preview_links {
let Some(points) = n.preview_link_points(link) else {
continue;
};
let selected = self.selected_preview_link.as_ref() == Some(link);
draw_arrow(
&mut f,
&[
Point::new(a.x + a.width, a.center_y()),
Point::new(b.x, b.center_y()),
],
style::MUTED,
1.,
&points,
if selected { style::TEAL } else { style::MUTED },
if selected { 3. } else { 1. },
);
}
for (id, name) in &n.preview {
let r = mini[id];
f.fill_rectangle(r.position(), r.size(), Color::WHITE);
let selected = self.selected_preview.as_ref() == Some(id);
f.fill_rectangle(
r.position(),
r.size(),
if selected {
Color::from_rgb8(224, 247, 242)
} else {
Color::WHITE
},
);
f.stroke(
&Path::rectangle(r.position(), r.size()),
Stroke::default().with_color(style::BOUNDARY),
Stroke::default()
.with_color(if selected {
style::TEAL
} else {
style::BOUNDARY
})
.with_width(if selected { 2.5 } else { 1. }),
);
draw_text(
&mut f,