mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Add additional stroke properties (#582)
* Add aditional stroke properties * Add comment explaining clones for closure * Improve labels * Fix doc test Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
5c99cdef7f
commit
3e08802c44
@@ -1,6 +1,6 @@
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::layers::style::Fill;
|
||||
use graphene::layers::style::{Fill, Stroke};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -12,7 +12,7 @@ pub enum PropertiesPanelMessage {
|
||||
ClearSelection,
|
||||
ModifyFill { fill: Fill },
|
||||
ModifyName { name: String },
|
||||
ModifyStroke { color: String, weight: f64 },
|
||||
ModifyStroke { stroke: Stroke },
|
||||
ModifyTransform { value: f64, transform_op: TransformOp },
|
||||
ResendActiveProperties,
|
||||
SetActiveLayers { paths: Vec<Vec<LayerId>> },
|
||||
|
||||
@@ -2,14 +2,15 @@ use super::layer_panel::LayerDataTypeDiscriminant;
|
||||
use crate::document::properties_panel_message::TransformOp;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{
|
||||
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
|
||||
ColorInput, IconLabel, LayoutRow, NumberInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, TextInput, TextLabel, Widget, WidgetCallback, WidgetHolder,
|
||||
WidgetLayout,
|
||||
};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||
use graphene::layers::style::{Fill, Stroke};
|
||||
use graphene::layers::style::{Fill, LineCap, LineJoin, Stroke};
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -165,16 +166,9 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||
}
|
||||
ModifyStroke { color, weight } => {
|
||||
ModifyStroke { stroke } => {
|
||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
||||
let layer = graphene_document.layer(&path).unwrap();
|
||||
if let Some(color) = Color::from_rgba_str(&color).or_else(|| Color::from_rgb_str(&color)) {
|
||||
let stroke = Stroke::new(color, weight as f32);
|
||||
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
||||
} else {
|
||||
// Failed to update, Show user unchanged state
|
||||
register_layer_properties(layer, responses)
|
||||
}
|
||||
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
||||
}
|
||||
CheckSelectedWasUpdated { path } => {
|
||||
if self.matches_selected(&path) {
|
||||
@@ -564,8 +558,19 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
||||
}
|
||||
|
||||
fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
||||
let color = stroke.color();
|
||||
let weight = stroke.width();
|
||||
// We have to make multiple variables because they get moved into different closures.
|
||||
let internal_stroke1 = stroke.clone();
|
||||
let internal_stroke2 = stroke.clone();
|
||||
let internal_stroke3 = stroke.clone();
|
||||
let internal_stroke4 = stroke.clone();
|
||||
let internal_stroke5 = stroke.clone();
|
||||
let internal_stroke6 = stroke.clone();
|
||||
let internal_stroke7 = stroke.clone();
|
||||
let internal_stroke8 = stroke.clone();
|
||||
let internal_stroke9 = stroke.clone();
|
||||
let internal_stroke10 = stroke.clone();
|
||||
let internal_stroke11 = stroke.clone();
|
||||
|
||||
LayoutRow::Section {
|
||||
name: "Stroke".into(),
|
||||
layout: vec![
|
||||
@@ -583,11 +588,10 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
||||
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
||||
value: stroke.color().rgba_hex(),
|
||||
on_update: WidgetCallback::new(move |text_input: &ColorInput| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
color: text_input.value.clone(),
|
||||
weight: weight as f64,
|
||||
}
|
||||
.into()
|
||||
internal_stroke1
|
||||
.clone()
|
||||
.with_color(&text_input.value)
|
||||
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||
}),
|
||||
})),
|
||||
],
|
||||
@@ -610,8 +614,178 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
color: color.rgba_hex(),
|
||||
weight: number_input.value,
|
||||
stroke: internal_stroke2.clone().with_width(number_input.value as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Dash Lengths".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextInput(TextInput {
|
||||
value: stroke.dash_lengths(),
|
||||
on_update: WidgetCallback::new(move |text_input: &TextInput| {
|
||||
internal_stroke3
|
||||
.clone()
|
||||
.with_dash_lengths(&text_input.value)
|
||||
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Dash Offset".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: stroke.dash_offset() as f64,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
unit: " px".into(),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke4.clone().with_dash_offset(number_input.value as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Line Cap".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||
selected_index: stroke.line_cap_index(),
|
||||
entries: vec![
|
||||
RadioEntryData {
|
||||
label: "Butt".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke6.clone().with_line_cap(LineCap::Butt),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
label: "Round".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke7.clone().with_line_cap(LineCap::Round),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
label: "Square".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke8.clone().with_line_cap(LineCap::Square),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Line Join".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||
selected_index: stroke.line_join_index(),
|
||||
entries: vec![
|
||||
RadioEntryData {
|
||||
label: "Miter".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke9.clone().with_line_join(LineJoin::Miter),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
label: "Bevel".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke10.clone().with_line_join(LineJoin::Bevel),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
label: "Round".into(),
|
||||
on_update: WidgetCallback::new(move |_| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke11.clone().with_line_join(LineJoin::Round),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
// TODO: Gray out this row when Line Join isn't set to Miter
|
||||
LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: "Miter Limit".into(),
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
value: stroke.miter_limit() as f64,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
unit: "".into(),
|
||||
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
|
||||
PropertiesPanelMessage::ModifyStroke {
|
||||
stroke: internal_stroke5.clone().with_miter_limit(number_input.value as f32),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user