mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 02:08:12 +08:00
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
This commit is contained in:
@@ -44,6 +44,8 @@ pub enum GradientToolMessage {
|
|||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub enum GradientOptionsUpdate {
|
pub enum GradientOptionsUpdate {
|
||||||
Type(GradientType),
|
Type(GradientType),
|
||||||
|
ReverseStops,
|
||||||
|
ReverseDirection,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolMetadata for GradientTool {
|
impl ToolMetadata for GradientTool {
|
||||||
@@ -63,52 +65,27 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
|
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
|
||||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
|
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
|
||||||
|
|
||||||
|
let has_gradient = has_gradient_on_selected_layers(context.document);
|
||||||
|
if has_gradient != self.data.has_selected_gradient {
|
||||||
|
self.data.has_selected_gradient = has_gradient;
|
||||||
|
responses.add(ToolMessage::RefreshToolOptions);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match options {
|
match options {
|
||||||
GradientOptionsUpdate::Type(gradient_type) => {
|
GradientOptionsUpdate::Type(gradient_type) => {
|
||||||
self.options.gradient_type = gradient_type;
|
self.options.gradient_type = gradient_type;
|
||||||
let selected_layers: Vec<_> = context
|
apply_gradient_update(&mut self.data, context, responses, |g| g.gradient_type != gradient_type, |g| g.gradient_type = gradient_type);
|
||||||
.document
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_layers(&context.document.network_interface)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut transaction_started = false;
|
|
||||||
for layer in selected_layers {
|
|
||||||
if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)
|
|
||||||
&& gradient.gradient_type != gradient_type
|
|
||||||
{
|
|
||||||
if !transaction_started {
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
|
||||||
transaction_started = true;
|
|
||||||
}
|
|
||||||
gradient.gradient_type = gradient_type;
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: Fill::Gradient(gradient),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if transaction_started {
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
|
||||||
}
|
|
||||||
if let Some(selected_gradient) = &mut self.data.selected_gradient
|
|
||||||
&& let Some(layer) = selected_gradient.layer
|
|
||||||
&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)
|
|
||||||
{
|
|
||||||
selected_gradient.gradient.gradient_type = gradient_type;
|
|
||||||
}
|
|
||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
|
||||||
responses.add(ToolMessage::UpdateCursor);
|
responses.add(ToolMessage::UpdateCursor);
|
||||||
responses.add(ToolMessage::RefreshToolOptions);
|
}
|
||||||
|
GradientOptionsUpdate::ReverseStops => {
|
||||||
|
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| g.stops = g.stops.reversed());
|
||||||
|
}
|
||||||
|
GradientOptionsUpdate::ReverseDirection => {
|
||||||
|
apply_gradient_update(&mut self.data, context, responses, |_| true, |g| std::mem::swap(&mut g.start, &mut g.end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,7 +119,52 @@ impl LayoutHolder for GradientTool {
|
|||||||
.selected_index(Some((self.options.gradient_type == GradientType::Radial) as u32))
|
.selected_index(Some((self.options.gradient_type == GradientType::Radial) as u32))
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets: vec![gradient_type] }])
|
let reverse_stops = IconButton::new("Reverse", 24)
|
||||||
|
.tooltip_label("Reverse Stops")
|
||||||
|
.tooltip_description("Reverse the gradient color stops.")
|
||||||
|
.disabled(!self.data.has_selected_gradient)
|
||||||
|
.on_update(|_| {
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::ReverseStops,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.widget_instance();
|
||||||
|
|
||||||
|
let mut widgets = vec![gradient_type, Separator::new(SeparatorStyle::Unrelated).widget_instance(), reverse_stops];
|
||||||
|
|
||||||
|
if self.options.gradient_type == GradientType::Radial {
|
||||||
|
let orientation = self
|
||||||
|
.data
|
||||||
|
.selected_gradient
|
||||||
|
.as_ref()
|
||||||
|
.map(|selected_gradient| {
|
||||||
|
let (start, end) = (selected_gradient.gradient.start, selected_gradient.gradient.end);
|
||||||
|
if (end.x - start.x).abs() > f64::EPSILON * 1e6 {
|
||||||
|
end.x > start.x
|
||||||
|
} else {
|
||||||
|
(start.x + start.y) < (end.x + end.y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
|
let reverse_direction = IconButton::new(if orientation { "ReverseRadialGradientToRight" } else { "ReverseRadialGradientToLeft" }, 24)
|
||||||
|
.tooltip_label("Reverse Direction")
|
||||||
|
.tooltip_description("Reverse which end the gradient radiates from.")
|
||||||
|
.disabled(!self.data.has_selected_gradient)
|
||||||
|
.on_update(|_| {
|
||||||
|
GradientToolMessage::UpdateOptions {
|
||||||
|
options: GradientOptionsUpdate::ReverseDirection,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.widget_instance();
|
||||||
|
|
||||||
|
widgets.push(Separator::new(SeparatorStyle::Related).widget_instance());
|
||||||
|
widgets.push(reverse_direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
Layout(vec![LayoutGroup::Row { widgets }])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,6 +514,7 @@ struct GradientToolData {
|
|||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
auto_pan_shift: DVec2,
|
auto_pan_shift: DVec2,
|
||||||
gradient_angle: f64,
|
gradient_angle: f64,
|
||||||
|
has_selected_gradient: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for GradientToolFsmState {
|
impl Fsm for GradientToolFsmState {
|
||||||
@@ -993,9 +1016,12 @@ impl Fsm for GradientToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize `gradient_angle` from the existing gradient so Ctrl (lock angle) works from the first mouse move
|
// Initialize `gradient_angle` from the existing gradient so Ctrl (lock angle) works from the first mouse move
|
||||||
if let Some(sg) = &tool_data.selected_gradient {
|
if let Some(selected_gradient) = &tool_data.selected_gradient {
|
||||||
let (vp_start, vp_end) = (sg.transform.transform_point2(sg.gradient.start), sg.transform.transform_point2(sg.gradient.end));
|
let (vp_start, vp_end) = (
|
||||||
let delta = match sg.dragging {
|
selected_gradient.transform.transform_point2(selected_gradient.gradient.start),
|
||||||
|
selected_gradient.transform.transform_point2(selected_gradient.gradient.end),
|
||||||
|
);
|
||||||
|
let delta = match selected_gradient.dragging {
|
||||||
// When dragging End, the fixed point is start and the mouse begins at end
|
// When dragging End, the fixed point is start and the mouse begins at end
|
||||||
GradientDragTarget::End => vp_start - vp_end,
|
GradientDragTarget::End => vp_start - vp_end,
|
||||||
// When dragging Start, the fixed point is end and the mouse begins at start
|
// 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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_gradient_update(
|
||||||
|
data: &mut GradientToolData,
|
||||||
|
context: &mut ToolActionMessageContext,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
condition: impl Fn(&Gradient) -> bool,
|
||||||
|
update: impl Fn(&mut Gradient),
|
||||||
|
) {
|
||||||
|
let selected_layers: Vec<_> = context
|
||||||
|
.document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_layers(&context.document.network_interface)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut transaction_started = false;
|
||||||
|
for layer in selected_layers {
|
||||||
|
if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut gradient) = get_gradient(layer, &context.document.network_interface)
|
||||||
|
&& condition(&gradient)
|
||||||
|
{
|
||||||
|
if !transaction_started {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
transaction_started = true;
|
||||||
|
}
|
||||||
|
update(&mut gradient);
|
||||||
|
responses.add(GraphOperationMessage::FillSet {
|
||||||
|
layer,
|
||||||
|
fill: Fill::Gradient(gradient),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if transaction_started {
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
}
|
||||||
|
if let Some(selected_gradient) = &mut data.selected_gradient
|
||||||
|
&& let Some(layer) = selected_gradient.layer
|
||||||
|
&& !NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface)
|
||||||
|
{
|
||||||
|
update(&mut selected_gradient.gradient);
|
||||||
|
}
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
data.has_selected_gradient = has_gradient_on_selected_layers(context.document);
|
||||||
|
responses.add(ToolMessage::RefreshToolOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_gradient_on_selected_layers(document: &DocumentMessageHandler) -> bool {
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_visible_layers(&document.network_interface)
|
||||||
|
.any(|layer| get_gradient(layer, &document.network_interface).is_some())
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn midpoint_is_resettable(value: f64) -> bool {
|
fn midpoint_is_resettable(value: f64) -> bool {
|
||||||
(value - 0.5).abs() >= f64::EPSILON * 1000.
|
(value - 0.5).abs() >= f64::EPSILON * 1000.
|
||||||
|
|||||||
Reference in New Issue
Block a user