mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 06:58:13 +08:00
Add shape fill overlays when closing a path (Pen tool) or filling it (Fill tool) (#2521)
* Make the Pen tool show a path being closed by drawing a filled overlay when hovering the endpoint * Add to_css to color.rs * Check before unwrapping layer * Close if in the right place * Fix typo * Format code * Support discontinuous paths for closing preview * Code review * Denser fill lines * Fill tool preview with strip lines only and revert pen shape-closing opacity * Small adjustments to fill preview * Fix line width of fill preview * Use a pattern to preview the fill tool and fix canvas clearing * Update pattern * Simplify code * Format code * Use secondary color to preview fill if shift is pressed * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -218,7 +218,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
document_data.primary_color = color;
|
||||
|
||||
self.tool_state.document_tool_data.update_working_colors(responses); // TODO: Make this an event
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectRandomPrimaryColor => {
|
||||
// Select a random primary color (rgba) based on an UUID
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use graphene_core::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FillTool {
|
||||
fsm_state: FillToolFsmState,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Fill)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum FillToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
WorkingColorChanged,
|
||||
Overlays(OverlayContext),
|
||||
|
||||
// Tool-specific messages
|
||||
PointerMove,
|
||||
PointerUp,
|
||||
FillPrimaryColor,
|
||||
FillSecondaryColor,
|
||||
@@ -45,8 +50,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillToo
|
||||
FillToolFsmState::Ready => actions!(FillToolMessageDiscriminant;
|
||||
FillPrimaryColor,
|
||||
FillSecondaryColor,
|
||||
PointerMove,
|
||||
),
|
||||
FillToolFsmState::Filling => actions!(FillToolMessageDiscriminant;
|
||||
PointerMove,
|
||||
PointerUp,
|
||||
Abort,
|
||||
),
|
||||
@@ -58,6 +65,8 @@ impl ToolTransition for FillTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(FillToolMessage::Abort.into()),
|
||||
working_color_changed: Some(FillToolMessage::WorkingColorChanged.into()),
|
||||
overlay_provider: Some(|overlay_context| FillToolMessage::Overlays(overlay_context).into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -82,6 +91,23 @@ impl Fsm for FillToolFsmState {
|
||||
|
||||
let ToolMessage::Fill(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(_, FillToolMessage::Overlays(mut overlay_context)) => {
|
||||
// Choose the working color to preview
|
||||
let use_secondary = input.keyboard.get(Key::Shift as usize);
|
||||
let preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color };
|
||||
|
||||
// Get the layer the user is hovering over
|
||||
if let Some(layer) = document.click(input) {
|
||||
overlay_context.fill_path_pattern(document.metadata().layer_outline(layer), document.metadata().transform_to_viewport(layer), &preview_color);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(_, FillToolMessage::PointerMove | FillToolMessage::WorkingColorChanged) => {
|
||||
// Generate the hover outline
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(FillToolFsmState::Ready, color_event) => {
|
||||
let Some(layer_identifier) = document.click(input) else {
|
||||
return self;
|
||||
|
||||
@@ -1030,7 +1030,7 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
match self {
|
||||
Self::Drawing { selection_shape } => {
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
|
||||
use crate::consts::{COLOR_OVERLAY_BLUE, DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::MouseKeys;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
|
||||
@@ -15,7 +15,7 @@ use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::Color;
|
||||
use graphene_core::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PenTool {
|
||||
@@ -1614,6 +1614,54 @@ impl Fsm for PenToolFsmState {
|
||||
overlay_context.manipulator_anchor(next_anchor, false, None);
|
||||
}
|
||||
|
||||
// Display a filled overlay of the shape if the new point closes the path
|
||||
if let Some(latest_point) = tool_data.latest_point() {
|
||||
let handle_start = latest_point.handle_start;
|
||||
let handle_end = tool_data.handle_end.unwrap_or(tool_data.next_handle_start);
|
||||
let next_point = tool_data.next_point;
|
||||
let start = latest_point.id;
|
||||
|
||||
if let Some(layer) = layer {
|
||||
let mut vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
|
||||
|
||||
let closest_point = vector_data.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
|
||||
vector_data.point_domain.position_from_id(id).map_or(false, |pos| {
|
||||
let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
|
||||
dist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2)
|
||||
})
|
||||
});
|
||||
|
||||
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||
if let Some(end) = closest_point {
|
||||
let segment_id = SegmentId::generate();
|
||||
vector_data.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
|
||||
let grouped_segments = vector_data.auto_join_paths();
|
||||
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||
|
||||
let subpaths: Vec<_> = closed_paths
|
||||
.filter_map(|path| {
|
||||
let segments = path.edges.iter().filter_map(|edge| {
|
||||
vector_data
|
||||
.segment_domain
|
||||
.iter()
|
||||
.find(|(id, _, _, _)| id == &edge.id)
|
||||
.map(|(_, start, end, bezier)| if start == edge.start { (bezier, start, end) } else { (bezier.reversed(), end, start) })
|
||||
});
|
||||
vector_data.subpath_from_segments_ignore_discontinuities(segments)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
fill_color.insert(0, '#');
|
||||
overlay_context.fill_path(subpaths.iter(), transform, fill_color.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the overlays that visualize current snapping
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, ROTATE_INCREMENT, SELECTION_DRAG_ANGLE,
|
||||
SELECTION_TOLERANCE,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COMPASS_ROSE_HOVER_RING_DIAMETER, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, RESIZE_HANDLE_SIZE, ROTATE_INCREMENT,
|
||||
SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE,
|
||||
};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
@@ -759,7 +759,7 @@ impl Fsm for SelectToolFsmState {
|
||||
}
|
||||
|
||||
// Update the selection box
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{COLOR_OVERLAY_RED, DRAG_THRESHOLD};
|
||||
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_RED, DRAG_THRESHOLD};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
@@ -456,7 +456,7 @@ impl Fsm for TextToolFsmState {
|
||||
font_cache,
|
||||
..
|
||||
} = transition_data;
|
||||
let fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
let fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
.unwrap()
|
||||
.with_alpha(0.05)
|
||||
.to_rgba_hex_srgb();
|
||||
|
||||
Reference in New Issue
Block a user