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:
0HyperCube
2023-12-18 11:17:43 +00:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 9e06e70aa2
commit c42d030f18
36 changed files with 552 additions and 1425 deletions
@@ -1,12 +1,13 @@
use super::tool_prelude::*;
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::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::RenderData;
use glam::{IVec2, Vec2Swizzles};
#[derive(Default)]
@@ -24,6 +25,8 @@ pub enum ArtboardToolMessage {
Abort,
#[remain::unsorted]
DocumentIsDirty,
#[remain::unsorted]
Overlays(OverlayContext),
// Tool-specific messages
DeleteSelected,
@@ -77,6 +80,7 @@ impl ToolTransition for ArtboardTool {
EventToMessageMap {
document_dirty: Some(ArtboardToolMessage::DocumentIsDirty.into()),
tool_abort: Some(ArtboardToolMessage::Abort.into()),
overlay_provider: Some(|overlay_context| ArtboardToolMessage::Overlays(overlay_context).into()),
..Default::default()
}
}
@@ -93,7 +97,7 @@ enum ArtboardToolFsmState {
#[derive(Clone, Debug, Default)]
struct ArtboardToolData {
bounding_box_overlays: Option<BoundingBoxOverlays>,
bounding_box_manager: Option<BoundingBoxManager>,
selected_artboard: Option<LayerNodeIdentifier>,
snap_manager: SnapManager,
cursor: MouseCursorIcon,
@@ -102,28 +106,8 @@ struct 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)> {
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 (top, bottom, left, right) = edges;
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);
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.;
}
}
@@ -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) {
let Some(bounds) = &self.bounding_box_overlays else {
let Some(bounds) = &self.bounding_box_manager else {
return;
};
let Some(movement) = &bounds.selected_edges else {
@@ -206,11 +190,20 @@ impl Fsm for ArtboardToolFsmState {
};
match (self, event) {
(state, ArtboardToolMessage::DocumentIsDirty) if state != ArtboardToolFsmState::Drawing => {
tool_data.refresh_overlays(document, responses);
(state, ArtboardToolMessage::Overlays(mut overlay_context)) if state != ArtboardToolFsmState::Drawing => {
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
}
(ArtboardToolFsmState::Ready, ArtboardToolMessage::PointerDown) => {
tool_data.drag_start = input.mouse.position;
tool_data.drag_current = input.mouse.position;
@@ -235,7 +228,7 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::ResizingBounds
}
(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 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::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 {
tool_data.cursor = cursor;
@@ -326,7 +319,7 @@ impl Fsm for ArtboardToolFsmState {
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
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();
}
@@ -335,20 +328,21 @@ impl Fsm for ArtboardToolFsmState {
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
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();
}
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready
}
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
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();
}
responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready
}
@@ -362,7 +356,7 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Ready
}
(_, 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 {
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(),
@@ -373,16 +367,13 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Ready
}
(_, 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
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
});
tool_data.snap_manager.cleanup(responses);
responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready
}
_ => self,
@@ -1,13 +1,12 @@
use super::tool_prelude::*;
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::snapping::SnapManager;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::layers::style::{Fill, Gradient, GradientType, PathStyle, RenderData, Stroke};
use document_legacy::LayerId;
use document_legacy::Operation;
use document_legacy::layers::style::{Fill, Gradient, GradientType, RenderData};
use graphene_core::raster::color::Color;
#[derive(Default)]
@@ -31,6 +30,8 @@ pub enum GradientToolMessage {
Abort,
#[remain::unsorted]
DocumentIsDirty,
#[remain::unsorted]
Overlays(OverlayContext),
// Tool-specific messages
DeleteStop,
@@ -124,99 +125,6 @@ fn gradient_space_transform(layer: LayerNodeIdentifier, document: &DocumentMessa
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)]
pub enum GradientDragTarget {
Start,
@@ -346,6 +254,7 @@ impl ToolTransition for GradientTool {
document_dirty: Some(GradientToolMessage::DocumentIsDirty.into()),
tool_abort: Some(GradientToolMessage::Abort.into()),
selection_changed: Some(GradientToolMessage::DocumentIsDirty.into()),
overlay_provider: Some(|overlay_context| GradientToolMessage::Overlays(overlay_context).into()),
..Default::default()
}
}
@@ -353,7 +262,6 @@ impl ToolTransition for GradientTool {
#[derive(Clone, Debug, Default)]
struct GradientToolData {
gradient_overlays: Vec<GradientOverlay>,
selected_gradient: Option<SelectedGradient>,
snap_manager: SnapManager,
drag_start: DVec2,
@@ -383,21 +291,33 @@ impl Fsm for GradientToolFsmState {
match (self, event) {
(_, GradientToolMessage::DocumentIsDirty) => {
while let Some(overlay) = tool_data.gradient_overlays.pop() {
overlay.delete_overlays(responses);
}
if self != GradientToolFsmState::Drawing {
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() {
if let Some(gradient) = get_gradient(layer, &document.document_legacy) {
let dragging = tool_data
.selected_gradient
.as_ref()
.and_then(|selected| if selected.layer == layer { Some(selected.dragging) } else { None });
tool_data.gradient_overlays.push(GradientOverlay::new(gradient, dragging, layer, document, responses))
let Some(gradient) = get_gradient(layer, &document.document_legacy) else { continue };
let transform = gradient_space_transform(layer, document);
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
let Gradient { start, end, positions, .. } = gradient;
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
}
(_, 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 (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
let distance = (end - start).angle_between(mouse - start).sin() * (mouse - start).length();
// If click is on the line then insert point
if distance < SELECTION_THRESHOLD {
let mut gradient = overlay.gradient.clone();
if distance < (SELECTION_THRESHOLD * 2.) {
// 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);
let mut selected_gradient = SelectedGradient::new(gradient, overlay.layer, document);
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
// Select the new point
selected_gradient.dragging = GradientDragTarget::Step(index);
@@ -487,36 +408,37 @@ impl Fsm for GradientToolFsmState {
let mouse = input.mouse.position;
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;
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
for (index, (pos, _)) in overlay.gradient.positions.iter().enumerate() {
let pos = overlay.transform.transform_point2(overlay.gradient.start.lerp(overlay.gradient.end, *pos));
for (index, (pos, _)) in gradient.positions.iter().enumerate() {
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
layer: overlay.layer,
transform: overlay.transform,
gradient: overlay.gradient.clone(),
layer,
transform,
gradient: gradient.clone(),
dragging: GradientDragTarget::Step(index),
})
}
}
// Check dragging start or end handle
for (pos, dragging_target) in [
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
] {
for (pos, dragging_target) in [(gradient.start, GradientDragTarget::Start), (gradient.end, GradientDragTarget::End)] {
let pos = transform.transform_point2(pos);
if pos.distance_squared(mouse) < tolerance {
dragging = true;
start_snap(&mut tool_data.snap_manager, document, input, render_data);
tool_data.selected_gradient = Some(SelectedGradient {
layer: overlay.layer,
transform: overlay.transform,
gradient: overlay.gradient.clone(),
layer,
transform,
gradient: gradient.clone(),
dragging: dragging_target,
})
}
@@ -530,20 +452,10 @@ impl Fsm for GradientToolFsmState {
// Apply the gradient to the 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) {
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);
@@ -592,10 +504,8 @@ impl Fsm for GradientToolFsmState {
(_, GradientToolMessage::Abort) => {
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
}
_ => self,
@@ -1,6 +1,5 @@
use super::tool_prelude::*;
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 document_legacy::document_metadata::LayerNodeIdentifier;
@@ -96,7 +95,6 @@ enum ImaginateToolFsmState {
#[derive(Clone, Debug, Default)]
struct ImaginateToolData {
data: Resize,
path_outlines: PathOutline,
}
impl Fsm for ImaginateToolFsmState {
@@ -118,14 +116,11 @@ impl Fsm for ImaginateToolFsmState {
};
match (self, event) {
(_, 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);
self
}
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
tool_data.path_outlines.clear_selected(responses);
shape_data.start(responses, document, input, render_data);
responses.add(DocumentMessage::StartTransaction);
shape_data.layer = Some(LayerNodeIdentifier::new(generate_uuid(), document.network()));
@@ -193,15 +188,10 @@ impl Fsm for ImaginateToolFsmState {
responses.add(DocumentMessage::AbortTransaction);
shape_data.cleanup(responses);
tool_data.path_outlines.clear_selected(responses);
ImaginateToolFsmState::Ready
}
(_, ImaginateToolMessage::Abort) => {
tool_data.path_outlines.clear_selected(responses);
ImaginateToolFsmState::Ready
}
(_, ImaginateToolMessage::Abort) => ImaginateToolFsmState::Ready,
_ => self,
}
}
@@ -1,18 +1,18 @@
use std::vec;
use super::tool_prelude::*;
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::overlay_renderer::OverlayRenderer;
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::transformation_cage::{add_bounding_box, remove_bounding_box, update_bounding_box};
use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use graphene_core::renderer::Quad;
use graphene_core::vector::{ManipulatorPointId, SelectedType};
use std::vec;
#[derive(Default)]
pub struct PathTool {
fsm_state: PathToolFsmState,
@@ -27,7 +27,7 @@ pub enum PathToolMessage {
#[remain::unsorted]
Abort,
#[remain::unsorted]
DocumentIsDirty,
Overlays(OverlayContext),
#[remain::unsorted]
SelectionChanged,
@@ -182,9 +182,9 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
impl ToolTransition for PathTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
document_dirty: Some(PathToolMessage::DocumentIsDirty.into()),
tool_abort: Some(PathToolMessage::Abort.into()),
selection_changed: Some(PathToolMessage::SelectionChanged.into()),
overlay_provider: Some(|overlay_context| PathToolMessage::Overlays(overlay_context).into()),
..Default::default()
}
}
@@ -205,34 +205,18 @@ struct PathToolData {
previous_mouse_position: DVec2,
alt_debounce: bool,
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).
/// The available information varies depending on whether `None`, `One`, or `Multiple` points are currently selected.
selection_status: SelectionStatus,
}
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(
&mut self,
shift: bool,
shape_editor: &mut ShapeState,
document: &DocumentMessageHandler,
input: &InputPreprocessorMessageHandler,
shape_overlay: &mut OverlayRenderer,
responses: &mut VecDeque<Message>,
) -> PathToolFsmState {
self.opposing_handle_lengths = None;
@@ -241,7 +225,7 @@ impl PathToolData {
// 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) {
self.start_dragging_point(selected_points, input, document, responses);
self.refresh_overlays(document, shape_editor, shape_overlay, responses);
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Dragging
}
@@ -261,7 +245,6 @@ impl PathToolData {
// Start drawing a box
self.drag_start_pos = input.mouse.position;
self.previous_mouse_position = input.mouse.position;
self.drag_box_overlay_layer = Some(add_bounding_box(responses));
PathToolFsmState::DrawingBox
}
@@ -328,13 +311,7 @@ impl Fsm for PathToolFsmState {
type ToolOptions = ();
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
document,
input,
shape_editor,
shape_overlay,
..
} = tool_action_data;
let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data;
let ToolMessage::Path(event) = event else {
return self;
};
@@ -345,17 +322,17 @@ impl Fsm for PathToolFsmState {
let target_layers = document.metadata().selected_layers().collect();
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);
// This can happen in any state (which is why we return self)
self
}
(_, PathToolMessage::DocumentIsDirty) => {
// When the document has moved / needs to be redraw, re-render the overlays
// 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);
(_, PathToolMessage::Overlays(mut overlay_context)) => {
path_overlays(document, shape_editor, &mut overlay_context);
if self == Self::DrawingBox {
overlay_context.quad(Quad::from_box([tool_data.drag_start_pos, tool_data.previous_mouse_position]))
}
responses.add(PathToolMessage::SelectedPointUpdated);
@@ -366,11 +343,11 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::DragStart { add_to_selection }) => {
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 { .. }) => {
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
}
@@ -389,9 +366,8 @@ impl Fsm for PathToolFsmState {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
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);
};
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
}
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
@@ -404,11 +380,10 @@ impl Fsm for PathToolFsmState {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
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);
};
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
}
responses.add(OverlaysMessage::Draw);
responses.add(PathToolMessage::SelectedPointUpdated);
PathToolFsmState::Ready
}
@@ -426,7 +401,7 @@ impl Fsm for PathToolFsmState {
if clicked_selected {
shape_editor.deselect_all();
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
}
(_, PathToolMessage::Abort) => {
// TODO Tell overlay manager to remove the overlays
shape_overlay.clear_all_overlays(responses);
remove_bounding_box(tool_data.drag_box_overlay_layer.take(), responses);
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
@@ -469,7 +442,7 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::SelectAllPoints) => {
shape_editor.select_all_points(&document.document_legacy);
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
@@ -1,6 +1,8 @@
use super::tool_prelude::*;
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
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::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
@@ -43,13 +45,13 @@ pub enum PenToolMessage {
#[remain::unsorted]
CanvasTransformed,
#[remain::unsorted]
DocumentIsDirty,
#[remain::unsorted]
Abort,
#[remain::unsorted]
SelectionChanged,
#[remain::unsorted]
WorkingColorChanged,
#[remain::unsorted]
Overlays(OverlayContext),
// Tool-specific messages
Confirm,
@@ -184,10 +186,11 @@ impl ToolTransition for PenTool {
fn event_to_message_map(&self) -> EventToMessageMap {
EventToMessageMap {
canvas_transformed: Some(PenToolMessage::CanvasTransformed.into()),
document_dirty: Some(PenToolMessage::DocumentIsDirty.into()),
tool_abort: Some(PenToolMessage::Abort.into()),
selection_changed: Some(PenToolMessage::SelectionChanged.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,
render_data,
shape_editor,
shape_overlay,
..
} = 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);
self
}
(_, PenToolMessage::DocumentIsDirty) => {
// When the document has moved / needs to be redraw, re-render the overlays
// 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);
}
(_, PenToolMessage::SelectionChanged) => {
responses.add(OverlaysMessage::Draw);
self
}
(_, PenToolMessage::SelectionChanged) => {
// 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);
}
(_, PenToolMessage::Overlays(mut overlay_context)) => {
path_overlays(document, shape_editor, &mut overlay_context);
// 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
}
(_, PenToolMessage::WorkingColorChanged) => {
@@ -660,8 +651,7 @@ impl Fsm for PenToolFsmState {
PenToolFsmState::Ready
}
(_, PenToolMessage::Abort) => {
// Clean up overlays
shape_overlay.clear_all_overlays(responses);
responses.add(OverlaysMessage::Draw);
self
}
@@ -1,18 +1,18 @@
#![allow(clippy::too_many_arguments)]
use super::tool_prelude::*;
use crate::consts::{ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
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::transformation::Selected;
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::snapping::{self, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*;
use document_legacy::document::Document;
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::LayerId;
use document_legacy::Operation;
use graphene_core::renderer::Quad;
use std::fmt;
@@ -62,6 +62,8 @@ pub enum SelectToolMessage {
DocumentIsDirty,
#[remain::unsorted]
SelectionChanged,
#[remain::unsorted]
Overlays(OverlayContext),
// Tool-specific messages
DragStart {
@@ -241,6 +243,7 @@ impl ToolTransition for SelectTool {
document_dirty: Some(SelectToolMessage::DocumentIsDirty.into()),
tool_abort: Some(SelectToolMessage::Abort.into()),
selection_changed: Some(SelectToolMessage::SelectionChanged.into()),
overlay_provider: Some(|overlay_context| SelectToolMessage::Overlays(overlay_context).into()),
..Default::default()
}
}
@@ -266,9 +269,7 @@ struct SelectToolData {
select_single_layer: Option<LayerNodeIdentifier>,
has_dragged: bool,
not_duplicated_layers: Option<Vec<LayerNodeIdentifier>>,
drag_box_overlay_layer: Option<Vec<LayerId>>,
path_outlines: PathOutline,
bounding_box_overlays: Option<BoundingBoxOverlays>,
bounding_box_manager: Option<BoundingBoxManager>,
snap_manager: SnapManager,
cursor: MouseCursorIcon,
pivot: Pivot,
@@ -387,30 +388,53 @@ impl Fsm for SelectToolFsmState {
return self;
};
match (self, event) {
(_, SelectToolMessage::DocumentIsDirty | SelectToolMessage::SelectionChanged) => {
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
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_count = selected_layers_count;
tool_data.path_outlines.update_selected(document.document_legacy.selected_visible_layers(), document, responses);
tool_data.path_outlines.intersect_test_hovered(input, document, responses);
// Outline selected layers
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()) {
(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));
// Get the layer the user is hovering over
let click = document.document_legacy.click(input.mouse.position, &document.document_legacy.document_network);
let not_selected_click = click.filter(|&hovered_layer| !document.metadata().selected_layers_contains(hovered_layer));
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;
bounding_box_overlays.transform = DAffine2::IDENTITY;
// Update bounds
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
}
@@ -426,12 +450,10 @@ impl Fsm for SelectToolFsmState {
self
}
(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_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);
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
@@ -444,7 +466,7 @@ impl Fsm for SelectToolFsmState {
});
let rotating_bounds = tool_data
.bounding_box_overlays
.bounding_box_manager
.as_ref()
.map(|bounding_box| bounding_box.check_rotate(input.mouse.position))
.unwrap_or_default();
@@ -479,8 +501,9 @@ impl Fsm for SelectToolFsmState {
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;
bounds.original_bound_transform = bounds.transform;
tool_data.layers_dragging.retain(|layer| document.document_network.nodes.contains_key(&layer.to_node()));
let mut selected = Selected::new(
@@ -499,7 +522,7 @@ impl Fsm for SelectToolFsmState {
} else if rotating_bounds {
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()));
let mut selected = Selected::new(
&mut bounds.original_transforms,
@@ -556,7 +579,6 @@ impl Fsm for SelectToolFsmState {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
}
tool_data.drag_box_overlay_layer = Some(add_bounding_box(responses));
SelectToolFsmState::DrawingBox
}
};
@@ -601,7 +623,7 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Dragging
}
(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 {
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 (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
let (delta, mut _pivot) = movement.bounds_to_scale_transform(position, size);
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 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()));
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::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 start_offset = tool_data.drag_start - 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 { .. }) => {
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::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
if tool_data.pivot.is_over(input.mouse.position) {
cursor = MouseCursorIcon::Move;
}
// Generate the select outline (but not if the user is going to use the bound overlays)
if cursor == MouseCursorIcon::Default {
tool_data.path_outlines.intersect_test_hovered(input, document, responses);
} else {
tool_data.path_outlines.clear_hovered(responses);
}
// Generate the hover outline
responses.add(OverlaysMessage::Draw);
if tool_data.cursor != cursor {
tool_data.cursor = cursor;
@@ -748,7 +763,7 @@ impl Fsm for SelectToolFsmState {
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();
}
@@ -761,7 +776,7 @@ impl Fsm for SelectToolFsmState {
};
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();
}
@@ -789,13 +804,8 @@ impl Fsm for SelectToolFsmState {
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, SelectToolMessage::Enter) => {
@@ -814,18 +824,13 @@ impl Fsm for SelectToolFsmState {
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
tool_data.snap_manager.cleanup(responses);
responses.add(DocumentMessage::Undo);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
responses.add(OverlaysMessage::Draw);
SelectToolFsmState::Ready
}
(_, 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()));
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(
&mut bounding_box_overlays.original_transforms,
&mut bounding_box_overlays.opposite_pivot,
@@ -837,13 +842,9 @@ impl Fsm for SelectToolFsmState {
);
selected.revert_operation();
bounding_box_overlays.delete(responses);
}
tool_data.path_outlines.clear_hovered(responses);
tool_data.path_outlines.clear_selected(responses);
tool_data.pivot.clear_overlays(responses);
responses.add(OverlaysMessage::Draw);
tool_data.snap_manager.cleanup(responses);
SelectToolFsmState::Ready
@@ -1,16 +1,15 @@
#![allow(clippy::too_many_arguments)]
use super::tool_prelude::*;
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::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
use document_legacy::document_metadata::LayerNodeIdentifier;
use document_legacy::intersection::Quad;
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
use document_legacy::LayerId;
use document_legacy::Operation;
use document_legacy::layers::style::{Fill, RenderData};
use graph_craft::document::value::TaggedValue;
use graphene_core::renderer::Quad;
use graphene_core::text::{load_face, Font};
use graphene_core::Color;
@@ -50,6 +49,8 @@ pub enum TextToolMessage {
DocumentIsDirty,
#[remain::unsorted]
WorkingColorChanged,
#[remain::unsorted]
Overlays(OverlayContext),
// Tool-specific messages
CommitText,
@@ -194,6 +195,7 @@ impl ToolTransition for TextTool {
tool_abort: Some(TextToolMessage::Abort.into()),
selection_changed: Some(TextToolMessage::DocumentIsDirty.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)]
struct TextToolData {
layer: LayerNodeIdentifier,
overlays: Vec<Vec<LayerId>>,
editing_text: Option<EditingText>,
new_text: String,
}
@@ -317,31 +318,10 @@ impl TextToolData {
// Removing old text as editable
self.set_editing(false, render_data, document, responses);
resize_overlays(&mut self.overlays, responses, 0);
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]> {
let editing_text = self.editing_text.as_ref()?;
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> {
let mut selected_layers = document.metadata().selected_layers();
@@ -440,17 +373,35 @@ impl Fsm for TextToolFsmState {
return self;
};
match (self, event) {
(TextToolFsmState::Editing, TextToolMessage::DocumentIsDirty) => {
(TextToolFsmState::Editing, TextToolMessage::Overlays(mut overlay_context)) => {
responses.add(FrontendMessage::DisplayEditableTextboxTransform {
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
}
(state, TextToolMessage::DocumentIsDirty) => {
update_overlays(document, tool_data, responses, render_data);
(_, TextToolMessage::Overlays(mut overlay_context)) => {
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) => {
tool_data.editing_text = Some(EditingText {
@@ -477,8 +428,6 @@ impl Fsm for TextToolFsmState {
tool_data.set_editing(false, render_data, document, responses);
}
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::CommitText) => {
@@ -497,13 +446,11 @@ impl Fsm for TextToolFsmState {
tool_data.set_editing(false, render_data, document, responses);
resize_overlays(&mut tool_data.overlays, responses, 0);
TextToolFsmState::Ready
}
(TextToolFsmState::Editing, TextToolMessage::UpdateBounds { new_text }) => {
tool_data.new_text = new_text;
tool_data.update_bounds_overlay(document, render_data, responses);
responses.add(OverlaysMessage::Draw);
TextToolFsmState::Editing
}
(_, TextToolMessage::WorkingColorChanged) => {