|
|
|
|
@@ -1,4 +1,5 @@
|
|
|
|
|
use super::layer_panel::LayerDataTypeDiscriminant;
|
|
|
|
|
use super::utility_types::TargetDocument;
|
|
|
|
|
use crate::document::properties_panel_message::TransformOp;
|
|
|
|
|
use crate::layout::layout_message::LayoutTarget;
|
|
|
|
|
use crate::layout::widgets::{
|
|
|
|
|
@@ -85,34 +86,50 @@ impl DAffine2Utils for DAffine2 {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
|
|
|
pub struct PropertiesPanelMessageHandler {
|
|
|
|
|
active_path: Option<Vec<LayerId>>,
|
|
|
|
|
active_selection: Option<(Vec<LayerId>, TargetDocument)>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PropertiesPanelMessageHandler {
|
|
|
|
|
fn matches_selected(&self, path: &[LayerId]) -> bool {
|
|
|
|
|
let last_active_path = self.active_path.as_ref().and_then(|v| v.last().copied());
|
|
|
|
|
let last_active_path_id = self.active_selection.as_ref().and_then(|(v, _)| v.last().copied());
|
|
|
|
|
let last_modified = path.last().copied();
|
|
|
|
|
matches!((last_active_path, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
|
|
|
|
|
matches!((last_active_path_id, last_modified), (Some(active_last), Some(modified_last)) if active_last == modified_last)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn create_document_operation(&self, operation: Operation) -> Message {
|
|
|
|
|
let (_, target_document) = self.active_selection.as_ref().unwrap();
|
|
|
|
|
match *target_document {
|
|
|
|
|
TargetDocument::Artboard => ArtboardMessage::DispatchOperation(Box::new(operation)).into(),
|
|
|
|
|
TargetDocument::Artwork => DocumentMessage::DispatchOperation(Box::new(operation)).into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPanelMessageHandler {
|
|
|
|
|
pub struct PropertiesPanelMessageHandlerData<'a> {
|
|
|
|
|
pub artwork_document: &'a GrapheneDocument,
|
|
|
|
|
pub artboard_document: &'a GrapheneDocument,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerData<'a>> for PropertiesPanelMessageHandler {
|
|
|
|
|
#[remain::check]
|
|
|
|
|
fn process_action(&mut self, message: PropertiesPanelMessage, data: &GrapheneDocument, responses: &mut VecDeque<Message>) {
|
|
|
|
|
let graphene_document = data;
|
|
|
|
|
fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) {
|
|
|
|
|
let PropertiesPanelMessageHandlerData { artwork_document, artboard_document } = data;
|
|
|
|
|
let get_document = |document_selector: TargetDocument| match document_selector {
|
|
|
|
|
TargetDocument::Artboard => artboard_document,
|
|
|
|
|
TargetDocument::Artwork => artwork_document,
|
|
|
|
|
};
|
|
|
|
|
use PropertiesPanelMessage::*;
|
|
|
|
|
match message {
|
|
|
|
|
SetActiveLayers { paths } => {
|
|
|
|
|
if paths.len() > 1 {
|
|
|
|
|
SetActiveLayers { paths, document } => {
|
|
|
|
|
if paths.len() != 1 {
|
|
|
|
|
// TODO: Allow for multiple selected layers
|
|
|
|
|
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
|
|
|
|
|
} else {
|
|
|
|
|
let path = paths.into_iter().next().unwrap();
|
|
|
|
|
let layer = graphene_document.layer(&path).unwrap();
|
|
|
|
|
register_layer_properties(layer, responses);
|
|
|
|
|
self.active_path = Some(path)
|
|
|
|
|
self.active_selection = Some((path, document));
|
|
|
|
|
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClearSelection => {
|
|
|
|
|
@@ -132,8 +149,8 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
ModifyTransform { value, transform_op } => {
|
|
|
|
|
let path = self.active_path.as_ref().expect("Received update for properties panel with no active layer");
|
|
|
|
|
let layer = graphene_document.layer(path).unwrap();
|
|
|
|
|
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
|
|
|
|
|
let layer = get_document(*target_document).layer(path).unwrap();
|
|
|
|
|
|
|
|
|
|
use TransformOp::*;
|
|
|
|
|
let action = match transform_op {
|
|
|
|
|
@@ -150,35 +167,31 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|
|
|
|
_ => 1.,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
responses.push_back(
|
|
|
|
|
Operation::SetLayerTransform {
|
|
|
|
|
path: path.clone(),
|
|
|
|
|
transform: action(layer.transform, value / scale).to_cols_array(),
|
|
|
|
|
}
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
responses.push_back(self.create_document_operation(Operation::SetLayerTransform {
|
|
|
|
|
path: path.clone(),
|
|
|
|
|
transform: action(layer.transform, value / scale).to_cols_array(),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
ModifyName { name } => {
|
|
|
|
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(DocumentMessage::SetLayerName { layer_path: path, name }.into())
|
|
|
|
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name }))
|
|
|
|
|
}
|
|
|
|
|
ModifyFill { fill } => {
|
|
|
|
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
|
|
|
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill }));
|
|
|
|
|
}
|
|
|
|
|
ModifyStroke { stroke } => {
|
|
|
|
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
|
|
|
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke }))
|
|
|
|
|
}
|
|
|
|
|
CheckSelectedWasUpdated { path } => {
|
|
|
|
|
if self.matches_selected(&path) {
|
|
|
|
|
let layer = graphene_document.layer(&path).unwrap();
|
|
|
|
|
register_layer_properties(layer, responses);
|
|
|
|
|
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CheckSelectedWasDeleted { path } => {
|
|
|
|
|
if self.matches_selected(&path) {
|
|
|
|
|
self.active_path = None;
|
|
|
|
|
self.active_selection = None;
|
|
|
|
|
responses.push_back(
|
|
|
|
|
LayoutMessage::SendLayout {
|
|
|
|
|
layout_target: LayoutTarget::PropertiesOptionsPanel,
|
|
|
|
|
@@ -196,9 +209,12 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ResendActiveProperties => {
|
|
|
|
|
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
let layer = graphene_document.layer(&path).unwrap();
|
|
|
|
|
register_layer_properties(layer, responses)
|
|
|
|
|
let (path, target_document) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
|
|
|
|
let layer = get_document(target_document).layer(&path).unwrap();
|
|
|
|
|
match target_document {
|
|
|
|
|
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses),
|
|
|
|
|
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -208,9 +224,189 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn register_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>) {
|
|
|
|
|
fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>) {
|
|
|
|
|
let options_bar = vec![LayoutRow::Row {
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::IconLabel(IconLabel {
|
|
|
|
|
icon: "NodeArtboard".into(),
|
|
|
|
|
gap_after: true,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Related,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Artboard".into(),
|
|
|
|
|
..TextLabel::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Unrelated,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::TextInput(TextInput {
|
|
|
|
|
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
|
|
|
|
|
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Related,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
|
|
|
|
title: "Options Bar".into(),
|
|
|
|
|
text: "The contents of this popover menu are coming soon".into(),
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
let properties_body = {
|
|
|
|
|
let shape = if let LayerDataType::Shape(shape) = &layer.data {
|
|
|
|
|
shape
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Artboards can only be shapes")
|
|
|
|
|
};
|
|
|
|
|
let color = if let Fill::Solid(color) = shape.style.fill() {
|
|
|
|
|
color
|
|
|
|
|
} else {
|
|
|
|
|
panic!("Artboard must have a solid fill")
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vec![LayoutRow::Section {
|
|
|
|
|
name: "Artboard".into(),
|
|
|
|
|
layout: vec![
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Location".into(),
|
|
|
|
|
..TextLabel::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Unrelated,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
|
|
|
|
value: layer.transform.x(),
|
|
|
|
|
label: "X".into(),
|
|
|
|
|
unit: " px".into(),
|
|
|
|
|
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
|
|
|
|
PropertiesPanelMessage::ModifyTransform {
|
|
|
|
|
value: number_input.value,
|
|
|
|
|
transform_op: TransformOp::X,
|
|
|
|
|
}
|
|
|
|
|
.into()
|
|
|
|
|
}),
|
|
|
|
|
..NumberInput::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Related,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
|
|
|
|
value: layer.transform.y(),
|
|
|
|
|
label: "Y".into(),
|
|
|
|
|
unit: " px".into(),
|
|
|
|
|
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
|
|
|
|
PropertiesPanelMessage::ModifyTransform {
|
|
|
|
|
value: number_input.value,
|
|
|
|
|
transform_op: TransformOp::Y,
|
|
|
|
|
}
|
|
|
|
|
.into()
|
|
|
|
|
}),
|
|
|
|
|
..NumberInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Dimensions".into(),
|
|
|
|
|
..TextLabel::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Unrelated,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
|
|
|
|
value: layer.bounding_transform().scale_x(),
|
|
|
|
|
label: "W".into(),
|
|
|
|
|
unit: " px".into(),
|
|
|
|
|
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
|
|
|
|
PropertiesPanelMessage::ModifyTransform {
|
|
|
|
|
value: number_input.value,
|
|
|
|
|
transform_op: TransformOp::Width,
|
|
|
|
|
}
|
|
|
|
|
.into()
|
|
|
|
|
}),
|
|
|
|
|
..NumberInput::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Related,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
|
|
|
|
value: layer.bounding_transform().scale_y(),
|
|
|
|
|
label: "H".into(),
|
|
|
|
|
unit: " px".into(),
|
|
|
|
|
on_update: WidgetCallback::new(|number_input: &NumberInput| {
|
|
|
|
|
PropertiesPanelMessage::ModifyTransform {
|
|
|
|
|
value: number_input.value,
|
|
|
|
|
transform_op: TransformOp::Height,
|
|
|
|
|
}
|
|
|
|
|
.into()
|
|
|
|
|
}),
|
|
|
|
|
..NumberInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Background".into(),
|
|
|
|
|
..TextLabel::default()
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::Separator(Separator {
|
|
|
|
|
separator_type: SeparatorType::Unrelated,
|
|
|
|
|
direction: SeparatorDirection::Horizontal,
|
|
|
|
|
})),
|
|
|
|
|
WidgetHolder::new(Widget::ColorInput(ColorInput {
|
|
|
|
|
value: Some(color.rgba_hex()),
|
|
|
|
|
on_update: WidgetCallback::new(|text_input: &ColorInput| {
|
|
|
|
|
if let Some(value) = &text_input.value {
|
|
|
|
|
if let Some(color) = Color::from_rgba_str(value).or_else(|| Color::from_rgb_str(value)) {
|
|
|
|
|
let new_fill = Fill::Solid(color);
|
|
|
|
|
PropertiesPanelMessage::ModifyFill { fill: new_fill }.into()
|
|
|
|
|
} else {
|
|
|
|
|
PropertiesPanelMessage::ResendActiveProperties.into()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
can_set_transparent: false,
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
responses.push_back(
|
|
|
|
|
LayoutMessage::SendLayout {
|
|
|
|
|
layout: WidgetLayout::new(options_bar),
|
|
|
|
|
layout_target: LayoutTarget::PropertiesOptionsPanel,
|
|
|
|
|
}
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
responses.push_back(
|
|
|
|
|
LayoutMessage::SendLayout {
|
|
|
|
|
layout: WidgetLayout::new(properties_body),
|
|
|
|
|
layout_target: LayoutTarget::PropertiesSectionsPanel,
|
|
|
|
|
}
|
|
|
|
|
.into(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>) {
|
|
|
|
|
let options_bar = vec![LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
match &layer.data {
|
|
|
|
|
LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
|
|
|
|
@@ -301,7 +497,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|
|
|
|
name: "Transform".into(),
|
|
|
|
|
layout: vec![
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Location".into(),
|
|
|
|
|
@@ -344,7 +539,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Rotation".into(),
|
|
|
|
|
@@ -370,7 +564,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Scale".into(),
|
|
|
|
|
@@ -413,7 +606,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Dimensions".into(),
|
|
|
|
|
@@ -464,7 +656,6 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|
|
|
|
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
|
|
|
|
name: "Fill".into(),
|
|
|
|
|
layout: vec![LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Color".into(),
|
|
|
|
|
@@ -488,6 +679,7 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|
|
|
|
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
..ColorInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
}],
|
|
|
|
|
@@ -499,7 +691,6 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|
|
|
|
name: "Fill".into(),
|
|
|
|
|
layout: vec![
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Gradient: 0%".into(),
|
|
|
|
|
@@ -532,11 +723,11 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
..ColorInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Gradient: 100%".into(),
|
|
|
|
|
@@ -569,6 +760,7 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
..ColorInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
@@ -596,7 +788,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
name: "Stroke".into(),
|
|
|
|
|
layout: vec![
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Color".into(),
|
|
|
|
|
@@ -614,11 +805,11 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
.with_color(&text_input.value)
|
|
|
|
|
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
|
|
|
|
}),
|
|
|
|
|
..ColorInput::default()
|
|
|
|
|
})),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Weight".into(),
|
|
|
|
|
@@ -644,7 +835,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Dash Lengths".into(),
|
|
|
|
|
@@ -666,7 +856,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Dash Offset".into(),
|
|
|
|
|
@@ -692,7 +881,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Line Cap".into(),
|
|
|
|
|
@@ -740,7 +928,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
LayoutRow::Row {
|
|
|
|
|
name: "".into(),
|
|
|
|
|
widgets: vec![
|
|
|
|
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
|
|
|
value: "Line Join".into(),
|
|
|
|
|
@@ -789,7 +976,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|
|
|
|
},
|
|
|
|
|
// 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(),
|
|
|
|
|
|