mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 07:08:11 +08:00
Split out viewport handling into its own message handler (#3331)
* extract viewport handeling * fix web overlays * some cleanup * remove some physical conversions * fix resize snapping * fixup * apply some review feedback * make viewport api more ergonomic * fix * fix web overlay canvas clear size * clear workaround for canvas * rename trigger message --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
@@ -52,10 +52,10 @@ pub struct DocumentMessageContext<'a> {
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub current_tool: &'a ToolType,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
pub data_panel_open: bool,
|
||||
pub layers_panel_open: bool,
|
||||
pub properties_panel_open: bool,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
@@ -195,9 +195,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
ipp,
|
||||
persistent_data,
|
||||
executor,
|
||||
viewport,
|
||||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio,
|
||||
data_panel_open,
|
||||
layers_panel_open,
|
||||
properties_panel_open,
|
||||
@@ -213,6 +213,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
document_ptz: &mut self.document_ptz,
|
||||
graph_view_overlay_open: self.graph_view_overlay_open,
|
||||
preferences,
|
||||
viewport,
|
||||
};
|
||||
|
||||
self.navigation_handler.process_message(message, responses, context);
|
||||
@@ -221,15 +222,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let visibility_settings = self.overlays_visibility_settings;
|
||||
|
||||
// Send the overlays message to the overlays message handler
|
||||
self.overlays_message_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
OverlaysMessageContext {
|
||||
visibility_settings,
|
||||
ipp,
|
||||
device_pixel_ratio,
|
||||
},
|
||||
);
|
||||
self.overlays_message_handler
|
||||
.process_message(message, responses, OverlaysMessageContext { visibility_settings, viewport });
|
||||
}
|
||||
DocumentMessage::PropertiesPanel(message) => {
|
||||
let context = PropertiesPanelMessageContext {
|
||||
@@ -268,6 +262,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
navigation_handler: &self.navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
viewport,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -380,8 +375,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
self.layer_range_selection_reference = None;
|
||||
}
|
||||
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(viewport, responses),
|
||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(viewport, responses),
|
||||
DocumentMessage::DocumentStructureChanged => {
|
||||
if layers_panel_open {
|
||||
self.network_interface.load_structure();
|
||||
@@ -836,7 +831,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let scale = DAffine2::from_scale(enlargement_factor);
|
||||
let pivot = DAffine2::from_translation(pivot);
|
||||
let transformation = pivot * scale * pivot.inverse();
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
|
||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
|
||||
@@ -861,10 +856,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
let image_size = DVec2::new(image.width as f64, image.height as f64);
|
||||
|
||||
// Align the layer with the mouse or center of viewport
|
||||
let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into());
|
||||
let viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into());
|
||||
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left));
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2()));
|
||||
let center_in_viewport_layerspace = center_in_viewport;
|
||||
|
||||
// Make layer the size of the image
|
||||
@@ -913,9 +908,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
mouse,
|
||||
parent_and_insert_index,
|
||||
} => {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let viewport_location = mouse.map_or(ipp.viewport_bounds.center() + ipp.viewport_bounds.top_left, |pos| pos.into());
|
||||
let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - ipp.viewport_bounds.top_left));
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
let viewport_location = mouse.map_or(viewport.center_in_viewport_space().into_dvec2() + viewport.offset().into_dvec2(), |pos| pos.into());
|
||||
let center_in_viewport = DAffine2::from_translation(document_to_viewport.inverse().transform_point2(viewport_location - viewport.offset().into_dvec2()));
|
||||
|
||||
let layer_node_id = NodeId::new();
|
||||
let layer_id = LayerNodeIdentifier::new_unchecked(layer_node_id);
|
||||
@@ -970,7 +965,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
} else {
|
||||
&self.document_ptz
|
||||
};
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), current_ptz);
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), current_ptz);
|
||||
|
||||
let ruler_scale = if !self.graph_view_overlay_open {
|
||||
self.navigation_handler.snapped_zoom(current_ptz.zoom())
|
||||
@@ -1006,12 +1001,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
});
|
||||
}
|
||||
DocumentMessage::RenderScrollbars => {
|
||||
let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom());
|
||||
let document_transform_scale = self.navigation_handler.snapped_zoom(self.document_ptz.zoom()) / viewport.scale();
|
||||
|
||||
let scale = 0.5 + ASYMPTOTIC_EFFECT + document_transform_scale * SCALE_EFFECT;
|
||||
|
||||
let viewport_size = ipp.viewport_bounds.size();
|
||||
let viewport_mid = ipp.viewport_bounds.center();
|
||||
let viewport_size = viewport.size().into_dvec2();
|
||||
let viewport_mid = viewport.center_in_viewport_space().into_dvec2();
|
||||
let [bounds1, bounds2] = if !self.graph_view_overlay_open {
|
||||
self.metadata().document_bounds_viewport_space().unwrap_or([viewport_mid; 2])
|
||||
} else {
|
||||
@@ -1333,7 +1328,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
}
|
||||
|
||||
for _ in 0..undo_count {
|
||||
self.undo(ipp, responses);
|
||||
self.undo(viewport, responses);
|
||||
}
|
||||
|
||||
self.network_interface.finish_transaction();
|
||||
@@ -1483,7 +1478,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
}
|
||||
DocumentMessage::PTZUpdate => {
|
||||
if !self.graph_view_overlay_open {
|
||||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
self.network_interface.set_document_to_viewport_transform(transform);
|
||||
// Ensure selection box is kept in sync with the pointer when the PTZ changes
|
||||
responses.add(SelectToolMessage::PointerMove {
|
||||
@@ -1502,7 +1497,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
|
||||
let transform = self
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
|
||||
.calculate_offset_transform(viewport.center_in_viewport_space().into(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz);
|
||||
self.network_interface.set_transform(transform, &self.breadcrumb_network_path);
|
||||
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
@@ -1656,29 +1651,30 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
/// Runs an intersection test with all layers and a viewport space quad
|
||||
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
let document_quad = document_to_viewport.inverse() * viewport_quad;
|
||||
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Quad(document_quad))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space quad; ignoring artboards
|
||||
pub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
pub fn intersect_quad_no_artboards<'a>(&'a self, viewport_quad: graphene_std::renderer::Quad, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.intersect_quad(viewport_quad, viewport).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath
|
||||
pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Polygon(viewport_polygon))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath; ignoring artboards
|
||||
pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.intersect_polygon(viewport_polygon, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath<PointId>, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.intersect_polygon(viewport_polygon, viewport)
|
||||
.filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
pub fn is_layer_fully_inside(&self, layer: &LayerNodeIdentifier, quad: graphene_std::renderer::Quad) -> bool {
|
||||
@@ -1704,8 +1700,8 @@ impl DocumentMessageHandler {
|
||||
layer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom
|
||||
}
|
||||
|
||||
pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, ipp: &InputPreprocessorMessageHandler, mut viewport_polygon: Subpath<PointId>) -> bool {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, viewport: &ViewportMessageHandler, mut viewport_polygon: Subpath<PointId>) -> bool {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
|
||||
let layer_click_targets = self.network_interface.document_metadata().click_targets(*layer);
|
||||
@@ -1728,8 +1724,8 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Find all of the layers that were clicked on from a viewport space location
|
||||
pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'_> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'_> {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
let point = document_to_viewport.inverse().transform_point2(ipp.mouse.position);
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Point(point))
|
||||
}
|
||||
@@ -1750,8 +1746,8 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
||||
pub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp)
|
||||
pub fn click_list<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp, viewport)
|
||||
.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)
|
||||
.scan(true, |last_had_children, layer| {
|
||||
@@ -1765,8 +1761,8 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Find layers (including artboards) under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
||||
pub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp)
|
||||
pub fn click_list_with_artboards<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp, viewport)
|
||||
.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)
|
||||
.scan(true, |last_had_children, layer| {
|
||||
if *last_had_children {
|
||||
@@ -1778,14 +1774,14 @@ impl DocumentMessageHandler {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp)
|
||||
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp, viewport)
|
||||
.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata()))
|
||||
}
|
||||
|
||||
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
||||
pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
self.click_list(ipp).last()
|
||||
pub fn click(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
self.click_list(ipp, viewport).last()
|
||||
}
|
||||
|
||||
pub fn click_based_on_position(&self, mouse_snapped_positon: DVec2) -> Option<LayerNodeIdentifier> {
|
||||
@@ -1943,8 +1939,8 @@ impl DocumentMessageHandler {
|
||||
structure_section.as_slice().into()
|
||||
}
|
||||
|
||||
pub fn undo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(previous_network) = self.undo(ipp, responses) else { return };
|
||||
pub fn undo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let Some(previous_network) = self.undo(viewport, responses) else { return };
|
||||
|
||||
self.document_redo_history.push_back(previous_network);
|
||||
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||
@@ -1952,7 +1948,7 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
pub fn undo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
// If there is no history return and don't broadcast SelectionChanged
|
||||
let mut network_interface = self.document_undo_history.pop_back()?;
|
||||
|
||||
@@ -1961,7 +1957,7 @@ impl DocumentMessageHandler {
|
||||
std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);
|
||||
|
||||
//Update the metadata transform based on document PTZ
|
||||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
network_interface.set_document_to_viewport_transform(transform);
|
||||
|
||||
// Ensure document structure is loaded so that updating the selected nodes has the correct metadata
|
||||
@@ -1980,9 +1976,9 @@ impl DocumentMessageHandler {
|
||||
|
||||
Some(previous_network)
|
||||
}
|
||||
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
pub fn redo_with_history(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Push the UpdateOpenDocumentsList message to the queue in order to update the save status of the open documents
|
||||
let Some(previous_network) = self.redo(ipp, responses) else { return };
|
||||
let Some(previous_network) = self.redo(viewport, responses) else { return };
|
||||
|
||||
self.document_undo_history.push_back(previous_network);
|
||||
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
|
||||
@@ -1990,7 +1986,7 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn redo(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
pub fn redo(&mut self, viewport: &ViewportMessageHandler, responses: &mut VecDeque<Message>) -> Option<NodeNetworkInterface> {
|
||||
// If there is no history return and don't broadcast SelectionChanged
|
||||
let mut network_interface = self.document_redo_history.pop_back()?;
|
||||
|
||||
@@ -1999,7 +1995,7 @@ impl DocumentMessageHandler {
|
||||
std::mem::swap(&mut network_interface.resolved_types, &mut self.network_interface.resolved_types);
|
||||
|
||||
//Update the metadata transform based on document PTZ
|
||||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
let transform = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||
network_interface.set_document_to_viewport_transform(transform);
|
||||
|
||||
let previous_network = std::mem::replace(&mut self.network_interface, network_interface);
|
||||
@@ -2045,11 +2041,11 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Finds the artboard that bounds the point in viewport space and be the container of any newly added layers.
|
||||
pub fn new_layer_bounding_artboard(&self, ipp: &InputPreprocessorMessageHandler) -> LayerNodeIdentifier {
|
||||
pub fn new_layer_bounding_artboard(&self, ipp: &InputPreprocessorMessageHandler, viewport: &ViewportMessageHandler) -> LayerNodeIdentifier {
|
||||
let container_based_on_selection = self.new_layer_parent(true);
|
||||
|
||||
let container_based_on_clicked_artboard = self
|
||||
.click_xray(ipp)
|
||||
.click_xray(ipp, viewport)
|
||||
.find(|layer| self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct NavigationMessageContext<'a> {
|
||||
pub document_ptz: &'a mut PTZ,
|
||||
pub graph_view_overlay_open: bool,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, ExtractField)]
|
||||
@@ -41,6 +42,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
document_ptz,
|
||||
graph_view_overlay_open,
|
||||
preferences,
|
||||
viewport,
|
||||
} = context;
|
||||
|
||||
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
|
||||
@@ -135,7 +137,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
log::error!("Could not get PTZ in CanvasPan");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
@@ -169,8 +171,8 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||
let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);
|
||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta * viewport.size().into_dvec2());
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
@@ -214,7 +216,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < ptz.zoom()).unwrap_or(&ptz.zoom());
|
||||
if center_on_mouse {
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
responses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
}
|
||||
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });
|
||||
}
|
||||
@@ -225,7 +227,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
|
||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > ptz.zoom()).unwrap_or(&ptz.zoom());
|
||||
if center_on_mouse {
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
responses.add(self.center_zoom(viewport.size().into(), new_scale / ptz.zoom(), ipp.mouse.position));
|
||||
}
|
||||
responses.add(NavigationMessage::CanvasZoomSet { zoom_factor: new_scale });
|
||||
}
|
||||
@@ -245,9 +247,9 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
return;
|
||||
};
|
||||
|
||||
zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, ipp);
|
||||
zoom_factor *= Self::clamp_zoom(ptz.zoom() * zoom_factor, document_bounds, old_zoom, viewport);
|
||||
|
||||
responses.add(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
|
||||
responses.add(self.center_zoom(viewport.size().into(), zoom_factor, ipp.mouse.position));
|
||||
responses.add(NavigationMessage::CanvasZoomSet {
|
||||
zoom_factor: ptz.zoom() * zoom_factor,
|
||||
});
|
||||
@@ -264,7 +266,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
return;
|
||||
};
|
||||
let zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, ipp);
|
||||
let zoom = zoom * Self::clamp_zoom(zoom, document_bounds, old_zoom, viewport);
|
||||
ptz.set_zoom(zoom);
|
||||
if graph_view_overlay_open {
|
||||
responses.add(NodeGraphMessage::UpdateGraphBarRight);
|
||||
@@ -343,7 +345,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
let (pos1, pos2) = (pos1.min(pos2), pos1.max(pos2));
|
||||
let diagonal = pos2 - pos1;
|
||||
|
||||
if diagonal.length() < f64::EPSILON * 1000. || ipp.viewport_bounds.size() == DVec2::ZERO {
|
||||
if diagonal.length() < f64::EPSILON * 1000. || viewport.size().into_dvec2() == DVec2::ZERO {
|
||||
warn!("Cannot center since the viewport size is 0");
|
||||
return;
|
||||
}
|
||||
@@ -352,10 +354,10 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
log::error!("Could not get node graph PTZ in CanvasPanByViewportFraction");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);
|
||||
|
||||
let v1 = document_to_viewport.inverse().transform_point2(DVec2::ZERO);
|
||||
let v2 = document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size());
|
||||
let v2 = document_to_viewport.inverse().transform_point2(viewport.size().into_dvec2());
|
||||
|
||||
let center = ((v2 + v1) - (pos2 + pos1)) / 2.;
|
||||
let size = (v2 - v1) / diagonal;
|
||||
@@ -397,7 +399,8 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
log::error!("Could not get node graph PTZ in FitViewportToSelection");
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = self.calculate_offset_transform(ipp.viewport_bounds.center(), ptz);
|
||||
|
||||
let document_to_viewport = self.calculate_offset_transform(viewport.center_in_viewport_space().into_dvec2(), ptz);
|
||||
responses.add(NavigationMessage::FitViewportToBounds {
|
||||
bounds: [document_to_viewport.inverse().transform_point2(bounds[0]), document_to_viewport.inverse().transform_point2(bounds[1])],
|
||||
prevent_zoom_past_100: false,
|
||||
@@ -419,7 +422,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
let tilt_raw_not_snapped = {
|
||||
// Compute the angle in document space to counter for the canvas being flipped
|
||||
let viewport_to_document = network_interface.document_metadata().document_to_viewport.inverse();
|
||||
let half_viewport = ipp.viewport_bounds.size() / 2.;
|
||||
let half_viewport = viewport.center_in_viewport_space().into_dvec2();
|
||||
let start_offset = viewport_to_document.transform_vector2(self.mouse_position - half_viewport);
|
||||
let end_offset = viewport_to_document.transform_vector2(ipp.mouse.position - half_viewport);
|
||||
let angle = start_offset.angle_to(end_offset);
|
||||
@@ -459,7 +462,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
||||
network_interface.graph_bounds_viewport_space(breadcrumb_network_path)
|
||||
};
|
||||
|
||||
updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, ipp)
|
||||
updated_zoom * Self::clamp_zoom(updated_zoom, document_bounds, old_zoom, viewport)
|
||||
};
|
||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||
log::error!("Could not get mutable PTZ in Zoom");
|
||||
@@ -563,9 +566,9 @@ impl NavigationMessageHandler {
|
||||
NavigationMessage::CanvasPan { delta }.into()
|
||||
}
|
||||
|
||||
pub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, ipp: &InputPreprocessorMessageHandler) -> f64 {
|
||||
pub fn clamp_zoom(zoom: f64, document_bounds: Option<[DVec2; 2]>, old_zoom: f64, viewport: &ViewportMessageHandler) -> f64 {
|
||||
let document_size = (document_bounds.map(|[min, max]| max - min).unwrap_or_default() / old_zoom) * zoom;
|
||||
let scale_factor = (document_size / ipp.viewport_bounds.size()).max_element();
|
||||
let scale_factor = (document_size / viewport.size().into_dvec2()).max_element();
|
||||
|
||||
if scale_factor <= f64::EPSILON * 100. || !scale_factor.is_finite() || scale_factor >= VIEWPORT_ZOOM_MIN_FRACTION_COVER {
|
||||
return 1.;
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
|
||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use crate::messages::viewport::{Position, Rect};
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
@@ -43,6 +44,7 @@ pub struct NodeGraphMessageContext<'a> {
|
||||
pub navigation_handler: &'a NavigationMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub layers_panel_open: bool,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
@@ -113,6 +115,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
viewport,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
@@ -783,12 +786,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
|
||||
// TODO: Create function
|
||||
let node_graph_shift = if matches!(context_menu_data, ContextMenuData::CreateNode { compatible_type: None }) {
|
||||
let appear_right_of_mouse = if click.x > ipp.viewport_bounds.size().x - 180. { -180. } else { 0. };
|
||||
let appear_above_mouse = if click.y > ipp.viewport_bounds.size().y - 200. { -200. } else { 0. };
|
||||
let appear_right_of_mouse = if click.x > viewport.size().x() - 180. { -180. } else { 0. };
|
||||
let appear_above_mouse = if click.y > viewport.size().y() - 200. { -200. } else { 0. };
|
||||
DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x
|
||||
} else {
|
||||
let appear_right_of_mouse = if click.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
let appear_above_mouse = if click.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
|
||||
let appear_right_of_mouse = if click.x > viewport.size().x() - 173. { -173. } else { 0. };
|
||||
let appear_above_mouse = if click.y > viewport.size().y() - 34. { -34. } else { 0. };
|
||||
DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x
|
||||
};
|
||||
|
||||
@@ -983,7 +986,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
|
||||
// Auto-panning
|
||||
let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];
|
||||
self.auto_panning.setup_by_mouse_position(ipp, &messages, responses);
|
||||
self.auto_panning.setup_by_mouse_position(ipp, viewport, &messages, responses);
|
||||
|
||||
let viewport_location = ipp.mouse.position;
|
||||
let point = network_metadata
|
||||
@@ -1218,8 +1221,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
};
|
||||
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
let appear_above_mouse = if ipp.mouse.position.y > ipp.viewport_bounds.size().y - 34. { -34. } else { 0. };
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > viewport.size().y() - 173. { -173. } else { 0. };
|
||||
let appear_above_mouse = if ipp.mouse.position.y > viewport.size().y() - 34. { -34. } else { 0. };
|
||||
let node_graph_shift = DVec2::new(appear_right_of_mouse, appear_above_mouse) / network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.matrix2.x_axis.x;
|
||||
|
||||
self.context_menu = Some(ContextMenuInformation {
|
||||
@@ -1403,7 +1406,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
NodeGraphMessage::PointerOutsideViewport { shift } => {
|
||||
if self.drag_start.is_some() || self.box_selection_start.is_some() || (self.wire_in_progress_from_connector.is_some() && self.context_menu.is_none()) {
|
||||
let _ = self.auto_panning.shift_viewport(ipp, responses);
|
||||
let _ = self.auto_panning.shift_viewport(ipp, viewport, responses);
|
||||
} else {
|
||||
// Auto-panning
|
||||
let messages = [NodeGraphMessage::PointerOutsideViewport { shift }.into(), NodeGraphMessage::PointerMove { shift }.into()];
|
||||
@@ -1611,7 +1614,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_bbox = ipp.document_bounds();
|
||||
let viewport_bounds = viewport.bounds();
|
||||
let viewport_bbox: [DVec2; 2] = viewport_bounds.into();
|
||||
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
@@ -1657,7 +1661,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
||||
}
|
||||
NodeGraphMessage::SetGridAlignedEdges => {
|
||||
if graph_view_overlay_open {
|
||||
network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path);
|
||||
let viewport_bounds = viewport.bounds();
|
||||
network_interface.set_grid_aligned_edges(DVec2::new(viewport_bounds.x() - viewport_bounds.width(), 0.), breadcrumb_network_path);
|
||||
// Send the new edges to the frontend
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
|
||||
|
||||
for primary in 0..2 {
|
||||
let secondary = 1 - primary;
|
||||
@@ -52,9 +54,11 @@ fn grid_overlay_rectangular_dot(document: &DocumentMessageHandler, overlay_conte
|
||||
let Some(spacing) = GridSnapping::compute_rectangle_spacing(spacing, &document.document_ptz) else {
|
||||
return;
|
||||
};
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
|
||||
|
||||
let min = bounds.0.iter().map(|corner| corner.y).min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
let max = bounds.0.iter().map(|corner| corner.y).max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or_default();
|
||||
@@ -85,9 +89,11 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
|
||||
@@ -128,9 +134,11 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
|
||||
let grid_color = "#".to_string() + &document.snapping_state.grid.grid_color.to_rgba_hex_srgb();
|
||||
let cmp = |a: &f64, b: &f64| a.partial_cmp(b).unwrap();
|
||||
let origin = document.snapping_state.grid.origin;
|
||||
let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let document_to_viewport = document
|
||||
.navigation_handler
|
||||
.calculate_offset_transform(overlay_context.viewport.center_in_viewport_space().into(), &document.document_ptz);
|
||||
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.size]);
|
||||
let bounds = document_to_viewport.inverse() * Quad::from_box([DVec2::ZERO, overlay_context.viewport.size().into()]);
|
||||
let tan_a = angle_a.to_radians().tan();
|
||||
let tan_b = angle_b.to_radians().tan();
|
||||
let spacing = DVec2::new(y_axis_spacing / (tan_a + tan_b), y_axis_spacing);
|
||||
|
||||
@@ -4,8 +4,7 @@ use crate::messages::prelude::*;
|
||||
#[derive(ExtractField)]
|
||||
pub struct OverlaysMessageContext<'a> {
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
pub viewport: &'a ViewportMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
@@ -20,19 +19,14 @@ pub struct OverlaysMessageHandler {
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
|
||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
|
||||
let OverlaysMessageContext {
|
||||
visibility_settings,
|
||||
ipp,
|
||||
device_pixel_ratio,
|
||||
..
|
||||
} = context;
|
||||
let OverlaysMessageContext { visibility_settings, viewport, .. } = context;
|
||||
|
||||
match message {
|
||||
#[cfg(target_family = "wasm")]
|
||||
OverlaysMessage::Draw => {
|
||||
use super::utility_functions::overlay_canvas_element;
|
||||
use super::utility_types::OverlayContext;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use crate::messages::viewport::{Position, ToPhysical};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
let canvas = match &self.canvas {
|
||||
@@ -48,28 +42,26 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
|
||||
});
|
||||
|
||||
let size = ipp.viewport_bounds.size().as_uvec2();
|
||||
let size_logical = viewport.size();
|
||||
let size_physical = size_logical.to_physical();
|
||||
let width = size_logical.x().max(size_physical.x());
|
||||
let height = size_logical.y().max(size_physical.y());
|
||||
|
||||
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array();
|
||||
let _ = canvas_context.set_transform(a, b, c, d, e, f);
|
||||
canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
|
||||
let _ = canvas_context.reset_transform();
|
||||
canvas_context.clear_rect(0., 0., width, height);
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays {
|
||||
context: OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
viewport: *viewport,
|
||||
},
|
||||
});
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
viewport: *viewport,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -78,9 +70,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
OverlaysMessage::Draw => {
|
||||
use super::utility_types::OverlayContext;
|
||||
|
||||
let size = ipp.viewport_bounds.size();
|
||||
|
||||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||
let overlay_context = OverlayContext::new(*viewport, visibility_settings);
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
|
||||
@@ -93,7 +83,7 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
||||
}
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
OverlaysMessage::Draw => {
|
||||
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
||||
let _ = (responses, visibility_settings, viewport);
|
||||
}
|
||||
OverlaysMessage::AddProvider { provider: message } => {
|
||||
self.overlay_providers.insert(message);
|
||||
|
||||
@@ -207,7 +207,6 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
@@ -2,10 +2,11 @@ use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SEGMENT_SELECTED_THICKNESS, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::viewport::ViewportMessageHandler;
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -141,10 +142,7 @@ pub struct OverlayContext {
|
||||
#[serde(skip, default = "overlay_canvas_context")]
|
||||
#[specta(skip)]
|
||||
pub render_context: web_sys::CanvasRenderingContext2d,
|
||||
pub size: DVec2,
|
||||
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||
pub device_pixel_ratio: f64,
|
||||
pub viewport: ViewportMessageHandler,
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
// Message hashing isn't used but is required by the message system macros
|
||||
@@ -499,11 +497,25 @@ impl OverlayContext {
|
||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
|
||||
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
|
||||
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
|
||||
}
|
||||
|
||||
pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
|
||||
let edge_dir = (edge_end - edge_start).normalize();
|
||||
let mid = edge_end.midpoint(edge_start);
|
||||
|
||||
for edge in [edge_dir, -edge_dir] {
|
||||
self.draw_triangle(mid + edge * (3. + SKEW_TRIANGLE_OFFSET), edge, SKEW_TRIANGLE_SIZE, None, None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the canvas context to adjust for DPI scaling
|
||||
///
|
||||
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
|
||||
fn start_dpi_aware_transform(&self) {
|
||||
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)).to_cols_array();
|
||||
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(self.viewport.scale())).to_cols_array();
|
||||
self.render_context
|
||||
.set_transform(a, b, c, d, e, f)
|
||||
.expect("transform should be able to be set to be able to account for DPI");
|
||||
@@ -967,7 +979,7 @@ impl OverlayContext {
|
||||
Pivot::End => -padding,
|
||||
};
|
||||
|
||||
let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.device_pixel_ratio)) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();
|
||||
let [a, b, c, d, e, f] = (DAffine2::from_scale(DVec2::splat(self.viewport.scale())) * transform * DAffine2::from_translation(DVec2::new(x, y))).to_cols_array();
|
||||
self.render_context.set_transform(a, b, c, d, e, f).expect("Failed to rotate the render context to the specified angle");
|
||||
|
||||
if let Some(background) = background_color {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::consts::{
|
||||
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::prelude::ViewportMessageHandler;
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -157,24 +158,18 @@ pub struct OverlayContext {
|
||||
#[serde(skip)]
|
||||
#[specta(skip)]
|
||||
internal: Arc<Mutex<OverlayContextInternal>>,
|
||||
pub size: DVec2,
|
||||
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||
pub device_pixel_ratio: f64,
|
||||
pub viewport: ViewportMessageHandler,
|
||||
pub visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
|
||||
impl Clone for OverlayContext {
|
||||
fn clone(&self) -> Self {
|
||||
let internal = self.internal.lock().expect("Failed to lock internal overlay context");
|
||||
let size = internal.size;
|
||||
let device_pixel_ratio = internal.device_pixel_ratio;
|
||||
let visibility_settings = internal.visibility_settings;
|
||||
drop(internal); // Explicitly release the lock before cloning the Arc<Mutex<_>>
|
||||
Self {
|
||||
internal: self.internal.clone(),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
viewport: self.viewport,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
@@ -183,7 +178,7 @@ impl Clone for OverlayContext {
|
||||
// Manual implementations since Scene doesn't implement PartialEq or Debug
|
||||
impl PartialEq for OverlayContext {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.size == other.size && self.device_pixel_ratio == other.device_pixel_ratio && self.visibility_settings == other.visibility_settings
|
||||
self.viewport == other.viewport && self.visibility_settings == other.visibility_settings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +186,7 @@ impl std::fmt::Debug for OverlayContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("OverlayContext")
|
||||
.field("scene", &"Scene { ... }")
|
||||
.field("size", &self.size)
|
||||
.field("device_pixel_ratio", &self.device_pixel_ratio)
|
||||
.field("viewport", &self.viewport)
|
||||
.field("visibility_settings", &self.visibility_settings)
|
||||
.finish()
|
||||
}
|
||||
@@ -203,8 +197,7 @@ impl Default for OverlayContext {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
internal: Mutex::new(OverlayContextInternal::default()).into(),
|
||||
size: DVec2::ZERO,
|
||||
device_pixel_ratio: 1.0,
|
||||
viewport: ViewportMessageHandler::default(),
|
||||
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||
}
|
||||
}
|
||||
@@ -217,11 +210,10 @@ impl core::hash::Hash for OverlayContext {
|
||||
|
||||
impl OverlayContext {
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
Self {
|
||||
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
internal: Arc::new(Mutex::new(OverlayContextInternal::new(viewport, visibility_settings))),
|
||||
viewport,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
@@ -280,6 +272,14 @@ impl OverlayContext {
|
||||
self.internal().manipulator_anchor(position, selected, color);
|
||||
}
|
||||
|
||||
pub fn resize_handle(&mut self, position: DVec2, rotation: f64) {
|
||||
self.internal().resize_handle(position, rotation);
|
||||
}
|
||||
|
||||
pub fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
|
||||
self.internal().skew_handles(edge_start, edge_end);
|
||||
}
|
||||
|
||||
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
self.internal().square(position, size, color_fill, color_stroke);
|
||||
}
|
||||
@@ -292,6 +292,7 @@ impl OverlayContext {
|
||||
self.internal().circle(position, radius, color_fill, color_stroke);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn dashed_ellipse(
|
||||
&mut self,
|
||||
center: DVec2,
|
||||
@@ -421,23 +422,21 @@ pub enum DrawHandles {
|
||||
|
||||
pub(super) struct OverlayContextInternal {
|
||||
scene: Scene,
|
||||
size: DVec2,
|
||||
device_pixel_ratio: f64,
|
||||
viewport: ViewportMessageHandler,
|
||||
visibility_settings: OverlaysVisibilitySettings,
|
||||
}
|
||||
|
||||
impl Default for OverlayContextInternal {
|
||||
fn default() -> Self {
|
||||
Self::new(DVec2::new(100., 100.), 1., OverlaysVisibilitySettings::default())
|
||||
Self::new(ViewportMessageHandler::default(), OverlaysVisibilitySettings::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl OverlayContextInternal {
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
pub(super) fn new(viewport: ViewportMessageHandler, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
Self {
|
||||
scene: Scene::new(),
|
||||
size,
|
||||
device_pixel_ratio,
|
||||
viewport,
|
||||
visibility_settings,
|
||||
}
|
||||
}
|
||||
@@ -473,7 +472,7 @@ impl OverlayContextInternal {
|
||||
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &path);
|
||||
}
|
||||
|
||||
fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
@@ -506,7 +505,7 @@ impl OverlayContextInternal {
|
||||
}
|
||||
|
||||
let stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
let mut stroke = kurbo::Stroke::new(1.0);
|
||||
let mut stroke = kurbo::Stroke::new(1.);
|
||||
|
||||
if let Some(dash_width) = dash_width {
|
||||
let dash_gap = dash_gap_width.unwrap_or(1.);
|
||||
@@ -551,7 +550,7 @@ impl OverlayContextInternal {
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
|
||||
|
||||
self.scene
|
||||
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
||||
.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
||||
}
|
||||
|
||||
fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||
@@ -563,13 +562,13 @@ impl OverlayContextInternal {
|
||||
|
||||
let fill = COLOR_OVERLAY_BLUE_50;
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
|
||||
|
||||
let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
|
||||
|
||||
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
||||
}
|
||||
|
||||
fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||
@@ -584,8 +583,22 @@ impl OverlayContextInternal {
|
||||
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
|
||||
fn resize_handle(&mut self, position: DVec2, rotation: f64) {
|
||||
let quad = DAffine2::from_angle_translation(rotation, position) * Quad::from_box([DVec2::splat(-RESIZE_HANDLE_SIZE / 2.), DVec2::splat(RESIZE_HANDLE_SIZE / 2.)]);
|
||||
self.quad(quad, None, Some(COLOR_OVERLAY_WHITE));
|
||||
}
|
||||
|
||||
fn skew_handles(&mut self, edge_start: DVec2, edge_end: DVec2) {
|
||||
let edge_dir = (edge_end - edge_start).normalize();
|
||||
let mid = edge_end.midpoint(edge_start);
|
||||
|
||||
for edge in [edge_dir, -edge_dir] {
|
||||
self.draw_triangle(mid + edge * 3. + SKEW_TRIANGLE_OFFSET, edge, SKEW_TRIANGLE_SIZE, None, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_transform(&self) -> kurbo::Affine {
|
||||
kurbo::Affine::scale(self.device_pixel_ratio)
|
||||
kurbo::Affine::scale(self.viewport.scale())
|
||||
}
|
||||
|
||||
fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||
@@ -601,7 +614,7 @@ impl OverlayContextInternal {
|
||||
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &rect);
|
||||
}
|
||||
|
||||
fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||
@@ -627,9 +640,10 @@ impl OverlayContextInternal {
|
||||
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color_stroke), None, &circle);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn dashed_ellipse(
|
||||
&mut self,
|
||||
_center: DVec2,
|
||||
@@ -678,7 +692,7 @@ impl OverlayContextInternal {
|
||||
);
|
||||
}
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||
@@ -700,7 +714,7 @@ impl OverlayContextInternal {
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
let fill_color = Some(fill_color.as_str());
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||
self.line(start + DVec2::X * radius * sign, start + DVec2::X * radius * scale.abs(), None, None);
|
||||
self.circle(start, radius, fill_color, None);
|
||||
self.circle(start, radius * scale.abs(), fill_color, None);
|
||||
self.text(
|
||||
@@ -714,14 +728,14 @@ impl OverlayContextInternal {
|
||||
}
|
||||
|
||||
fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
||||
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
||||
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
||||
const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;
|
||||
const HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
|
||||
const HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
|
||||
const MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
|
||||
const MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
|
||||
let hover_ring_outer_radius: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
||||
let main_ring_outer_radius: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
||||
let main_ring_inner_radius: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
||||
let arrow_radius: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;
|
||||
let hover_ring_stroke_width: f64 = hover_ring_outer_radius - main_ring_inner_radius;
|
||||
let hover_ring_centerline_radius: f64 = (hover_ring_outer_radius + main_ring_inner_radius) / 2.;
|
||||
let main_ring_stroke_width: f64 = main_ring_outer_radius - main_ring_inner_radius;
|
||||
let main_ring_centerline_radius: f64 = (main_ring_outer_radius + main_ring_inner_radius) / 2.;
|
||||
|
||||
let Some(show_hover_ring) = show_compass_with_hover_ring else { return };
|
||||
|
||||
@@ -733,9 +747,9 @@ impl OverlayContextInternal {
|
||||
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
|
||||
let circle = kurbo::Circle::new((center.x, center.y), HOVER_RING_CENTERLINE_RADIUS);
|
||||
let circle = kurbo::Circle::new((center.x, center.y), hover_ring_centerline_radius);
|
||||
self.scene
|
||||
.stroke(&kurbo::Stroke::new(HOVER_RING_STROKE_WIDTH), transform, Self::parse_color(&fill_color), None, &circle);
|
||||
.stroke(&kurbo::Stroke::new(hover_ring_stroke_width), transform, Self::parse_color(&fill_color), None, &circle);
|
||||
}
|
||||
|
||||
// Arrows
|
||||
@@ -743,11 +757,11 @@ impl OverlayContextInternal {
|
||||
let direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle);
|
||||
let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN };
|
||||
|
||||
let tip = center + direction * HOVER_RING_OUTER_RADIUS;
|
||||
let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.;
|
||||
let tip = center + direction * hover_ring_outer_radius;
|
||||
let base = center + direction * (main_ring_inner_radius + main_ring_outer_radius) / 2.;
|
||||
|
||||
let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt();
|
||||
let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r);
|
||||
let r = (arrow_radius.powi(2) + main_ring_inner_radius.powi(2)).sqrt();
|
||||
let (cos, sin) = (main_ring_inner_radius / r, arrow_radius / r);
|
||||
let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos);
|
||||
let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos);
|
||||
|
||||
@@ -764,9 +778,9 @@ impl OverlayContextInternal {
|
||||
}
|
||||
|
||||
// Main ring
|
||||
let circle = kurbo::Circle::new((center.x, center.y), MAIN_RING_CENTERLINE_RADIUS);
|
||||
let circle = kurbo::Circle::new((center.x, center.y), main_ring_centerline_radius);
|
||||
self.scene
|
||||
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
||||
.stroke(&kurbo::Stroke::new(main_ring_stroke_width), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
||||
}
|
||||
|
||||
fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||
@@ -780,22 +794,22 @@ impl OverlayContextInternal {
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &circle);
|
||||
|
||||
// Crosshair
|
||||
const CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;
|
||||
let crosshair_radius: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;
|
||||
|
||||
let mut stroke = kurbo::Stroke::new(PIVOT_CROSSHAIR_THICKNESS);
|
||||
stroke = stroke.with_caps(kurbo::Cap::Round);
|
||||
|
||||
// Horizontal line
|
||||
let mut path = BezPath::new();
|
||||
path.move_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y));
|
||||
path.line_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y));
|
||||
path.move_to(kurbo::Point::new(x + crosshair_radius * uv.x, y + crosshair_radius * uv.y));
|
||||
path.line_to(kurbo::Point::new(x - crosshair_radius * uv.x, y - crosshair_radius * uv.y));
|
||||
|
||||
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||
|
||||
// Vertical line
|
||||
let mut path = BezPath::new();
|
||||
path.move_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x));
|
||||
path.line_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x));
|
||||
path.move_to(kurbo::Point::new(x - crosshair_radius * uv.y, y + crosshair_radius * uv.x));
|
||||
path.line_to(kurbo::Point::new(x + crosshair_radius * uv.y, y - crosshair_radius * uv.x));
|
||||
|
||||
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||
}
|
||||
@@ -809,15 +823,15 @@ impl OverlayContextInternal {
|
||||
// Draw the background circle with a white fill and colored outline
|
||||
let circle = kurbo::Circle::new((x, y), DOWEL_PIN_RADIUS);
|
||||
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_WHITE), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color), None, &circle);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), transform, Self::parse_color(color), None, &circle);
|
||||
|
||||
// Draw the two filled sectors using paths
|
||||
let mut path = BezPath::new();
|
||||
|
||||
// Top-left sector
|
||||
path.move_to(kurbo::Point::new(x, y));
|
||||
let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).cos();
|
||||
let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).sin();
|
||||
let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.cos());
|
||||
let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle.sin());
|
||||
path.line_to(kurbo::Point::new(end_x, end_y));
|
||||
// Draw arc manually
|
||||
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
|
||||
@@ -828,8 +842,8 @@ impl OverlayContextInternal {
|
||||
|
||||
// Bottom-right sector
|
||||
path.move_to(kurbo::Point::new(x, y));
|
||||
let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).cos();
|
||||
let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).sin();
|
||||
let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.cos());
|
||||
let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle.sin());
|
||||
path.line_to(kurbo::Point::new(end_x, end_y));
|
||||
// Draw arc manually
|
||||
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), PI + FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
|
||||
@@ -861,7 +875,7 @@ impl OverlayContextInternal {
|
||||
self.bezier_to_path(bezier, transform, move_to, &mut path);
|
||||
}
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
@@ -870,7 +884,7 @@ impl OverlayContextInternal {
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
@@ -963,7 +977,7 @@ impl OverlayContextInternal {
|
||||
let path = self.push_path(subpaths.iter(), transform);
|
||||
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(color), None, &path);
|
||||
self.scene.stroke(&kurbo::Stroke::new(1.), self.get_transform(), Self::parse_color(color), None, &path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1010,7 +1024,7 @@ impl OverlayContextInternal {
|
||||
x_extend: peniko::Extend::Repeat,
|
||||
y_extend: peniko::Extend::Repeat,
|
||||
quality: peniko::ImageQuality::default(),
|
||||
alpha: 1.0,
|
||||
alpha: 1.,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user