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:
Keavon Chambers
2024-06-09 22:55:13 -07:00
committed by GitHub
co-authored by 0hypercube
parent 449729f1e1
commit a9a4b5cd19
48 changed files with 1380 additions and 664 deletions
@@ -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 }]))
+1 -1
View File
@@ -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())