Add control over gradient stops (#834)
* Add gradient stops * Better step adding * Steps can be dragged past each other * Swapping and switching gradient/fill * Fix convert to gradient * Skip non finite transforms for overlays Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
a1e061fa14
commit
1462d2b662
|
|
@ -1,4 +1,5 @@
|
|||
use super::utility_types::TransformOp;
|
||||
use crate::application::generate_uuid;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::layout::utility_types::widgets::assist_widgets::PivotAssist;
|
||||
|
|
@ -10,9 +11,10 @@ use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel,
|
|||
use crate::messages::portfolio::utility_types::{ImaginateServerStatus, PersistentData};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::pick_layer_safe_imaginate_resolution;
|
||||
use graphene::layers::imaginate_layer::{ImaginateLayer, ImaginateSamplingMethod, ImaginateStatus};
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
||||
use graphene::layers::text_layer::{FontCache, TextLayer};
|
||||
|
||||
|
|
@ -1137,7 +1139,7 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
|
|||
RadioEntryData {
|
||||
value: "linear".into(),
|
||||
label: "Linear".into(),
|
||||
tooltip: "Linear Gradient".into(),
|
||||
tooltip: "Linear gradient changes colors from one side to the other along a line".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyFill {
|
||||
fill: Fill::Gradient(cloned_gradient_linear.clone()),
|
||||
|
|
@ -1149,7 +1151,7 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
|
|||
RadioEntryData {
|
||||
value: "radial".into(),
|
||||
label: "Radial".into(),
|
||||
tooltip: "Radial Gradient".into(),
|
||||
tooltip: "Radial gradient changes colors from the inside to the outside of a circular area".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyFill {
|
||||
fill: Fill::Gradient(cloned_gradient_radial.clone()),
|
||||
|
|
@ -1165,60 +1167,210 @@ fn node_gradient_type(gradient: &Gradient) -> LayoutGroup {
|
|||
}
|
||||
}
|
||||
|
||||
fn node_gradient_color(gradient: &Gradient, percent_label: &'static str, position: usize) -> LayoutGroup {
|
||||
fn node_gradient_color(gradient: &Gradient, position: usize) -> LayoutGroup {
|
||||
let gradient_clone = Rc::new(gradient.clone());
|
||||
let gradient_2 = gradient_clone.clone();
|
||||
let gradient_3 = gradient_clone.clone();
|
||||
let send_fill_message = move |new_gradient: Gradient| PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into();
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: format!("Gradient: {}", percent_label),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||
value: gradient_clone.positions[position].1,
|
||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
||||
let mut new_gradient = (*gradient_clone).clone();
|
||||
new_gradient.positions[position].1 = text_input.value;
|
||||
send_fill_message(new_gradient)
|
||||
}),
|
||||
..ColorInput::default()
|
||||
})),
|
||||
],
|
||||
|
||||
let value = format!("Gradient: {:.0}%", gradient_clone.positions[position].0 * 100.);
|
||||
let mut widgets = vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value,
|
||||
tooltip: "Adjustable by dragging the gradient stops in the viewport with the Gradient tool active".into(),
|
||||
..TextLabel::default()
|
||||
}))];
|
||||
widgets.push(WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})));
|
||||
widgets.push(WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||
value: gradient_clone.positions[position].1,
|
||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
||||
let mut new_gradient = (*gradient_clone).clone();
|
||||
new_gradient.positions[position].1 = text_input.value;
|
||||
send_fill_message(new_gradient)
|
||||
}),
|
||||
..ColorInput::default()
|
||||
})));
|
||||
|
||||
let mut skip_separator = false;
|
||||
// Remove button
|
||||
if gradient.positions.len() != position + 1 && position != 0 {
|
||||
let on_update = WidgetCallback::new(move |_| {
|
||||
let mut new_gradient = (*gradient_3).clone();
|
||||
new_gradient.positions.remove(position);
|
||||
send_fill_message(new_gradient)
|
||||
});
|
||||
|
||||
skip_separator = true;
|
||||
widgets.push(WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})));
|
||||
widgets.push(WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "Remove".to_string(),
|
||||
tooltip: "Remove this gradient stop".to_string(),
|
||||
size: 16,
|
||||
on_update,
|
||||
..Default::default()
|
||||
})));
|
||||
}
|
||||
// Add button
|
||||
if gradient.positions.len() != position + 1 {
|
||||
let on_update = WidgetCallback::new(move |_| {
|
||||
let mut gradient = (*gradient_2).clone();
|
||||
|
||||
let get_color = |index: usize| match (gradient.positions[index].1, gradient.positions.get(index + 1).and_then(|x| x.1)) {
|
||||
(Some(a), Some(b)) => Color::from_rgbaf32((a.r() + b.r()) / 2., (a.g() + b.g()) / 2., (a.b() + b.b()) / 2., ((a.a() + b.a()) / 2.).clamp(0., 1.)),
|
||||
(Some(v), _) | (_, Some(v)) => Some(v),
|
||||
_ => Some(Color::WHITE),
|
||||
};
|
||||
let get_pos = |index: usize| (gradient.positions[index].0 + gradient.positions.get(index + 1).map(|v| v.0).unwrap_or(1.)) / 2.;
|
||||
|
||||
gradient.positions.push((get_pos(position), get_color(position)));
|
||||
|
||||
gradient.positions.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
|
||||
send_fill_message(gradient)
|
||||
});
|
||||
|
||||
if !skip_separator {
|
||||
widgets.push(WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})));
|
||||
}
|
||||
widgets.push(WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "Add".to_string(),
|
||||
tooltip: "Add a gradient stop after this".to_string(),
|
||||
size: 16,
|
||||
on_update,
|
||||
..Default::default()
|
||||
})));
|
||||
}
|
||||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
|
||||
fn node_section_fill(fill: &Fill) -> Option<LayoutGroup> {
|
||||
let initial_color = if let Fill::Solid(color) = fill { *color } else { Color::BLACK };
|
||||
|
||||
match fill {
|
||||
Fill::Solid(_) | Fill::None => Some(LayoutGroup::Section {
|
||||
name: "Fill".into(),
|
||||
layout: vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Color".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||
value: if let Fill::Solid(color) = fill { Some(*color) } else { None },
|
||||
on_update: WidgetCallback::new(|text_input: &ColorInput| {
|
||||
let fill = if let Some(value) = text_input.value { Fill::Solid(value) } else { Fill::None };
|
||||
PropertiesPanelMessage::ModifyFill { fill }.into()
|
||||
}),
|
||||
..ColorInput::default()
|
||||
})),
|
||||
],
|
||||
}],
|
||||
layout: vec![
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Color".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||
value: if let Fill::Solid(color) = fill { Some(*color) } else { None },
|
||||
on_update: WidgetCallback::new(|text_input: &ColorInput| {
|
||||
let fill = if let Some(value) = text_input.value { Fill::Solid(value) } else { Fill::None };
|
||||
PropertiesPanelMessage::ModifyFill { fill }.into()
|
||||
}),
|
||||
..ColorInput::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Use Gradient".into(),
|
||||
tooltip: "Change this fill from a solid color to a gradient".into(),
|
||||
on_update: WidgetCallback::new(move |_: &TextButton| {
|
||||
let (r, g, b, _) = initial_color.components();
|
||||
let opposite_color = Color::from_rgbaf32(1. - r, 1. - g, 1. - b, 1.).unwrap();
|
||||
|
||||
PropertiesPanelMessage::ModifyFill {
|
||||
fill: Fill::Gradient(Gradient::new(
|
||||
DVec2::new(0., 0.5),
|
||||
initial_color,
|
||||
DVec2::new(1., 0.5),
|
||||
opposite_color,
|
||||
DAffine2::IDENTITY,
|
||||
generate_uuid(),
|
||||
GradientType::Linear,
|
||||
)),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..TextButton::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
Fill::Gradient(gradient) => Some(LayoutGroup::Section {
|
||||
name: "Fill".into(),
|
||||
layout: vec![node_gradient_type(gradient), node_gradient_color(gradient, "0%", 0), node_gradient_color(gradient, "100%", 1)],
|
||||
layout: {
|
||||
let cloned_gradient = gradient.clone();
|
||||
let first_color = gradient.positions.get(0).unwrap_or(&(0., None)).1;
|
||||
|
||||
let mut layout = vec![node_gradient_type(gradient)];
|
||||
layout.extend((0..gradient.positions.len()).map(|pos| node_gradient_color(gradient, pos)));
|
||||
layout.push(LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Invert".into(),
|
||||
icon: Some("Swap".into()),
|
||||
tooltip: "Reverse the order of each color stop".into(),
|
||||
on_update: WidgetCallback::new(move |_: &TextButton| {
|
||||
let mut new_gradient = cloned_gradient.clone();
|
||||
new_gradient.positions = new_gradient.positions.iter().map(|(distance, color)| (1. - distance, *color)).collect();
|
||||
new_gradient.positions.reverse();
|
||||
PropertiesPanelMessage::ModifyFill { fill: Fill::Gradient(new_gradient) }.into()
|
||||
}),
|
||||
..TextButton::default()
|
||||
})),
|
||||
],
|
||||
});
|
||||
layout.push(LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: "Use Solid Color".into(),
|
||||
tooltip: "Change this fill from a gradient to a solid color, keeping the 0% stop color".into(),
|
||||
on_update: WidgetCallback::new(move |_: &TextButton| {
|
||||
PropertiesPanelMessage::ModifyFill {
|
||||
fill: Fill::Solid(first_color.unwrap_or_default()),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..TextButton::default()
|
||||
})),
|
||||
],
|
||||
});
|
||||
layout
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &Document
|
|||
pub struct GradientOverlay {
|
||||
pub handles: [Vec<LayerId>; 2],
|
||||
pub line: Vec<LayerId>,
|
||||
pub steps: Vec<Vec<LayerId>>,
|
||||
path: Vec<LayerId>,
|
||||
transform: DAffine2,
|
||||
gradient: Gradient,
|
||||
|
|
@ -205,22 +206,35 @@ impl GradientOverlay {
|
|||
path
|
||||
}
|
||||
|
||||
pub fn new(fill: &Gradient, dragging_start: Option<bool>, path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, font_cache: &FontCache) -> Self {
|
||||
pub fn new(
|
||||
fill: &Gradient,
|
||||
dragging: Option<GradientDragTarget>,
|
||||
path: &[LayerId],
|
||||
layer: &Layer,
|
||||
document: &DocumentMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
font_cache: &FontCache,
|
||||
) -> Self {
|
||||
let transform = gradient_space_transform(path, layer, document, font_cache);
|
||||
let Gradient { start, end, .. } = fill;
|
||||
let Gradient { start, end, positions, .. } = fill;
|
||||
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_start == Some(true)),
|
||||
Self::generate_overlay_handle(end, responses, dragging_start == Some(false)),
|
||||
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();
|
||||
|
||||
let path = path.to_vec();
|
||||
let gradient = fill.clone();
|
||||
|
||||
Self {
|
||||
handles,
|
||||
steps,
|
||||
line,
|
||||
path,
|
||||
transform,
|
||||
|
|
@ -233,6 +247,9 @@ impl GradientOverlay {
|
|||
let [start, end] = self.handles;
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: start }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: end }.into()).into());
|
||||
for step in self.steps {
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: step }.into()).into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_gradient_start(&self) -> DVec2 {
|
||||
|
|
@ -244,13 +261,21 @@ impl GradientOverlay {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)]
|
||||
pub enum GradientDragTarget {
|
||||
Start,
|
||||
#[default]
|
||||
End,
|
||||
Step(usize),
|
||||
}
|
||||
|
||||
/// Contains information about the selected gradient handle
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct SelectedGradient {
|
||||
path: Vec<LayerId>,
|
||||
transform: DAffine2,
|
||||
gradient: Gradient,
|
||||
dragging_start: bool,
|
||||
dragging: GradientDragTarget,
|
||||
}
|
||||
|
||||
impl SelectedGradient {
|
||||
|
|
@ -260,7 +285,7 @@ impl SelectedGradient {
|
|||
path: path.to_vec(),
|
||||
transform,
|
||||
gradient,
|
||||
dragging_start: false,
|
||||
dragging: GradientDragTarget::End,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,8 +297,8 @@ impl SelectedGradient {
|
|||
pub fn update_gradient(&mut self, mut mouse: DVec2, responses: &mut VecDeque<Message>, snap_rotate: bool, gradient_type: GradientType) {
|
||||
self.gradient.gradient_type = gradient_type;
|
||||
|
||||
if snap_rotate {
|
||||
let point = if self.dragging_start {
|
||||
if snap_rotate && matches!(self.dragging, GradientDragTarget::End | GradientDragTarget::Start) {
|
||||
let point = if self.dragging == GradientDragTarget::Start {
|
||||
self.transform.transform_point2(self.gradient.end)
|
||||
} else {
|
||||
self.transform.transform_point2(self.gradient.start)
|
||||
|
|
@ -293,10 +318,22 @@ impl SelectedGradient {
|
|||
|
||||
mouse = self.transform.inverse().transform_point2(mouse);
|
||||
|
||||
if self.dragging_start {
|
||||
self.gradient.start = mouse;
|
||||
} else {
|
||||
self.gradient.end = mouse;
|
||||
match self.dragging {
|
||||
GradientDragTarget::Start => self.gradient.start = mouse,
|
||||
GradientDragTarget::End => self.gradient.end = mouse,
|
||||
GradientDragTarget::Step(s) => {
|
||||
// Calculate the new position by finding the closest point on the line
|
||||
let new_pos = ((self.gradient.end - self.gradient.start).angle_between(mouse - self.gradient.start)).cos() * self.gradient.start.distance(mouse)
|
||||
/ self.gradient.start.distance(self.gradient.end);
|
||||
|
||||
// Should not go off end but can swap (like inscape)
|
||||
let clamped = new_pos.clamp(0., 1.);
|
||||
self.gradient.positions[s].0 = clamped;
|
||||
let new_pos = self.gradient.positions[s];
|
||||
|
||||
self.gradient.positions.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
self.dragging = GradientDragTarget::Step(self.gradient.positions.iter().position(|x| *x == new_pos).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
self.gradient.transform = self.transform;
|
||||
|
|
@ -348,16 +385,17 @@ impl Fsm for GradientToolFsmState {
|
|||
}
|
||||
|
||||
for path in document.selected_visible_layers() {
|
||||
if !document.graphene_document.multiply_transforms(path).unwrap().inverse().is_finite() {
|
||||
continue;
|
||||
}
|
||||
let layer = document.graphene_document.layer(path).unwrap();
|
||||
|
||||
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
|
||||
let dragging_start = tool_data
|
||||
let dragging = tool_data
|
||||
.selected_gradient
|
||||
.as_ref()
|
||||
.and_then(|selected| if selected.path == path { Some(selected.dragging_start) } else { None });
|
||||
tool_data
|
||||
.gradient_overlays
|
||||
.push(GradientOverlay::new(gradient, dragging_start, path, layer, document, responses, font_cache))
|
||||
.and_then(|selected| if selected.path == path { Some(selected.dragging) } else { None });
|
||||
tool_data.gradient_overlays.push(GradientOverlay::new(gradient, dragging, path, layer, document, responses, font_cache))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -371,25 +409,35 @@ impl Fsm for GradientToolFsmState {
|
|||
|
||||
let mut dragging = false;
|
||||
for overlay in &tool_data.gradient_overlays {
|
||||
if overlay.evaluate_gradient_start().distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, font_cache);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging_start: true,
|
||||
})
|
||||
// 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));
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: GradientDragTarget::Step(index),
|
||||
})
|
||||
}
|
||||
}
|
||||
if overlay.evaluate_gradient_end().distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, font_cache);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging_start: false,
|
||||
})
|
||||
|
||||
// Check dragging start or end handle
|
||||
for (pos, dragging_target) in [
|
||||
(overlay.evaluate_gradient_start(), GradientDragTarget::Start),
|
||||
(overlay.evaluate_gradient_end(), GradientDragTarget::End),
|
||||
] {
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
start_snap(&mut tool_data.snap_manager, document, font_cache);
|
||||
tool_data.selected_gradient = Some(SelectedGradient {
|
||||
path: overlay.path.clone(),
|
||||
transform: overlay.transform,
|
||||
gradient: overlay.gradient.clone(),
|
||||
dragging: dragging_target,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
if dragging {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<rect width="8" height="2" x="2" y="5" />
|
||||
<rect width="2" height="8" x="5" y="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 154 B |
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
|
||||
<rect width="8" height="2" x="2" y="5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 111 B |
|
|
@ -8,6 +8,7 @@ const GRAPHICS = {
|
|||
} as const;
|
||||
|
||||
// 12px Solid
|
||||
import Add from "@/../assets/icon-12px-solid/add.svg";
|
||||
import Checkmark from "@/../assets/icon-12px-solid/checkmark.svg";
|
||||
import CloseX from "@/../assets/icon-12px-solid/close-x.svg";
|
||||
import DropdownArrow from "@/../assets/icon-12px-solid/dropdown-arrow.svg";
|
||||
|
|
@ -30,6 +31,7 @@ import KeyboardSpace from "@/../assets/icon-12px-solid/keyboard-space.svg";
|
|||
import KeyboardTab from "@/../assets/icon-12px-solid/keyboard-tab.svg";
|
||||
import Link from "@/../assets/icon-12px-solid/link.svg";
|
||||
import Overlays from "@/../assets/icon-12px-solid/overlays.svg";
|
||||
import Remove from "@/../assets/icon-12px-solid/remove.svg";
|
||||
import ResetColors from "@/../assets/icon-12px-solid/reset-colors.svg";
|
||||
import Snapping from "@/../assets/icon-12px-solid/snapping.svg";
|
||||
import Swap from "@/../assets/icon-12px-solid/swap.svg";
|
||||
|
|
@ -41,6 +43,7 @@ import WindowButtonWinMinimize from "@/../assets/icon-12px-solid/window-button-w
|
|||
import WindowButtonWinRestoreDown from "@/../assets/icon-12px-solid/window-button-win-restore-down.svg";
|
||||
|
||||
const SOLID_12PX = {
|
||||
Add: { component: Add, size: 12 },
|
||||
Checkmark: { component: Checkmark, size: 12 },
|
||||
CloseX: { component: CloseX, size: 12 },
|
||||
DropdownArrow: { component: DropdownArrow, size: 12 },
|
||||
|
|
@ -63,6 +66,7 @@ const SOLID_12PX = {
|
|||
KeyboardTab: { component: KeyboardTab, size: 12 },
|
||||
Link: { component: Link, size: 12 },
|
||||
Overlays: { component: Overlays, size: 12 },
|
||||
Remove: { component: Remove, size: 12 },
|
||||
ResetColors: { component: ResetColors, size: 12 },
|
||||
Snapping: { component: Snapping, size: 12 },
|
||||
Swap: { component: Swap, size: 12 },
|
||||
|
|
|
|||
Loading…
Reference in New Issue