349 lines
16 KiB
Rust
349 lines
16 KiB
Rust
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<Vec<LayerId>>,
|
|
pub in_handle: Option<Vec<LayerId>>,
|
|
pub in_line: Option<Vec<LayerId>>,
|
|
pub out_handle: Option<Vec<LayerId>>,
|
|
pub out_line: Option<Vec<LayerId>>,
|
|
}
|
|
impl ManipulatorGroupOverlays {
|
|
pub fn iter(&self) -> impl Iterator<Item = &'_ Option<Vec<LayerId>>> {
|
|
[&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter()
|
|
}
|
|
}
|
|
|
|
type GraphiteManipulatorGroup = ManipulatorGroup<ManipulatorGroupId>;
|
|
|
|
const POINT_STROKE_WEIGHT: f64 = 2.;
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct OverlayRenderer {
|
|
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
|
|
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<LayerId>, responses: &mut VecDeque<Message>) {
|
|
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<DVec2>| 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<LayerId>, responses: &mut VecDeque<Message>) {
|
|
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<LayerId>, visibility: bool, responses: &mut VecDeque<Message>) {
|
|
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<Message>) -> Vec<LayerId> {
|
|
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<Message>) -> Vec<LayerId> {
|
|
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<Message>) -> Vec<LayerId> {
|
|
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<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
|
handle.map(|_| Self::create_handle_overlay(responses))
|
|
}
|
|
|
|
/// Remove an overlay at the specified path
|
|
fn remove_overlay(path: Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
|
|
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<Message>) -> Vec<LayerId> {
|
|
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<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
|
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
|
|
}
|
|
|
|
fn place_outline_overlays(outline_path: Vec<LayerId>, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
|
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<LayerId>, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) {
|
|
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<Message>) {
|
|
let anchor = manipulator_group.anchor;
|
|
|
|
let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option<Vec<LayerId>>| {
|
|
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<Message>) {
|
|
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<LayerId>, responses: &mut VecDeque<Message>) {
|
|
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<Message>) {
|
|
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<LayerId>, visibility: bool, responses: &mut VecDeque<Message>) {
|
|
responses.add(Self::overlay_visibility_message(overlay_path, visibility));
|
|
}
|
|
|
|
/// Create a visibility message for an overlay.
|
|
fn overlay_visibility_message(layer_path: Vec<LayerId>, 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<LayerId>, 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<LayerId>, 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<Message>) {
|
|
// 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()));
|
|
}
|
|
}
|
|
}
|
|
}
|