mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 06:58:12 +08:00
Add properties panel entries for artboards (#572)
* Artboards can have properties * fix crash when renaming artboards * moved target document to utility types * moved import and added test for file version information * fixed missing import * fix error from merging * - typed properties message handler data - removed name from WidgetRow * clippy warnings * artboards have seperate properties section * - color input can be forced to have selection - crop tool shows on switch - select tool shows on switch * variable renamed * change to use PropType<boolean> instead of PropType<Boolean> * Add an artboard icon * Add the "Delete Artboard" hint * fix unselect glitch * even better * Remove the Transform properties group Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
ca28bc3947
commit
916a575446
@@ -421,4 +421,32 @@ mod test {
|
|||||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_if_graphite_file_version_upgrade_is_needed() {
|
||||||
|
init_logger();
|
||||||
|
set_uuid_seed(0);
|
||||||
|
let mut editor = Editor::new();
|
||||||
|
let test_file = include_str!("./graphite-test-document.graphite");
|
||||||
|
let responses = editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||||
|
document_name: "Graphite Version Test".into(),
|
||||||
|
document_serialized_content: test_file.into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
for response in responses {
|
||||||
|
if let FrontendMessage::DisplayDialogError { title, description } = response {
|
||||||
|
println!();
|
||||||
|
println!("-------------------------------------------------");
|
||||||
|
println!("Failed test due to receiving a DisplayDialogError while loading the graphite sample file!");
|
||||||
|
println!("This is most likely caused by forgetting to bump the `GRAPHITE_DOCUMENT_VERSION` in `editor/src/consts.rs`");
|
||||||
|
println!("Once bumping this version number please replace the `graphite-test-document.graphite` with a valid file");
|
||||||
|
println!("DisplayDialogError details:");
|
||||||
|
println!("Title: {}", title);
|
||||||
|
println!("description: {}", description);
|
||||||
|
println!("-------------------------------------------------");
|
||||||
|
println!();
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":919378319526168453,"layer_ids":[919378319526168452],"layers":[{"visible":true,"name":null,"data":{"Shape":{"path":[{"MoveTo":{"x":0.0,"y":0.0}},{"LineTo":{"x":1.0,"y":0.0}},{"LineTo":{"x":1.0,"y":1.0}},{"LineTo":{"x":0.0,"y":1.0}},"ClosePath"],"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1,"closed":true}},"transform":{"matrix2":[303.890625,0.0,-0.0,362.10546875],"translation":[-148.83984375,-235.8828125]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":0,"name":"Untitled Document","layer_metadata":[[[919378319526168452],{"selected":true,"expanded":false}],[[],{"selected":false,"expanded":true}]],"layer_range_selection_reference":[919378319526168452],"movement_handler":{"pan":[-118.8,-45.60000000000001],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[259.88359375,366.9]},"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[919378319526168452],"Artwork"]},"overlays_visible":true,"snapping_enabled":true,"view_mode":"Normal","version":"0.0.5"}
|
||||||
@@ -57,5 +57,5 @@ pub const FILE_EXPORT_SUFFIX: &str = ".svg";
|
|||||||
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
|
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
|
||||||
|
|
||||||
// Document
|
// Document
|
||||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.4";
|
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.5";
|
||||||
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
|
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::message_prelude::*;
|
|||||||
use graphene::color::Color;
|
use graphene::color::Color;
|
||||||
use graphene::document::Document as GrapheneDocument;
|
use graphene::document::Document as GrapheneDocument;
|
||||||
use graphene::layers::style::{self, Fill, ViewMode};
|
use graphene::layers::style::{self, Fill, ViewMode};
|
||||||
|
use graphene::DocumentResponse;
|
||||||
use graphene::Operation as DocumentOperation;
|
use graphene::Operation as DocumentOperation;
|
||||||
|
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
@@ -31,7 +32,18 @@ impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler {
|
|||||||
// Sub-messages
|
// Sub-messages
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(*operation) {
|
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(*operation) {
|
||||||
Ok(_) => (),
|
Ok(Some(document_responses)) => {
|
||||||
|
for response in document_responses {
|
||||||
|
match &response {
|
||||||
|
DocumentResponse::LayerChanged { path } => responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: path.clone() }.into()),
|
||||||
|
DocumentResponse::DeletedLayer { path } => responses.push_back(PropertiesPanelMessage::CheckSelectedWasDeleted { path: path.clone() }.into()),
|
||||||
|
DocumentResponse::DocumentChanged => responses.push_back(ArtboardMessage::RenderArtboards.into()),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
Err(e) => log::error!("Artboard Error: {:?}", e),
|
Err(e) => log::error!("Artboard Error: {:?}", e),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use super::clipboards::Clipboard;
|
use super::clipboards::Clipboard;
|
||||||
use super::layer_panel::{layer_panel_entry, LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
|
use super::layer_panel::{layer_panel_entry, LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||||
|
use super::properties_panel_message_handler::PropertiesPanelMessageHandlerData;
|
||||||
|
use super::utility_types::TargetDocument;
|
||||||
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
|
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
|
||||||
use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
|
use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
|
||||||
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
||||||
@@ -503,7 +505,6 @@ impl DocumentMessageHandler {
|
|||||||
impl PropertyHolder for DocumentMessageHandler {
|
impl PropertyHolder for DocumentMessageHandler {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
||||||
checked: self.snapping_enabled,
|
checked: self.snapping_enabled,
|
||||||
@@ -704,7 +705,14 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
|||||||
}
|
}
|
||||||
#[remain::unsorted]
|
#[remain::unsorted]
|
||||||
PropertiesPanel(message) => {
|
PropertiesPanel(message) => {
|
||||||
self.properties_panel_message_handler.process_action(message, &self.graphene_document, responses);
|
self.properties_panel_message_handler.process_action(
|
||||||
|
message,
|
||||||
|
PropertiesPanelMessageHandlerData {
|
||||||
|
artwork_document: &self.graphene_document,
|
||||||
|
artboard_document: &self.artboard_message_handler.artboards_graphene_document,
|
||||||
|
},
|
||||||
|
responses,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
@@ -721,7 +729,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
|||||||
if selected_paths.is_empty() {
|
if selected_paths.is_empty() {
|
||||||
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
|
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
|
||||||
} else {
|
} else {
|
||||||
responses.push_back(PropertiesPanelMessage::SetActiveLayers { paths: selected_paths }.into())
|
responses.push_back(
|
||||||
|
PropertiesPanelMessage::SetActiveLayers {
|
||||||
|
paths: selected_paths,
|
||||||
|
document: TargetDocument::Artwork,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Correctly update layer panel in clear_selection instead of here
|
// TODO: Correctly update layer panel in clear_selection instead of here
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
|
||||||
|
use super::utility_types::TargetDocument;
|
||||||
|
|
||||||
use graphene::layers::style::{Fill, Stroke};
|
use graphene::layers::style::{Fill, Stroke};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -15,10 +17,10 @@ pub enum PropertiesPanelMessage {
|
|||||||
ModifyStroke { stroke: Stroke },
|
ModifyStroke { stroke: Stroke },
|
||||||
ModifyTransform { value: f64, transform_op: TransformOp },
|
ModifyTransform { value: f64, transform_op: TransformOp },
|
||||||
ResendActiveProperties,
|
ResendActiveProperties,
|
||||||
SetActiveLayers { paths: Vec<Vec<LayerId>> },
|
SetActiveLayers { paths: Vec<Vec<LayerId>>, document: TargetDocument },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
pub enum TransformOp {
|
pub enum TransformOp {
|
||||||
X,
|
X,
|
||||||
Y,
|
Y,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::layer_panel::LayerDataTypeDiscriminant;
|
use super::layer_panel::LayerDataTypeDiscriminant;
|
||||||
|
use super::utility_types::TargetDocument;
|
||||||
use crate::document::properties_panel_message::TransformOp;
|
use crate::document::properties_panel_message::TransformOp;
|
||||||
use crate::layout::layout_message::LayoutTarget;
|
use crate::layout::layout_message::LayoutTarget;
|
||||||
use crate::layout::widgets::{
|
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 {
|
pub struct PropertiesPanelMessageHandler {
|
||||||
active_path: Option<Vec<LayerId>>,
|
active_selection: Option<(Vec<LayerId>, TargetDocument)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropertiesPanelMessageHandler {
|
impl PropertiesPanelMessageHandler {
|
||||||
fn matches_selected(&self, path: &[LayerId]) -> bool {
|
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();
|
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]
|
#[remain::check]
|
||||||
fn process_action(&mut self, message: PropertiesPanelMessage, data: &GrapheneDocument, responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, message: PropertiesPanelMessage, data: PropertiesPanelMessageHandlerData, responses: &mut VecDeque<Message>) {
|
||||||
let graphene_document = data;
|
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::*;
|
use PropertiesPanelMessage::*;
|
||||||
match message {
|
match message {
|
||||||
SetActiveLayers { paths } => {
|
SetActiveLayers { paths, document } => {
|
||||||
if paths.len() > 1 {
|
if paths.len() != 1 {
|
||||||
// TODO: Allow for multiple selected layers
|
// TODO: Allow for multiple selected layers
|
||||||
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
|
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
|
||||||
} else {
|
} else {
|
||||||
let path = paths.into_iter().next().unwrap();
|
let path = paths.into_iter().next().unwrap();
|
||||||
let layer = graphene_document.layer(&path).unwrap();
|
self.active_selection = Some((path, document));
|
||||||
register_layer_properties(layer, responses);
|
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
|
||||||
self.active_path = Some(path)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClearSelection => {
|
ClearSelection => {
|
||||||
@@ -132,8 +149,8 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
ModifyTransform { value, transform_op } => {
|
ModifyTransform { value, transform_op } => {
|
||||||
let path = self.active_path.as_ref().expect("Received update for properties panel with no active layer");
|
let (path, target_document) = self.active_selection.as_ref().expect("Received update for properties panel with no active layer");
|
||||||
let layer = graphene_document.layer(path).unwrap();
|
let layer = get_document(*target_document).layer(path).unwrap();
|
||||||
|
|
||||||
use TransformOp::*;
|
use TransformOp::*;
|
||||||
let action = match transform_op {
|
let action = match transform_op {
|
||||||
@@ -150,35 +167,31 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|||||||
_ => 1.,
|
_ => 1.,
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.push_back(
|
responses.push_back(self.create_document_operation(Operation::SetLayerTransform {
|
||||||
Operation::SetLayerTransform {
|
path: path.clone(),
|
||||||
path: path.clone(),
|
transform: action(layer.transform, value / scale).to_cols_array(),
|
||||||
transform: action(layer.transform, value / scale).to_cols_array(),
|
}));
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
ModifyName { name } => {
|
ModifyName { name } => {
|
||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||||
responses.push_back(DocumentMessage::SetLayerName { layer_path: path, name }.into())
|
responses.push_back(self.create_document_operation(Operation::SetLayerName { path, name }))
|
||||||
}
|
}
|
||||||
ModifyFill { fill } => {
|
ModifyFill { fill } => {
|
||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
responses.push_back(self.create_document_operation(Operation::SetLayerFill { path, fill }));
|
||||||
}
|
}
|
||||||
ModifyStroke { stroke } => {
|
ModifyStroke { stroke } => {
|
||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let (path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||||
responses.push_back(Operation::SetLayerStroke { path, stroke }.into())
|
responses.push_back(self.create_document_operation(Operation::SetLayerStroke { path, stroke }))
|
||||||
}
|
}
|
||||||
CheckSelectedWasUpdated { path } => {
|
CheckSelectedWasUpdated { path } => {
|
||||||
if self.matches_selected(&path) {
|
if self.matches_selected(&path) {
|
||||||
let layer = graphene_document.layer(&path).unwrap();
|
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
|
||||||
register_layer_properties(layer, responses);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CheckSelectedWasDeleted { path } => {
|
CheckSelectedWasDeleted { path } => {
|
||||||
if self.matches_selected(&path) {
|
if self.matches_selected(&path) {
|
||||||
self.active_path = None;
|
self.active_selection = None;
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
LayoutMessage::SendLayout {
|
LayoutMessage::SendLayout {
|
||||||
layout_target: LayoutTarget::PropertiesOptionsPanel,
|
layout_target: LayoutTarget::PropertiesOptionsPanel,
|
||||||
@@ -196,9 +209,12 @@ impl MessageHandler<PropertiesPanelMessage, &GrapheneDocument> for PropertiesPan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResendActiveProperties => {
|
ResendActiveProperties => {
|
||||||
let path = self.active_path.clone().expect("Received update for properties panel with no active layer");
|
let (path, target_document) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||||
let layer = graphene_document.layer(&path).unwrap();
|
let layer = get_document(target_document).layer(&path).unwrap();
|
||||||
register_layer_properties(layer, responses)
|
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 {
|
let options_bar = vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
match &layer.data {
|
match &layer.data {
|
||||||
LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
LayerDataType::Folder(_) => WidgetHolder::new(Widget::IconLabel(IconLabel {
|
||||||
@@ -301,7 +497,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|||||||
name: "Transform".into(),
|
name: "Transform".into(),
|
||||||
layout: vec![
|
layout: vec![
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Location".into(),
|
value: "Location".into(),
|
||||||
@@ -344,7 +539,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Rotation".into(),
|
value: "Rotation".into(),
|
||||||
@@ -370,7 +564,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Scale".into(),
|
value: "Scale".into(),
|
||||||
@@ -413,7 +606,6 @@ fn node_section_transform(layer: &Layer) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Dimensions".into(),
|
value: "Dimensions".into(),
|
||||||
@@ -464,7 +656,6 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
Fill::Solid(_) | Fill::None => Some(LayoutRow::Section {
|
||||||
name: "Fill".into(),
|
name: "Fill".into(),
|
||||||
layout: vec![LayoutRow::Row {
|
layout: vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Color".into(),
|
value: "Color".into(),
|
||||||
@@ -488,6 +679,7 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
|
PropertiesPanelMessage::ModifyFill { fill: Fill::None }.into()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
..ColorInput::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
@@ -499,7 +691,6 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
name: "Fill".into(),
|
name: "Fill".into(),
|
||||||
layout: vec![
|
layout: vec![
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Gradient: 0%".into(),
|
value: "Gradient: 0%".into(),
|
||||||
@@ -532,11 +723,11 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
..ColorInput::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Gradient: 100%".into(),
|
value: "Gradient: 100%".into(),
|
||||||
@@ -569,6 +760,7 @@ fn node_section_fill(fill: &Fill) -> Option<LayoutRow> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
..ColorInput::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -596,7 +788,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
name: "Stroke".into(),
|
name: "Stroke".into(),
|
||||||
layout: vec![
|
layout: vec![
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Color".into(),
|
value: "Color".into(),
|
||||||
@@ -614,11 +805,11 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
.with_color(&text_input.value)
|
.with_color(&text_input.value)
|
||||||
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
.map_or(PropertiesPanelMessage::ResendActiveProperties.into(), |stroke| PropertiesPanelMessage::ModifyStroke { stroke }.into())
|
||||||
}),
|
}),
|
||||||
|
..ColorInput::default()
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Weight".into(),
|
value: "Weight".into(),
|
||||||
@@ -644,7 +835,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Dash Lengths".into(),
|
value: "Dash Lengths".into(),
|
||||||
@@ -666,7 +856,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Dash Offset".into(),
|
value: "Dash Offset".into(),
|
||||||
@@ -692,7 +881,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Line Cap".into(),
|
value: "Line Cap".into(),
|
||||||
@@ -740,7 +928,6 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Line Join".into(),
|
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
|
// TODO: Gray out this row when Line Join isn't set to Miter
|
||||||
LayoutRow::Row {
|
LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||||
value: "Miter Limit".into(),
|
value: "Miter Limit".into(),
|
||||||
|
|||||||
@@ -26,3 +26,9 @@ pub enum AlignAggregate {
|
|||||||
Center,
|
Center,
|
||||||
Average,
|
Average,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
pub enum TargetDocument {
|
||||||
|
Artboard,
|
||||||
|
Artwork,
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ pub type SubLayout = Vec<LayoutRow>;
|
|||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum LayoutRow {
|
pub enum LayoutRow {
|
||||||
Row { name: String, widgets: Vec<WidgetHolder> },
|
Row { widgets: Vec<WidgetHolder> },
|
||||||
Section { name: String, layout: SubLayout },
|
Section { name: String, layout: SubLayout },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ impl<'a> Iterator for WidgetIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(LayoutRow::Row { name: _, widgets }) => {
|
Some(LayoutRow::Row { widgets }) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@ impl<'a> Iterator for WidgetIterMut<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(LayoutRow::Row { name: _, widgets }) => {
|
Some(LayoutRow::Row { widgets }) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
@@ -207,6 +207,9 @@ pub struct ColorInput {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
pub on_update: WidgetCallback<ColorInput>,
|
pub on_update: WidgetCallback<ColorInput>,
|
||||||
|
#[serde(rename = "canSetTransparent")]
|
||||||
|
#[derivative(Default(value = "true"))]
|
||||||
|
pub can_set_transparent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::consts::SELECTION_TOLERANCE;
|
use crate::consts::SELECTION_TOLERANCE;
|
||||||
|
use crate::document::utility_types::TargetDocument;
|
||||||
use crate::document::DocumentMessageHandler;
|
use crate::document::DocumentMessageHandler;
|
||||||
use crate::frontend::utility_types::MouseCursorIcon;
|
use crate::frontend::utility_types::MouseCursorIcon;
|
||||||
use crate::input::keyboard::{Key, MouseMotion};
|
use crate::input::keyboard::{Key, MouseMotion};
|
||||||
@@ -125,6 +126,13 @@ impl Fsm for CropToolFsmState {
|
|||||||
data.bounding_box_overlays = Some(bounding_box_overlays);
|
data.bounding_box_overlays = Some(bounding_box_overlays);
|
||||||
|
|
||||||
responses.push_back(OverlaysMessage::Rerender.into());
|
responses.push_back(OverlaysMessage::Rerender.into());
|
||||||
|
responses.push_back(
|
||||||
|
PropertiesPanelMessage::SetActiveLayers {
|
||||||
|
paths: vec![vec![data.selected_board.unwrap()]],
|
||||||
|
document: TargetDocument::Artboard,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
@@ -168,6 +176,14 @@ impl Fsm for CropToolFsmState {
|
|||||||
|
|
||||||
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(intersection[0])), true, true);
|
data.snap_handler.start_snap(document, document.bounding_boxes(None, Some(intersection[0])), true, true);
|
||||||
|
|
||||||
|
responses.push_back(
|
||||||
|
PropertiesPanelMessage::SetActiveLayers {
|
||||||
|
paths: vec![intersection.clone()],
|
||||||
|
document: TargetDocument::Artboard,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
CropToolFsmState::Dragging
|
CropToolFsmState::Dragging
|
||||||
} else {
|
} else {
|
||||||
let id = generate_uuid();
|
let id = generate_uuid();
|
||||||
@@ -184,6 +200,8 @@ impl Fsm for CropToolFsmState {
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
responses.push_back(PropertiesPanelMessage::ClearSelection.into());
|
||||||
|
|
||||||
CropToolFsmState::Drawing
|
CropToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,6 +291,16 @@ impl Fsm for CropToolFsmState {
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Have to put message here instead of when Artboard is created
|
||||||
|
// This might result in a few more calls but it is not reliant on the order of messages
|
||||||
|
responses.push_back(
|
||||||
|
PropertiesPanelMessage::SetActiveLayers {
|
||||||
|
paths: vec![vec![data.selected_board.unwrap()]],
|
||||||
|
document: TargetDocument::Artboard,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
|
||||||
CropToolFsmState::Drawing
|
CropToolFsmState::Drawing
|
||||||
@@ -328,6 +356,15 @@ impl Fsm for CropToolFsmState {
|
|||||||
bounding_box_overlays.delete(responses);
|
bounding_box_overlays.delete(responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register properties when switching back to other tools
|
||||||
|
responses.push_back(
|
||||||
|
PropertiesPanelMessage::SetActiveLayers {
|
||||||
|
paths: document.selected_layers().map(|path| path.to_vec()).collect(),
|
||||||
|
document: TargetDocument::Artwork,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
data.snap_handler.cleanup(responses);
|
data.snap_handler.cleanup(responses);
|
||||||
CropToolFsmState::Ready
|
CropToolFsmState::Ready
|
||||||
}
|
}
|
||||||
@@ -353,6 +390,12 @@ impl Fsm for CropToolFsmState {
|
|||||||
label: String::from("Move Artboard"),
|
label: String::from("Move Artboard"),
|
||||||
plus: false,
|
plus: false,
|
||||||
}]),
|
}]),
|
||||||
|
HintGroup(vec![HintInfo {
|
||||||
|
key_groups: vec![KeysGroup(vec![Key::KeyBackspace])],
|
||||||
|
mouse: None,
|
||||||
|
label: String::from("Delete Artboard"),
|
||||||
|
plus: false,
|
||||||
|
}]),
|
||||||
]),
|
]),
|
||||||
CropToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
|
CropToolFsmState::Dragging => HintData(vec![HintGroup(vec![HintInfo {
|
||||||
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
key_groups: vec![KeysGroup(vec![Key::KeyShift])],
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ enum FreehandToolFsmState {
|
|||||||
impl PropertyHolder for FreehandTool {
|
impl PropertyHolder for FreehandTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
label: "Weight".into(),
|
label: "Weight".into(),
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ pub enum LineOptionsUpdate {
|
|||||||
impl PropertyHolder for LineTool {
|
impl PropertyHolder for LineTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
label: "Weight".into(),
|
label: "Weight".into(),
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ pub enum PenOptionsUpdate {
|
|||||||
impl PropertyHolder for PenTool {
|
impl PropertyHolder for PenTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
label: "Weight".into(),
|
label: "Weight".into(),
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ pub enum SelectToolMessage {
|
|||||||
impl PropertyHolder for SelectTool {
|
impl PropertyHolder for SelectTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![
|
widgets: vec![
|
||||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||||
icon: "AlignLeft".into(),
|
icon: "AlignLeft".into(),
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ pub enum ShapeOptionsUpdate {
|
|||||||
impl PropertyHolder for ShapeTool {
|
impl PropertyHolder for ShapeTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
label: "Sides".into(),
|
label: "Sides".into(),
|
||||||
value: self.options.vertices as f64,
|
value: self.options.vertices as f64,
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ pub enum SplineOptionsUpdate {
|
|||||||
impl PropertyHolder for SplineTool {
|
impl PropertyHolder for SplineTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
label: "Weight".into(),
|
label: "Weight".into(),
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ pub enum TextOptionsUpdate {
|
|||||||
impl PropertyHolder for TextTool {
|
impl PropertyHolder for TextTool {
|
||||||
fn properties(&self) -> WidgetLayout {
|
fn properties(&self) -> WidgetLayout {
|
||||||
WidgetLayout::new(vec![LayoutRow::Row {
|
WidgetLayout::new(vec![LayoutRow::Row {
|
||||||
name: "".into(),
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||||
unit: " px".into(),
|
unit: " px".into(),
|
||||||
label: "Font Size".into(),
|
label: "Font Size".into(),
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M13,6h-2V4h-1v2H8v1h2v2h1V7h2V6z" />
|
|
||||||
<path d="M1,2v6.3L6.7,14H15V2H1z M14,13H7V8H2V3h12V13z" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 175 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13,4H5v1h7v9h1V4z" />
|
||||||
|
<path d="M4,11V2H3v10h8v-1H4z" />
|
||||||
|
<rect x="14" y="11" width="1" height="1" />
|
||||||
|
<rect x="1" y="4" width="1" height="1" />
|
||||||
|
<rect x="5" y="6" width="6" height="4" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 267 B |
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<LayoutRow class="color-input">
|
<LayoutRow class="color-input">
|
||||||
<OptionalInput :icon="'CloseX'" :checked="!!value" @update:checked="(val) => updateEnabled(val)"></OptionalInput>
|
<OptionalInput v-if="canSetTransparent" :icon="'CloseX'" :checked="!!value" @update:checked="(val) => updateEnabled(val)"></OptionalInput>
|
||||||
<TextInput :value="displayValue" :label="label" :disabled="disabled || !value" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
<TextInput :value="displayValue" :label="label" :disabled="disabled || !value" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
||||||
<Separator :type="'Related'" />
|
<Separator :type="'Related'" />
|
||||||
<LayoutRow class="swatch">
|
<LayoutRow class="swatch">
|
||||||
@@ -84,6 +84,7 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
value: { type: String as PropType<string | undefined>, required: true },
|
value: { type: String as PropType<string | undefined>, required: true },
|
||||||
label: { type: String as PropType<string>, required: false },
|
label: { type: String as PropType<string>, required: false },
|
||||||
|
canSetTransparent: { type: Boolean as PropType<boolean>, required: false, default: true },
|
||||||
disabled: { type: Boolean as PropType<boolean>, default: false },
|
disabled: { type: Boolean as PropType<boolean>, default: false },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ export function defaultWidgetLayout(): WidgetLayout {
|
|||||||
|
|
||||||
export type LayoutRow = WidgetRow | WidgetSection;
|
export type LayoutRow = WidgetRow | WidgetSection;
|
||||||
|
|
||||||
export type WidgetRow = { name: string; widgets: Widget[] };
|
export type WidgetRow = { widgets: Widget[] };
|
||||||
export function isWidgetRow(layoutRow: WidgetRow | WidgetSection): layoutRow is WidgetRow {
|
export function isWidgetRow(layoutRow: WidgetRow | WidgetSection): layoutRow is WidgetRow {
|
||||||
return Boolean((layoutRow as WidgetRow).widgets);
|
return Boolean((layoutRow as WidgetRow).widgets);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ import File from "@/../assets/16px-solid/file.svg";
|
|||||||
import FlipHorizontal from "@/../assets/16px-solid/flip-horizontal.svg";
|
import FlipHorizontal from "@/../assets/16px-solid/flip-horizontal.svg";
|
||||||
import FlipVertical from "@/../assets/16px-solid/flip-vertical.svg";
|
import FlipVertical from "@/../assets/16px-solid/flip-vertical.svg";
|
||||||
import GraphiteLogo from "@/../assets/16px-solid/graphite-logo.svg";
|
import GraphiteLogo from "@/../assets/16px-solid/graphite-logo.svg";
|
||||||
import NewLayer from "@/../assets/16px-solid/new-layer.svg";
|
import NodeArtboard from "@/../assets/16px-solid/node-artboard.svg";
|
||||||
import NodeFolder from "@/../assets/16px-solid/node-folder.svg";
|
import NodeFolder from "@/../assets/16px-solid/node-folder.svg";
|
||||||
import NodeImage from "@/../assets/16px-solid/node-image.svg";
|
import NodeImage from "@/../assets/16px-solid/node-image.svg";
|
||||||
import NodeShape from "@/../assets/16px-solid/node-shape.svg";
|
import NodeShape from "@/../assets/16px-solid/node-shape.svg";
|
||||||
@@ -156,12 +156,12 @@ export const ICON_LIST = {
|
|||||||
FlipHorizontal: { component: FlipHorizontal, size: size16 },
|
FlipHorizontal: { component: FlipHorizontal, size: size16 },
|
||||||
FlipVertical: { component: FlipVertical, size: size16 },
|
FlipVertical: { component: FlipVertical, size: size16 },
|
||||||
GraphiteLogo: { component: GraphiteLogo, size: size16 },
|
GraphiteLogo: { component: GraphiteLogo, size: size16 },
|
||||||
NewLayer: { component: NewLayer, size: size16 },
|
NodeArtboard: { component: NodeArtboard, size: size16 },
|
||||||
Paste: { component: Paste, size: size16 },
|
|
||||||
NodeFolder: { component: NodeFolder, size: size16 },
|
NodeFolder: { component: NodeFolder, size: size16 },
|
||||||
NodeImage: { component: NodeImage, size: size16 },
|
NodeImage: { component: NodeImage, size: size16 },
|
||||||
NodeShape: { component: NodeShape, size: size16 },
|
NodeShape: { component: NodeShape, size: size16 },
|
||||||
NodeText: { component: NodeText, size: size16 },
|
NodeText: { component: NodeText, size: size16 },
|
||||||
|
Paste: { component: Paste, size: size16 },
|
||||||
Trash: { component: Trash, size: size16 },
|
Trash: { component: Trash, size: size16 },
|
||||||
ViewModeNormal: { component: ViewModeNormal, size: size16 },
|
ViewModeNormal: { component: ViewModeNormal, size: size16 },
|
||||||
ViewModeOutline: { component: ViewModeOutline, size: size16 },
|
ViewModeOutline: { component: ViewModeOutline, size: size16 },
|
||||||
|
|||||||
Reference in New Issue
Block a user