use super::*;
use brep_render::sheets::dimension::{DimensionDrawing, VERTEX};
use brep_render::sheets::ordinate::OrdinateDrawing;
use brep_render::sheets::project::{SheetDrawing, ViewDrawing};
use std::collections::HashMap;
enum SheetIntent {
Move(String, [f64; 2]),
MoveEnd,
OpenObject(Option<String>),
SelectObject(String),
MoveDimension(String, [f64; 2]),
MoveDimensionEnd,
MoveOrdinate(String, [f64; 2]),
MoveOrdinateEnd,
RefPick(String),
}
#[derive(Debug, Clone, PartialEq)]
struct SheetPick {
picked: Vec<String>,
prompt: String,
}
enum Grab {
Dimension(String),
Ordinate(String),
}
const ANCHOR_PICK_RADIUS: f32 = 9.0;
const ANCHOR_MARK: f32 = 2.2;
const FIT_MARGIN: f32 = 1.06;
const ZOOM_STEP: f32 = 1.1;
const MIN_ZOOM: f32 = 0.02;
const MAX_ZOOM: f32 = 200.0;
const EDGE_WIDTH: f32 = 1.1;
const ANNOTATION_WIDTH: f32 = 0.9;
const FRAME_WIDTH: f32 = 1.3;
const CAP_PER_EM: f32 = 0.72;
#[derive(Default)]
pub struct SheetViewport {
fitted: Option<String>,
zoom: f32,
offset: egui::Vec2,
panning: bool,
dragging: Option<(String, egui::Vec2)>,
dim_dragging: Option<String>,
ord_dragging: Option<String>,
selected: Option<String>,
menu: Option<(String, egui::Pos2)>,
menu_request: Option<(Option<String>, egui::Pos2)>,
hits: HashMap<String, egui::Rect>,
}
impl SheetViewport {
pub fn request_fit(&mut self) {
self.fitted = None;
}
pub fn forget(&mut self) {
self.fitted = None;
self.panning = false;
self.dragging = None;
self.dim_dragging = None;
self.ord_dragging = None;
self.menu = None;
self.menu_request = None;
self.hits.clear();
}
pub fn hits_json(&self) -> String {
crate::automation::hit_rects::hits_json(&self.hits)
}
fn to_screen(&self, rect: egui::Rect, mm: [f64; 2]) -> egui::Pos2 {
rect.min + self.offset + egui::vec2(mm[0] as f32, mm[1] as f32) * self.zoom
}
fn to_sheet(&self, rect: egui::Rect, pos: egui::Pos2) -> egui::Vec2 {
(pos - rect.min - self.offset) / self.zoom
}
fn fit(&mut self, rect: egui::Rect, width_mm: f64, height_mm: f64) {
let (w, h) = (width_mm.max(1.0) as f32, height_mm.max(1.0) as f32);
self.zoom = (rect.width() / (w * FIT_MARGIN))
.min(rect.height() / (h * FIT_MARGIN))
.clamp(MIN_ZOOM, MAX_ZOOM);
self.offset = egui::vec2(
(rect.width() - w * self.zoom) * 0.5,
(rect.height() - h * self.zoom) * 0.5,
);
}
fn grab_rect(&self, rect: egui::Rect, view: &ViewDrawing) -> egui::Rect {
let bounds = egui::Rect::from_two_pos(
self.to_screen(rect, [view.bounds[0], view.bounds[1]]),
self.to_screen(rect, [view.bounds[2], view.bounds[3]]),
);
egui::Rect::from_center_size(
bounds.center(),
egui::vec2(bounds.width().max(12.0), bounds.height().max(12.0)),
)
}
fn placement_at(
&self,
rect: egui::Rect,
drawing: &brep_render::sheets::project::SheetDrawing,
pos: egui::Pos2,
) -> Option<String> {
drawing
.views
.iter()
.rev()
.find(|view| self.grab_rect(rect, view).contains(pos))
.map(|view| view.id.clone())
}
fn anchor_at(
&self,
rect: egui::Rect,
drawing: &SheetDrawing,
pos: egui::Pos2,
) -> Option<String> {
let mut best: Option<(u8, f32, &str)> = None;
for view in &drawing.views {
for candidate in &view.anchors {
let at = self.to_screen(rect, candidate.at);
let away = at.distance(pos);
if away > ANCHOR_PICK_RADIUS {
continue;
}
let rank = match candidate.kind.as_str() {
VERTEX => 0u8,
brep_render::sheets::dimension::CIRCLE => 1,
_ => 2,
};
if best.is_none_or(|(r, d, _)| (rank, away) < (r, d)) {
best = Some((rank, away, candidate.anchor.as_str()));
}
}
}
best.map(|(_, _, anchor)| anchor.to_string())
}
fn value_rect(&self, rect: egui::Rect, dimension: &DimensionDrawing) -> egui::Rect {
let centre = self.to_screen(rect, dimension.label);
let (w, h) = match dimension.texts.first() {
Some(run) => (
run.text.chars().count() as f32 * 0.75 * run.height as f32 * self.zoom,
run.height as f32 * self.zoom,
),
None => (0.0, 0.0),
};
egui::Rect::from_center_size(centre, egui::vec2(w.max(14.0), h.max(12.0)))
}
fn dimension_at(
&self,
rect: egui::Rect,
drawing: &SheetDrawing,
pos: egui::Pos2,
) -> Option<String> {
drawing
.dimensions
.iter()
.rev()
.find(|dimension| self.value_rect(rect, dimension).contains(pos))
.map(|dimension| dimension.id.clone())
}
fn ordinate_rect(&self, rect: egui::Rect, set: &OrdinateDrawing) -> egui::Rect {
let centre = self.to_screen(rect, set.label);
let datum = set.stations.iter().find(|station| station.datum);
let run = datum.and_then(|station| {
set.texts.iter().find(|text| text.text == station.text)
});
let (w, h) = match run {
Some(run) => (
run.text.chars().count() as f32 * 0.75 * run.height as f32 * self.zoom,
run.height as f32 * self.zoom,
),
None => (0.0, 0.0),
};
egui::Rect::from_center_size(centre, egui::vec2(w.max(14.0), h.max(12.0)))
}
fn ordinate_at(
&self,
rect: egui::Rect,
drawing: &SheetDrawing,
pos: egui::Pos2,
) -> Option<String> {
drawing
.ordinates
.iter()
.rev()
.find(|set| set.error.is_empty() && self.ordinate_rect(rect, set).contains(pos))
.map(|set| set.id.clone())
}
fn zoom_about(&mut self, rect: egui::Rect, pivot: egui::Pos2, factor: f32) {
let before = self.to_sheet(rect, pivot);
self.zoom = (self.zoom * factor).clamp(MIN_ZOOM, MAX_ZOOM);
let after = self.to_sheet(rect, pivot);
self.offset += (after - before) * self.zoom;
}
}
fn sheet_pick(state: &EngineState) -> Option<SheetPick> {
state.ref_select_is_sheet().then(|| SheetPick {
picked: state.ref_select_names(),
prompt: format!("picking {} \u{00b7} click anchors on the paper \u{00b7} Done on the field commits, Escape cancels", state.ref_select_prompt()),
})
}
impl Viewport {
pub(super) fn show_sheet(
&mut self,
ui: &mut egui::Ui,
rect: egui::Rect,
response: &egui::Response,
state: &mut EngineState,
) -> bool {
let Some(open) = state.sheet_open().map(String::from) else {
self.sheet.forget();
return false;
};
if !state.sheet_project(&open) {
self.sheet.forget();
return false;
}
let mut intents: Vec<SheetIntent> = Vec::new();
let pick = sheet_pick(state);
self.sheet.selected = state.sheet_selected_object().map(String::from);
let Some(drawing) = state.sheet_drawing_cached(&open) else {
self.sheet.forget();
return false;
};
self.paint_and_drive_sheet(ui, rect, response, &open, drawing, pick.as_ref(), &mut intents);
self.apply_sheet_intents(state, intents);
self.show_sheet_menu(ui, state, &open);
true
}
fn show_sheet_menu(&mut self, ui: &egui::Ui, state: &mut EngineState, open: &str) {
let mut chosen: Option<(String, String)> = None;
if let Some((id, pos)) = self.sheet.menu.clone() {
let actions = crate::panels::sheets::object_actions(&state.sheet_state(), Some(open), &id);
if actions.is_empty() {
self.sheet.menu = None;
} else {
let hits = &mut self.sheet.hits;
let (action, still_open) = crate::column_tree::action_menu(
ui.ctx(),
ui.layer_id(),
egui::Id::new("brep-sheet-menu"),
pos,
&actions,
|entry, rect| {
hits.insert(format!("menuitem:{id}:{}", entry.id), rect);
},
);
if !still_open {
self.sheet.menu = None;
}
chosen = action.map(|action| (id, action));
}
}
if let Some((id, action)) = chosen {
if let Err(error) = crate::panels::sheets::run_row_action(state, &id, &action) {
state.push_notice(format!("Sheets: {error}"));
}
}
if let Some((object, pos)) = self.sheet.menu_request.take() {
self.sheet.menu = Some((object.unwrap_or_else(|| open.to_string()), pos));
}
}
#[allow(clippy::too_many_arguments)]
fn paint_and_drive_sheet(
&mut self,
ui: &mut egui::Ui,
rect: egui::Rect,
response: &egui::Response,
open: &str,
drawing: &SheetDrawing,
pick: Option<&SheetPick>,
intents: &mut Vec<SheetIntent>,
) {
self.sheet.hits.clear();
if self.sheet.fitted.as_deref() != Some(open) || self.sheet.zoom <= 0.0 {
self.sheet.fit(rect, drawing.width_mm, drawing.height_mm);
self.sheet.fitted = Some(open.to_string());
}
self.handle_sheet_input(ui, rect, response, drawing, pick.is_some(), intents);
let painter = ui.painter_at(rect);
let visuals = ui.visuals();
let desk = if visuals.dark_mode {
egui::Color32::from_rgb(0x1b, 0x1f, 0x24)
} else {
egui::Color32::from_rgb(0x8a, 0x8f, 0x96)
};
painter.rect_filled(rect, 0.0, desk);
let paper = egui::Rect::from_min_max(
self.sheet.to_screen(rect, [0.0, 0.0]),
self.sheet.to_screen(rect, [drawing.width_mm, drawing.height_mm]),
);
painter.rect_filled(paper, 0.0, egui::Color32::from_rgb(0xfa, 0xfa, 0xf7));
painter.rect_stroke(
paper,
0.0,
egui::Stroke::new(1.0, egui::Color32::from_rgb(0x33, 0x33, 0x33)),
egui::StrokeKind::Inside,
);
self.sheet.hits.insert("paper".into(), paper);
let ink = egui::Color32::from_rgb(0x11, 0x11, 0x11);
if let Some(frame) = &drawing.frame {
for line in &frame.lines {
let points: Vec<egui::Pos2> =
line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(FRAME_WIDTH, ink)));
}
for text in &frame.texts {
self.paint_sheet_text(&painter, rect, text, ink);
}
if let Some(table) = &frame.revision_table {
for line in &table.lines {
let points: Vec<egui::Pos2> =
line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(FRAME_WIDTH, ink)));
}
for text in &table.texts {
self.paint_sheet_text(&painter, rect, text, ink);
}
}
}
for view in &drawing.views {
self.paint_sheet_view(ui, &painter, rect, view, ink);
}
for dimension in &drawing.dimensions {
self.paint_sheet_dimension(&painter, rect, dimension, ink);
}
for set in &drawing.ordinates {
self.paint_sheet_ordinate(&painter, rect, set, ink);
}
if let Some(pick) = pick {
self.paint_anchor_candidates(&painter, rect, drawing, &pick.picked);
}
self.paint_sheet_caption(&painter, rect, drawing, pick);
let _ = ui;
}
fn paint_sheet_caption(
&self,
painter: &egui::Painter,
rect: egui::Rect,
drawing: &SheetDrawing,
pick: Option<&SheetPick>,
) {
let at = rect.min + egui::vec2(10.0, 8.0);
painter.text(
at,
egui::Align2::LEFT_TOP,
format!(
"{} \u{00b7} {:.0} \u{00d7} {:.0} mm",
drawing.name, drawing.width_mm, drawing.height_mm
),
egui::FontId::proportional(12.0),
egui::Color32::from_rgb(0xd0, 0xd4, 0xd8),
);
if let Some(pick) = pick {
painter.text(
at + egui::vec2(0.0, 16.0),
egui::Align2::LEFT_TOP,
&pick.prompt,
egui::FontId::proportional(12.0),
egui::Color32::from_rgb(0x58, 0xa6, 0xff),
);
}
}
fn apply_sheet_intents(&mut self, state: &mut EngineState, intents: Vec<SheetIntent>) {
for intent in intents {
match intent {
SheetIntent::Move(id, position) => {
let _ = state.sheet_move_view(&id, position, true);
}
SheetIntent::MoveEnd => state.sheet_move_view_end(),
SheetIntent::OpenObject(id) => {
if id.is_none() {
state.sheet_select_object(None);
}
state.sheet_set_object_open(id.as_deref());
}
SheetIntent::SelectObject(id) => {
state.sheet_select_object(Some(&id));
}
SheetIntent::MoveDimension(id, at) => {
if let Some(offset) = state.sheet_dimension_offset_at(&id, at) {
let _ = state.sheet_move_dimension(&id, offset, true);
}
}
SheetIntent::MoveDimensionEnd => state.sheet_move_dimension_end(),
SheetIntent::MoveOrdinate(id, at) => {
if let Some(offset) = state.sheet_ordinate_offset_at(&id, at) {
let _ = state.sheet_move_ordinate(&id, offset, true);
}
}
SheetIntent::MoveOrdinateEnd => state.sheet_move_ordinate_end(),
SheetIntent::RefPick(anchor) => {
if let Err(error) = state.ref_select_pick_sheet_anchor(&anchor) {
state.push_notice(format!("Sheets: {error}"));
}
}
}
}
}
fn paint_sheet_view(
&mut self,
ui: &egui::Ui,
painter: &egui::Painter,
rect: egui::Rect,
view: &ViewDrawing,
ink: egui::Color32,
) {
let grab = self.sheet.grab_rect(rect, view);
self.sheet.hits.insert(format!("view:{}", view.id), grab);
let dragged = self.sheet.dragging.as_ref().is_some_and(|(id, _)| *id == view.id)
|| self.sheet.selected.as_deref() == Some(view.id.as_str());
if dragged || !view.error.is_empty() {
let color = if view.error.is_empty() {
egui::Color32::from_rgb(0x58, 0xa6, 0xff)
} else {
egui::Color32::from_rgb(0xf8, 0x51, 0x49)
};
painter.rect_stroke(
grab.expand(2.0),
2.0,
egui::Stroke::new(1.0, color),
egui::StrokeKind::Outside,
);
}
if !view.error.is_empty() {
painter.text(
grab.center(),
egui::Align2::CENTER_CENTER,
&view.error,
egui::FontId::proportional(11.0),
egui::Color32::from_rgb(0xf8, 0x51, 0x49),
);
return;
}
for run in &view.edges {
let points: Vec<egui::Pos2> = run.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(EDGE_WIDTH, ink)));
}
if view.hidden_line == "mesh" {
painter.text(
grab.left_top() + egui::vec2(0.0, -3.0),
egui::Align2::LEFT_BOTTOM,
"approximate hidden lines (mesh)",
egui::FontId::proportional(11.0),
egui::Color32::from_rgb(0xb0, 0x70, 0x10),
);
}
if let Some(section) = &view.section {
for line in §ion.hatch {
let points: Vec<egui::Pos2> =
line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(ANNOTATION_WIDTH, ink)));
}
for text in §ion.texts {
self.paint_sheet_text(painter, rect, text, ink);
}
}
for mark in &view.section_marks {
for line in &mark.lines {
let points: Vec<egui::Pos2> =
line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(FRAME_WIDTH, ink)));
}
for text in &mark.texts {
self.paint_sheet_text(painter, rect, text, ink);
}
}
if let Some(detail) = &view.detail {
let points: Vec<egui::Pos2> =
detail.circle.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(ANNOTATION_WIDTH, ink)));
for text in &detail.texts {
self.paint_sheet_text(painter, rect, text, ink);
}
}
for mark in &view.detail_marks {
for line in &mark.lines {
let points: Vec<egui::Pos2> =
line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(FRAME_WIDTH, ink)));
}
for text in &mark.texts {
self.paint_sheet_text(painter, rect, text, ink);
}
}
for annotation in &view.annotations {
for line in &annotation.lines {
let points: Vec<egui::Pos2> = line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(ANNOTATION_WIDTH, ink)));
}
for text in &annotation.texts {
self.paint_sheet_text(painter, rect, text, ink);
}
}
let _ = ui;
}
fn paint_sheet_dimension(
&mut self,
painter: &egui::Painter,
rect: egui::Rect,
dimension: &DimensionDrawing,
ink: egui::Color32,
) {
let box_rect = self.sheet.value_rect(rect, dimension);
self.sheet.hits.insert(format!("dim:{}", dimension.id), box_rect);
if !dimension.error.is_empty() {
let red = egui::Color32::from_rgb(0xf8, 0x51, 0x49);
painter.text(
self.sheet.to_screen(rect, dimension.label),
egui::Align2::CENTER_CENTER,
format!("{} \u{2014} {}", dimension.id, dimension.error),
egui::FontId::proportional(11.0),
red,
);
return;
}
let dragged = self.sheet.dim_dragging.as_deref() == Some(dimension.id.as_str())
|| self.sheet.selected.as_deref() == Some(dimension.id.as_str());
let colour = if dragged { egui::Color32::from_rgb(0x58, 0xa6, 0xff) } else { ink };
for line in &dimension.lines {
let points: Vec<egui::Pos2> = line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(ANNOTATION_WIDTH, colour)));
}
for text in &dimension.texts {
self.paint_sheet_text(painter, rect, text, colour);
}
}
fn paint_sheet_ordinate(
&mut self,
painter: &egui::Painter,
rect: egui::Rect,
set: &OrdinateDrawing,
ink: egui::Color32,
) {
let red = egui::Color32::from_rgb(0xf8, 0x51, 0x49);
if !set.error.is_empty() {
painter.text(
self.sheet.to_screen(rect, set.label),
egui::Align2::CENTER_CENTER,
format!("{} \u{2014} {}", set.id, set.error),
egui::FontId::proportional(11.0),
red,
);
return;
}
let box_rect = self.sheet.ordinate_rect(rect, set);
self.sheet.hits.insert(format!("ord:{}", set.id), box_rect);
let dragged = self.sheet.ord_dragging.as_deref() == Some(set.id.as_str())
|| self.sheet.selected.as_deref() == Some(set.id.as_str());
let colour = if dragged { egui::Color32::from_rgb(0x58, 0xa6, 0xff) } else { ink };
for line in &set.lines {
let points: Vec<egui::Pos2> = line.iter().map(|p| self.sheet.to_screen(rect, *p)).collect();
painter.add(egui::Shape::line(points, egui::Stroke::new(ANNOTATION_WIDTH, colour)));
}
for text in &set.texts {
self.paint_sheet_text(painter, rect, text, colour);
}
let lost = set.lost();
if lost > 0 {
painter.text(
box_rect.center_bottom() + egui::vec2(0.0, 2.0),
egui::Align2::CENTER_TOP,
format!("{lost} member{} lost", if lost == 1 { "" } else { "s" }),
egui::FontId::proportional(10.0),
red,
);
}
}
fn paint_anchor_candidates(
&mut self,
painter: &egui::Painter,
rect: egui::Rect,
drawing: &SheetDrawing,
picked: &[String],
) {
let candidate_mark = egui::Color32::from_rgb(0x58, 0xa6, 0xff);
let picked_mark = egui::Color32::from_rgb(0xff, 0x9f, 0x0a);
for view in &drawing.views {
for candidate in &view.anchors {
let at = self.sheet.to_screen(rect, candidate.at);
if !rect.contains(at) {
continue;
}
let is_picked = picked.iter().any(|name| *name == candidate.anchor);
let (mark, size) = if is_picked {
(picked_mark, ANCHOR_MARK * 2.0)
} else {
(candidate_mark, ANCHOR_MARK)
};
if candidate.kind == VERTEX {
painter.rect_filled(
egui::Rect::from_center_size(at, egui::Vec2::splat(size * 2.0)),
0.0,
mark,
);
} else {
painter.circle_filled(at, size, mark);
}
self.sheet.hits.insert(
format!("anchor:{}", candidate.anchor),
egui::Rect::from_center_size(at, egui::Vec2::splat(ANCHOR_PICK_RADIUS)),
);
}
}
}
fn paint_sheet_text(
&self,
painter: &egui::Painter,
rect: egui::Rect,
text: &brep_render::sheets::project::SheetText,
ink: egui::Color32,
) {
let size = (text.height as f32 * self.sheet.zoom / CAP_PER_EM).clamp(1.0, 200.0);
let galley =
painter.layout_no_wrap(text.text.clone(), egui::FontId::proportional(size), ink);
let centre = galley.rect.center().to_vec2();
let mut shape = egui::epaint::TextShape::new(egui::Pos2::ZERO, galley, ink)
.with_angle_and_anchor((text.angle as f32).to_radians(), egui::Align2::CENTER_CENTER);
shape.pos += self.sheet.to_screen(rect, text.anchor).to_vec2() - centre;
painter.add(shape);
}
#[allow(clippy::too_many_arguments)]
fn handle_sheet_input(
&mut self,
ui: &mut egui::Ui,
rect: egui::Rect,
response: &egui::Response,
drawing: &SheetDrawing,
picking: bool,
intents: &mut Vec<SheetIntent>,
) {
let pointer = ui.ctx().pointer_interact_pos();
let menu_up = self.sheet.menu.is_some();
let wheel = super::interaction::raw_wheel_delta_y(ui.ctx());
if response.hovered() && wheel.abs() > 0.01 {
let pivot = pointer.unwrap_or(rect.center());
self.sheet.zoom_about(rect, pivot, ZOOM_STEP.powf(wheel / 50.0));
}
if response.drag_started() {
let start = ui
.ctx()
.input(|i| i.pointer.press_origin())
.or(pointer)
.unwrap_or(rect.center());
let middle = ui.ctx().input(|i| i.pointer.middle_down());
let grab = (!middle && !picking)
.then(|| {
self.sheet
.dimension_at(rect, drawing, start)
.map(Grab::Dimension)
.or_else(|| self.sheet.ordinate_at(rect, drawing, start).map(Grab::Ordinate))
})
.flatten();
if let Some(grab) = grab {
match grab {
Grab::Dimension(id) => self.sheet.dim_dragging = Some(id),
Grab::Ordinate(id) => self.sheet.ord_dragging = Some(id),
}
} else {
let hit = (!middle && !picking)
.then(|| self.sheet.placement_at(rect, drawing, start))
.flatten();
match hit {
Some(id) => {
let position = drawing
.views
.iter()
.find(|view| view.id == id)
.map(|view| view.position)
.unwrap_or([0.0, 0.0]);
let grab = self.sheet.to_sheet(rect, start)
- egui::vec2(position[0] as f32, position[1] as f32);
self.sheet.dragging = Some((id, grab));
}
None => self.sheet.panning = true,
}
}
}
if response.dragged() {
let delta = response.drag_delta();
if let Some(id) = self.sheet.dim_dragging.clone() {
if let Some(pos) = pointer {
let at = self.sheet.to_sheet(rect, pos);
intents.push(SheetIntent::MoveDimension(id, [at.x as f64, at.y as f64]));
}
} else if let Some(id) = self.sheet.ord_dragging.clone() {
if let Some(pos) = pointer {
let at = self.sheet.to_sheet(rect, pos);
intents.push(SheetIntent::MoveOrdinate(id, [at.x as f64, at.y as f64]));
}
} else if self.sheet.panning {
self.sheet.offset += delta;
} else if let Some((id, grab)) = self.sheet.dragging.clone() {
if let Some(pos) = pointer {
let target = self.sheet.to_sheet(rect, pos) - grab;
intents.push(SheetIntent::Move(id, [target.x as f64, target.y as f64]));
}
}
}
if response.drag_stopped() {
self.sheet.panning = false;
if self.sheet.dragging.take().is_some() {
intents.push(SheetIntent::MoveEnd);
}
if self.sheet.dim_dragging.take().is_some() {
intents.push(SheetIntent::MoveDimensionEnd);
}
if self.sheet.ord_dragging.take().is_some() {
intents.push(SheetIntent::MoveOrdinateEnd);
}
}
if response.secondary_clicked() && !picking {
let at = pointer.unwrap_or(rect.center());
let object = self
.sheet
.dimension_at(rect, drawing, at)
.or_else(|| self.sheet.ordinate_at(rect, drawing, at))
.or_else(|| self.sheet.placement_at(rect, drawing, at));
if let Some(id) = &object {
intents.push(SheetIntent::SelectObject(id.clone()));
}
self.sheet.menu_request = Some((object, at));
}
if response.clicked() && !menu_up {
let start = pointer.unwrap_or(rect.center());
if picking {
if let Some(anchor) = self.sheet.anchor_at(rect, drawing, start) {
intents.push(SheetIntent::RefPick(anchor));
}
return;
}
let annotation = self
.sheet
.dimension_at(rect, drawing, start)
.or_else(|| self.sheet.ordinate_at(rect, drawing, start));
if let Some(id) = annotation {
intents.push(if crate::panels::tree::is_double_click(&response) {
SheetIntent::OpenObject(Some(id))
} else {
SheetIntent::SelectObject(id)
});
return;
}
intents.push(SheetIntent::OpenObject(self.sheet.placement_at(rect, drawing, start)));
}
}
}