Skip to content

Commit 8117ddc

Browse files
authored
Add Gradient tool control bar buttons, Reverse Stops and Reverse Direction (#3830)
* Add Gradient tool control bar buttons, Reverse Stops and Reverse Direction * Consolidate reused gradient line updating code
1 parent b4679b0 commit 8117ddc

File tree

1 file changed

+126
-43
lines changed

1 file changed

+126
-43
lines changed

editor/src/messages/tool/tool_messages/gradient_tool.rs

Lines changed: 126 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ pub enum GradientToolMessage {
4444
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
4545
pub enum GradientOptionsUpdate {
4646
Type(GradientType),
47+
ReverseStops,
48+
ReverseDirection,
4749
}
4850

4951
impl ToolMetadata for GradientTool {
@@ -63,52 +65,27 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
6365
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
6466
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
6567
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
68+
69+
let has_gradient = has_gradient_on_selected_layers(context.document);
70+
if has_gradient != self.data.has_selected_gradient {
71+
self.data.has_selected_gradient = has_gradient;
72+
responses.add(ToolMessage::RefreshToolOptions);
73+
}
74+
6675
return;
6776
};
6877
match options {
6978
GradientOptionsUpdate::Type(gradient_type) => {
7079
self.options.gradient_type = gradient_type;
71-
let selected_layers: Vec<_> = context
72-
.document
73-
.network_interface
74-
.selected_nodes()
75-
.selected_visible_layers(&context.document.network_interface)
76-
.collect();
77-
78-
let mut transaction_started = false;
79-
for layer in selected_layers {
80-
if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
81-
continue;
82-
}
83-
84-
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)
85-
&& gradient.gradient_type != gradient_type
86-
{
87-
if !transaction_started {
88-
responses.add(DocumentMessage::StartTransaction);
89-
transaction_started = true;
90-
}
91-
gradient.gradient_type = gradient_type;
92-
responses.add(GraphOperationMessage::FillSet {
93-
layer,
94-
fill: Fill::Gradient(gradient),
95-
});
96-
}
97-
}
98-
99-
if transaction_started {
100-
responses.add(DocumentMessage::AddTransaction);
101-
}
102-
if let Some(selected_gradient) = &mut self.data.selected_gradient
103-
&& let Some(layer) = selected_gradient.layer
104-
&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)
105-
{
106-
selected_gradient.gradient.gradient_type = gradient_type;
107-
}
80+
apply_gradient_update(&mut self.data, context, responses, |g| g.gradient_type != gradient_type, |g| g.gradient_type = gradient_type);
10881
responses.add(ToolMessage::UpdateHints);
109-
responses.add(PropertiesPanelMessage::Refresh);
11082
responses.add(ToolMessage::UpdateCursor);
111-
responses.add(ToolMessage::RefreshToolOptions);
83+
}
84+
GradientOptionsUpdate::ReverseStops => {
85+
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| g.stops = g.stops.reversed());
86+
}
87+
GradientOptionsUpdate::ReverseDirection => {
88+
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
11289
}
11390
}
11491
}
@@ -142,7 +119,52 @@ impl LayoutHolder for GradientTool {
142119
.selected_index(Some((self.options.gradient_type == GradientType::Radial) as u32))
143120
.widget_instance();
144121

145-
Layout(vec![LayoutGroup::Row { widgets: vec![gradient_type] }])
122+
let reverse_stops = IconButton::new("Reverse", 24)
123+
.tooltip_label("Reverse Stops")
124+
.tooltip_description("Reverse the gradient color stops.")
125+
.disabled(!self.data.has_selected_gradient)
126+
.on_update(|_| {
127+
GradientToolMessage::UpdateOptions {
128+
options: GradientOptionsUpdate::ReverseStops,
129+
}
130+
.into()
131+
})
132+
.widget_instance();
133+
134+
let mut widgets = vec![gradient_type, Separator::new(SeparatorStyle::Unrelated).widget_instance(), reverse_stops];
135+
136+
if self.options.gradient_type == GradientType::Radial {
137+
let orientation = self
138+
.data
139+
.selected_gradient
140+
.as_ref()
141+
.map(|selected_gradient| {
142+
let (start, end) = (selected_gradient.gradient.start, selected_gradient.gradient.end);
143+
if (end.x - start.x).abs() > f64::EPSILON * 1e6 {
144+
end.x > start.x
145+
} else {
146+
(start.x + start.y) < (end.x + end.y)
147+
}
148+
})
149+
.unwrap_or(true);
150+
151+
let reverse_direction = IconButton::new(if orientation { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" }, 24)
152+
.tooltip_label("Reverse Direction")
153+
.tooltip_description("Reverse which end the gradient radiates from.")
154+
.disabled(!self.data.has_selected_gradient)
155+
.on_update(|_| {
156+
GradientToolMessage::UpdateOptions {
157+
options: GradientOptionsUpdate::ReverseDirection,
158+
}
159+
.into()
160+
})
161+
.widget_instance();
162+
163+
widgets.push(Separator::new(SeparatorStyle::Related).widget_instance());
164+
widgets.push(reverse_direction);
165+
}
166+
167+
Layout(vec![LayoutGroup::Row { widgets }])
146168
}
147169
}
148170

@@ -492,6 +514,7 @@ struct GradientToolData {
492514
auto_panning: AutoPanning,
493515
auto_pan_shift: DVec2,
494516
gradient_angle: f64,
517+
has_selected_gradient: bool,
495518
}
496519

497520
impl Fsm for GradientToolFsmState {
@@ -993,9 +1016,12 @@ impl Fsm for GradientToolFsmState {
9931016
}
9941017

9951018
// Initialize `gradient_angle` from the existing gradient so Ctrl (lock angle) works from the first mouse move
996-
if let Some(sg) = &tool_data.selected_gradient {
997-
let (vp_start, vp_end) = (sg.transform.transform_point2(sg.gradient.start), sg.transform.transform_point2(sg.gradient.end));
998-
let delta = match sg.dragging {
1019+
if let Some(selected_gradient) = &tool_data.selected_gradient {
1020+
let (vp_start, vp_end) = (
1021+
selected_gradient.transform.transform_point2(selected_gradient.gradient.start),
1022+
selected_gradient.transform.transform_point2(selected_gradient.gradient.end),
1023+
);
1024+
let delta = match selected_gradient.dragging {
9991025
// When dragging End, the fixed point is start and the mouse begins at end
10001026
GradientDragTarget::End => vp_start - vp_end,
10011027
// When dragging Start, the fixed point is end and the mouse begins at start
@@ -1318,6 +1344,63 @@ fn compute_selected_target(tool_data: &GradientToolData) -> GradientSelectedTarg
13181344
}
13191345
}
13201346

1347+
fn apply_gradient_update(
1348+
data: &mut GradientToolData,
1349+
context: &mut ToolActionMessageContext,
1350+
responses: &mut VecDeque<Message>,
1351+
condition: impl Fn(&Gradient) -> bool,
1352+
update: impl Fn(&mut Gradient),
1353+
) {
1354+
let selected_layers: Vec<_> = context
1355+
.document
1356+
.network_interface
1357+
.selected_nodes()
1358+
.selected_visible_layers(&context.document.network_interface)
1359+
.collect();
1360+
1361+
let mut transaction_started = false;
1362+
for layer in selected_layers {
1363+
if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
1364+
continue;
1365+
}
1366+
1367+
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)
1368+
&& condition(&gradient)
1369+
{
1370+
if !transaction_started {
1371+
responses.add(DocumentMessage::StartTransaction);
1372+
transaction_started = true;
1373+
}
1374+
update(&mut gradient);
1375+
responses.add(GraphOperationMessage::FillSet {
1376+
layer,
1377+
fill: Fill::Gradient(gradient),
1378+
});
1379+
}
1380+
}
1381+
1382+
if transaction_started {
1383+
responses.add(DocumentMessage::AddTransaction);
1384+
}
1385+
if let Some(selected_gradient) = &mut data.selected_gradient
1386+
&& let Some(layer) = selected_gradient.layer
1387+
&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)
1388+
{
1389+
update(&mut selected_gradient.gradient);
1390+
}
1391+
responses.add(PropertiesPanelMessage::Refresh);
1392+
data.has_selected_gradient = has_gradient_on_selected_layers(context.document);
1393+
responses.add(ToolMessage::RefreshToolOptions);
1394+
}
1395+
1396+
fn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {
1397+
document
1398+
.network_interface
1399+
.selected_nodes()
1400+
.selected_visible_layers(&document.network_interface)
1401+
.any(|layer| get_gradient(layer, &document.network_interface).is_some())
1402+
}
1403+
13211404
#[inline(always)]
13221405
fn midpoint_is_resettable(value: f64) -> bool {
13231406
(value - 0.5).abs() >= f64::EPSILON * 1000.

0 commit comments

Comments
 (0)