mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 04:28:12 +08:00
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:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
9e06e70aa2
commit
c42d030f18
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user