use super::shape_editor::SelectedShapeState; use crate::application::generate_uuid; use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS; use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT}; use crate::messages::prelude::*; use bezier_rs::ManipulatorGroup; use document_legacy::document::Document; use document_legacy::layers::style::{self, Fill, Stroke}; use document_legacy::{LayerId, Operation}; use graphene_core::raster::color::Color; use graphene_core::uuid::ManipulatorGroupId; use graphene_core::vector::{ManipulatorPointId, SelectedType}; use glam::{DAffine2, DVec2}; /// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor. #[derive(Clone, Debug, Default)] struct ManipulatorGroupOverlays { pub anchor: Option>, pub in_handle: Option>, pub in_line: Option>, pub out_handle: Option>, pub out_line: Option>, } impl ManipulatorGroupOverlays { pub fn iter(&self) -> impl Iterator>> { [&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter() } } type GraphiteManipulatorGroup = ManipulatorGroup; const POINT_STROKE_WEIGHT: f64 = 2.; #[derive(Clone, Debug, Default)] pub struct OverlayRenderer { shape_overlay_cache: HashMap>, manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorGroupId), ManipulatorGroupOverlays>, } impl OverlayRenderer { pub fn new() -> Self { Self::default() } pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec, responses: &mut VecDeque) { let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap(); if let Ok(layer) = document.layer(&layer_path) { let layer_id = layer_path.last().unwrap(); self.layer_overlay_visibility(document, layer_path.clone(), true, responses); if let Some(vector_data) = layer.as_vector_data() { let outline_cache = self.shape_overlay_cache.get(layer_id); trace!("Overlay: Outline cache {:?}", &outline_cache); // Create an outline if we do not have a cached one if outline_cache.is_none() { let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_rs(&vector_data.subpaths), responses); self.shape_overlay_cache.insert(*layer_id, outline_path.clone()); Self::place_outline_overlays(outline_path.clone(), &transform, responses); trace!("Overlay: Creating new outline {:?}", &outline_path); } else if let Some(outline_path) = outline_cache { trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id); Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_rs(&vector_data.subpaths), responses); Self::place_outline_overlays(outline_path.clone(), &transform, responses); } // Create, place, and style the manipulator overlays for manipulator_group in vector_data.manipulator_groups() { let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, manipulator_group.id)).or_default(); // Only view in and out handles if they are not on top of the anchor let [in_handle, out_handle] = { let anchor = manipulator_group.anchor; let anchor_position = transform.transform_point2(anchor); let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE; let filter_handle = |manipulator: Option| manipulator.filter(not_under_anchor); [filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)] }; // Create anchor manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses))); // Create or delete in handle if in_handle.is_none() { Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses); Self::remove_overlay(manipulator_group_cache.in_line.take(), responses); } else { manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses)); manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses)); } // Create or delete out handle if out_handle.is_none() { Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses); Self::remove_overlay(manipulator_group_cache.out_line.take(), responses); } else { manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses)); manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses)); } // Update placement and style Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses); Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses); } // TODO Handle removing shapes from cache so we don't memory leak // Eventually will get replaced with am immediate mode renderer for overlays } } responses.add(OverlaysMessage::Rerender); } pub fn clear_subpath_overlays(&mut self, document: &Document, layer_path: Vec, responses: &mut VecDeque) { let layer_id = layer_path.last().unwrap(); // Remove the shape outline overlays if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { Self::remove_outline_overlays(overlay_path.clone(), responses) } self.shape_overlay_cache.remove(layer_id); // Remove the ManipulatorGroup overlays if let Ok(layer) = document.layer(&layer_path) { if let Some(vector_data) = layer.as_vector_data() { for manipulator_group in vector_data.manipulator_groups() { let id = manipulator_group.id; if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) { Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses); self.manipulator_group_overlay_cache.remove(&(*layer_id, id)); } } } } } pub fn layer_overlay_visibility(&mut self, document: &Document, layer_path: Vec, visibility: bool, responses: &mut VecDeque) { let layer_id = layer_path.last().unwrap(); // Hide the shape outline overlays if let Some(overlay_path) = self.shape_overlay_cache.get(layer_id) { Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses); } // Hide the manipulator group overlays if let Ok(layer) = document.layer(&layer_path) { if let Some(vector_data) = layer.as_vector_data() { for manipulator_group in vector_data.manipulator_groups() { let id = manipulator_group.id; if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) { Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses); } } } } } /// Create the kurbo shape that matches the selected viewport shape. fn create_shape_outline_overlay(&self, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; let operation = Operation::AddShape { path: layer_path.clone(), subpath, style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), PATH_OUTLINE_WEIGHT)), Fill::None), insert_index: -1, transform: DAffine2::IDENTITY.to_cols_array(), }; responses.add(DocumentMessage::Overlays(operation.into())); layer_path } /// Create a single anchor overlay and return its layer ID. fn create_anchor_overlay(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; let operation = Operation::AddRect { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 2.0)), Fill::solid(Color::WHITE)), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); layer_path } /// Create a single handle overlay and return its layer ID. fn create_handle_overlay(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; let operation = Operation::AddEllipse { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 2.0)), Fill::solid(Color::WHITE)), insert_index: -1, }; responses.add(DocumentMessage::Overlays(operation.into())); layer_path } /// Create a single handle overlay and return its layer id if it exists. fn create_handle_overlay_if_exists(handle: Option, responses: &mut VecDeque) -> Option> { handle.map(|_| Self::create_handle_overlay(responses)) } /// Remove an overlay at the specified path fn remove_overlay(path: Option>, responses: &mut VecDeque) { if let Some(path) = path { responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into())); } } /// Create the shape outline overlay and return its layer ID. fn create_handle_line_overlay(responses: &mut VecDeque) -> Vec { let layer_path = vec![generate_uuid()]; let operation = Operation::AddLine { path: layer_path.clone(), transform: DAffine2::IDENTITY.to_cols_array(), style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None), insert_index: -1, }; responses.add_front(DocumentMessage::Overlays(operation.into())); layer_path } /// Create the shape outline overlay and return its layer ID. fn create_handle_line_overlay_if_exists(handle: Option, responses: &mut VecDeque) -> Option> { handle.as_ref().map(|_| Self::create_handle_line_overlay(responses)) } fn place_outline_overlays(outline_path: Vec, parent_transform: &DAffine2, responses: &mut VecDeque) { let transform_message = Self::overlay_transform_message(outline_path, parent_transform.to_cols_array()); responses.add(transform_message); } fn modify_outline_overlays(outline_path: Vec, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque) { let outline_modify_message = Self::overlay_modify_message(outline_path, subpath); responses.add(outline_modify_message); } /// Updates the position of the overlays based on the [Subpath] points. fn place_manipulator_group_overlays(manipulator_group: &GraphiteManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque) { let anchor = manipulator_group.anchor; let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option>| { let line_vector = parent_transform.transform_point2(anchor) - parent_transform.transform_point2(handle_position); let scale = DVec2::splat(line_vector.length()); let angle = -line_vector.angle_between(DVec2::X); let translation = (parent_transform.transform_point2(handle_position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.add(Self::overlay_transform_message(line_overlay.to_vec(), transform)); let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses)); let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); let angle = 0.; let translation = (parent_transform.transform_point2(handle_position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); responses.add(Self::overlay_transform_message(marker_overlay.clone(), transform)); *marker_source = Some(marker_overlay); }; // Place the handle overlays if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.in_handle, overlays.in_line.as_mut()) { place_handle_and_line(handle_position, line_overlay, &mut overlays.in_handle); } if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.out_handle, overlays.out_line.as_ref()) { place_handle_and_line(handle_position, line_overlay, &mut overlays.out_handle); } // Place the anchor point overlay if let Some(anchor_overlay) = &overlays.anchor { let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE); let angle = 0.; let translation = (parent_transform.transform_point2(anchor) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round(); let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array(); let message = Self::overlay_transform_message(anchor_overlay.clone(), transform); responses.add(message); } } /// Removes the manipulator overlays from the overlay document. fn remove_manipulator_group_overlays(overlay_paths: &ManipulatorGroupOverlays, responses: &mut VecDeque) { overlay_paths.iter().flatten().for_each(|layer_id| { trace!("Overlay: Sending delete message for: {:?}", layer_id); responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer_id.clone() }.into())); }); } fn remove_outline_overlays(overlay_path: Vec, responses: &mut VecDeque) { responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: overlay_path }.into())); } /// Sets the visibility of the handles overlay. fn set_manipulator_group_overlay_visibility(manipulator_group_overlays: &ManipulatorGroupOverlays, visibility: bool, responses: &mut VecDeque) { manipulator_group_overlays.iter().flatten().for_each(|layer_id| { responses.add(Self::overlay_visibility_message(layer_id.clone(), visibility)); }); } fn set_outline_overlay_visibility(overlay_path: Vec, visibility: bool, responses: &mut VecDeque) { responses.add(Self::overlay_visibility_message(overlay_path, visibility)); } /// Create a visibility message for an overlay. fn overlay_visibility_message(layer_path: Vec, visibility: bool) -> Message { DocumentMessage::Overlays( Operation::SetLayerVisibility { path: layer_path, visible: visibility, } .into(), ) .into() } /// Create a transform message for an overlay. fn overlay_transform_message(layer_path: Vec, transform: [f64; 6]) -> Message { DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: layer_path, transform }.into()).into() } /// Create an update message for an overlay. fn overlay_modify_message(layer_path: Vec, subpath: graphene_core::vector::Subpath) -> Message { DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into() } /// Sets the overlay style for this point. fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque) { // TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar let selected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT)); let deselected_style = style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE)); let selected_shape_state = state.get(layer_path); // Update if the manipulator points are shown as selected // Here the index is important, even though overlays[..] has five elements we only care about the first three for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() { let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index]; if let Some(overlay_path) = overlay { let selected = selected_shape_state .filter(|state| state.is_selected(ManipulatorPointId::new(manipulator_group.id, selected_type))) .is_some(); let style = if selected { selected_style.clone() } else { deselected_style.clone() }; responses.add(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay_path.clone(), style }.into())); } } } }