New overlay system (#1516)
* Port gradient tool overlays * Fix tests * Text tool * Artboard tool and some of select tool * Port select tool drawing box * Pen and path tool * Remove overlays document * Show the overlay refactor as done on the website roadmap * Select tool bounds in layer space (first layer) * Code review and fixes --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
9e06e70aa2
commit
c42d030f18
|
|
@ -2326,6 +2326,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
"wgpu-executor",
|
"wgpu-executor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ num-traits = { version = "0.2.15", default-features = false, features = [
|
||||||
"i128",
|
"i128",
|
||||||
] }
|
] }
|
||||||
js-sys = { version = "0.3.55" }
|
js-sys = { version = "0.3.55" }
|
||||||
usvg = "0.35.0"
|
|
||||||
web-sys = { version = "0.3.55" }
|
web-sys = { version = "0.3.55" }
|
||||||
|
usvg = "0.35.0"
|
||||||
spirv = "0.2.0"
|
spirv = "0.2.0"
|
||||||
fern = { version = "0.6", features = ["colored"] }
|
fern = { version = "0.6", features = ["colored"] }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use glam::{DAffine2, DVec2};
|
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
|
||||||
use graphene_core::renderer::ClickTarget;
|
use graphene_core::renderer::ClickTarget;
|
||||||
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
use graph_craft::document::{DocumentNode, NodeId, NodeNetwork};
|
|
||||||
|
|
||||||
use graphene_core::renderer::Quad;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DocumentMetadata {
|
pub struct DocumentMetadata {
|
||||||
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||||
|
|
@ -338,11 +338,10 @@ impl DocumentMetadata {
|
||||||
.reduce(Quad::combine_bounds)
|
.reduce(Quad::combine_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> graphene_core::vector::Subpath {
|
pub fn layer_outline<'a>(&'a self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &'a bezier_rs::Subpath<ManipulatorGroupId>> {
|
||||||
let Some(click_targets) = self.click_targets.get(&layer) else {
|
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||||
return graphene_core::vector::Subpath::new();
|
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||||
};
|
click_targets.iter().map(|click_target| &click_target.subpath)
|
||||||
graphene_core::vector::Subpath::from_bezier_rs(click_targets.iter().map(|click_target| &click_target.subpath))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,13 @@ wasm-bindgen-futures = { workspace = true, optional = true }
|
||||||
document-legacy = { workspace = true }
|
document-legacy = { workspace = true }
|
||||||
# Remove when `core::cell::LazyCell` is stabilized (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>)
|
# Remove when `core::cell::LazyCell` is stabilized (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>)
|
||||||
once_cell = "1.13.0"
|
once_cell = "1.13.0"
|
||||||
|
web-sys = { workspace = true, features = [
|
||||||
|
"Document",
|
||||||
|
"Element",
|
||||||
|
"HtmlCanvasElement",
|
||||||
|
"CanvasRenderingContext2d",
|
||||||
|
] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ pub struct DispatcherMessageHandlers {
|
||||||
/// In addition, these messages do not change any state in the backend (aside from caches).
|
/// In addition, these messages do not change any state in the backend (aside from caches).
|
||||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Rerender))),
|
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
||||||
PropertiesPanelMessageDiscriminant::ResendActiveProperties,
|
PropertiesPanelMessageDiscriminant::ResendActiveProperties,
|
||||||
|
|
@ -44,6 +43,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)),
|
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)),
|
||||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::InputFrameRasterizeRegionBelowLayer)),
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::InputFrameRasterizeRegionBelowLayer)),
|
||||||
|
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
|
||||||
];
|
];
|
||||||
|
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
|
|
|
||||||
|
|
@ -128,9 +128,6 @@ pub enum FrontendMessage {
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
},
|
},
|
||||||
UpdateDocumentArtboards {
|
|
||||||
svg: String,
|
|
||||||
},
|
|
||||||
UpdateDocumentArtwork {
|
UpdateDocumentArtwork {
|
||||||
svg: String,
|
svg: String,
|
||||||
},
|
},
|
||||||
|
|
@ -155,12 +152,6 @@ pub enum FrontendMessage {
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
},
|
},
|
||||||
UpdateDocumentNodeRender {
|
|
||||||
svg: String,
|
|
||||||
},
|
|
||||||
UpdateDocumentOverlays {
|
|
||||||
svg: String,
|
|
||||||
},
|
|
||||||
UpdateDocumentRulers {
|
UpdateDocumentRulers {
|
||||||
origin: (f64, f64),
|
origin: (f64, f64),
|
||||||
spacing: f64,
|
spacing: f64,
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
Overlays(message) => {
|
Overlays(message) => {
|
||||||
self.overlays_message_handler.process_message(message, responses, (self.overlays_visible, persistent_data, ipp));
|
self.overlays_message_handler.process_message(message, responses, (self.overlays_visible, ipp));
|
||||||
}
|
}
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
PropertiesPanel(message) => {
|
PropertiesPanel(message) => {
|
||||||
|
|
@ -569,9 +569,10 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||||
}
|
}
|
||||||
RenameLayer { layer_path, new_name } => responses.add(DocumentOperation::RenameLayer { layer_path, new_name }),
|
RenameLayer { layer_path, new_name } => responses.add(DocumentOperation::RenameLayer { layer_path, new_name }),
|
||||||
RenderDocument => {
|
RenderDocument => {
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork {
|
// responses.add(FrontendMessage::UpdateDocumentArtwork {
|
||||||
svg: self.document_legacy.render_root(&render_data),
|
// svg: self.document_legacy.render_root(&render_data),
|
||||||
});
|
// });
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
RenderRulers => {
|
RenderRulers => {
|
||||||
let document_transform_scale = self.navigation_handler.snapped_scale();
|
let document_transform_scale = self.navigation_handler.snapped_scale();
|
||||||
|
|
@ -743,8 +744,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||||
SetOverlaysVisibility { visible } => {
|
SetOverlaysVisibility { visible } => {
|
||||||
self.overlays_visible = visible;
|
self.overlays_visible = visible;
|
||||||
responses.add(BroadcastEvent::ToolAbort);
|
responses.add(BroadcastEvent::ToolAbort);
|
||||||
responses.add(OverlaysMessage::ClearAllOverlays);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(OverlaysMessage::Rerender);
|
|
||||||
}
|
}
|
||||||
SetRangeSelectionLayer { new_layer } => {
|
SetRangeSelectionLayer { new_layer } => {
|
||||||
self.layer_range_selection_reference = new_layer;
|
self.layer_range_selection_reference = new_layer;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
mod overlays_message;
|
mod overlays_message;
|
||||||
mod overlays_message_handler;
|
mod overlays_message_handler;
|
||||||
|
pub mod utility_functions;
|
||||||
|
pub mod utility_types;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
pub use overlays_message::*;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use overlays_message_handler::OverlaysMessageHandler;
|
pub use overlays_message_handler::*;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,14 @@
|
||||||
|
use super::utility_types::{empty_provider, OverlayProvider};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::Operation as DocumentOperation;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[remain::sorted]
|
|
||||||
#[impl_message(Message, DocumentMessage, Overlays)]
|
#[impl_message(Message, DocumentMessage, Overlays)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum OverlaysMessage {
|
pub enum OverlaysMessage {
|
||||||
// Sub-messages
|
Draw,
|
||||||
#[remain::unsorted]
|
|
||||||
DispatchOperation(Box<DocumentOperation>),
|
|
||||||
|
|
||||||
// Messages
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
ClearAllOverlays,
|
AddProvider(#[serde(skip, default = "empty_provider")] OverlayProvider),
|
||||||
Rerender,
|
RemoveProvider(#[serde(skip, default = "empty_provider")] OverlayProvider),
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DocumentOperation> for OverlaysMessage {
|
|
||||||
fn from(operation: DocumentOperation) -> OverlaysMessage {
|
|
||||||
Self::DispatchOperation(Box::new(operation))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,51 @@
|
||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use super::utility_functions::overlay_canvas_element;
|
||||||
|
use super::utility_types::{OverlayContext, OverlayProvider};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::document::Document as DocumentLegacy;
|
use wasm_bindgen::JsCast;
|
||||||
use document_legacy::layers::style::{RenderData, ViewMode};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct OverlaysMessageHandler {
|
pub struct OverlaysMessageHandler {
|
||||||
pub overlays_document: DocumentLegacy,
|
pub overlay_providers: HashSet<OverlayProvider>,
|
||||||
|
canvas: Option<web_sys::HtmlCanvasElement>,
|
||||||
|
context: Option<web_sys::CanvasRenderingContext2d>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<OverlaysMessage, (bool, &PersistentData, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
|
impl MessageHandler<OverlaysMessage, (bool, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
|
||||||
#[remain::check]
|
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, (overlays_visible, ipp): (bool, &InputPreprocessorMessageHandler)) {
|
||||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, (overlays_visible, persistent_data, ipp): (bool, &PersistentData, &InputPreprocessorMessageHandler)) {
|
|
||||||
use OverlaysMessage::*;
|
|
||||||
|
|
||||||
#[remain::sorted]
|
|
||||||
match message {
|
match message {
|
||||||
// Sub-messages
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[remain::unsorted]
|
OverlaysMessage::Draw => {
|
||||||
DispatchOperation(operation) => match self.overlays_document.handle_operation(*operation) {
|
let canvas = self.canvas.get_or_insert_with(|| overlay_canvas_element().expect("Failed to get canvas element"));
|
||||||
Ok(_) => responses.add(OverlaysMessage::Rerender),
|
|
||||||
Err(e) => error!("OverlaysError: {e:?}"),
|
|
||||||
},
|
|
||||||
|
|
||||||
// Messages
|
let context = self.context.get_or_insert_with(|| {
|
||||||
ClearAllOverlays => {
|
let context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
|
||||||
self.overlays_document = DocumentLegacy::default();
|
context.dyn_into().expect("Context should be a canvas 2d context")
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.set_width(ipp.viewport_bounds.size().x as u32);
|
||||||
|
canvas.set_height(ipp.viewport_bounds.size().y as u32);
|
||||||
|
|
||||||
|
context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
|
||||||
|
|
||||||
|
if overlays_visible {
|
||||||
|
for provider in &self.overlay_providers {
|
||||||
|
responses.add(provider(OverlayContext { render_context: context.clone() }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Rerender =>
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
// Render overlays
|
OverlaysMessage::Draw => {
|
||||||
{
|
warn!("Cannot render overlays on non-Wasm targets {overlays_visible} {ipp:?}.");
|
||||||
responses.add(FrontendMessage::UpdateDocumentOverlays {
|
}
|
||||||
svg: if overlays_visible {
|
OverlaysMessage::AddProvider(message) => {
|
||||||
let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, Some(ipp.document_bounds()));
|
self.overlay_providers.insert(message);
|
||||||
self.overlays_document.render_root(&render_data)
|
}
|
||||||
} else {
|
OverlaysMessage::RemoveProvider(message) => {
|
||||||
String::from("")
|
self.overlay_providers.remove(&message);
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
advertise_actions!(OverlaysMessage;);
|
||||||
actions!(OverlaysMessageDiscriminant;
|
|
||||||
ClearAllOverlays,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
use super::utility_types::OverlayContext;
|
||||||
|
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_groups, get_subpaths};
|
||||||
|
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||||
|
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||||
|
|
||||||
|
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||||
|
|
||||||
|
use glam::DVec2;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
|
||||||
|
let window = web_sys::window()?;
|
||||||
|
let document = window.document()?;
|
||||||
|
let canvas = document.query_selector("[data-overlays-canvas]").ok().flatten()?;
|
||||||
|
canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
||||||
|
let create_context = || {
|
||||||
|
let context = overlay_canvas_element()?.get_context("2d").ok().flatten()?;
|
||||||
|
context.dyn_into().ok()
|
||||||
|
};
|
||||||
|
create_context().expect("Failed to get canvas context")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||||
|
for layer in document.metadata().selected_layers() {
|
||||||
|
let Some(subpaths) = get_subpaths(layer, &document.document_legacy) else { continue };
|
||||||
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
|
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||||
|
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||||
|
overlay_context.outline(subpaths.iter(), transform);
|
||||||
|
|
||||||
|
for manipulator_group in get_manipulator_groups(subpaths) {
|
||||||
|
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;
|
||||||
|
if let Some(in_handle) = manipulator_group.in_handle.filter(not_under_anchor) {
|
||||||
|
let handle_position = transform.transform_point2(in_handle);
|
||||||
|
overlay_context.line(handle_position, anchor_position);
|
||||||
|
overlay_context.handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)));
|
||||||
|
}
|
||||||
|
if let Some(out_handle) = manipulator_group.out_handle.filter(not_under_anchor) {
|
||||||
|
let handle_position = transform.transform_point2(out_handle);
|
||||||
|
overlay_context.line(handle_position, anchor_position);
|
||||||
|
overlay_context.handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)));
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_context.square(anchor_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
use super::utility_functions::overlay_canvas_context;
|
||||||
|
use crate::consts::{COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_INNER, PIVOT_OUTER};
|
||||||
|
use crate::messages::prelude::Message;
|
||||||
|
|
||||||
|
use bezier_rs::Subpath;
|
||||||
|
use graphene_core::renderer::Quad;
|
||||||
|
use graphene_core::uuid::ManipulatorGroupId;
|
||||||
|
|
||||||
|
use core::f64::consts::PI;
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||||
|
|
||||||
|
pub fn empty_provider() -> OverlayProvider {
|
||||||
|
|_| Message::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct OverlayContext {
|
||||||
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
|
#[serde(skip, default = "overlay_canvas_context")]
|
||||||
|
pub render_context: web_sys::CanvasRenderingContext2d,
|
||||||
|
}
|
||||||
|
// Message hashing isn't used but is required by the message system macros
|
||||||
|
impl core::hash::Hash for OverlayContext {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayContext {
|
||||||
|
fn accent_hex() -> String {
|
||||||
|
format!("#{}", COLOR_ACCENT.rgb_hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quad(&mut self, quad: Quad) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.move_to(quad.0[3].x.round(), quad.0[3].y.round());
|
||||||
|
for i in 0..4 {
|
||||||
|
self.render_context.line_to(quad.0[i].x.round(), quad.0[i].y.round());
|
||||||
|
}
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&mut self, start: DVec2, end: DVec2) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.move_to(start.x.round(), start.y.round());
|
||||||
|
self.render_context.line_to(end.x.round(), end.y.round());
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
let position = position.round();
|
||||||
|
self.render_context
|
||||||
|
.arc(position.x + 0.5, position.y + 0.5, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., PI * 2.)
|
||||||
|
.expect("draw circle");
|
||||||
|
|
||||||
|
let fill = if selected { Self::accent_hex() } else { "white".to_string() };
|
||||||
|
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(&fill));
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn square(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
let corner = position - DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE) / 2.;
|
||||||
|
self.render_context
|
||||||
|
.rect(corner.x.round(), corner.y.round(), MANIPULATOR_GROUP_MARKER_SIZE, MANIPULATOR_GROUP_MARKER_SIZE);
|
||||||
|
let fill = if selected { Self::accent_hex() } else { "white".to_string() };
|
||||||
|
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(&fill));
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot(&mut self, pivot: DVec2) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.arc(pivot.x + 0.5, pivot.y + 0.5, PIVOT_OUTER / 2., 0., PI * 2.).expect("draw circle");
|
||||||
|
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(&"white"));
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.arc(pivot.x, pivot.y, PIVOT_INNER / 2., 0., PI * 2.).expect("draw circle");
|
||||||
|
self.render_context.set_fill_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline<'a>(&mut self, subpaths: impl Iterator<Item = &'a Subpath<ManipulatorGroupId>>, transform: DAffine2) {
|
||||||
|
let transform = |point| transform.transform_point2(point);
|
||||||
|
self.render_context.begin_path();
|
||||||
|
for subpath in subpaths {
|
||||||
|
let mut curves = subpath.iter().peekable();
|
||||||
|
let Some(first) = curves.peek() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
self.render_context.move_to(transform(first.start()).x, transform(first.start()).y);
|
||||||
|
for curve in curves {
|
||||||
|
match curve.handles {
|
||||||
|
bezier_rs::BezierHandles::Linear => self.render_context.line_to(transform(curve.end()).x, transform(curve.end()).y),
|
||||||
|
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||||
|
self.render_context
|
||||||
|
.quadratic_curve_to(transform(handle).x, transform(handle).y, transform(curve.end()).x, transform(curve.end()).y)
|
||||||
|
}
|
||||||
|
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(
|
||||||
|
transform(handle_start).x,
|
||||||
|
transform(handle_start).y,
|
||||||
|
transform(handle_end).x,
|
||||||
|
transform(handle_end).y,
|
||||||
|
transform(curve.end()).x,
|
||||||
|
transform(curve.end()).y,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if subpath.closed() {
|
||||||
|
self.render_context.close_path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(&Self::accent_hex()));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
|
|
||||||
use document_legacy::document::Document;
|
use document_legacy::document::Document;
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
|
|
@ -400,11 +401,8 @@ impl<'a> Selected<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
pub fn apply_transformation(&mut self, transformation: DAffine2) {
|
||||||
if !self.selected.is_empty() {
|
if !self.selected.is_empty() {
|
||||||
let pivot = DAffine2::from_translation(*self.pivot);
|
|
||||||
let transformation = pivot * delta * pivot.inverse();
|
|
||||||
|
|
||||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||||
for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
||||||
let layer = *layer_ancestors.last().unwrap();
|
let layer = *layer_ancestors.last().unwrap();
|
||||||
|
|
@ -418,6 +416,12 @@ impl<'a> Selected<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||||
|
let pivot = DAffine2::from_translation(*self.pivot);
|
||||||
|
let transformation = pivot * delta * pivot.inverse();
|
||||||
|
self.apply_transformation(transformation);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn revert_operation(&mut self) {
|
pub fn revert_operation(&mut self) {
|
||||||
for layer in self.selected.iter().copied() {
|
for layer in self.selected.iter().copied() {
|
||||||
let original_transform = &self.original_transforms;
|
let original_transform = &self.original_transforms;
|
||||||
|
|
|
||||||
|
|
@ -467,7 +467,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||||
|
|
||||||
if self.active_document().is_some() {
|
if self.active_document().is_some() {
|
||||||
responses.add(BroadcastEvent::ToolAbort);
|
responses.add(BroadcastEvent::ToolAbort);
|
||||||
responses.add(OverlaysMessage::ClearAllOverlays);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this message in favor of having tools have specific data per document instance
|
// TODO: Remove this message in favor of having tools have specific data per document instance
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
pub mod color_selector;
|
pub mod color_selector;
|
||||||
pub mod graph_modification_utils;
|
pub mod graph_modification_utils;
|
||||||
pub mod overlay_renderer;
|
|
||||||
pub mod path_outline;
|
|
||||||
pub mod pivot;
|
pub mod pivot;
|
||||||
pub mod resize;
|
pub mod resize;
|
||||||
pub mod shape_editor;
|
pub mod shape_editor;
|
||||||
|
|
|
||||||
|
|
@ -1,359 +0,0 @@
|
||||||
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 crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_groups, get_subpaths};
|
|
||||||
|
|
||||||
use bezier_rs::ManipulatorGroup;
|
|
||||||
use document_legacy::document::Document;
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
|
||||||
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<LayerNodeIdentifier, Vec<LayerId>>,
|
|
||||||
manipulator_group_overlay_cache: HashMap<LayerNodeIdentifier, HashMap<ManipulatorGroupId, ManipulatorGroupOverlays>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OverlayRenderer {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_cache(&self, layer: &LayerNodeIdentifier) -> Option<&Vec<LayerId>> {
|
|
||||||
self.shape_overlay_cache.get(layer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
|
||||||
let transform = document.metadata.transform_to_viewport(layer);
|
|
||||||
|
|
||||||
let Some(subpaths) = get_subpaths(layer, document) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.layer_overlay_visibility(document, layer, true, responses);
|
|
||||||
|
|
||||||
let outline_cache = self.shape_overlay_cache.get(&layer);
|
|
||||||
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(subpaths), responses);
|
|
||||||
self.shape_overlay_cache.insert(layer, 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 {outline_path:?} owning layer: {layer:?}");
|
|
||||||
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_rs(subpaths), responses);
|
|
||||||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create, place, and style the manipulator overlays
|
|
||||||
for manipulator_group in get_manipulator_groups(subpaths) {
|
|
||||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry(layer).or_default().entry(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, manipulator_group, manipulator_group_cache, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(layer_overlays) = self.manipulator_group_overlay_cache.get_mut(&layer) {
|
|
||||||
if layer_overlays.len() > subpaths.iter().map(|subpath| subpath.len()).sum() {
|
|
||||||
layer_overlays.retain(|manipulator, manipulator_group_overlays| {
|
|
||||||
if get_manipulator_groups(subpaths).any(|current_manipulator| current_manipulator.id == *manipulator) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete all cached overlays
|
|
||||||
pub fn clear_all_overlays(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
for (_, overlay_path) in self.shape_overlay_cache.drain() {
|
|
||||||
Self::remove_outline_overlays(overlay_path, responses)
|
|
||||||
}
|
|
||||||
for (_, layer_cache) in self.manipulator_group_overlay_cache.drain() {
|
|
||||||
for manipulator_group_overlays in layer_cache.values() {
|
|
||||||
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layer_overlay_visibility(&mut self, document: &Document, layer: LayerNodeIdentifier, visibility: bool, responses: &mut VecDeque<Message>) {
|
|
||||||
// Hide the shape outline overlays
|
|
||||||
if let Some(overlay_path) = self.shape_overlay_cache.get(&layer) {
|
|
||||||
Self::set_outline_overlay_visibility(overlay_path.clone(), visibility, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the manipulator group overlays
|
|
||||||
let Some(manipulator_groups) = self.manipulator_group_overlay_cache.get(&layer) else { return };
|
|
||||||
if visibility {
|
|
||||||
let Some(subpaths) = get_subpaths(layer, document) else { return };
|
|
||||||
for manipulator_group in get_manipulator_groups(subpaths) {
|
|
||||||
let id = manipulator_group.id;
|
|
||||||
if let Some(manipulator_group_overlays) = manipulator_groups.get(&id) {
|
|
||||||
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for manipulator_group_overlays in manipulator_groups.values() {
|
|
||||||
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: LayerNodeIdentifier, 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);
|
|
||||||
// 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
use crate::application::generate_uuid;
|
|
||||||
use crate::consts::{COLOR_ACCENT, PATH_OUTLINE_WEIGHT};
|
|
||||||
use crate::messages::prelude::*;
|
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
|
||||||
use document_legacy::layers::style::{self, Fill, Stroke};
|
|
||||||
use document_legacy::{LayerId, Operation};
|
|
||||||
|
|
||||||
use glam::DAffine2;
|
|
||||||
|
|
||||||
/// Manages the overlay used by the select tool for outlining selected shapes and when hovering over a non selected shape.
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct PathOutline {
|
|
||||||
hovered_layer_path: Option<LayerNodeIdentifier>,
|
|
||||||
hovered_overlay_path: Option<Vec<LayerId>>,
|
|
||||||
selected_overlay_paths: Vec<Vec<LayerId>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathOutline {
|
|
||||||
/// Creates an outline of a layer either with a pre-existing overlay or by generating a new one
|
|
||||||
fn try_create_outline(layer: LayerNodeIdentifier, overlay_path: Option<Vec<LayerId>>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
|
||||||
let subpath = document.metadata().layer_outline(layer);
|
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
|
||||||
|
|
||||||
// Generate a new overlay layer if necessary
|
|
||||||
let overlay = overlay_path.unwrap_or_else(|| {
|
|
||||||
let overlay_path = vec![generate_uuid()];
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::Overlays(
|
|
||||||
(Operation::AddShape {
|
|
||||||
path: overlay_path.clone(),
|
|
||||||
subpath: Default::default(),
|
|
||||||
style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), PATH_OUTLINE_WEIGHT)), Fill::None),
|
|
||||||
insert_index: -1,
|
|
||||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
|
|
||||||
overlay_path
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update the shape subpath
|
|
||||||
responses.add(DocumentMessage::Overlays((Operation::SetShapePath { path: overlay.clone(), subpath }).into()));
|
|
||||||
|
|
||||||
// Update the transform to match the document
|
|
||||||
responses.add(DocumentMessage::Overlays(
|
|
||||||
(Operation::SetLayerTransform {
|
|
||||||
path: overlay.clone(),
|
|
||||||
transform: transform.to_cols_array(),
|
|
||||||
})
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
|
|
||||||
Some(overlay)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an outline of a layer either with a pre-existing overlay or by generating a new one.
|
|
||||||
///
|
|
||||||
/// Creates an outline, discarding the overlay on failure.
|
|
||||||
fn create_outline(layer: LayerNodeIdentifier, overlay_path: Option<Vec<LayerId>>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
|
||||||
let copied_overlay_path = overlay_path.clone();
|
|
||||||
let result = Self::try_create_outline(layer, overlay_path, document, responses);
|
|
||||||
if result.is_none() {
|
|
||||||
// Discard the overlay layer if it exists
|
|
||||||
if let Some(overlay_path) = copied_overlay_path {
|
|
||||||
let operation = Operation::DeleteLayer { path: overlay_path };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the hovered overlay and deletes path references
|
|
||||||
pub fn clear_hovered(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
if let Some(path) = self.hovered_overlay_path.take() {
|
|
||||||
let operation = Operation::DeleteLayer { path };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
self.hovered_layer_path = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an intersect test and generates a hovered overlay if necessary
|
|
||||||
pub fn intersect_test_hovered(&mut self, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
|
||||||
// Get the layer the user is hovering over
|
|
||||||
let intersection = document.document_legacy.click(input.mouse.position, &document.document_legacy.document_network);
|
|
||||||
|
|
||||||
let Some(hovered_layer) = intersection else {
|
|
||||||
self.clear_hovered(responses);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if document.metadata().selected_layers_contains(hovered_layer) {
|
|
||||||
self.clear_hovered(responses);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates the overlay, generating a new one if necessary
|
|
||||||
self.hovered_overlay_path = Self::create_outline(hovered_layer, self.hovered_overlay_path.take(), document, responses);
|
|
||||||
if self.hovered_overlay_path.is_none() {
|
|
||||||
self.clear_hovered(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hovered_layer_path = Some(hovered_layer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears overlays for the selected paths and removes references
|
|
||||||
pub fn clear_selected(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
while let Some(path) = self.selected_overlay_paths.pop() {
|
|
||||||
let operation = Operation::DeleteLayer { path };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the selected overlays, generating or removing overlays if necessary
|
|
||||||
pub fn update_selected(&mut self, selected: impl Iterator<Item = LayerNodeIdentifier>, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
|
||||||
let mut old_overlay_paths = std::mem::take(&mut self.selected_overlay_paths);
|
|
||||||
|
|
||||||
for layer_identifier in selected {
|
|
||||||
if let Some(overlay_path) = Self::create_outline(layer_identifier, old_overlay_paths.pop(), document, responses) {
|
|
||||||
self.selected_overlay_paths.push(overlay_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for path in old_overlay_paths {
|
|
||||||
let operation = Operation::DeleteLayer { path };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.
|
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer.
|
||||||
|
|
||||||
use crate::application::generate_uuid;
|
use super::graph_modification_utils;
|
||||||
use crate::consts::{COLOR_ACCENT, PIVOT_INNER, PIVOT_OUTER, PIVOT_OUTER_OUTLINE_THICKNESS};
|
use crate::consts::PIVOT_OUTER;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::layers::style;
|
|
||||||
use document_legacy::{LayerId, Operation};
|
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use super::graph_modification_utils;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Pivot {
|
pub struct Pivot {
|
||||||
/// Pivot between (0,0) and (1,1)
|
/// Pivot between (0,0) and (1,1)
|
||||||
|
|
@ -22,8 +19,6 @@ pub struct Pivot {
|
||||||
transform_from_normalized: DAffine2,
|
transform_from_normalized: DAffine2,
|
||||||
/// The viewspace pivot position (if applicable)
|
/// The viewspace pivot position (if applicable)
|
||||||
pivot: Option<DVec2>,
|
pivot: Option<DVec2>,
|
||||||
/// A reference to the previous overlays so we can destroy them
|
|
||||||
pivot_overlay_circles: Option<[Vec<LayerId>; 2]>,
|
|
||||||
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
/// The old pivot position in the GUI, used to reduce refreshes of the document bar
|
||||||
old_pivot_position: PivotPosition,
|
old_pivot_position: PivotPosition,
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +29,6 @@ impl Default for Pivot {
|
||||||
normalized_pivot: DVec2::splat(0.5),
|
normalized_pivot: DVec2::splat(0.5),
|
||||||
transform_from_normalized: Default::default(),
|
transform_from_normalized: Default::default(),
|
||||||
pivot: Default::default(),
|
pivot: Default::default(),
|
||||||
pivot_overlay_circles: Default::default(),
|
|
||||||
old_pivot_position: PivotPosition::Center,
|
old_pivot_position: PivotPosition::Center,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -87,59 +81,11 @@ impl Pivot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_overlays(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||||
if let Some(overlays) = self.pivot_overlay_circles.take() {
|
|
||||||
for path in overlays {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw_pivot(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
self.clear_overlays(responses);
|
|
||||||
|
|
||||||
let pivot = match self.pivot {
|
|
||||||
Some(pivot) => pivot,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let layer_paths = [vec![generate_uuid()], vec![generate_uuid()]];
|
|
||||||
responses.add(DocumentMessage::Overlays(
|
|
||||||
Operation::AddEllipse {
|
|
||||||
path: layer_paths[0].clone(),
|
|
||||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
|
||||||
style: style::PathStyle::new(
|
|
||||||
Some(style::Stroke::new(Some(COLOR_ACCENT), PIVOT_OUTER_OUTLINE_THICKNESS)),
|
|
||||||
style::Fill::Solid(graphene_core::raster::color::Color::WHITE),
|
|
||||||
),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
responses.add(DocumentMessage::Overlays(
|
|
||||||
Operation::AddEllipse {
|
|
||||||
path: layer_paths[1].clone(),
|
|
||||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
|
||||||
style: style::PathStyle::new(None, style::Fill::Solid(COLOR_ACCENT)),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
|
|
||||||
self.pivot_overlay_circles = Some(layer_paths.clone());
|
|
||||||
let [outer, inner] = layer_paths;
|
|
||||||
|
|
||||||
let pivot_diameter_without_outline = PIVOT_OUTER - PIVOT_OUTER_OUTLINE_THICKNESS;
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(pivot_diameter_without_outline), 0., pivot - DVec2::splat(pivot_diameter_without_outline / 2.)).to_cols_array();
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::TransformLayerInViewport { path: outer, transform }.into()));
|
|
||||||
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(PIVOT_INNER), 0., pivot - DVec2::splat(PIVOT_INNER / 2.)).to_cols_array();
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::TransformLayerInViewport { path: inner, transform }.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
|
||||||
self.recalculate_pivot(document);
|
self.recalculate_pivot(document);
|
||||||
self.redraw_pivot(responses);
|
if let Some(pivot) = self.pivot {
|
||||||
|
overlay_context.pivot(pivot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
|
||||||
|
|
|
||||||
|
|
@ -1,169 +1,19 @@
|
||||||
use super::shape_editor::ManipulatorPointInfo;
|
use super::shape_editor::ManipulatorPointInfo;
|
||||||
use crate::application::generate_uuid;
|
use crate::consts::{SNAP_AXIS_TOLERANCE, SNAP_POINT_TOLERANCE};
|
||||||
use crate::consts::{
|
|
||||||
COLOR_ACCENT, SNAP_AXIS_OVERLAY_FADE_DISTANCE, SNAP_AXIS_TOLERANCE, SNAP_AXIS_UNSNAPPED_OPACITY, SNAP_POINT_OVERLAY_FADE_FAR, SNAP_POINT_OVERLAY_FADE_NEAR, SNAP_POINT_SIZE, SNAP_POINT_TOLERANCE,
|
|
||||||
SNAP_POINT_UNSNAPPED_OPACITY,
|
|
||||||
};
|
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::layers::layer_info::LegacyLayer;
|
use document_legacy::layers::layer_info::LegacyLayer;
|
||||||
use document_legacy::layers::style::{self, Stroke};
|
use document_legacy::LayerId;
|
||||||
use document_legacy::{LayerId, Operation};
|
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::DVec2;
|
||||||
use std::f64::consts::PI;
|
|
||||||
|
|
||||||
// Handles snap overlays
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
struct SnapOverlays {
|
|
||||||
axis_overlay_paths: Vec<Vec<LayerId>>,
|
|
||||||
point_overlay_paths: Vec<Vec<LayerId>>,
|
|
||||||
axis_index: usize,
|
|
||||||
point_index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SnapOverlays {
|
|
||||||
/// Draws an overlay (axis or point) with the correct transform and fade opacity, reusing lines from the pool if available.
|
|
||||||
fn add_overlay(is_axis: bool, responses: &mut VecDeque<Message>, transform: [f64; 6], opacity: Option<f64>, index: usize, overlay_paths: &mut Vec<Vec<LayerId>>) {
|
|
||||||
// If there isn't one in the pool to ruse, add a new alignment line to the pool with the intended transform
|
|
||||||
let layer_path = if index >= overlay_paths.len() {
|
|
||||||
let layer_path = vec![generate_uuid()];
|
|
||||||
responses.add(DocumentMessage::Overlays(
|
|
||||||
if is_axis {
|
|
||||||
Operation::AddLine {
|
|
||||||
path: layer_path.clone(),
|
|
||||||
transform,
|
|
||||||
style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), style::Fill::None),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Operation::AddEllipse {
|
|
||||||
path: layer_path.clone(),
|
|
||||||
transform,
|
|
||||||
style: style::PathStyle::new(None, style::Fill::Solid(COLOR_ACCENT)),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
overlay_paths.push(layer_path.clone());
|
|
||||||
layer_path
|
|
||||||
}
|
|
||||||
// Otherwise, reuse an overlay from the pool and update its new transform
|
|
||||||
else {
|
|
||||||
let layer_path = overlay_paths[index].clone();
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into()));
|
|
||||||
layer_path
|
|
||||||
};
|
|
||||||
|
|
||||||
// Then set its opacity to the fade amount
|
|
||||||
if let Some(opacity) = opacity {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::SetLayerOpacity { path: layer_path, opacity }.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw the alignment lines for an axis
|
|
||||||
/// Note: horizontal refers to the overlay line being horizontal and the snap being along the Y axis
|
|
||||||
fn draw_alignment_lines(&mut self, is_horizontal: bool, distances: impl Iterator<Item = (DVec2, DVec2, f64)>, responses: &mut VecDeque<Message>, closest_distance: DVec2) {
|
|
||||||
for (target, goal, distance) in distances.filter(|(_target, _pos, dist)| dist.abs() < SNAP_AXIS_OVERLAY_FADE_DISTANCE) {
|
|
||||||
let offset = if is_horizontal { target.y } else { target.x }.round() - 0.5;
|
|
||||||
let offset_other = if is_horizontal { target.x } else { target.y }.round() - 0.5;
|
|
||||||
let goal_axis = if is_horizontal { goal.x } else { goal.y }.round() - 0.5;
|
|
||||||
|
|
||||||
let scale = DVec2::new(offset_other - goal_axis, 1.);
|
|
||||||
let angle = if is_horizontal { 0. } else { PI / 2. };
|
|
||||||
let translation = if is_horizontal { DVec2::new(goal_axis, offset) } else { DVec2::new(offset, goal_axis) };
|
|
||||||
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
|
||||||
let closest = if is_horizontal { closest_distance.y } else { closest_distance.x };
|
|
||||||
|
|
||||||
let opacity = if (closest - distance).abs() < 1. {
|
|
||||||
1.
|
|
||||||
} else {
|
|
||||||
SNAP_AXIS_UNSNAPPED_OPACITY - distance.abs() / (SNAP_AXIS_OVERLAY_FADE_DISTANCE / SNAP_AXIS_UNSNAPPED_OPACITY)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add line
|
|
||||||
Self::add_overlay(true, responses, transform, Some(opacity), self.axis_index, &mut self.axis_overlay_paths);
|
|
||||||
self.axis_index += 1;
|
|
||||||
|
|
||||||
let size = DVec2::splat(SNAP_POINT_SIZE);
|
|
||||||
|
|
||||||
// Add point at target
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(size, 0., target - size / 2.).to_cols_array();
|
|
||||||
Self::add_overlay(false, responses, transform, Some(opacity), self.point_index, &mut self.point_overlay_paths);
|
|
||||||
self.point_index += 1;
|
|
||||||
|
|
||||||
// Add point along line but towards goal
|
|
||||||
let translation = if is_horizontal { DVec2::new(goal.x, target.y) } else { DVec2::new(target.x, goal.y) };
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(size, 0., translation - size / 2.).to_cols_array();
|
|
||||||
Self::add_overlay(false, responses, transform, Some(opacity), self.point_index, &mut self.point_overlay_paths);
|
|
||||||
self.point_index += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw the snap points
|
|
||||||
fn draw_snap_points(&mut self, distances: impl Iterator<Item = (DVec2, DVec2, f64)>, responses: &mut VecDeque<Message>, closest_distance: DVec2) {
|
|
||||||
for (target, offset, distance) in distances.filter(|(_pos, _offset, dist)| dist.abs() < SNAP_POINT_OVERLAY_FADE_FAR) {
|
|
||||||
let active = (closest_distance - offset).length_squared() < 1.;
|
|
||||||
|
|
||||||
if active {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let opacity = (1. - (distance - SNAP_POINT_OVERLAY_FADE_NEAR) / (SNAP_POINT_OVERLAY_FADE_FAR - SNAP_POINT_OVERLAY_FADE_NEAR)).min(1.) / SNAP_POINT_UNSNAPPED_OPACITY;
|
|
||||||
|
|
||||||
let size = DVec2::splat(SNAP_POINT_SIZE);
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(size, 0., target - size / 2.).to_cols_array();
|
|
||||||
Self::add_overlay(false, responses, transform, Some(opacity), self.point_index, &mut self.point_overlay_paths);
|
|
||||||
self.point_index += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the snapping overlays with the specified distances.
|
|
||||||
/// `positions_and_distances` is a tuple of `x`, `y` & `point` iterators,, each with `(position, goal, distance)` values.
|
|
||||||
fn update_overlays<X, Y, P>(&mut self, responses: &mut VecDeque<Message>, positions_and_distances: (X, Y, P), closest_distance: DVec2, snapped_to_point: bool)
|
|
||||||
where
|
|
||||||
X: Iterator<Item = (DVec2, DVec2, f64)>,
|
|
||||||
Y: Iterator<Item = (DVec2, DVec2, f64)>,
|
|
||||||
P: Iterator<Item = (DVec2, DVec2, f64)>,
|
|
||||||
{
|
|
||||||
self.axis_index = 0;
|
|
||||||
self.point_index = 0;
|
|
||||||
|
|
||||||
let (x, y, points) = positions_and_distances;
|
|
||||||
if !snapped_to_point {
|
|
||||||
self.draw_alignment_lines(true, y, responses, closest_distance);
|
|
||||||
self.draw_alignment_lines(false, x, responses, closest_distance);
|
|
||||||
self.draw_snap_points(points, responses, closest_distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::remove_unused_overlays(&mut self.axis_overlay_paths, responses, self.axis_index);
|
|
||||||
Self::remove_unused_overlays(&mut self.point_overlay_paths, responses, self.point_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove overlays from the pool beyond a given index. Pool entries up through that index will be kept.
|
|
||||||
fn remove_unused_overlays(overlay_paths: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Message>, remove_after_index: usize) {
|
|
||||||
while overlay_paths.len() > remove_after_index {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: overlay_paths.pop().unwrap() }.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all overlays
|
|
||||||
fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
Self::remove_unused_overlays(&mut self.axis_overlay_paths, responses, 0);
|
|
||||||
Self::remove_unused_overlays(&mut self.point_overlay_paths, responses, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handles snapping and snap overlays
|
/// Handles snapping and snap overlays
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct SnapManager {
|
pub struct SnapManager {
|
||||||
point_targets: Option<Vec<DVec2>>,
|
point_targets: Option<Vec<DVec2>>,
|
||||||
bound_targets: Option<Vec<DVec2>>,
|
bound_targets: Option<Vec<DVec2>>,
|
||||||
snap_overlays: SnapOverlays,
|
|
||||||
snap_x: bool,
|
snap_x: bool,
|
||||||
snap_y: bool,
|
snap_y: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -193,7 +43,7 @@ impl SnapManager {
|
||||||
let min_points = points.clone().min_by(|a, b| a.2.abs().partial_cmp(&b.2.abs()).expect("Could not compare position."));
|
let min_points = points.clone().min_by(|a, b| a.2.abs().partial_cmp(&b.2.abs()).expect("Could not compare position."));
|
||||||
|
|
||||||
// Snap to a point if possible
|
// Snap to a point if possible
|
||||||
let (clamped_closest_distance, snapped_to_point) = if let Some(min_points) = min_points.filter(|&(_, _, dist)| dist <= SNAP_POINT_TOLERANCE) {
|
let (clamped_closest_distance, _snapped_to_point) = if let Some(min_points) = min_points.filter(|&(_, _, dist)| dist <= SNAP_POINT_TOLERANCE) {
|
||||||
(min_points.1, true)
|
(min_points.1, true)
|
||||||
} else {
|
} else {
|
||||||
// Do not move if over snap tolerance
|
// Do not move if over snap tolerance
|
||||||
|
|
@ -206,8 +56,7 @@ impl SnapManager {
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
self.snap_overlays.update_overlays(responses, (x_axis, y_axis, points), clamped_closest_distance, snapped_to_point);
|
|
||||||
|
|
||||||
clamped_closest_distance
|
clamped_closest_distance
|
||||||
}
|
}
|
||||||
|
|
@ -336,9 +185,9 @@ impl SnapManager {
|
||||||
|
|
||||||
/// Removes snap target data and overlays. Call this when snapping is done.
|
/// Removes snap target data and overlays. Call this when snapping is done.
|
||||||
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
|
||||||
self.snap_overlays.cleanup(responses);
|
|
||||||
self.bound_targets = None;
|
self.bound_targets = None;
|
||||||
self.point_targets = None;
|
self.point_targets = None;
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
use crate::application::generate_uuid;
|
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, SELECTION_DRAG_ANGLE};
|
||||||
use crate::consts::{BOUNDS_ROTATE_THRESHOLD, BOUNDS_SELECT_THRESHOLD, COLOR_ACCENT, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_DRAG_ANGLE};
|
|
||||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::OriginalTransforms;
|
use crate::messages::portfolio::document::utility_types::transformation::OriginalTransforms;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::layers::style::{self, Fill, Stroke};
|
use graphene_core::renderer::Quad;
|
||||||
use document_legacy::LayerId;
|
|
||||||
use document_legacy::Operation;
|
|
||||||
use graphene_core::raster::color::Color;
|
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
|
|
@ -149,71 +146,6 @@ impl SelectedEdges {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a viewport relative bounding box overlay with no transform handles
|
|
||||||
pub fn add_bounding_box(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|
||||||
let path = vec![generate_uuid()];
|
|
||||||
|
|
||||||
let operation = Operation::AddRect {
|
|
||||||
path: path.clone(),
|
|
||||||
transform: DAffine2::ZERO.to_cols_array(),
|
|
||||||
style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None),
|
|
||||||
insert_index: -1,
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the location of a bounding box with no handles
|
|
||||||
pub fn update_bounding_box(pos1: DVec2, pos2: DVec2, layer: &Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
|
|
||||||
if let Some(path) = layer.as_ref().cloned() {
|
|
||||||
let transform = transform_from_box(pos1, pos2, DAffine2::IDENTITY).to_cols_array();
|
|
||||||
let operation = Operation::SetLayerTransformInViewport { path, transform };
|
|
||||||
responses.add_front(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the bounding box overlay with no transform handles
|
|
||||||
pub fn remove_bounding_box(layer_path: Option<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
|
|
||||||
if let Some(path) = layer_path {
|
|
||||||
let operation = Operation::DeleteLayer { path };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add the transform handle overlay
|
|
||||||
fn add_transform_handles(responses: &mut VecDeque<Message>) -> [Vec<LayerId>; 8] {
|
|
||||||
const EMPTY_VEC: Vec<LayerId> = Vec::new();
|
|
||||||
let mut transform_handle_paths = [EMPTY_VEC; 8];
|
|
||||||
|
|
||||||
for item in &mut transform_handle_paths {
|
|
||||||
let current_path = vec![generate_uuid()];
|
|
||||||
|
|
||||||
let operation = Operation::AddRect {
|
|
||||||
path: current_path.clone(),
|
|
||||||
transform: DAffine2::ZERO.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()));
|
|
||||||
|
|
||||||
*item = current_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
transform_handle_paths
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a bounding box to a rounded transform (with translation and scale)
|
|
||||||
pub fn transform_from_box(pos1: DVec2, pos2: DVec2, transform: DAffine2) -> DAffine2 {
|
|
||||||
let inverse = transform.inverse();
|
|
||||||
transform
|
|
||||||
* DAffine2::from_scale_angle_translation(
|
|
||||||
inverse.transform_vector2(transform.transform_vector2(pos2 - pos1).round()),
|
|
||||||
0.,
|
|
||||||
inverse.transform_point2(transform.transform_point2(pos1).round() - DVec2::splat(0.5)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Aligns the mouse position to the closest axis
|
/// Aligns the mouse position to the closest axis
|
||||||
pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2 {
|
pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2 {
|
||||||
if axis_align {
|
if axis_align {
|
||||||
|
|
@ -229,27 +161,17 @@ pub fn axis_align_drag(axis_align: bool, position: DVec2, start: DVec2) -> DVec2
|
||||||
|
|
||||||
/// Contains info on the overlays for the bounding box and transform handles
|
/// Contains info on the overlays for the bounding box and transform handles
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct BoundingBoxOverlays {
|
pub struct BoundingBoxManager {
|
||||||
pub bounding_box: Vec<LayerId>,
|
|
||||||
pub transform_handles: [Vec<LayerId>; 8],
|
|
||||||
pub bounds: [DVec2; 2],
|
pub bounds: [DVec2; 2],
|
||||||
pub transform: DAffine2,
|
pub transform: DAffine2,
|
||||||
|
pub original_bound_transform: DAffine2,
|
||||||
pub selected_edges: Option<SelectedEdges>,
|
pub selected_edges: Option<SelectedEdges>,
|
||||||
pub original_transforms: OriginalTransforms,
|
pub original_transforms: OriginalTransforms,
|
||||||
pub opposite_pivot: DVec2,
|
pub opposite_pivot: DVec2,
|
||||||
pub center_of_transformation: DVec2,
|
pub center_of_transformation: DVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BoundingBoxOverlays {
|
impl BoundingBoxManager {
|
||||||
#[must_use]
|
|
||||||
pub fn new(responses: &mut VecDeque<Message>) -> Self {
|
|
||||||
Self {
|
|
||||||
bounding_box: add_bounding_box(responses),
|
|
||||||
transform_handles: add_transform_handles(responses),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculates the transformed handle positions based on the bounding box and the transform
|
/// Calculates the transformed handle positions based on the bounding box and the transform
|
||||||
pub fn evaluate_transform_handle_positions(&self) -> [DVec2; 8] {
|
pub fn evaluate_transform_handle_positions(&self) -> [DVec2; 8] {
|
||||||
let (left, top): (f64, f64) = self.bounds[0].into();
|
let (left, top): (f64, f64) = self.bounds[0].into();
|
||||||
|
|
@ -267,20 +189,11 @@ impl BoundingBoxOverlays {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the position of the bounding box and transform handles
|
/// Update the position of the bounding box and transform handles
|
||||||
pub fn transform(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn render_overlays(&mut self, overlay_context: &mut OverlayContext) {
|
||||||
let transform = transform_from_box(self.bounds[0], self.bounds[1], self.transform).to_cols_array();
|
overlay_context.quad(self.transform * Quad::from_box(self.bounds));
|
||||||
let path = self.bounding_box.clone();
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path, transform }.into()));
|
|
||||||
|
|
||||||
// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function
|
for position in self.evaluate_transform_handle_positions() {
|
||||||
const BIAS: f64 = 0.0001;
|
overlay_context.square(position, false);
|
||||||
|
|
||||||
for (position, path) in self.evaluate_transform_handle_positions().into_iter().zip(&self.transform_handles) {
|
|
||||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
|
||||||
let translation = (position - (scale / 2.) - 0.5 + BIAS).round();
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(scale, 0., translation).to_cols_array();
|
|
||||||
let path = path.clone();
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path, transform }.into()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,14 +268,4 @@ impl BoundingBoxOverlays {
|
||||||
MouseCursorIcon::Default
|
MouseCursorIcon::Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the overlays
|
|
||||||
pub fn delete(self, responses: &mut VecDeque<Message>) {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: self.bounding_box }.into()));
|
|
||||||
responses.extend(
|
|
||||||
self.transform_handles
|
|
||||||
.iter()
|
|
||||||
.map(|path| DocumentMessage::Overlays(Operation::DeleteLayer { path: path.clone() }.into()).into()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use super::common_functionality::overlay_renderer::OverlayRenderer;
|
|
||||||
use super::common_functionality::shape_editor::ShapeState;
|
use super::common_functionality::shape_editor::ShapeState;
|
||||||
use super::utility_types::{tool_message_to_tool_type, ToolActionHandlerData, ToolFsmState};
|
use super::utility_types::{tool_message_to_tool_type, ToolActionHandlerData, ToolFsmState};
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
|
|
@ -15,7 +14,6 @@ use graphene_core::raster::color::Color;
|
||||||
pub struct ToolMessageHandler {
|
pub struct ToolMessageHandler {
|
||||||
pub tool_state: ToolFsmState,
|
pub tool_state: ToolFsmState,
|
||||||
pub transform_layer_handler: TransformLayerMessageHandler,
|
pub transform_layer_handler: TransformLayerMessageHandler,
|
||||||
pub shape_overlay: OverlayRenderer,
|
|
||||||
pub shape_editor: ShapeState,
|
pub shape_editor: ShapeState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,7 +90,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||||
global_tool_data: &self.tool_state.document_tool_data,
|
global_tool_data: &self.tool_state.document_tool_data,
|
||||||
input,
|
input,
|
||||||
render_data: &render_data,
|
render_data: &render_data,
|
||||||
shape_overlay: &mut self.shape_overlay,
|
|
||||||
shape_editor: &mut self.shape_editor,
|
shape_editor: &mut self.shape_editor,
|
||||||
node_graph,
|
node_graph,
|
||||||
};
|
};
|
||||||
|
|
@ -175,7 +172,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||||
global_tool_data: &self.tool_state.document_tool_data,
|
global_tool_data: &self.tool_state.document_tool_data,
|
||||||
input,
|
input,
|
||||||
render_data: &render_data,
|
render_data: &render_data,
|
||||||
shape_overlay: &mut self.shape_overlay,
|
|
||||||
shape_editor: &mut self.shape_editor,
|
shape_editor: &mut self.shape_editor,
|
||||||
node_graph,
|
node_graph,
|
||||||
};
|
};
|
||||||
|
|
@ -246,7 +242,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||||
global_tool_data: &self.tool_state.document_tool_data,
|
global_tool_data: &self.tool_state.document_tool_data,
|
||||||
input,
|
input,
|
||||||
render_data: &render_data,
|
render_data: &render_data,
|
||||||
shape_overlay: &mut self.shape_overlay,
|
|
||||||
shape_editor: &mut self.shape_editor,
|
shape_editor: &mut self.shape_editor,
|
||||||
node_graph,
|
node_graph,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
||||||
use document_legacy::layers::RenderData;
|
use document_legacy::layers::RenderData;
|
||||||
|
|
||||||
use glam::{IVec2, Vec2Swizzles};
|
use glam::{IVec2, Vec2Swizzles};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -24,6 +25,8 @@ pub enum ArtboardToolMessage {
|
||||||
Abort,
|
Abort,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
|
|
@ -77,6 +80,7 @@ impl ToolTransition for ArtboardTool {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()),
|
document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()),
|
||||||
tool_abort: Some(ArtboardToolMessage::Abort.into()),
|
tool_abort: Some(ArtboardToolMessage::Abort.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| ArtboardToolMessage::Overlays(overlay_context).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +97,7 @@ enum ArtboardToolFsmState {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct ArtboardToolData {
|
struct ArtboardToolData {
|
||||||
bounding_box_overlays: Option<BoundingBoxOverlays>,
|
bounding_box_manager: Option<BoundingBoxManager>,
|
||||||
selected_artboard: Option<LayerNodeIdentifier>,
|
selected_artboard: Option<LayerNodeIdentifier>,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
|
|
@ -102,28 +106,8 @@ struct ArtboardToolData {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArtboardToolData {
|
impl ArtboardToolData {
|
||||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
|
||||||
let current_artboard = self.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer));
|
|
||||||
match (current_artboard, self.bounding_box_overlays.take()) {
|
|
||||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
|
||||||
(Some(bounds), paths) => {
|
|
||||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
|
||||||
|
|
||||||
bounding_box_overlays.bounds = bounds;
|
|
||||||
bounding_box_overlays.transform = document.metadata().document_to_viewport;
|
|
||||||
|
|
||||||
bounding_box_overlays.transform(responses);
|
|
||||||
|
|
||||||
self.bounding_box_overlays = Some(bounding_box_overlays);
|
|
||||||
|
|
||||||
responses.add(OverlaysMessage::Rerender);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {
|
fn check_dragging_bounds(&mut self, cursor: DVec2) -> Option<(bool, bool, bool, bool)> {
|
||||||
let bounding_box = self.bounding_box_overlays.as_mut()?;
|
let bounding_box = self.bounding_box_manager.as_mut()?;
|
||||||
let edges = bounding_box.check_selected_edges(cursor)?;
|
let edges = bounding_box.check_selected_edges(cursor)?;
|
||||||
let (top, bottom, left, right) = edges;
|
let (top, bottom, left, right) = edges;
|
||||||
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
let selected_edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||||
|
|
@ -142,7 +126,7 @@ impl ArtboardToolData {
|
||||||
.start_snap(document, input, document.bounding_boxes(None, Some(artboard.to_node()), render_data), snap_x, snap_y);
|
.start_snap(document, input, document.bounding_boxes(None, Some(artboard.to_node()), render_data), snap_x, snap_y);
|
||||||
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
self.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||||
|
|
||||||
if let Some(bounds) = &mut self.bounding_box_overlays {
|
if let Some(bounds) = &mut self.bounding_box_manager {
|
||||||
bounds.center_of_transformation = (bounds.bounds[0] + bounds.bounds[1]) / 2.;
|
bounds.center_of_transformation = (bounds.bounds[0] + bounds.bounds[1]) / 2.;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,7 +158,7 @@ impl ArtboardToolData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
|
fn resize_artboard(&mut self, responses: &mut VecDeque<Message>, document: &DocumentMessageHandler, mouse_position: DVec2, from_center: bool, constrain_square: bool) {
|
||||||
let Some(bounds) = &self.bounding_box_overlays else {
|
let Some(bounds) = &self.bounding_box_manager else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(movement) = &bounds.selected_edges else {
|
let Some(movement) = &bounds.selected_edges else {
|
||||||
|
|
@ -206,11 +190,20 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
|
(state, ArtboardToolMessage::Overlays(mut overlay_context)) if state != ArtboardToolFsmState::Drawing => {
|
||||||
tool_data.refresh_overlays(document, responses);
|
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
|
||||||
|
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||||
|
bounding_box_manager.bounds = bounds;
|
||||||
|
bounding_box_manager.transform = document.metadata().document_to_viewport;
|
||||||
|
|
||||||
|
bounding_box_manager.render_overlays(&mut overlay_context);
|
||||||
|
} else {
|
||||||
|
tool_data.bounding_box_manager.take();
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
|
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
|
||||||
tool_data.drag_start = input.mouse.position;
|
tool_data.drag_start = input.mouse.position;
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.drag_current = input.mouse.position;
|
||||||
|
|
@ -235,7 +228,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::ResizingBounds
|
ArtboardToolFsmState::ResizingBounds
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
|
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, .. }) => {
|
||||||
if let Some(bounds) = &tool_data.bounding_box_overlays {
|
if let Some(bounds) = &tool_data.bounding_box_manager {
|
||||||
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
|
let axis_align = input.keyboard.get(constrain_axis_or_aspect as usize);
|
||||||
|
|
||||||
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
let mouse_position = axis_align_drag(axis_align, input.mouse.position, tool_data.drag_start);
|
||||||
|
|
@ -314,7 +307,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::Drawing
|
ArtboardToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
|
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerMove { .. }) => {
|
||||||
let cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
|
let cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, false));
|
||||||
|
|
||||||
if tool_data.cursor != cursor {
|
if tool_data.cursor != cursor {
|
||||||
tool_data.cursor = cursor;
|
tool_data.cursor = cursor;
|
||||||
|
|
@ -326,7 +319,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,20 +328,21 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
|
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready
|
ArtboardToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
}
|
}
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready
|
ArtboardToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -362,7 +356,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::Ready
|
ArtboardToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
id: tool_data.selected_artboard.unwrap().to_node(),
|
id: tool_data.selected_artboard.unwrap().to_node(),
|
||||||
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
||||||
|
|
@ -373,16 +367,13 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
ArtboardToolFsmState::Ready
|
ArtboardToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, ArtboardToolMessage::Abort) => {
|
(_, ArtboardToolMessage::Abort) => {
|
||||||
if let Some(bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
|
||||||
bounding_box_overlays.delete(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register properties when switching back to other tools
|
// Register properties when switching back to other tools
|
||||||
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
responses.add(PropertiesPanelMessage::SetActiveLayers {
|
||||||
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
|
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
|
||||||
});
|
});
|
||||||
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
ArtboardToolFsmState::Ready
|
ArtboardToolFsmState::Ready
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::{COLOR_ACCENT, LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD};
|
use crate::consts::{LINE_ROTATE_SNAP_ANGLE, MANIPULATOR_GROUP_MARKER_SIZE, SELECTION_THRESHOLD};
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_gradient;
|
use crate::messages::tool::common_functionality::graph_modification_utils::get_gradient;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::layers::style::{Fill, Gradient, GradientType, PathStyle, RenderData, Stroke};
|
use document_legacy::layers::style::{Fill, Gradient, GradientType, RenderData};
|
||||||
use document_legacy::LayerId;
|
|
||||||
use document_legacy::Operation;
|
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -31,6 +30,8 @@ pub enum GradientToolMessage {
|
||||||
Abort,
|
Abort,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
DeleteStop,
|
DeleteStop,
|
||||||
|
|
@ -124,99 +125,6 @@ fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessa
|
||||||
multiplied * bound_transform
|
multiplied * bound_transform
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains info on the overlays for a single gradient
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct GradientOverlay {
|
|
||||||
pub handles: [Vec<LayerId>; 2],
|
|
||||||
pub line: Vec<LayerId>,
|
|
||||||
pub steps: Vec<Vec<LayerId>>,
|
|
||||||
layer: LayerNodeIdentifier,
|
|
||||||
transform: DAffine2,
|
|
||||||
gradient: Gradient,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GradientOverlay {
|
|
||||||
fn generate_overlay_handle(translation: DVec2, responses: &mut VecDeque<Message>, selected: bool) -> Vec<LayerId> {
|
|
||||||
let path = vec![generate_uuid()];
|
|
||||||
|
|
||||||
let size = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
|
||||||
|
|
||||||
let fill = if selected { Fill::solid(COLOR_ACCENT) } else { Fill::solid(Color::WHITE) };
|
|
||||||
|
|
||||||
let operation = Operation::AddEllipse {
|
|
||||||
path: path.clone(),
|
|
||||||
transform: DAffine2::from_scale_angle_translation(size, 0., translation - size / 2.).to_cols_array(),
|
|
||||||
style: PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), fill),
|
|
||||||
insert_index: -1,
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
fn generate_overlay_line(start: DVec2, end: DVec2, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|
||||||
let path = vec![generate_uuid()];
|
|
||||||
|
|
||||||
let line_vector = end - start;
|
|
||||||
let scale = DVec2::splat(line_vector.length());
|
|
||||||
let angle = -line_vector.angle_between(DVec2::X);
|
|
||||||
let translation = start;
|
|
||||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
|
||||||
|
|
||||||
let operation = Operation::AddLine {
|
|
||||||
path: path.clone(),
|
|
||||||
transform,
|
|
||||||
style: PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None),
|
|
||||||
insert_index: -1,
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(gradient: Gradient, dragging: Option<GradientDragTarget>, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Self {
|
|
||||||
let transform = gradient_space_transform(layer, document);
|
|
||||||
let Gradient { start, end, positions, .. } = &gradient;
|
|
||||||
let [start, end] = [transform.transform_point2(*start), transform.transform_point2(*end)];
|
|
||||||
|
|
||||||
let line = Self::generate_overlay_line(start, end, responses);
|
|
||||||
let handles = [
|
|
||||||
Self::generate_overlay_handle(start, responses, dragging == Some(GradientDragTarget::Start)),
|
|
||||||
Self::generate_overlay_handle(end, responses, dragging == Some(GradientDragTarget::End)),
|
|
||||||
];
|
|
||||||
|
|
||||||
let not_at_end = |(_, x): &(_, f64)| x.abs() > f64::EPSILON * 1000. && (1. - x).abs() > f64::EPSILON * 1000.;
|
|
||||||
let create_step = |(index, pos)| Self::generate_overlay_handle(start.lerp(end, pos), responses, dragging == Some(GradientDragTarget::Step(index)));
|
|
||||||
let steps = positions.iter().map(|(pos, _)| *pos).enumerate().filter(not_at_end).map(create_step).collect();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
handles,
|
|
||||||
steps,
|
|
||||||
line,
|
|
||||||
layer,
|
|
||||||
transform,
|
|
||||||
gradient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_overlays(self, responses: &mut VecDeque<Message>) {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: self.line }.into()));
|
|
||||||
let [start, end] = self.handles;
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: start }.into()));
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: end }.into()));
|
|
||||||
for step in self.steps {
|
|
||||||
responses.add(DocumentMessage::Overlays(Operation::DeleteLayer { path: step }.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate_gradient_start(&self) -> DVec2 {
|
|
||||||
self.transform.transform_point2(self.gradient.start)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate_gradient_end(&self) -> DVec2 {
|
|
||||||
self.transform.transform_point2(self.gradient.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
|
||||||
pub enum GradientDragTarget {
|
pub enum GradientDragTarget {
|
||||||
Start,
|
Start,
|
||||||
|
|
@ -346,6 +254,7 @@ impl ToolTransition for GradientTool {
|
||||||
document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()),
|
document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()),
|
||||||
tool_abort: Some(GradientToolMessage::Abort.into()),
|
tool_abort: Some(GradientToolMessage::Abort.into()),
|
||||||
selection_changed: Some(GradientToolMessage::DocumentIsDirty.into()),
|
selection_changed: Some(GradientToolMessage::DocumentIsDirty.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| GradientToolMessage::Overlays(overlay_context).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -353,7 +262,6 @@ impl ToolTransition for GradientTool {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct GradientToolData {
|
struct GradientToolData {
|
||||||
gradient_overlays: Vec<GradientOverlay>,
|
|
||||||
selected_gradient: Option<SelectedGradient>,
|
selected_gradient: Option<SelectedGradient>,
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
drag_start: DVec2,
|
drag_start: DVec2,
|
||||||
|
|
@ -383,21 +291,33 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, GradientToolMessage::DocumentIsDirty) => {
|
(_, GradientToolMessage::DocumentIsDirty) => {
|
||||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
|
||||||
overlay.delete_overlays(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self != GradientToolFsmState::Drawing {
|
if self != GradientToolFsmState::Drawing {
|
||||||
SelectedGradient::update(&mut tool_data.selected_gradient, document, responses);
|
SelectedGradient::update(&mut tool_data.selected_gradient, document, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
|
||||||
|
let selected = tool_data.selected_gradient.as_ref();
|
||||||
|
|
||||||
for layer in document.document_legacy.selected_visible_layers() {
|
for layer in document.document_legacy.selected_visible_layers() {
|
||||||
if let Some(gradient) = get_gradient(layer, &document.document_legacy) {
|
let Some(gradient) = get_gradient(layer, &document.document_legacy) else { continue };
|
||||||
let dragging = tool_data
|
let transform = gradient_space_transform(layer, document);
|
||||||
.selected_gradient
|
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
|
||||||
.as_ref()
|
|
||||||
.and_then(|selected| if selected.layer == layer { Some(selected.dragging) } else { None });
|
let Gradient { start, end, positions, .. } = gradient;
|
||||||
tool_data.gradient_overlays.push(GradientOverlay::new(gradient, dragging, layer, document, responses))
|
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
||||||
|
|
||||||
|
overlay_context.line(start, end);
|
||||||
|
overlay_context.handle(start, dragging == Some(GradientDragTarget::Start));
|
||||||
|
overlay_context.handle(end, dragging == Some(GradientDragTarget::End));
|
||||||
|
|
||||||
|
for (index, (position, _)) in positions.into_iter().enumerate() {
|
||||||
|
if position.abs() < f64::EPSILON * 1000. || (1. - position).abs() < f64::EPSILON * 1000. {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
overlay_context.handle(start.lerp(end, position), dragging == Some(GradientDragTarget::Step(index)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -450,22 +370,23 @@ impl Fsm for GradientToolFsmState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, GradientToolMessage::InsertStop) => {
|
(_, GradientToolMessage::InsertStop) => {
|
||||||
for overlay in &tool_data.gradient_overlays {
|
for layer in document.document_legacy.selected_visible_layers() {
|
||||||
|
let Some(mut gradient) = get_gradient(layer, &document.document_legacy) else { continue };
|
||||||
|
let transform = gradient_space_transform(layer, document);
|
||||||
|
|
||||||
let mouse = input.mouse.position;
|
let mouse = input.mouse.position;
|
||||||
let (start, end) = (overlay.evaluate_gradient_start(), overlay.evaluate_gradient_end());
|
let (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));
|
||||||
|
|
||||||
// Compute the distance from the mouse to the gradient line in viewport space
|
// Compute the distance from the mouse to the gradient line in viewport space
|
||||||
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
|
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
|
||||||
|
|
||||||
// If click is on the line then insert point
|
// If click is on the line then insert point
|
||||||
if distance < SELECTION_THRESHOLD {
|
if distance < (SELECTION_THRESHOLD * 2.) {
|
||||||
let mut gradient = overlay.gradient.clone();
|
|
||||||
|
|
||||||
// Try and insert the new stop
|
// Try and insert the new stop
|
||||||
if let Some(index) = gradient.insert_stop(mouse, overlay.transform) {
|
if let Some(index) = gradient.insert_stop(mouse, transform) {
|
||||||
document.backup_nonmut(responses);
|
document.backup_nonmut(responses);
|
||||||
|
|
||||||
let mut selected_gradient = SelectedGradient::new(gradient, overlay.layer, document);
|
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
|
||||||
|
|
||||||
// Select the new point
|
// Select the new point
|
||||||
selected_gradient.dragging = GradientDragTarget::Step(index);
|
selected_gradient.dragging = GradientDragTarget::Step(index);
|
||||||
|
|
@ -487,36 +408,37 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
let mouse = input.mouse.position;
|
let mouse = input.mouse.position;
|
||||||
tool_data.drag_start = mouse;
|
tool_data.drag_start = mouse;
|
||||||
let tolerance = MANIPULATOR_GROUP_MARKER_SIZE.powi(2);
|
let tolerance = (MANIPULATOR_GROUP_MARKER_SIZE * 2.).powi(2);
|
||||||
|
|
||||||
let mut dragging = false;
|
let mut dragging = false;
|
||||||
for overlay in &tool_data.gradient_overlays {
|
for layer in document.document_legacy.selected_visible_layers() {
|
||||||
|
let Some(gradient) = get_gradient(layer, &document.document_legacy) else { continue };
|
||||||
|
let transform = gradient_space_transform(layer, document);
|
||||||
|
|
||||||
// Check for dragging step
|
// Check for dragging step
|
||||||
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
|
for (index, (pos, _)) in gradient.positions.iter().enumerate() {
|
||||||
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
|
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
|
||||||
if pos.distance_squared(mouse) < tolerance {
|
if pos.distance_squared(mouse) < tolerance {
|
||||||
dragging = true;
|
dragging = true;
|
||||||
tool_data.selected_gradient = Some(SelectedGradient {
|
tool_data.selected_gradient = Some(SelectedGradient {
|
||||||
layer: overlay.layer,
|
layer,
|
||||||
transform: overlay.transform,
|
transform,
|
||||||
gradient: overlay.gradient.clone(),
|
gradient: gradient.clone(),
|
||||||
dragging: GradientDragTarget::Step(index),
|
dragging: GradientDragTarget::Step(index),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check dragging start or end handle
|
// Check dragging start or end handle
|
||||||
for (pos, dragging_target) in [
|
for (pos, dragging_target) in [(gradient.start, GradientDragTarget::Start), (gradient.end, GradientDragTarget::End)] {
|
||||||
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
|
let pos = transform.transform_point2(pos);
|
||||||
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
|
|
||||||
] {
|
|
||||||
if pos.distance_squared(mouse) < tolerance {
|
if pos.distance_squared(mouse) < tolerance {
|
||||||
dragging = true;
|
dragging = true;
|
||||||
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
start_snap(&mut tool_data.snap_manager, document, input, render_data);
|
||||||
tool_data.selected_gradient = Some(SelectedGradient {
|
tool_data.selected_gradient = Some(SelectedGradient {
|
||||||
layer: overlay.layer,
|
layer,
|
||||||
transform: overlay.transform,
|
transform,
|
||||||
gradient: overlay.gradient.clone(),
|
gradient: gradient.clone(),
|
||||||
dragging: dragging_target,
|
dragging: dragging_target,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -530,20 +452,10 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
// Apply the gradient to the selected layer
|
// Apply the gradient to the selected layer
|
||||||
if let Some(layer) = selected_layer {
|
if let Some(layer) = selected_layer {
|
||||||
// let is_bitmap = document
|
|
||||||
// .document_legacy
|
|
||||||
// .layer(&layer)
|
|
||||||
// .ok()
|
|
||||||
// .and_then(|layer| layer.as_layer().ok())
|
|
||||||
// .map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
|
|
||||||
// if is_bitmap {
|
|
||||||
// return self;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if !document.metadata().selected_layers_contains(layer) {
|
if !document.metadata().selected_layers_contains(layer) {
|
||||||
let replacement_selected_layers = vec![layer.to_path()];
|
let nodes = vec![layer.to_node()];
|
||||||
|
|
||||||
responses.add(DocumentMessage::SetSelectedLayers { replacement_selected_layers });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
@ -592,10 +504,8 @@ impl Fsm for GradientToolFsmState {
|
||||||
|
|
||||||
(_, GradientToolMessage::Abort) => {
|
(_, GradientToolMessage::Abort) => {
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
while let Some(overlay) = tool_data.gradient_overlays.pop() {
|
|
||||||
overlay.delete_overlays(responses);
|
|
||||||
}
|
|
||||||
GradientToolFsmState::Ready
|
GradientToolFsmState::Ready
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
|
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
|
||||||
use crate::messages::tool::common_functionality::path_outline::PathOutline;
|
|
||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
|
|
@ -96,7 +95,6 @@ enum ImaginateToolFsmState {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct ImaginateToolData {
|
struct ImaginateToolData {
|
||||||
data: Resize,
|
data: Resize,
|
||||||
path_outlines: PathOutline,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for ImaginateToolFsmState {
|
impl Fsm for ImaginateToolFsmState {
|
||||||
|
|
@ -118,14 +116,11 @@ impl Fsm for ImaginateToolFsmState {
|
||||||
};
|
};
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => {
|
(_, ImaginateToolMessage::DocumentIsDirty | ImaginateToolMessage::SelectionChanged) => {
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
//tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses, render_data);
|
//tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses, render_data);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
|
|
||||||
shape_data.start(responses, document, input, render_data);
|
shape_data.start(responses, document, input, render_data);
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_data.layer = Some(LayerNodeIdentifier::new(generate_uuid(), document.network()));
|
shape_data.layer = Some(LayerNodeIdentifier::new(generate_uuid(), document.network()));
|
||||||
|
|
@ -193,15 +188,10 @@ impl Fsm for ImaginateToolFsmState {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
|
|
||||||
shape_data.cleanup(responses);
|
shape_data.cleanup(responses);
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
|
|
||||||
ImaginateToolFsmState::Ready
|
|
||||||
}
|
|
||||||
(_, ImaginateToolMessage::Abort) => {
|
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
|
|
||||||
ImaginateToolFsmState::Ready
|
ImaginateToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
(_, ImaginateToolMessage::Abort) => ImaginateToolFsmState::Ready,
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
use crate::consts::{DRAG_THRESHOLD, SELECTION_THRESHOLD, SELECTION_TOLERANCE};
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_from_id, get_mirror_handles, get_subpaths};
|
||||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::{add_bounding_box, remove_bounding_box, update_bounding_box};
|
|
||||||
|
|
||||||
use document_legacy::document::Document;
|
use document_legacy::document::Document;
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::LayerId;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||||
|
|
||||||
|
use std::vec;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PathTool {
|
pub struct PathTool {
|
||||||
fsm_state: PathToolFsmState,
|
fsm_state: PathToolFsmState,
|
||||||
|
|
@ -27,7 +27,7 @@ pub enum PathToolMessage {
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
Abort,
|
Abort,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
DocumentIsDirty,
|
Overlays(OverlayContext),
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
SelectionChanged,
|
SelectionChanged,
|
||||||
|
|
||||||
|
|
@ -182,9 +182,9 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||||
impl ToolTransition for PathTool {
|
impl ToolTransition for PathTool {
|
||||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
document_dirty: Some(PathToolMessage::DocumentIsDirty.into()),
|
|
||||||
tool_abort: Some(PathToolMessage::Abort.into()),
|
tool_abort: Some(PathToolMessage::Abort.into()),
|
||||||
selection_changed: Some(PathToolMessage::SelectionChanged.into()),
|
selection_changed: Some(PathToolMessage::SelectionChanged.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| PathToolMessage::Overlays(overlay_context).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,34 +205,18 @@ struct PathToolData {
|
||||||
previous_mouse_position: DVec2,
|
previous_mouse_position: DVec2,
|
||||||
alt_debounce: bool,
|
alt_debounce: bool,
|
||||||
opposing_handle_lengths: Option<OpposingHandleLengths>,
|
opposing_handle_lengths: Option<OpposingHandleLengths>,
|
||||||
drag_box_overlay_layer: Option<Vec<LayerId>>,
|
|
||||||
/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).
|
/// Describes information about the selected point(s), if any, across one or multiple shapes and manipulator point types (anchor or handle).
|
||||||
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
|
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
|
||||||
selection_status: SelectionStatus,
|
selection_status: SelectionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PathToolData {
|
impl PathToolData {
|
||||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
|
|
||||||
// Set the previously selected layers to invisible
|
|
||||||
for layer in document.metadata().all_layers() {
|
|
||||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the new overlays
|
|
||||||
for &layer in shape_editor.selected_shape_state.keys() {
|
|
||||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.opposing_handle_lengths = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_down(
|
fn mouse_down(
|
||||||
&mut self,
|
&mut self,
|
||||||
shift: bool,
|
shift: bool,
|
||||||
shape_editor: &mut ShapeState,
|
shape_editor: &mut ShapeState,
|
||||||
document: &DocumentMessageHandler,
|
document: &DocumentMessageHandler,
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
shape_overlay: &mut OverlayRenderer,
|
|
||||||
responses: &mut VecDeque<Message>,
|
responses: &mut VecDeque<Message>,
|
||||||
) -> PathToolFsmState {
|
) -> PathToolFsmState {
|
||||||
self.opposing_handle_lengths = None;
|
self.opposing_handle_lengths = None;
|
||||||
|
|
@ -241,7 +225,7 @@ impl PathToolData {
|
||||||
// Select the first point within the threshold (in pixels)
|
// Select the first point within the threshold (in pixels)
|
||||||
if let Some(selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift) {
|
if let Some(selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift) {
|
||||||
self.start_dragging_point(selected_points, input, document, responses);
|
self.start_dragging_point(selected_points, input, document, responses);
|
||||||
self.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
PathToolFsmState::Dragging
|
PathToolFsmState::Dragging
|
||||||
}
|
}
|
||||||
|
|
@ -261,7 +245,6 @@ impl PathToolData {
|
||||||
// Start drawing a box
|
// Start drawing a box
|
||||||
self.drag_start_pos = input.mouse.position;
|
self.drag_start_pos = input.mouse.position;
|
||||||
self.previous_mouse_position = input.mouse.position;
|
self.previous_mouse_position = input.mouse.position;
|
||||||
self.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
|
||||||
|
|
||||||
PathToolFsmState::DrawingBox
|
PathToolFsmState::DrawingBox
|
||||||
}
|
}
|
||||||
|
|
@ -328,13 +311,7 @@ impl Fsm for PathToolFsmState {
|
||||||
type ToolOptions = ();
|
type ToolOptions = ();
|
||||||
|
|
||||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
|
||||||
let ToolActionHandlerData {
|
let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data;
|
||||||
document,
|
|
||||||
input,
|
|
||||||
shape_editor,
|
|
||||||
shape_overlay,
|
|
||||||
..
|
|
||||||
} = tool_action_data;
|
|
||||||
let ToolMessage::Path(event) = event else {
|
let ToolMessage::Path(event) = event else {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
|
|
@ -345,17 +322,17 @@ impl Fsm for PathToolFsmState {
|
||||||
let target_layers = document.metadata().selected_layers().collect();
|
let target_layers = document.metadata().selected_layers().collect();
|
||||||
shape_editor.set_selected_layers(target_layers);
|
shape_editor.set_selected_layers(target_layers);
|
||||||
|
|
||||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
// This can happen in any state (which is why we return self)
|
// This can happen in any state (which is why we return self)
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::DocumentIsDirty) => {
|
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||||
// When the document has moved / needs to be redraw, re-render the overlays
|
path_overlays(document, shape_editor, &mut overlay_context);
|
||||||
// TODO the overlay system should probably receive this message instead of the tool
|
|
||||||
for layer in document.metadata().selected_layers() {
|
if self == Self::DrawingBox {
|
||||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position]))
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
|
|
@ -366,11 +343,11 @@ impl Fsm for PathToolFsmState {
|
||||||
(_, PathToolMessage::DragStart { add_to_selection }) => {
|
(_, PathToolMessage::DragStart { add_to_selection }) => {
|
||||||
let shift = input.keyboard.get(add_to_selection as usize);
|
let shift = input.keyboard.get(add_to_selection as usize);
|
||||||
|
|
||||||
tool_data.mouse_down(shift, shape_editor, document, input, shape_overlay, responses)
|
tool_data.mouse_down(shift, shape_editor, document, input, responses)
|
||||||
}
|
}
|
||||||
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
|
(PathToolFsmState::DrawingBox, PathToolMessage::PointerMove { .. }) => {
|
||||||
tool_data.previous_mouse_position = input.mouse.position;
|
tool_data.previous_mouse_position = input.mouse.position;
|
||||||
update_bounding_box(tool_data.drag_start_pos, input.mouse.position, &tool_data.drag_box_overlay_layer, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
PathToolFsmState::DrawingBox
|
PathToolFsmState::DrawingBox
|
||||||
}
|
}
|
||||||
|
|
@ -389,9 +366,8 @@ impl Fsm for PathToolFsmState {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||||
} else {
|
} else {
|
||||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
}
|
||||||
};
|
responses.add(OverlaysMessage::Draw);
|
||||||
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
|
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -404,11 +380,10 @@ impl Fsm for PathToolFsmState {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||||
} else {
|
} else {
|
||||||
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
shape_editor.select_all_in_quad(&document.document_legacy, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
|
||||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
}
|
||||||
};
|
responses.add(OverlaysMessage::Draw);
|
||||||
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
|
|
||||||
|
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -426,7 +401,7 @@ impl Fsm for PathToolFsmState {
|
||||||
if clicked_selected {
|
if clicked_selected {
|
||||||
shape_editor.deselect_all();
|
shape_editor.deselect_all();
|
||||||
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
|
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
|
||||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,9 +430,7 @@ impl Fsm for PathToolFsmState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::Abort) => {
|
(_, PathToolMessage::Abort) => {
|
||||||
// TODO Tell overlay manager to remove the overlays
|
responses.add(OverlaysMessage::Draw);
|
||||||
shape_overlay.clear_all_overlays(responses);
|
|
||||||
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
|
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
|
@ -469,7 +442,7 @@ impl Fsm for PathToolFsmState {
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectAllPoints) => {
|
(_, PathToolMessage::SelectAllPoints) => {
|
||||||
shape_editor.select_all_points(&document.document_legacy);
|
shape_editor.select_all_points(&document.document_legacy);
|
||||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
||||||
|
|
@ -43,13 +45,13 @@ pub enum PenToolMessage {
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
CanvasTransformed,
|
CanvasTransformed,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
DocumentIsDirty,
|
|
||||||
#[remain::unsorted]
|
|
||||||
Abort,
|
Abort,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
SelectionChanged,
|
SelectionChanged,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
WorkingColorChanged,
|
WorkingColorChanged,
|
||||||
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
Confirm,
|
Confirm,
|
||||||
|
|
@ -184,10 +186,11 @@ impl ToolTransition for PenTool {
|
||||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||||
EventToMessageMap {
|
EventToMessageMap {
|
||||||
canvas_transformed: Some(PenToolMessage::CanvasTransformed.into()),
|
canvas_transformed: Some(PenToolMessage::CanvasTransformed.into()),
|
||||||
document_dirty: Some(PenToolMessage::DocumentIsDirty.into()),
|
|
||||||
tool_abort: Some(PenToolMessage::Abort.into()),
|
tool_abort: Some(PenToolMessage::Abort.into()),
|
||||||
selection_changed: Some(PenToolMessage::SelectionChanged.into()),
|
selection_changed: Some(PenToolMessage::SelectionChanged.into()),
|
||||||
working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),
|
working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| PenToolMessage::Overlays(overlay_context).into()),
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -542,7 +545,6 @@ impl Fsm for PenToolFsmState {
|
||||||
input,
|
input,
|
||||||
render_data,
|
render_data,
|
||||||
shape_editor,
|
shape_editor,
|
||||||
shape_overlay,
|
|
||||||
..
|
..
|
||||||
} = tool_action_data;
|
} = tool_action_data;
|
||||||
|
|
||||||
|
|
@ -569,24 +571,13 @@ impl Fsm for PenToolFsmState {
|
||||||
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(None, None, render_data), true, true);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::DocumentIsDirty) => {
|
(_, PenToolMessage::SelectionChanged) => {
|
||||||
// When the document has moved / needs to be redraw, re-render the overlays
|
responses.add(OverlaysMessage::Draw);
|
||||||
// TODO the overlay system should probably receive this message instead of the tool
|
|
||||||
for layer in document.metadata().selected_layers() {
|
|
||||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::SelectionChanged) => {
|
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||||
// Set the previously selected layers to invisible
|
path_overlays(document, shape_editor, &mut overlay_context);
|
||||||
for layer in document.metadata().all_layers() {
|
|
||||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer, false, responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redraw the overlays of the newly selected layers
|
|
||||||
for layer in document.metadata().selected_layers() {
|
|
||||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer, responses);
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::WorkingColorChanged) => {
|
(_, PenToolMessage::WorkingColorChanged) => {
|
||||||
|
|
@ -660,8 +651,7 @@ impl Fsm for PenToolFsmState {
|
||||||
PenToolFsmState::Ready
|
PenToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::Abort) => {
|
(_, PenToolMessage::Abort) => {
|
||||||
// Clean up overlays
|
responses.add(OverlaysMessage::Draw);
|
||||||
shape_overlay.clear_all_overlays(responses);
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
|
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis};
|
||||||
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
use crate::messages::portfolio::document::utility_types::transformation::Selected;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
|
||||||
use crate::messages::tool::common_functionality::path_outline::*;
|
|
||||||
use crate::messages::tool::common_functionality::pivot::Pivot;
|
use crate::messages::tool::common_functionality::pivot::Pivot;
|
||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
|
|
||||||
use document_legacy::document::Document;
|
use document_legacy::document::Document;
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::LayerId;
|
|
||||||
use document_legacy::Operation;
|
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -62,6 +62,8 @@ pub enum SelectToolMessage {
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
SelectionChanged,
|
SelectionChanged,
|
||||||
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
DragStart {
|
DragStart {
|
||||||
|
|
@ -241,6 +243,7 @@ impl ToolTransition for SelectTool {
|
||||||
document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()),
|
document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()),
|
||||||
tool_abort: Some(SelectToolMessage::Abort.into()),
|
tool_abort: Some(SelectToolMessage::Abort.into()),
|
||||||
selection_changed: Some(SelectToolMessage::SelectionChanged.into()),
|
selection_changed: Some(SelectToolMessage::SelectionChanged.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| SelectToolMessage::Overlays(overlay_context).into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,9 +269,7 @@ struct SelectToolData {
|
||||||
select_single_layer: Option<LayerNodeIdentifier>,
|
select_single_layer: Option<LayerNodeIdentifier>,
|
||||||
has_dragged: bool,
|
has_dragged: bool,
|
||||||
not_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
|
not_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
|
||||||
drag_box_overlay_layer: Option<Vec<LayerId>>,
|
bounding_box_manager: Option<BoundingBoxManager>,
|
||||||
path_outlines: PathOutline,
|
|
||||||
bounding_box_overlays: Option<BoundingBoxOverlays>,
|
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
pivot: Pivot,
|
pivot: Pivot,
|
||||||
|
|
@ -387,30 +388,53 @@ impl Fsm for SelectToolFsmState {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, SelectToolMessage::DocumentIsDirty | SelectToolMessage::SelectionChanged) => {
|
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
|
||||||
let selected_layers_count = document.metadata().selected_layers().count();
|
let selected_layers_count = document.metadata().selected_layers().count();
|
||||||
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
tool_data.selected_layers_changed = selected_layers_count != tool_data.selected_layers_count;
|
||||||
tool_data.selected_layers_count = selected_layers_count;
|
tool_data.selected_layers_count = selected_layers_count;
|
||||||
|
|
||||||
tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses);
|
// Outline selected layers
|
||||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses);
|
for layer in document.document_legacy.selected_visible_layers() {
|
||||||
|
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||||
|
}
|
||||||
|
|
||||||
match (document.document_legacy.selected_visible_layers_bounding_box_viewport(), tool_data.bounding_box_overlays.take()) {
|
// Get the layer the user is hovering over
|
||||||
(None, Some(bounding_box_overlays)) => bounding_box_overlays.delete(responses),
|
let click = document.document_legacy.click(input.mouse.position, &document.document_legacy.document_network);
|
||||||
(Some(bounds), paths) => {
|
let not_selected_click = click.filter(|&hovered_layer| !document.metadata().selected_layers_contains(hovered_layer));
|
||||||
let mut bounding_box_overlays = paths.unwrap_or_else(|| BoundingBoxOverlays::new(responses));
|
if let Some(layer) = not_selected_click {
|
||||||
|
overlay_context.outline(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer));
|
||||||
|
}
|
||||||
|
|
||||||
bounding_box_overlays.bounds = bounds;
|
// Update bounds
|
||||||
bounding_box_overlays.transform = DAffine2::IDENTITY;
|
let transform = document.document_legacy.selected_visible_layers().next().map(|layer| document.metadata().transform_to_viewport(layer));
|
||||||
|
let transform = transform.unwrap_or(DAffine2::IDENTITY);
|
||||||
|
let bounds = document
|
||||||
|
.document_legacy
|
||||||
|
.selected_visible_layers()
|
||||||
|
.filter_map(|layer| {
|
||||||
|
document
|
||||||
|
.metadata()
|
||||||
|
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
|
||||||
|
})
|
||||||
|
.reduce(graphene_core::renderer::Quad::combine_bounds);
|
||||||
|
if let Some(bounds) = bounds {
|
||||||
|
let bounding_box_manager = tool_data.bounding_box_manager.get_or_insert(BoundingBoxManager::default());
|
||||||
|
|
||||||
bounding_box_overlays.transform(responses);
|
bounding_box_manager.bounds = bounds;
|
||||||
|
bounding_box_manager.transform = transform;
|
||||||
|
|
||||||
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
|
bounding_box_manager.render_overlays(&mut overlay_context);
|
||||||
}
|
} else {
|
||||||
(_, _) => {}
|
tool_data.bounding_box_manager.take();
|
||||||
};
|
}
|
||||||
|
|
||||||
tool_data.pivot.update_pivot(document, responses);
|
// Update pivot
|
||||||
|
tool_data.pivot.update_pivot(document, &mut overlay_context);
|
||||||
|
|
||||||
|
// Update dragging box
|
||||||
|
if self == Self::DrawingBox {
|
||||||
|
overlay_context.quad(Quad::from_box([tool_data.drag_start, tool_data.drag_current]));
|
||||||
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -426,12 +450,10 @@ impl Fsm for SelectToolFsmState {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready, SelectToolMessage::DragStart { add_to_selection, select_deepest: _ }) => {
|
(SelectToolFsmState::Ready, SelectToolMessage::DragStart { add_to_selection, select_deepest: _ }) => {
|
||||||
tool_data.path_outlines.clear_hovered(responses);
|
|
||||||
|
|
||||||
tool_data.drag_start = input.mouse.position;
|
tool_data.drag_start = input.mouse.position;
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.drag_current = input.mouse.position;
|
||||||
|
|
||||||
let dragging_bounds = tool_data.bounding_box_overlays.as_mut().and_then(|bounding_box| {
|
let dragging_bounds = tool_data.bounding_box_manager.as_mut().and_then(|bounding_box| {
|
||||||
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
let edges = bounding_box.check_selected_edges(input.mouse.position);
|
||||||
|
|
||||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||||
|
|
@ -444,7 +466,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
});
|
});
|
||||||
|
|
||||||
let rotating_bounds = tool_data
|
let rotating_bounds = tool_data
|
||||||
.bounding_box_overlays
|
.bounding_box_manager
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
|
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
@ -479,8 +501,9 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
tool_data.layers_dragging = selected;
|
tool_data.layers_dragging = selected;
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
let document = &document.document_legacy;
|
let document = &document.document_legacy;
|
||||||
|
bounds.original_bound_transform = bounds.transform;
|
||||||
|
|
||||||
tool_data.layers_dragging.retain(|layer| document.document_network.nodes.contains_key(&layer.to_node()));
|
tool_data.layers_dragging.retain(|layer| document.document_network.nodes.contains_key(&layer.to_node()));
|
||||||
let mut selected = Selected::new(
|
let mut selected = Selected::new(
|
||||||
|
|
@ -499,7 +522,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
} else if rotating_bounds {
|
} else if rotating_bounds {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
||||||
let mut selected = Selected::new(
|
let mut selected = Selected::new(
|
||||||
&mut bounds.original_transforms,
|
&mut bounds.original_transforms,
|
||||||
|
|
@ -556,7 +579,6 @@ impl Fsm for SelectToolFsmState {
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
responses.add(DocumentMessage::DeselectAllLayers);
|
||||||
tool_data.layers_dragging.clear();
|
tool_data.layers_dragging.clear();
|
||||||
}
|
}
|
||||||
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
|
|
||||||
SelectToolFsmState::DrawingBox
|
SelectToolFsmState::DrawingBox
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -601,7 +623,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
SelectToolFsmState::Dragging
|
SelectToolFsmState::Dragging
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => {
|
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { axis_align, center, .. }) => {
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
if let Some(movement) = &mut bounds.selected_edges {
|
if let Some(movement) = &mut bounds.selected_edges {
|
||||||
let (center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align));
|
let (center, axis_align) = (input.keyboard.key(center), input.keyboard.key(axis_align));
|
||||||
|
|
||||||
|
|
@ -609,20 +631,23 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||||
|
|
||||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
|
let (position, size) = movement.new_size(snapped_mouse_position, bounds.original_bound_transform, center, bounds.center_of_transformation, axis_align);
|
||||||
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size);
|
let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size);
|
||||||
|
|
||||||
|
let pivot_transform = DAffine2::from_translation(pivot);
|
||||||
|
let transformation = pivot_transform * delta * pivot_transform.inverse();
|
||||||
|
|
||||||
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
||||||
let selected = &tool_data.layers_dragging;
|
let selected = &tool_data.layers_dragging;
|
||||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut _pivot, selected, responses, &document.document_legacy, None, &ToolType::Select);
|
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, selected, responses, &document.document_legacy, None, &ToolType::Select);
|
||||||
|
|
||||||
selected.update_transforms(delta);
|
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SelectToolFsmState::ResizingBounds
|
SelectToolFsmState::ResizingBounds
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { snap_angle, .. }) => {
|
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { snap_angle, .. }) => {
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
let angle = {
|
let angle = {
|
||||||
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
|
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
|
||||||
let end_offset = input.mouse.position - bounds.center_of_transformation;
|
let end_offset = input.mouse.position - bounds.center_of_transformation;
|
||||||
|
|
@ -664,30 +689,20 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::DrawingBox, SelectToolMessage::PointerMove { .. }) => {
|
(SelectToolFsmState::DrawingBox, SelectToolMessage::PointerMove { .. }) => {
|
||||||
tool_data.drag_current = input.mouse.position;
|
tool_data.drag_current = input.mouse.position;
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
responses.add_front(DocumentMessage::Overlays(
|
|
||||||
Operation::SetLayerTransformInViewport {
|
|
||||||
path: tool_data.drag_box_overlay_layer.clone().unwrap(),
|
|
||||||
transform: transform_from_box(tool_data.drag_start, tool_data.drag_current, DAffine2::IDENTITY).to_cols_array(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
SelectToolFsmState::DrawingBox
|
SelectToolFsmState::DrawingBox
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready, SelectToolMessage::PointerMove { .. }) => {
|
(SelectToolFsmState::Ready, SelectToolMessage::PointerMove { .. }) => {
|
||||||
let mut cursor = tool_data.bounding_box_overlays.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
let mut cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true));
|
||||||
|
|
||||||
// Dragging the pivot overrules the other operations
|
// Dragging the pivot overrules the other operations
|
||||||
if tool_data.pivot.is_over(input.mouse.position) {
|
if tool_data.pivot.is_over(input.mouse.position) {
|
||||||
cursor = MouseCursorIcon::Move;
|
cursor = MouseCursorIcon::Move;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the select outline (but not if the user is going to use the bound overlays)
|
// Generate the hover outline
|
||||||
if cursor == MouseCursorIcon::Default {
|
responses.add(OverlaysMessage::Draw);
|
||||||
tool_data.path_outlines.intersect_test_hovered(input, document, responses);
|
|
||||||
} else {
|
|
||||||
tool_data.path_outlines.clear_hovered(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
if tool_data.cursor != cursor {
|
if tool_data.cursor != cursor {
|
||||||
tool_data.cursor = cursor;
|
tool_data.cursor = cursor;
|
||||||
|
|
@ -748,7 +763,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -761,7 +776,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
};
|
};
|
||||||
responses.add(response);
|
responses.add(response);
|
||||||
|
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
bounds.original_transforms.clear();
|
bounds.original_transforms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -789,13 +804,8 @@ impl Fsm for SelectToolFsmState {
|
||||||
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
|
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
responses.add_front(DocumentMessage::Overlays(
|
|
||||||
Operation::DeleteLayer {
|
|
||||||
path: tool_data.drag_box_overlay_layer.take().unwrap(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
SelectToolFsmState::Ready
|
SelectToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::Ready, SelectToolMessage::Enter) => {
|
(SelectToolFsmState::Ready, SelectToolMessage::Enter) => {
|
||||||
|
|
@ -814,18 +824,13 @@ impl Fsm for SelectToolFsmState {
|
||||||
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
|
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
responses.add(DocumentMessage::Undo);
|
responses.add(DocumentMessage::Undo);
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
tool_data.pivot.clear_overlays(responses);
|
|
||||||
|
|
||||||
SelectToolFsmState::Ready
|
SelectToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::Abort) => {
|
(_, SelectToolMessage::Abort) => {
|
||||||
if let Some(path) = tool_data.drag_box_overlay_layer.take() {
|
|
||||||
responses.add_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()))
|
|
||||||
};
|
|
||||||
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
|
||||||
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_overlays.take() {
|
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_manager.take() {
|
||||||
let mut selected = Selected::new(
|
let mut selected = Selected::new(
|
||||||
&mut bounding_box_overlays.original_transforms,
|
&mut bounding_box_overlays.original_transforms,
|
||||||
&mut bounding_box_overlays.opposite_pivot,
|
&mut bounding_box_overlays.opposite_pivot,
|
||||||
|
|
@ -837,13 +842,9 @@ impl Fsm for SelectToolFsmState {
|
||||||
);
|
);
|
||||||
|
|
||||||
selected.revert_operation();
|
selected.revert_operation();
|
||||||
|
|
||||||
bounding_box_overlays.delete(responses);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.path_outlines.clear_hovered(responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
tool_data.path_outlines.clear_selected(responses);
|
|
||||||
tool_data.pivot.clear_overlays(responses);
|
|
||||||
|
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
SelectToolFsmState::Ready
|
SelectToolFsmState::Ready
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::COLOR_ACCENT;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
|
||||||
|
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::intersection::Quad;
|
use document_legacy::layers::style::{Fill, RenderData};
|
||||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
|
||||||
use document_legacy::LayerId;
|
|
||||||
use document_legacy::Operation;
|
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::text::{load_face, Font};
|
use graphene_core::text::{load_face, Font};
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
|
|
@ -50,6 +49,8 @@ pub enum TextToolMessage {
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
WorkingColorChanged,
|
WorkingColorChanged,
|
||||||
|
#[remain::unsorted]
|
||||||
|
Overlays(OverlayContext),
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
CommitText,
|
CommitText,
|
||||||
|
|
@ -194,6 +195,7 @@ impl ToolTransition for TextTool {
|
||||||
tool_abort: Some(TextToolMessage::Abort.into()),
|
tool_abort: Some(TextToolMessage::Abort.into()),
|
||||||
selection_changed: Some(TextToolMessage::DocumentIsDirty.into()),
|
selection_changed: Some(TextToolMessage::DocumentIsDirty.into()),
|
||||||
working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),
|
working_color_changed: Some(TextToolMessage::WorkingColorChanged.into()),
|
||||||
|
overlay_provider: Some(|overlay_context| TextToolMessage::Overlays(overlay_context).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +218,6 @@ pub struct EditingText {
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct TextToolData {
|
struct TextToolData {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
overlays: Vec<Vec<LayerId>>,
|
|
||||||
editing_text: Option<EditingText>,
|
editing_text: Option<EditingText>,
|
||||||
new_text: String,
|
new_text: String,
|
||||||
}
|
}
|
||||||
|
|
@ -317,31 +318,10 @@ impl TextToolData {
|
||||||
// Removing old text as editable
|
// Removing old text as editable
|
||||||
self.set_editing(false, render_data, document, responses);
|
self.set_editing(false, render_data, document, responses);
|
||||||
|
|
||||||
resize_overlays(&mut self.overlays, responses, 0);
|
|
||||||
|
|
||||||
TextToolFsmState::Ready
|
TextToolFsmState::Ready
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_bounds_overlay(&mut self, document: &DocumentMessageHandler, render_data: &RenderData, responses: &mut VecDeque<Message>) -> Option<()> {
|
|
||||||
resize_overlays(&mut self.overlays, responses, 1);
|
|
||||||
|
|
||||||
let editing_text = self.editing_text.as_ref()?;
|
|
||||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
|
||||||
let far = graphene_core::text::bounding_box(&self.new_text, buzz_face, editing_text.font_size, None);
|
|
||||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
|
||||||
|
|
||||||
let transformed_quad = document.metadata().transform_to_viewport(self.layer) * quad;
|
|
||||||
let bounds = transformed_quad.bounding_box();
|
|
||||||
|
|
||||||
let operation = Operation::SetLayerTransformInViewport {
|
|
||||||
path: self.overlays[0].clone(),
|
|
||||||
transform: transform_from_box(bounds[0], bounds[1]),
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bounds(&self, text: &str, render_data: &RenderData) -> Option<[DVec2; 2]> {
|
fn get_bounds(&self, text: &str, render_data: &RenderData) -> Option<[DVec2; 2]> {
|
||||||
let editing_text = self.editing_text.as_ref()?;
|
let editing_text = self.editing_text.as_ref()?;
|
||||||
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||||
|
|
@ -361,53 +341,6 @@ impl TextToolData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
|
|
||||||
DAffine2::from_scale_angle_translation((pos2 - pos1).round(), 0., pos1.round() - DVec2::splat(0.5)).to_cols_array()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Message>, newlen: usize) {
|
|
||||||
while overlays.len() > newlen {
|
|
||||||
let operation = Operation::DeleteLayer { path: overlays.pop().unwrap() };
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
while overlays.len() < newlen {
|
|
||||||
let path = vec![generate_uuid()];
|
|
||||||
overlays.push(path.clone());
|
|
||||||
|
|
||||||
let operation = Operation::AddRect {
|
|
||||||
path,
|
|
||||||
transform: DAffine2::ZERO.to_cols_array(),
|
|
||||||
style: style::PathStyle::new(Some(Stroke::new(Some(COLOR_ACCENT), 1.0)), Fill::None),
|
|
||||||
insert_index: -1,
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_overlays(document: &DocumentMessageHandler, tool_data: &mut TextToolData, responses: &mut VecDeque<Message>, render_data: &RenderData) {
|
|
||||||
let get_bounds = |layer: LayerNodeIdentifier, document: &DocumentMessageHandler, render_data: &RenderData| {
|
|
||||||
let (text, font, font_size) = graph_modification_utils::get_text(layer, &document.document_legacy)?;
|
|
||||||
let buzz_face = render_data.font_cache.get(font).map(|data| load_face(data));
|
|
||||||
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None);
|
|
||||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
|
||||||
let multiplied = document.metadata().transform_to_viewport(layer) * quad;
|
|
||||||
Some(multiplied.bounding_box())
|
|
||||||
};
|
|
||||||
let bounds = document.metadata().selected_layers().filter_map(|layer| get_bounds(layer, document, render_data));
|
|
||||||
let bounds = bounds.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let new_len = bounds.len();
|
|
||||||
|
|
||||||
for (bounds, overlay_path) in bounds.iter().zip(&tool_data.overlays) {
|
|
||||||
let operation = Operation::SetLayerTransformInViewport {
|
|
||||||
path: overlay_path.to_vec(),
|
|
||||||
transform: transform_from_box(bounds[0], bounds[1]),
|
|
||||||
};
|
|
||||||
responses.add(DocumentMessage::Overlays(operation.into()));
|
|
||||||
}
|
|
||||||
resize_overlays(&mut tool_data.overlays, responses, new_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||||
let mut selected_layers = document.metadata().selected_layers();
|
let mut selected_layers = document.metadata().selected_layers();
|
||||||
|
|
||||||
|
|
@ -440,17 +373,35 @@ impl Fsm for TextToolFsmState {
|
||||||
return self;
|
return self;
|
||||||
};
|
};
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
|
(TextToolFsmState::Editing, TextToolMessage::Overlays(mut overlay_context)) => {
|
||||||
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
|
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
|
||||||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||||
});
|
});
|
||||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
if let Some(editing_text) = tool_data.editing_text.as_ref() {
|
||||||
|
let buzz_face = render_data.font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||||
|
let far = graphene_core::text::bounding_box(&tool_data.new_text, buzz_face, editing_text.font_size, None);
|
||||||
|
if far.x != 0. && far.y != 0. {
|
||||||
|
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||||
|
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||||
|
overlay_context.quad(transformed_quad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TextToolFsmState::Editing
|
TextToolFsmState::Editing
|
||||||
}
|
}
|
||||||
(state, TextToolMessage::DocumentIsDirty) => {
|
(_, TextToolMessage::Overlays(mut overlay_context)) => {
|
||||||
update_overlays(document, tool_data, responses, render_data);
|
for layer in document.metadata().selected_layers() {
|
||||||
|
let Some((text, font, font_size)) = graph_modification_utils::get_text(layer, &document.document_legacy) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let buzz_face = render_data.font_cache.get(font).map(|data| load_face(data));
|
||||||
|
let far = graphene_core::text::bounding_box(text, buzz_face, font_size, None);
|
||||||
|
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||||
|
let multiplied = document.metadata().transform_to_viewport(layer) * quad;
|
||||||
|
overlay_context.quad(multiplied);
|
||||||
|
}
|
||||||
|
|
||||||
state
|
self
|
||||||
}
|
}
|
||||||
(state, TextToolMessage::Interact) => {
|
(state, TextToolMessage::Interact) => {
|
||||||
tool_data.editing_text = Some(EditingText {
|
tool_data.editing_text = Some(EditingText {
|
||||||
|
|
@ -477,8 +428,6 @@ impl Fsm for TextToolFsmState {
|
||||||
tool_data.set_editing(false, render_data, document, responses);
|
tool_data.set_editing(false, render_data, document, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
|
||||||
|
|
||||||
TextToolFsmState::Ready
|
TextToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
|
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
|
||||||
|
|
@ -497,13 +446,11 @@ impl Fsm for TextToolFsmState {
|
||||||
|
|
||||||
tool_data.set_editing(false, render_data, document, responses);
|
tool_data.set_editing(false, render_data, document, responses);
|
||||||
|
|
||||||
resize_overlays(&mut tool_data.overlays, responses, 0);
|
|
||||||
|
|
||||||
TextToolFsmState::Ready
|
TextToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
|
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
|
||||||
tool_data.new_text = new_text;
|
tool_data.new_text = new_text;
|
||||||
tool_data.update_bounds_overlay(document, render_data, responses);
|
responses.add(OverlaysMessage::Draw);
|
||||||
TextToolFsmState::Editing
|
TextToolFsmState::Editing
|
||||||
}
|
}
|
||||||
(_, TextToolMessage::WorkingColorChanged) => {
|
(_, TextToolMessage::WorkingColorChanged) => {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
use super::common_functionality::overlay_renderer::OverlayRenderer;
|
|
||||||
use super::common_functionality::shape_editor::ShapeState;
|
use super::common_functionality::shape_editor::ShapeState;
|
||||||
use super::tool_messages::*;
|
use super::tool_messages::*;
|
||||||
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGrou
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
use crate::messages::input_mapper::utility_types::misc::ActionKeys;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
|
|
||||||
|
|
@ -23,7 +24,6 @@ pub struct ToolActionHandlerData<'a> {
|
||||||
pub global_tool_data: &'a DocumentToolData,
|
pub global_tool_data: &'a DocumentToolData,
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub render_data: &'a RenderData<'a>,
|
pub render_data: &'a RenderData<'a>,
|
||||||
pub shape_overlay: &'a mut OverlayRenderer,
|
|
||||||
pub shape_editor: &'a mut ShapeState,
|
pub shape_editor: &'a mut ShapeState,
|
||||||
pub node_graph: &'a NodeGraphExecutor,
|
pub node_graph: &'a NodeGraphExecutor,
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,6 @@ impl<'a> ToolActionHandlerData<'a> {
|
||||||
global_tool_data: &'a DocumentToolData,
|
global_tool_data: &'a DocumentToolData,
|
||||||
input: &'a InputPreprocessorMessageHandler,
|
input: &'a InputPreprocessorMessageHandler,
|
||||||
render_data: &'a RenderData<'a>,
|
render_data: &'a RenderData<'a>,
|
||||||
shape_overlay: &'a mut OverlayRenderer,
|
|
||||||
shape_editor: &'a mut ShapeState,
|
shape_editor: &'a mut ShapeState,
|
||||||
node_graph: &'a NodeGraphExecutor,
|
node_graph: &'a NodeGraphExecutor,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -44,7 +43,6 @@ impl<'a> ToolActionHandlerData<'a> {
|
||||||
global_tool_data,
|
global_tool_data,
|
||||||
input,
|
input,
|
||||||
render_data,
|
render_data,
|
||||||
shape_overlay,
|
|
||||||
shape_editor,
|
shape_editor,
|
||||||
node_graph,
|
node_graph,
|
||||||
}
|
}
|
||||||
|
|
@ -174,6 +172,7 @@ pub struct EventToMessageMap {
|
||||||
pub selection_changed: Option<ToolMessage>,
|
pub selection_changed: Option<ToolMessage>,
|
||||||
pub tool_abort: Option<ToolMessage>,
|
pub tool_abort: Option<ToolMessage>,
|
||||||
pub working_color_changed: Option<ToolMessage>,
|
pub working_color_changed: Option<ToolMessage>,
|
||||||
|
pub overlay_provider: Option<OverlayProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ToolTransition {
|
pub trait ToolTransition {
|
||||||
|
|
@ -195,6 +194,9 @@ pub trait ToolTransition {
|
||||||
subscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort);
|
subscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort);
|
||||||
subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
|
subscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
|
||||||
subscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
|
subscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
|
||||||
|
if let Some(overlay_provider) = event_to_tool_map.overlay_provider {
|
||||||
|
responses.add(OverlaysMessage::AddProvider(overlay_provider));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(&self, responses: &mut VecDeque<Message>) {
|
fn deactivate(&self, responses: &mut VecDeque<Message>) {
|
||||||
|
|
@ -213,6 +215,9 @@ pub trait ToolTransition {
|
||||||
unsubscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort);
|
unsubscribe_message(event_to_tool_map.tool_abort, BroadcastEvent::ToolAbort);
|
||||||
unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
|
unsubscribe_message(event_to_tool_map.selection_changed, BroadcastEvent::SelectionChanged);
|
||||||
unsubscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
|
unsubscribe_message(event_to_tool_map.working_color_changed, BroadcastEvent::WorkingColorChanged);
|
||||||
|
if let Some(overlay_provider) = event_to_tool_map.overlay_provider {
|
||||||
|
responses.add(OverlaysMessage::RemoveProvider(overlay_provider));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||||
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
|
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
|
use crate::messages::portfolio::document::utility_types::misc::{LayerMetadata, LayerPanelEntry};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use document_legacy::document::Document as DocumentLegacy;
|
use document_legacy::document::Document as DocumentLegacy;
|
||||||
use document_legacy::document_metadata::LayerNodeIdentifier;
|
use document_legacy::document_metadata::LayerNodeIdentifier;
|
||||||
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType};
|
use document_legacy::layers::layer_info::{LayerDataTypeDiscriminant, LegacyLayerType};
|
||||||
|
|
@ -21,7 +22,6 @@ use graphene_core::text::FontCache;
|
||||||
use graphene_core::transform::{Footprint, Transform};
|
use graphene_core::transform::{Footprint, Transform};
|
||||||
use graphene_core::vector::style::ViewMode;
|
use graphene_core::vector::style::ViewMode;
|
||||||
use graphene_core::vector::VectorData;
|
use graphene_core::vector::VectorData;
|
||||||
|
|
||||||
use graphene_core::{Color, GraphicElement, SurfaceFrame, SurfaceId};
|
use graphene_core::{Color, GraphicElement, SurfaceFrame, SurfaceId};
|
||||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||||
|
|
@ -654,7 +654,7 @@ impl NodeGraphExecutor {
|
||||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||||
responses.add(DocumentMessage::DirtyRenderDocument);
|
responses.add(DocumentMessage::DirtyRenderDocument);
|
||||||
responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender));
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
|
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
|
||||||
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties))
|
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties))
|
||||||
|
|
@ -679,7 +679,7 @@ impl NodeGraphExecutor {
|
||||||
let svg = render.svg.to_string();
|
let svg = render.svg.to_string();
|
||||||
|
|
||||||
// Send to frontend
|
// Send to frontend
|
||||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
|
|
@ -692,7 +692,7 @@ impl NodeGraphExecutor {
|
||||||
}
|
}
|
||||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
|
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::Svg(svg)) => {
|
||||||
// Send to frontend
|
// Send to frontend
|
||||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
}
|
}
|
||||||
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
|
TaggedValue::RenderOutput(graphene_std::wasm_application_io::RenderOutput::CanvasFrame(frame)) => {
|
||||||
|
|
@ -710,7 +710,7 @@ impl NodeGraphExecutor {
|
||||||
"#,
|
"#,
|
||||||
1920, 1080, matrix, frame.surface_id.0
|
1920, 1080, matrix, frame.surface_id.0
|
||||||
);
|
);
|
||||||
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
}
|
}
|
||||||
TaggedValue::Bool(render_object) => Self::render(render_object, transform, responses),
|
TaggedValue::Bool(render_object) => Self::render(render_object, transform, responses),
|
||||||
TaggedValue::String(render_object) => Self::render(render_object, transform, responses),
|
TaggedValue::String(render_object) => Self::render(render_object, transform, responses),
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,11 @@
|
||||||
DisplayRemoveEditableTextbox,
|
DisplayRemoveEditableTextbox,
|
||||||
TriggerTextCommit,
|
TriggerTextCommit,
|
||||||
TriggerViewportResize,
|
TriggerViewportResize,
|
||||||
UpdateDocumentArtboards,
|
|
||||||
UpdateDocumentArtwork,
|
UpdateDocumentArtwork,
|
||||||
UpdateDocumentOverlays,
|
|
||||||
UpdateDocumentRulers,
|
UpdateDocumentRulers,
|
||||||
UpdateDocumentScrollbars,
|
UpdateDocumentScrollbars,
|
||||||
UpdateEyedropperSamplingState,
|
UpdateEyedropperSamplingState,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateDocumentNodeRender,
|
|
||||||
isWidgetSpanRow,
|
isWidgetSpanRow,
|
||||||
} from "@graphite/wasm-communication/messages";
|
} from "@graphite/wasm-communication/messages";
|
||||||
|
|
||||||
|
|
@ -62,9 +59,6 @@
|
||||||
|
|
||||||
// Rendered SVG viewport data
|
// Rendered SVG viewport data
|
||||||
let artworkSvg = "";
|
let artworkSvg = "";
|
||||||
let nodeRenderSvg = "";
|
|
||||||
let artboardSvg = "";
|
|
||||||
let overlaysSvg = "";
|
|
||||||
|
|
||||||
// Rasterized SVG viewport data, or none if it's not up-to-date
|
// Rasterized SVG viewport data, or none if it's not up-to-date
|
||||||
let rasterizedCanvas: HTMLCanvasElement | undefined = undefined;
|
let rasterizedCanvas: HTMLCanvasElement | undefined = undefined;
|
||||||
|
|
@ -160,7 +154,12 @@
|
||||||
|
|
||||||
// Update rendered SVGs
|
// Update rendered SVGs
|
||||||
export async function updateDocumentArtwork(svg: string) {
|
export async function updateDocumentArtwork(svg: string) {
|
||||||
artworkSvg = svg;
|
// TODO: Sort this out so we're either sending only the SVG inner contents from the backend or not setting the width/height attributes here
|
||||||
|
// TODO: (but preserving the rounding-up-to-the-next-even-number to prevent antialiasing).
|
||||||
|
artworkSvg = svg
|
||||||
|
.trim()
|
||||||
|
.replace(/<svg[^>]*>/, "")
|
||||||
|
.slice(0, -"</svg>".length);
|
||||||
rasterizedCanvas = undefined;
|
rasterizedCanvas = undefined;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
@ -177,20 +176,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateDocumentOverlays(svg: string) {
|
|
||||||
overlaysSvg = svg;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocumentArtboards(svg: string) {
|
|
||||||
artboardSvg = svg;
|
|
||||||
rasterizedCanvas = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateDocumentNodeRender(svg: string) {
|
|
||||||
nodeRenderSvg = svg;
|
|
||||||
rasterizedCanvas = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateEyedropperSamplingState(mousePosition: XY | undefined, colorPrimary: string, colorSecondary: string): Promise<[number, number, number] | undefined> {
|
export async function updateEyedropperSamplingState(mousePosition: XY | undefined, colorPrimary: string, colorSecondary: string): Promise<[number, number, number] | undefined> {
|
||||||
if (mousePosition === undefined) {
|
if (mousePosition === undefined) {
|
||||||
cursorEyedropper = false;
|
cursorEyedropper = false;
|
||||||
|
|
@ -211,7 +196,7 @@
|
||||||
const outsideArtboards = `<rect x="0" y="0" width="100%" height="100%" fill="${outsideArtboardsColor}" />`;
|
const outsideArtboards = `<rect x="0" y="0" width="100%" height="100%" fill="${outsideArtboardsColor}" />`;
|
||||||
|
|
||||||
const svg = `
|
const svg = `
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${outsideArtboards}${artboardSvg}${nodeRenderSvg}</svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">${outsideArtboards}${artworkSvg}</svg>
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
if (!rasterizedCanvas) {
|
if (!rasterizedCanvas) {
|
||||||
|
|
@ -370,21 +355,6 @@
|
||||||
|
|
||||||
updateDocumentArtwork(data.svg);
|
updateDocumentArtwork(data.svg);
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentOverlays, async (data) => {
|
|
||||||
await tick();
|
|
||||||
|
|
||||||
updateDocumentOverlays(data.svg);
|
|
||||||
});
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentArtboards, async (data) => {
|
|
||||||
await tick();
|
|
||||||
|
|
||||||
updateDocumentArtboards(data.svg);
|
|
||||||
});
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateDocumentNodeRender, async (data) => {
|
|
||||||
await tick();
|
|
||||||
|
|
||||||
updateDocumentNodeRender(data.svg);
|
|
||||||
});
|
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateEyedropperSamplingState, async (data) => {
|
editor.subscriptions.subscribeJsMessage(UpdateEyedropperSamplingState, async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
|
|
@ -509,22 +479,14 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} on:dragover={(e) => e.preventDefault()} on:drop={(e) => pasteFile(e)} bind:this={viewport} data-viewport>
|
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} on:dragover={(e) => e.preventDefault()} on:drop={(e) => pasteFile(e)} bind:this={viewport} data-viewport>
|
||||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||||
{@html artboardSvg}
|
|
||||||
</svg>
|
|
||||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
|
||||||
{@html nodeRenderSvg}
|
|
||||||
</svg>
|
|
||||||
<svg class="artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
|
||||||
{@html artworkSvg}
|
{@html artworkSvg}
|
||||||
</svg>
|
</svg>
|
||||||
<svg class="overlays" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
||||||
{@html overlaysSvg}
|
|
||||||
</svg>
|
|
||||||
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
|
||||||
{#if showTextInput}
|
{#if showTextInput}
|
||||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" />
|
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<canvas class="overlays" style:width={canvasWidthCSS} style:height={canvasHeightCSS} data-overlays-canvas></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork="80%" data-graph>
|
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork="80%" data-graph>
|
||||||
<Graph />
|
<Graph />
|
||||||
|
|
@ -701,19 +663,17 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
svg {
|
.artwork,
|
||||||
|
.text-input,
|
||||||
|
.overlays {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
// Fallback values if JS hasn't set these to integers yet
|
// Fallback values if JS hasn't set these to integers yet
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
// Allows dev tools to select the artwork without being blocked by the SVG containers
|
// Allows dev tools to select the artwork without being blocked by the SVG containers
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent inheritance from reaching the child elements
|
// Prevent inheritance from reaching the child elements
|
||||||
> * {
|
> * {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
|
||||||
|
|
@ -449,18 +449,6 @@ export class UpdateDocumentArtwork extends JsMessage {
|
||||||
readonly svg!: string;
|
readonly svg!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateDocumentOverlays extends JsMessage {
|
|
||||||
readonly svg!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateDocumentArtboards extends JsMessage {
|
|
||||||
readonly svg!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateDocumentNodeRender extends JsMessage {
|
|
||||||
readonly svg!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UpdateDocumentScrollbars extends JsMessage {
|
export class UpdateDocumentScrollbars extends JsMessage {
|
||||||
@TupleToVec2
|
@TupleToVec2
|
||||||
readonly position!: XY;
|
readonly position!: XY;
|
||||||
|
|
@ -1416,14 +1404,11 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
UpdateDialogButtons,
|
UpdateDialogButtons,
|
||||||
UpdateDialogColumn1,
|
UpdateDialogColumn1,
|
||||||
UpdateDialogColumn2,
|
UpdateDialogColumn2,
|
||||||
UpdateDocumentArtboards,
|
|
||||||
UpdateDocumentArtwork,
|
UpdateDocumentArtwork,
|
||||||
UpdateDocumentBarLayout,
|
UpdateDocumentBarLayout,
|
||||||
UpdateDocumentLayerDetails,
|
UpdateDocumentLayerDetails,
|
||||||
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
|
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
|
||||||
UpdateDocumentModeLayout,
|
UpdateDocumentModeLayout,
|
||||||
UpdateDocumentNodeRender,
|
|
||||||
UpdateDocumentOverlays,
|
|
||||||
UpdateDocumentRulers,
|
UpdateDocumentRulers,
|
||||||
UpdateDocumentScrollbars,
|
UpdateDocumentScrollbars,
|
||||||
UpdateEyedropperSamplingState,
|
UpdateEyedropperSamplingState,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
|
mod quad;
|
||||||
|
|
||||||
use crate::raster::{BlendMode, Image, ImageFrame};
|
use crate::raster::{BlendMode, Image, ImageFrame};
|
||||||
use crate::uuid::{generate_uuid, ManipulatorGroupId};
|
use crate::uuid::{generate_uuid, ManipulatorGroupId};
|
||||||
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
|
use crate::{vector::VectorData, Artboard, Color, GraphicElement, GraphicGroup};
|
||||||
use base64::Engine;
|
|
||||||
use bezier_rs::Subpath;
|
|
||||||
|
|
||||||
pub use quad::Quad;
|
pub use quad::Quad;
|
||||||
|
|
||||||
|
use bezier_rs::Subpath;
|
||||||
|
|
||||||
|
use base64::Engine;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use usvg::TreeParsing;
|
use usvg::TreeParsing;
|
||||||
|
|
||||||
mod quad;
|
|
||||||
|
|
||||||
/// Represents a clickable target for the layer
|
/// Represents a clickable target for the layer
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ClickTarget {
|
pub struct ClickTarget {
|
||||||
|
|
@ -83,7 +83,7 @@ impl SvgRender {
|
||||||
self.svg.push("\t".repeat(self.indent));
|
self.svg.push("\t".repeat(self.indent));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an outer `<svg />` tag with a `viewBox` and the `<defs />`
|
/// Add an outer `<svg>...</svg>` tag with a `viewBox` and the `<defs />`
|
||||||
pub fn format_svg(&mut self, bounds_min: DVec2, bounds_max: DVec2) {
|
pub fn format_svg(&mut self, bounds_min: DVec2, bounds_max: DVec2) {
|
||||||
let (x, y) = bounds_min.into();
|
let (x, y) = bounds_min.into();
|
||||||
let (size_x, size_y) = (bounds_max - bounds_min).into();
|
let (size_x, size_y) = (bounds_max - bounds_min).into();
|
||||||
|
|
@ -93,7 +93,7 @@ impl SvgRender {
|
||||||
self.svg.push("</svg>");
|
self.svg.push("</svg>");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps the SVG with `<svg><g transform="...">`, which allows for rotation
|
/// Wraps the SVG with `<svg><g transform="...">...</g></svg>`, which allows for rotation
|
||||||
pub fn wrap_with_transform(&mut self, transform: DAffine2, size: Option<DVec2>) {
|
pub fn wrap_with_transform(&mut self, transform: DAffine2, size: Option<DVec2>) {
|
||||||
let defs = &self.svg_defs;
|
let defs = &self.svg_defs;
|
||||||
let view_box = size
|
let view_box = size
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Copy)]
|
#[derive(Debug, Clone, Default, Copy)]
|
||||||
/// A quad defined by four vertices.
|
/// A quad defined by four vertices.
|
||||||
pub struct Quad([DVec2; 4]);
|
pub struct Quad(pub [DVec2; 4]);
|
||||||
|
|
||||||
impl Quad {
|
impl Quad {
|
||||||
/// Create a zero sized quad at the point
|
/// Create a zero sized quad at the point
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use core::future::Future;
|
|
||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, RenderConfig, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
use graphene_core::application_io::{ApplicationError, ApplicationIo, ExportFormat, RenderConfig, ResourceFuture, SurfaceHandle, SurfaceHandleFrame, SurfaceId};
|
||||||
use graphene_core::raster::Image;
|
use graphene_core::raster::Image;
|
||||||
|
use graphene_core::raster::{color::SRGBA8, ImageFrame};
|
||||||
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
|
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
use graphene_core::{
|
use graphene_core::Node;
|
||||||
raster::{color::SRGBA8, ImageFrame},
|
#[cfg(feature = "wgpu")]
|
||||||
Node,
|
use wgpu_executor::WgpuExecutor;
|
||||||
};
|
|
||||||
|
use core::future::Future;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use js_sys::{Object, Reflect};
|
use js_sys::{Object, Reflect};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
@ -25,8 +25,6 @@ use wasm_bindgen::{Clamped, JsCast};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use web_sys::window;
|
use web_sys::window;
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
#[cfg(feature = "wgpu")]
|
|
||||||
use wgpu_executor::WgpuExecutor;
|
|
||||||
|
|
||||||
pub struct Canvas(CanvasRenderingContext2d);
|
pub struct Canvas(CanvasRenderingContext2d);
|
||||||
|
|
||||||
|
|
@ -293,7 +291,6 @@ pub struct RenderNode<Data, Surface, Parameter> {
|
||||||
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput {
|
fn render_svg(data: impl GraphicElementRendered, mut render: SvgRender, render_params: RenderParams, footprint: Footprint) -> RenderOutput {
|
||||||
if !data.contains_artboard() && !render_params.hide_artboards {
|
if !data.contains_artboard() && !render_params.hide_artboards {
|
||||||
render.leaf_tag("rect", |attributes| {
|
render.leaf_tag("rect", |attributes| {
|
||||||
attributes.push("x", "0");
|
|
||||||
attributes.push("x", "0");
|
attributes.push("x", "0");
|
||||||
attributes.push("y", "0");
|
attributes.push("y", "0");
|
||||||
attributes.push("width", footprint.resolution.x.to_string());
|
attributes.push("width", footprint.resolution.x.to_string());
|
||||||
|
|
|
||||||
|
|
@ -107,10 +107,6 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
||||||
<img class="atlas" style="--atlas-index: 4" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
<img class="atlas" style="--atlas-index: 4" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
<span>Basic brush tool</span>
|
<span>Basic brush tool</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="informational complete" title="Development Complete">
|
|
||||||
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
|
||||||
<span>Resolution-agnostic raster rendering</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational complete" title="Development Complete">
|
<div class="informational complete" title="Development Complete">
|
||||||
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
<img class="atlas" style="--atlas-index: 2" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
<span>Graph-based layer stacks</span>
|
<span>Graph-based layer stacks</span>
|
||||||
|
|
@ -119,14 +115,18 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
||||||
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
<span>Fully graph-driven documents</span>
|
<span>Fully graph-driven documents</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="informational complete" title="Development Complete">
|
||||||
|
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>New viewport overlays system</span>
|
||||||
|
</div>
|
||||||
|
<div class="informational ongoing" title="Development Ongoing">
|
||||||
|
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
|
<span>Resolution-agnostic raster rendering</span>
|
||||||
|
</div>
|
||||||
<div class="informational ongoing" title="Development Ongoing">
|
<div class="informational ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
<img class="atlas" style="--atlas-index: 11" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
<span>WebGPU accelerated rendering</span>
|
<span>WebGPU accelerated rendering</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="informational">
|
|
||||||
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
|
||||||
<span>New viewport overlays system</span>
|
|
||||||
</div>
|
|
||||||
<div class="informational">
|
<div class="informational">
|
||||||
<img class="atlas" style="--atlas-index: 15" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
<img class="atlas" style="--atlas-index: 15" src="https://static.graphite.rs/icons/icon-atlas-roadmap.png" alt="" />
|
||||||
<span>Snapping between layers</span>
|
<span>Snapping between layers</span>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue