mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 16:08:12 +08:00
Move gradient picking into the color picker (#1778)
* Gradient picker * Fix up color picker layout CSS problems * Begin hooking up SpectrumInput for gradient in the ColorPicker * Working gradient picking on the frontend only * Plumb FillColorChoice into the backend * Hook everything else up, just with a weird bug remaining * Fix some svelty reactivity issues * Add and remove stops * Cleanup * Rename type * Fill node document format upgrading * Fix lint * Polish the color picker UX and fix a bug --------- Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
co-authored by
0hypercube
parent
449729f1e1
commit
a9a4b5cd19
@@ -2,6 +2,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::Message;
|
||||
|
||||
use graphene_core::Color;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum ToolColorType {
|
||||
@@ -96,7 +97,9 @@ impl ToolColorOptions {
|
||||
widgets.push(radio);
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
||||
let color_button = ColorButton::new(self.active_color()).allow_none(color_allow_none).on_update(color_callback);
|
||||
let color_button = ColorButton::new(FillChoice::from_optional_color(self.active_color()))
|
||||
.allow_none(color_allow_none)
|
||||
.on_update(color_callback);
|
||||
widgets.push(color_button.widget_holder());
|
||||
|
||||
widgets
|
||||
|
||||
@@ -7,7 +7,7 @@ use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput,
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::{FillType, Gradient};
|
||||
use graphene_core::vector::style::Gradient;
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
@@ -95,38 +95,21 @@ pub fn get_colinear_manipulators(layer: LayerNodeIdentifier, document_network: &
|
||||
|
||||
/// Get the current gradient of a layer from the closest Fill node
|
||||
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
|
||||
let TaggedValue::FillType(FillType::Gradient) = inputs.get(1)?.as_value()? else {
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::GradientType(gradient_type) = inputs.get(3)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::DVec2(start) = inputs.get(4)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::DVec2(end) = inputs.get(5)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::DAffine2(transform) = inputs.get(6)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
let TaggedValue::GradientPositions(positions) = inputs.get(7)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(Gradient {
|
||||
start: *start,
|
||||
end: *end,
|
||||
transform: *transform,
|
||||
positions: positions.clone(),
|
||||
gradient_type: *gradient_type,
|
||||
})
|
||||
Some(gradient.clone())
|
||||
}
|
||||
|
||||
/// Get the current fill of a layer from the closest Fill node
|
||||
pub fn get_fill_color(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Color> {
|
||||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, document_network).find_node_inputs("Fill")?;
|
||||
let TaggedValue::Color(color) = inputs.get(2)?.as_value()? else {
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(*color)
|
||||
|
||||
@@ -152,7 +152,7 @@ impl LayoutHolder for BrushTool {
|
||||
false,
|
||||
|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value)).into(),
|
||||
|color: &ColorButton| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid())).into(),
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
||||
@@ -90,7 +90,7 @@ impl LayoutHolder for EllipseTool {
|
||||
true,
|
||||
|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -100,7 +100,7 @@ impl LayoutHolder for EllipseTool {
|
||||
true,
|
||||
|_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| EllipseToolMessage::UpdateOptions(EllipseOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -99,7 +99,7 @@ impl LayoutHolder for FreehandTool {
|
||||
true,
|
||||
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -109,7 +109,7 @@ impl LayoutHolder for FreehandTool {
|
||||
true,
|
||||
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -185,11 +185,11 @@ impl SelectedGradient {
|
||||
|
||||
// Should not go off end but can swap
|
||||
let clamped = new_pos.clamp(0., 1.);
|
||||
self.gradient.positions[s].0 = clamped;
|
||||
let new_pos = self.gradient.positions[s];
|
||||
self.gradient.stops.0[s].0 = clamped;
|
||||
let new_pos = self.gradient.stops.0[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.stops.0.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
self.dragging = GradientDragTarget::Step(self.gradient.stops.0.iter().position(|x| *x == new_pos).unwrap());
|
||||
}
|
||||
}
|
||||
self.render_gradient(responses);
|
||||
@@ -256,14 +256,14 @@ impl Fsm for GradientToolFsmState {
|
||||
.filter(|selected| selected.layer.map_or(false, |selected_layer| selected_layer == layer))
|
||||
.map(|selected| selected.dragging);
|
||||
|
||||
let Gradient { start, end, positions, .. } = gradient;
|
||||
let Gradient { start, end, stops, .. } = gradient;
|
||||
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
||||
|
||||
overlay_context.line(start, end, None, None);
|
||||
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start));
|
||||
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End));
|
||||
|
||||
for (index, (position, _)) in positions.into_iter().enumerate() {
|
||||
for (index, (position, _)) in stops.0.into_iter().enumerate() {
|
||||
if position.abs() < f64::EPSILON * 1000. || (1. - position).abs() < f64::EPSILON * 1000. {
|
||||
continue;
|
||||
}
|
||||
@@ -280,32 +280,31 @@ impl Fsm for GradientToolFsmState {
|
||||
};
|
||||
|
||||
// Skip if invalid gradient
|
||||
if selected_gradient.gradient.positions.len() < 2 {
|
||||
if selected_gradient.gradient.stops.0.len() < 2 {
|
||||
return self;
|
||||
}
|
||||
|
||||
// Remove the selected point
|
||||
match selected_gradient.dragging {
|
||||
GradientDragTarget::Start => selected_gradient.gradient.positions.remove(0),
|
||||
GradientDragTarget::End => selected_gradient.gradient.positions.pop().unwrap(),
|
||||
GradientDragTarget::Step(index) => selected_gradient.gradient.positions.remove(index),
|
||||
GradientDragTarget::Start => selected_gradient.gradient.stops.0.remove(0),
|
||||
GradientDragTarget::End => selected_gradient.gradient.stops.0.pop().unwrap(),
|
||||
GradientDragTarget::Step(index) => selected_gradient.gradient.stops.0.remove(index),
|
||||
};
|
||||
|
||||
// The gradient has only one point and so should become a fill
|
||||
if selected_gradient.gradient.positions.len() == 1 {
|
||||
if selected_gradient.gradient.stops.0.len() == 1 {
|
||||
if let Some(layer) = selected_gradient.layer {
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer,
|
||||
fill: Fill::Solid(selected_gradient.gradient.positions[0].1),
|
||||
fill: Fill::Solid(selected_gradient.gradient.stops.0[0].1),
|
||||
});
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Find the minimum and maximum positions
|
||||
let min_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
|
||||
let max_position = selected_gradient.gradient.positions.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
|
||||
let min_position = selected_gradient.gradient.stops.0.iter().map(|(pos, _)| *pos).reduce(f64::min).expect("No min");
|
||||
let max_position = selected_gradient.gradient.stops.0.iter().map(|(pos, _)| *pos).reduce(f64::max).expect("No max");
|
||||
|
||||
// Recompute the start and end position of the gradient (in viewport transform)
|
||||
let transform = selected_gradient.transform;
|
||||
@@ -315,7 +314,7 @@ impl Fsm for GradientToolFsmState {
|
||||
selected_gradient.gradient.end = transform.inverse().transform_point2(new_end);
|
||||
|
||||
// Remap the positions
|
||||
for (position, _) in selected_gradient.gradient.positions.iter_mut() {
|
||||
for (position, _) in selected_gradient.gradient.stops.0.iter_mut() {
|
||||
*position = (*position - min_position) / (max_position - min_position);
|
||||
}
|
||||
|
||||
@@ -369,7 +368,7 @@ impl Fsm for GradientToolFsmState {
|
||||
let transform = gradient_space_transform(layer, document);
|
||||
|
||||
// Check for dragging step
|
||||
for (index, (pos, _)) in gradient.positions.iter().enumerate() {
|
||||
for (index, (pos, _)) in gradient.stops.0.iter().enumerate() {
|
||||
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
|
||||
if pos.distance_squared(mouse) < tolerance {
|
||||
dragging = true;
|
||||
|
||||
@@ -87,7 +87,7 @@ impl LayoutHolder for LineTool {
|
||||
true,
|
||||
|_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| LineToolMessage::UpdateOptions(LineOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
);
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -107,7 +107,7 @@ impl LayoutHolder for PenTool {
|
||||
true,
|
||||
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -117,7 +117,7 @@ impl LayoutHolder for PenTool {
|
||||
true,
|
||||
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -133,7 +133,7 @@ impl LayoutHolder for PolygonTool {
|
||||
true,
|
||||
|_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -143,7 +143,7 @@ impl LayoutHolder for PolygonTool {
|
||||
true,
|
||||
|_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| PolygonToolMessage::UpdateOptions(PolygonOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -77,7 +77,7 @@ impl LayoutHolder for RectangleTool {
|
||||
true,
|
||||
|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -87,7 +87,7 @@ impl LayoutHolder for RectangleTool {
|
||||
true,
|
||||
|_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| RectangleToolMessage::UpdateOptions(RectangleOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -99,7 +99,7 @@ impl LayoutHolder for SplineTool {
|
||||
true,
|
||||
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -109,7 +109,7 @@ impl LayoutHolder for SplineTool {
|
||||
true,
|
||||
|_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value)).into(),
|
||||
|color: &ColorButton| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -127,7 +127,7 @@ impl LayoutHolder for TextTool {
|
||||
true,
|
||||
|_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorButton| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value)).into(),
|
||||
|color: &ColorButton| TextToolMessage::UpdateOptions(TextOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
));
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||
|
||||
@@ -141,7 +141,7 @@ impl DocumentToolData {
|
||||
},
|
||||
LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
IconButton::new("Swap", 16)
|
||||
IconButton::new("SwapVertical", 16)
|
||||
.tooltip("Swap")
|
||||
.tooltip_shortcut(action_keys!(ToolMessageDiscriminant::SwapColors))
|
||||
.on_update(|_| ToolMessage::SwapColors.into())
|
||||
|
||||
Reference in New Issue
Block a user