Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions crates/app/src/ui/canvas/authoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fn create_object(app: &mut PlotxApp, ci: usize, tool: Tool, frame: ObjectFrame)
Tool::PanelLabel => {
let mut t = app.doc.style_library.panel_label.clone();
t.text = "a".to_owned();
("Panel label".to_owned(), CanvasObjectKind::PanelLabel(t))
("Text".to_owned(), CanvasObjectKind::Text(t))
}
Tool::Text => {
let mut t = app.doc.style_library.text.clone();
Expand All @@ -116,7 +116,6 @@ fn create_object(app: &mut PlotxApp, ci: usize, tool: Tool, frame: ObjectFrame)
frame,
locked: false,
visible: true,
group: None,
kind,
};
let selection_before = app.session.ui.selection.clone();
Expand Down
15 changes: 12 additions & 3 deletions crates/app/src/ui/canvas/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,9 +534,6 @@ fn activate_page(app: &mut PlotxApp, ci: usize) {
}

pub(crate) fn dispatch_frame_gesture(app: &mut PlotxApp, rect: egui::Rect, ui: &Ui) -> bool {
if board_marquee::handle(app, rect, ui) {
return true;
}
let (pressed, double, hover, extend) = ui.input(|i| {
(
i.pointer.primary_pressed(),
Expand All @@ -546,6 +543,18 @@ pub(crate) fn dispatch_frame_gesture(app: &mut PlotxApp, rect: egui::Rect, ui: &
i.modifiers.shift || i.modifiers.command || i.modifiers.ctrl,
)
});
if pressed
&& let Some(point) = hover
&& let Some((canvas, _)) = object_at_screen(app, rect, point)
{
if app.session.active_canvas != Some(canvas) {
activate_frame(app, FrameRef::Page(canvas));
}
return false;
}
if board_marquee::handle(app, rect, ui) {
return true;
}
if extend
&& pressed
&& let Some(p) = hover
Expand Down
1 change: 1 addition & 0 deletions crates/app/src/ui/canvas/board_marquee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(super) fn handle(app: &mut PlotxApp, rect: egui::Rect, ui: &Ui) -> bool {
&& pressed
&& let Some(point) = hover
&& rect.contains(point)
&& object_at_screen(app, rect, point).is_none()
&& frame_at(app, rect, point).is_none()
&& frame_header_at(app, rect, point).is_none()
{
Expand Down
44 changes: 13 additions & 31 deletions crates/app/src/ui/canvas/board_notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,25 @@ pub(crate) fn handle_frame_caption_interactions(
ui.id().with(("panel_note_row", ci, object_id)),
Sense::click(),
)
.on_hover_text("Click to select. Double-click to edit this panel note.");
.on_hover_text("Click to edit this panel description.");
if resp.hovered() {
ui.ctx().set_cursor_icon(egui::CursorIcon::PointingHand);
consumed = true;
}
if resp.double_clicked() {
if resp.clicked() {
app.session.active_canvas = Some(ci);
app.select_object(ci, object_id);
open_inline_panel_note_editor(app, ci, object_id);
consumed = true;
} else if resp.clicked() {
app.session.active_canvas = Some(ci);
app.select_object(ci, object_id);
app.session.status =
"Panel selected. Double-click its note to edit in place.".to_owned();
consumed = true;
}
resp.context_menu(|ui| {
app.session.active_canvas = Some(ci);
app.select_object(ci, object_id);
if ui.button("Edit note in place").clicked() {
if ui.button("Edit description in place").clicked() {
open_inline_panel_note_editor(app, ci, object_id);
ui.close();
}
if ui.button("Edit note in dialog").clicked() {
if ui.button("Edit description in dialog").clicked() {
app.session.ui.panel_note_inline_edit = None;
open_panel_note_editor(app, ci, object_id);
ui.close();
Expand All @@ -83,9 +77,7 @@ pub(crate) fn open_inline_panel_note_editor(app: &mut PlotxApp, ci: usize, objec
.doc
.canvases
.get(ci)
.and_then(|canvas| canvas.object(object_id))
.and_then(|object| object.plot())
.map(|plot| plot.panel.clone())
.and_then(|canvas| canvas.panel_meta_for_content(object_id))
else {
return;
};
Expand Down Expand Up @@ -131,11 +123,9 @@ pub(crate) fn render_inline_panel_note_editor(app: &mut PlotxApp, screen: egui::
if let Some(edit) = app.session.ui.panel_note_inline_edit.as_mut() {
edit.buffer.clone_from(&buffer);
}
if let Some(plot) = app.doc.canvases[ci]
.object_mut(object_id)
.and_then(|object| object.plot_mut())
{
plot.panel.user_note = buffer.clone();
if let Some(mut panel) = app.doc.canvases[ci].panel_meta_for_content(object_id) {
panel.user_note = buffer.clone();
app.doc.canvases[ci].set_panel_meta_for_content(object_id, panel);
app.mark_document_dirty();
}
}
Expand Down Expand Up @@ -202,29 +192,21 @@ fn commit_inline_panel_note_edit(app: &mut PlotxApp) {
.doc
.canvases
.get(ci)
.and_then(|canvas| canvas.object(id))
.and_then(|object| object.plot())
.map(|plot| plot.panel.clone())
.and_then(|canvas| canvas.panel_meta_for_content(id))
else {
return;
};
app.execute_action(Action::set_panel_meta(ci, id, before, after));
app.session.status = "Panel note updated.".to_owned();
app.session.status = "Panel description updated.".to_owned();
}

fn cancel_inline_panel_note_edit(app: &mut PlotxApp) {
app.session.ui.panel_note_inline_edit = None;
let Some((ci, id, before)) = app.session.ui.note_edit_before.take() else {
return;
};
if let Some(plot) = app
.doc
.canvases
.get_mut(ci)
.and_then(|canvas| canvas.object_mut(id))
.and_then(|object| object.plot_mut())
{
plot.panel = before;
if let Some(canvas) = app.doc.canvases.get_mut(ci) {
canvas.set_panel_meta_for_content(id, before);
}
app.session.status = "Panel note edit cancelled.".to_owned();
app.session.status = "Panel description edit cancelled.".to_owned();
}
120 changes: 112 additions & 8 deletions crates/app/src/ui/canvas/geometry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ pub(crate) fn hit_object(canvas: &CanvasDocument, p: Pos2, zoom: f32) -> Option<
if !object.visible {
return None;
}
let frame = canvas.layout_frame(object.id)?;
let r = egui::Rect::from_min_size(
Pos2::new(object.frame.x, object.frame.y),
egui::vec2(object.frame.width, object.frame.height),
Pos2::new(frame.x, frame.y),
egui::vec2(frame.width, frame.height),
);
let handles = [
(r.left_top(), ResizeHandle::TopLeft),
Expand All @@ -37,6 +38,27 @@ pub(crate) fn hit_object(canvas: &CanvasDocument, p: Pos2, zoom: f32) -> Option<
})
}

/// Topmost canvas object under a screen point, including objects outside their
/// owning page. The active page wins ties, matching page/frame hit ordering.
pub(crate) fn object_at_screen(
app: &PlotxApp,
screen: egui::Rect,
point: Pos2,
) -> Option<(usize, ObjectHit)> {
let mut canvases: Vec<_> = (0..app.doc.canvases.len()).rev().collect();
if let Some(active) = app.session.active_canvas
&& canvases.contains(&active)
{
canvases.retain(|candidate| *candidate != active);
canvases.insert(0, active);
}
let transform = BoardTransform::from_board(app.session.board, screen);
canvases.into_iter().find_map(|canvas| {
let page = transform.screen_to_page(&app.doc.canvases[canvas], point);
hit_object(&app.doc.canvases[canvas], page, app.session.board.zoom).map(|hit| (canvas, hit))
})
}

pub(crate) fn drag_frame(
frame: ObjectFrame,
kind: ObjectDragKind,
Expand Down Expand Up @@ -160,15 +182,26 @@ impl BoardTransform {
object_id: ObjectId,
) -> Option<PlotRect> {
let page = self.page_screen_rect(canvas);
let object = canvas.object(object_id)?;
let frame = canvas.layout_frame(object_id)?;
Some(PlotRect::new(
page.left() + object.frame.x * self.zoom,
page.top() + object.frame.y * self.zoom,
object.frame.width * self.zoom,
object.frame.height * self.zoom,
page.left() + frame.x * self.zoom,
page.top() + frame.y * self.zoom,
frame.width * self.zoom,
frame.height * self.zoom,
))
}

pub fn canvas_editor_screen_rect(&self, canvas: &CanvasDocument) -> egui::Rect {
let mut bounds = self.page_screen_rect(canvas);
for object in &canvas.objects {
let Some(frame) = self.object_screen_rect(canvas, object.id) else {
continue;
};
bounds = bounds.union(plot_rect(frame));
}
bounds
}

/// Screen px → board world (pt), before any per-page `board_pos` offset.
pub fn screen_to_world(&self, p: Pos2) -> Pos2 {
Pos2::new(
Expand Down Expand Up @@ -415,7 +448,6 @@ mod tests {
frame,
locked: false,
visible: true,
group: None,
kind: CanvasObjectKind::Text(TextBox::label("x".to_owned())),
}
}
Expand Down Expand Up @@ -652,4 +684,76 @@ mod tests {
let hit = hit_object(&canvas, Pos2::new(35.0, 35.0), 1.0).unwrap();
assert_eq!(hit.object, ObjectId::new(2));
}

#[test]
fn panel_local_content_hits_and_moves_by_distinct_page_frames() {
let mut canvas = CanvasDocument::new("t".to_owned(), [100.0, 100.0]);
let a = ObjectId::new(1);
let b = ObjectId::new(2);
canvas
.objects
.push(text_object(1, ObjectFrame::new(0.0, 0.0, 40.0, 30.0)));
canvas
.objects
.push(text_object(2, ObjectFrame::new(0.0, 0.0, 40.0, 30.0)));
let panel_a = canvas.create_panel("a".into(), ObjectFrame::new(5.0, 10.0, 40.0, 30.0));
let panel_b = canvas.create_panel("b".into(), ObjectFrame::new(55.0, 10.0, 40.0, 30.0));
canvas.panel_mut(panel_a).unwrap().item_order.push(a);
canvas.panel_mut(panel_b).unwrap().item_order.push(b);

assert_eq!(
hit_object(&canvas, Pos2::new(20.0, 20.0), 1.0)
.unwrap()
.object,
a
);
assert_eq!(
hit_object(&canvas, Pos2::new(70.0, 20.0), 1.0)
.unwrap()
.object,
b
);

canvas.set_layout_frame(b, ObjectFrame::new(55.0, 50.0, 40.0, 30.0));
assert_eq!(canvas.layout_frame(a).unwrap().y, 10.0);
assert_eq!(canvas.layout_frame(b).unwrap().y, 50.0);
assert_eq!(
hit_object(&canvas, Pos2::new(70.0, 60.0), 1.0)
.unwrap()
.object,
b
);
}

#[test]
fn outside_page_object_remains_in_editor_bounds_and_hit_testing() {
let mut app = PlotxApp::new();
let mut canvas = CanvasDocument::new("t".to_owned(), [20.0, 20.0]);
canvas.board_pos = [30.0, 40.0];
let [page_width, _] = canvas.size_pt();
let id = ObjectId::new(1);
canvas.objects.push(text_object(
1,
ObjectFrame::new(page_width + 15.0, 10.0, 40.0, 30.0),
));
app.doc.canvases.push(canvas);
app.session.active_canvas = Some(0);
app.session.board = BoardViewport {
zoom: 1.0,
pan: [0.0, 0.0],
auto_fit: false,
};
let screen = egui::Rect::from_min_size(Pos2::ZERO, egui::vec2(500.0, 400.0));
let transform = BoardTransform::from_board(app.session.board, screen);
let page = transform.page_screen_rect(&app.doc.canvases[0]);
let point = Pos2::new(page.left() + page_width + 25.0, page.top() + 20.0);

assert!(!page.contains(point));
assert!(
transform
.canvas_editor_screen_rect(&app.doc.canvases[0])
.contains(point)
);
assert_eq!(object_at_screen(&app, screen, point).unwrap().1.object, id);
}
}
Loading
Loading