Move layouts definitions to backend and fix Firefox overlay scrollbars (#647)

* Fix two-axis scrollbars in scrollable regions on Firefox

* Move Document Mode dropdown to the backend; and related code cleanup

* Port the Layer Tree options bar layout to the backend

* Port the tool shelf to the backend

* Clean up initialization and wasm wrapper

* Fix crash

* Fix missing document bar

* Remove unused functions in api.rs

* Code review

* Tool initalisation

* Remove some frontend functions

* Initalise -> Init so en-US/GB doesn't have to matter :)

* Remove blend_mode and opacity from LayerPanelEntry

Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2022-05-17 13:12:52 -07:00
co-authored by 0hypercube
parent 95435d8bf1
commit a8f09da5a2
50 changed files with 1034 additions and 978 deletions
+6 -9
View File
@@ -41,7 +41,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
ArtboardMessageDiscriminant::RenderArtboards,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayer),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerDetails),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerTreeStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateOpenDocumentsList),
MessageDiscriminant::Tool(ToolMessageDiscriminant::DocumentIsDirty),
@@ -142,12 +142,7 @@ impl Dispatcher {
fn log_message(&self, message: &Message) {
use Message::*;
if log::max_level() == log::LevelFilter::Trace
&& !(matches!(
message,
InputPreprocessor(_) | Frontend(FrontendMessage::UpdateCanvasZoom { .. }) | Frontend(FrontendMessage::UpdateCanvasRotation { .. })
) || MessageDiscriminant::from(message).local_name().ends_with("PointerMove"))
{
if log::max_level() == log::LevelFilter::Trace && !(matches!(message, InputPreprocessor(_)) || MessageDiscriminant::from(message).local_name().ends_with("PointerMove")) {
log::trace!("Message: {:?}", message);
// log::trace!("Hints: {:?}", self.input_mapper_message_handler.hints(self.collect_actions()));
}
@@ -447,6 +442,8 @@ mod test {
#[test]
fn check_if_graphite_file_version_upgrade_is_needed() {
use crate::layout::widgets::{LayoutRow, TextLabel, Widget};
init_logger();
set_uuid_seed(0);
let mut editor = Editor::new();
@@ -458,8 +455,8 @@ mod test {
for response in responses {
if let FrontendMessage::UpdateDialogDetails { layout_target: _, layout } = response {
if let crate::layout::widgets::LayoutRow::Row { widgets } = &layout[0] {
if let crate::layout::widgets::Widget::TextLabel(crate::layout::widgets::TextLabel { value, .. }) = &widgets[0].widget {
if let LayoutRow::Row { widgets } = &layout[0] {
if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
println!();
println!("-------------------------------------------------");
println!("Failed test due to receiving a DisplayDialogError while loading the Graphite sample file!");
@@ -1 +1 @@
{"graphene_document":{"font_cache": {"data":{},"default":null}, "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":{"font_cache": {"data": {}}, "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.7"}
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":9872175665704159397,"layer_ids":[9872175665704159396],"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":[376.0,0.0,-0.0,214.0],"translation":[903.0,393.0]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"blend_mode":"Normal","opacity":1.0},"font_cache":{"data":{},"default_font":null}},"saved_document_identifier":15130871412783076140,"name":"Untitled Document","version":"0.0.8","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[9872175665704159396],{"selected":false,"expanded":false}]],"layer_range_selection_reference":[],"movement_handler":{"pan":[0.0,0.0],"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":11149268176678832548,"layer_ids":[11149268176678832547],"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":1.0,"green":1.0,"blue":1.0,"alpha":1.0}}},"render_index":1,"closed":true}},"transform":{"matrix2":[885.0,0.0,-0.0,461.0],"translation":[657.0,273.0]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"blend_mode":"Normal","opacity":1.0},"font_cache":{"data":{},"default_font":null}},"artboard_ids":[11149268176678832547]},"properties_panel_message_handler":{"active_selection":null}}
+1 -1
View File
@@ -66,5 +66,5 @@ pub const FILE_SAVE_SUFFIX: &str = ".graphite";
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
// Document
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.7";
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.8"; // Remember to save a simple document and replace the test file at: editor\src\communication\graphite-test-document.graphite
pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f32 = 1.05;
+7 -7
View File
@@ -64,7 +64,7 @@ impl PropertyHolder for Export {
let mut export_area_options = vec![(ExportBounds::AllArtwork, "All Artwork".to_string())];
export_area_options.extend(artboards);
let index = export_area_options.iter().position(|(val, _)| val == &self.bounds).unwrap();
let menu_entries = vec![export_area_options
let entries = vec![export_area_options
.into_iter()
.map(|(val, name)| DropdownEntryData {
label: name,
@@ -84,8 +84,8 @@ impl PropertyHolder for Export {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
selected_index: index as u32,
menu_entries,
selected_index: Some(index as u32),
entries,
..Default::default()
})),
];
@@ -101,19 +101,19 @@ impl PropertyHolder for Export {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: self.scale_factor,
value: Some(self.scale_factor),
label: "".into(),
unit: " ".into(),
disabled: self.file_type == FileType::Svg,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogUpdate::ScaleFactor(number_input.value).into()),
disabled: self.file_type == FileType::Svg,
on_update: WidgetCallback::new(|number_input: &NumberInput| ExportDialogUpdate::ScaleFactor(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
];
let button_widgets = vec![
WidgetHolder::new(Widget::TextButton(TextButton {
label: "OK".to_string(),
label: "Export".to_string(),
min_width: 96,
emphasized: true,
on_update: WidgetCallback::new(|_| {
@@ -66,13 +66,13 @@ impl PropertyHolder for NewDocument {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: self.dimensions.x as f64,
value: Some(self.dimensions.x as f64),
label: "W".into(),
unit: " px".into(),
disabled: self.infinite,
is_integer: true,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsX(number_input.value).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsX(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
@@ -80,13 +80,13 @@ impl PropertyHolder for NewDocument {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: self.dimensions.y as f64,
value: Some(self.dimensions.y as f64),
label: "H".into(),
unit: " px".into(),
disabled: self.infinite,
is_integer: true,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsY(number_input.value).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| NewDocumentDialogUpdate::DimensionsY(number_input.value.unwrap()).into()),
..NumberInput::default()
})),
];
+196 -27
View File
@@ -1,16 +1,17 @@
use super::clipboards::Clipboard;
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::{DocumentMode, TargetDocument};
use super::{vectorize_layer_metadata, PropertiesPanelMessageHandler};
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
use crate::consts::{ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR};
use crate::frontend::utility_types::{FileType, FrontendImageData};
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::{
IconButton, LayoutRow, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, PropertyHolder, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, Widget,
WidgetCallback, WidgetHolder, WidgetLayout,
DropdownEntryData, DropdownInput, IconButton, LayoutRow, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, RadioEntryData, RadioInput, Separator, SeparatorDirection,
SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout,
};
use crate::message_prelude::*;
use crate::viewport_tools::vector_editor::vector_shape::VectorShape;
@@ -18,6 +19,7 @@ use crate::EditorError;
use graphene::color::Color;
use graphene::document::Document as GrapheneDocument;
use graphene::layers::blend_mode::BlendMode;
use graphene::layers::folder_layer::FolderLayer;
use graphene::layers::layer_info::LayerDataType;
use graphene::layers::style::{Fill, ViewMode};
@@ -32,15 +34,24 @@ use std::collections::VecDeque;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DocumentMessageHandler {
pub graphene_document: GrapheneDocument,
pub saved_document_identifier: u64,
pub name: String,
pub version: String,
pub document_mode: DocumentMode,
pub view_mode: ViewMode,
pub snapping_enabled: bool,
pub overlays_visible: bool,
#[serde(skip)]
pub document_undo_history: Vec<DocumentSave>,
#[serde(skip)]
pub document_redo_history: Vec<DocumentSave>,
pub saved_document_identifier: u64,
pub name: String,
#[serde(with = "vectorize_layer_metadata")]
pub layer_metadata: HashMap<Vec<LayerId>, LayerMetadata>,
layer_range_selection_reference: Vec<LayerId>,
movement_handler: MovementMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
@@ -48,31 +59,32 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
transform_layer_handler: TransformLayerMessageHandler,
properties_panel_message_handler: PropertiesPanelMessageHandler,
pub overlays_visible: bool,
pub snapping_enabled: bool,
pub view_mode: ViewMode,
pub version: String,
}
impl Default for DocumentMessageHandler {
fn default() -> Self {
Self {
graphene_document: GrapheneDocument::default(),
document_undo_history: Vec::new(),
document_redo_history: Vec::new(),
saved_document_identifier: 0,
name: String::from("Untitled Document"),
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
document_mode: DocumentMode::DesignMode,
view_mode: ViewMode::default(),
snapping_enabled: true,
overlays_visible: true,
document_undo_history: Vec::new(),
document_redo_history: Vec::new(),
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
layer_range_selection_reference: Vec::new(),
movement_handler: MovementMessageHandler::default(),
overlays_message_handler: OverlaysMessageHandler::default(),
artboard_message_handler: ArtboardMessageHandler::default(),
transform_layer_handler: TransformLayerMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
snapping_enabled: true,
overlays_visible: true,
view_mode: ViewMode::default(),
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
}
}
}
@@ -130,7 +142,7 @@ impl DocumentMessageHandler {
if let Some(layer) = self.layer_metadata.get_mut(path) {
layer.selected = true;
let data = self.layer_panel_entry(path.to_vec()).ok()?;
(!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayer { data }.into())
(!path.is_empty()).then(|| FrontendMessage::UpdateDocumentLayerDetails { data }.into())
} else {
log::warn!("Tried to select non existing layer {:?}", path);
None
@@ -516,11 +528,9 @@ impl DocumentMessageHandler {
responses.push_back(FrontendMessage::TriggerFontLoadDefault.into())
}
}
}
impl PropertyHolder for DocumentMessageHandler {
fn properties(&self) -> WidgetLayout {
WidgetLayout::new(vec![LayoutRow::Row {
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
let document_bar_layout = WidgetLayout::new(vec![LayoutRow::Row {
widgets: vec![
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
checked: self.snapping_enabled,
@@ -600,11 +610,11 @@ impl PropertyHolder for DocumentMessageHandler {
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: "°".into(),
value: self.movement_handler.tilt / (std::f64::consts::PI / 180.),
value: Some(self.movement_handler.tilt / (std::f64::consts::PI / 180.)),
increment_factor: 15.,
on_update: WidgetCallback::new(|number_input: &NumberInput| {
MovementMessage::SetCanvasRotation {
angle_radians: number_input.value * (std::f64::consts::PI / 180.),
angle_radians: number_input.value.unwrap() * (std::f64::consts::PI / 180.),
}
.into()
}),
@@ -641,12 +651,12 @@ impl PropertyHolder for DocumentMessageHandler {
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: "%".into(),
value: self.movement_handler.zoom * 100.,
value: Some(self.movement_handler.zoom * 100.),
min: Some(0.000001),
max: Some(1000000.),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
MovementMessage::SetCanvasZoom {
zoom_factor: number_input.value / 100.,
zoom_factor: number_input.value.unwrap() / 100.,
}
.into()
}),
@@ -656,7 +666,163 @@ impl PropertyHolder for DocumentMessageHandler {
..NumberInput::default()
})),
],
}])
}]);
let document_mode_layout = WidgetLayout::new(vec![LayoutRow::Row {
widgets: vec![WidgetHolder::new(Widget::DropdownInput(DropdownInput {
entries: vec![vec![
DropdownEntryData {
label: DocumentMode::DesignMode.to_string(),
icon: DocumentMode::DesignMode.icon_name(),
..DropdownEntryData::default()
},
DropdownEntryData {
label: DocumentMode::SelectMode.to_string(),
icon: DocumentMode::SelectMode.icon_name(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(330) }.into()),
..DropdownEntryData::default()
},
DropdownEntryData {
label: DocumentMode::GuideMode.to_string(),
icon: DocumentMode::GuideMode.icon_name(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(331) }.into()),
..DropdownEntryData::default()
},
]],
selected_index: Some(self.document_mode as u32),
draw_icon: true,
..Default::default()
}))],
}]);
responses.push_back(
LayoutMessage::SendLayout {
layout: document_bar_layout,
layout_target: LayoutTarget::DocumentBar,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: document_mode_layout,
layout_target: LayoutTarget::DocumentMode,
}
.into(),
);
}
pub fn update_layer_tree_options_bar_widgets(&self, responses: &mut VecDeque<Message>) {
let mut opacity = None;
let mut opacity_is_mixed = false;
let mut blend_mode = None;
let mut blend_mode_is_mixed = false;
self.layer_metadata
.keys()
.filter_map(|path| self.layer_panel_entry_from_path(path))
.filter(|layer_panel_entry| layer_panel_entry.layer_metadata.selected)
.flat_map(|layer_panel_entry| self.graphene_document.layer(layer_panel_entry.path.as_slice()))
.for_each(|layer| {
match opacity {
None => opacity = Some(layer.opacity),
Some(opacity) => {
if (opacity - layer.opacity).abs() > (1. / 1_000_000.) {
opacity_is_mixed = true;
}
}
}
match blend_mode {
None => blend_mode = Some(layer.blend_mode),
Some(blend_mode) => {
if blend_mode != layer.blend_mode {
blend_mode_is_mixed = true;
}
}
}
});
if opacity_is_mixed {
opacity = None;
}
if blend_mode_is_mixed {
blend_mode = None;
}
let blend_mode_menu_entries = BlendMode::list_modes_in_groups()
.iter()
.map(|modes| {
modes
.iter()
.map(|mode| DropdownEntryData {
label: mode.to_string(),
value: mode.to_string(),
on_update: WidgetCallback::new(|_| DocumentMessage::SetBlendModeForSelectedLayers { blend_mode: *mode }.into()),
..Default::default()
})
.collect()
})
.collect();
let layer_tree_options = WidgetLayout::new(vec![LayoutRow::Row {
widgets: vec![
WidgetHolder::new(Widget::DropdownInput(DropdownInput {
entries: blend_mode_menu_entries,
selected_index: blend_mode.map(|blend_mode| blend_mode as u32),
disabled: blend_mode.is_none() && !blend_mode_is_mixed,
draw_icon: false,
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Related,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "Opacity".into(),
unit: "%".into(),
display_decimal_places: 2,
disabled: opacity.is_none() && !opacity_is_mixed,
value: opacity.map(|opacity| opacity * 100.),
min: Some(0.),
max: Some(100.),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
if let Some(value) = number_input.value {
DocumentMessage::SetOpacityForSelectedLayers { opacity: value / 100. }.into()
} else {
Message::NoOp
}
}),
..NumberInput::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Section,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "NodeFolder".into(),
tooltip: "New Folder (Ctrl+Shift+N)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::CreateEmptyFolder { container_path: vec![] }.into()),
..Default::default()
})),
WidgetHolder::new(Widget::IconButton(IconButton {
icon: "Trash".into(),
tooltip: "Delete Selected (Del)".into(), // TODO: Customize this tooltip for the Mac version of the keyboard shortcut
size: 24,
on_update: WidgetCallback::new(|_| DocumentMessage::DeleteSelectedLayers.into()),
..Default::default()
})),
],
}]);
responses.push_back(
LayoutMessage::SendLayout {
layout: layer_tree_options,
layout_target: LayoutTarget::LayerTreeOptions,
}
.into(),
);
}
}
@@ -757,6 +923,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
// TODO: Correctly update layer panel in clear_selection instead of here
responses.push_back(FolderChanged { affected_folder_path: vec![] }.into());
responses.push_back(DocumentMessage::SelectionChanged.into());
self.update_layer_tree_options_bar_widgets(responses);
}
AlignSelectedLayers { axis, aggregate } => {
self.backup(responses);
@@ -796,7 +964,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}
}
BooleanOperation(op) => {
// convert Vec<&[LayerId]> to Vec<Vec<&LayerId>> because Vec<&[LayerId]> does not implement several traits (Debug, Serialize, Deserialize, ...) required by DocumentOperation enum
// Convert Vec<&[LayerId]> to Vec<Vec<&LayerId>> because Vec<&[LayerId]> does not implement several traits (Debug, Serialize, Deserialize, ...) required by DocumentOperation enum
responses.push_back(StartTransaction.into());
responses.push_back(
DocumentOperation::BooleanOperation {
@@ -974,9 +1142,10 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}
LayerChanged { affected_layer_path } => {
if let Ok(layer_entry) = self.layer_panel_entry(affected_layer_path.clone()) {
responses.push_back(FrontendMessage::UpdateDocumentLayer { data: layer_entry }.into());
responses.push_back(FrontendMessage::UpdateDocumentLayerDetails { data: layer_entry }.into());
}
responses.push_back(PropertiesPanelMessage::CheckSelectedWasUpdated { path: affected_layer_path }.into());
self.update_layer_tree_options_bar_widgets(responses);
}
LoadFont { font } => {
if !self.graphene_document.font_cache.loaded_font(&font) {
-5
View File
@@ -1,5 +1,4 @@
use graphene::document::FontCache;
use graphene::layers::blend_mode::BlendMode;
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
use graphene::layers::style::ViewMode;
use graphene::LayerId;
@@ -48,8 +47,6 @@ pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, la
LayerPanelEntry {
name,
visible: layer.visible,
blend_mode: layer.blend_mode,
opacity: layer.opacity,
layer_type: (&layer.data).into(),
layer_metadata: *layer_metadata,
path,
@@ -88,8 +85,6 @@ impl Serialize for RawBuffer {
pub struct LayerPanelEntry {
pub name: String,
pub visible: bool,
pub blend_mode: BlendMode,
pub opacity: f64,
pub layer_type: LayerDataTypeDiscriminant,
pub layer_metadata: LayerMetadata,
pub path: Vec<LayerId>,
@@ -153,10 +153,9 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
self.zoom = 1.
}
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.zoom }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
responses.push_back(PortfolioMessage::UpdateDocumentBar.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
IncreaseCanvasZoom { center_on_mouse } => {
@@ -245,15 +244,13 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
self.tilt = angle_radians;
self.create_document_transform(&ipp.viewport_bounds, responses);
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(FrontendMessage::UpdateCanvasRotation { angle_radians: self.snapped_angle() }.into());
responses.push_back(PortfolioMessage::UpdateDocumentBar.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
}
SetCanvasZoom { zoom_factor } => {
self.zoom = zoom_factor.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.snapped_scale() }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
responses.push_back(PortfolioMessage::UpdateDocumentBar.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
self.create_document_transform(&ipp.viewport_bounds, responses);
}
TransformCanvasEnd => {
+1 -1
View File
@@ -67,6 +67,6 @@ pub enum PortfolioMessage {
SetActiveDocument {
document_id: u64,
},
UpdateDocumentBar,
UpdateDocumentWidgets,
UpdateOpenDocumentsList,
}
@@ -73,9 +73,10 @@ impl PortfolioMessageHandler {
.layer_metadata
.keys()
.filter_map(|path| new_document.layer_panel_entry_from_path(path))
.map(|entry| FrontendMessage::UpdateDocumentLayer { data: entry }.into())
.map(|entry| FrontendMessage::UpdateDocumentLayerDetails { data: entry }.into())
.collect::<Vec<_>>(),
);
new_document.update_layer_tree_options_bar_widgets(responses);
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
new_document.load_default_font(responses);
@@ -432,14 +433,14 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
responses.push_back(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() }.into());
}
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(PortfolioMessage::UpdateDocumentBar.into());
responses.push_back(PortfolioMessage::UpdateDocumentWidgets.into());
}
SetActiveDocument { document_id } => {
self.active_document_id = document_id;
}
UpdateDocumentBar => {
UpdateDocumentWidgets => {
let active_document = self.active_document();
active_document.register_properties(responses, LayoutTarget::DocumentBar)
active_document.update_document_widgets(responses);
}
UpdateOpenDocumentsList => {
// Send the list of document tab names
@@ -137,14 +137,14 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(vec![]),
layout_target: LayoutTarget::PropertiesOptionsPanel,
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(vec![]),
layout_target: LayoutTarget::PropertiesSectionsPanel,
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
@@ -217,14 +217,14 @@ impl<'a> MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageHandlerDat
self.active_selection = None;
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesOptionsPanel,
layout_target: LayoutTarget::PropertiesOptions,
layout: WidgetLayout::default(),
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout_target: LayoutTarget::PropertiesSectionsPanel,
layout_target: LayoutTarget::PropertiesSections,
layout: WidgetLayout::default(),
}
.into(),
@@ -308,12 +308,12 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.x(),
value: Some(layer.transform.x()),
label: "X".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::X,
}
.into()
@@ -325,12 +325,12 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.y(),
value: Some(layer.transform.y()),
label: "Y".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Y,
}
.into()
@@ -350,12 +350,12 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.bounding_transform(font_cache).scale_x(),
value: Some(layer.bounding_transform(font_cache).scale_x()),
label: "W".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Width,
}
.into()
@@ -367,12 +367,12 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.bounding_transform(font_cache).scale_y(),
value: Some(layer.bounding_transform(font_cache).scale_y()),
label: "H".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Height,
}
.into()
@@ -416,14 +416,14 @@ fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDeque<Me
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(options_bar),
layout_target: LayoutTarget::PropertiesOptionsPanel,
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(properties_body),
layout_target: LayoutTarget::PropertiesSectionsPanel,
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
@@ -504,14 +504,14 @@ fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Mes
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(options_bar),
layout_target: LayoutTarget::PropertiesOptionsPanel,
layout_target: LayoutTarget::PropertiesOptions,
}
.into(),
);
responses.push_back(
LayoutMessage::SendLayout {
layout: WidgetLayout::new(properties_body),
layout_target: LayoutTarget::PropertiesSectionsPanel,
layout_target: LayoutTarget::PropertiesSections,
}
.into(),
);
@@ -532,12 +532,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.x(),
value: Some(layer.transform.x()),
label: "X".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::X,
}
.into()
@@ -549,12 +549,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.y(),
value: Some(layer.transform.y()),
label: "Y".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Y,
}
.into()
@@ -574,12 +574,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.rotation() * 180. / PI,
value: Some(layer.transform.rotation() * 180. / PI),
label: "".into(),
unit: "°".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value / 180. * PI,
value: number_input.value.unwrap() / 180. * PI,
transform_op: TransformOp::Rotation,
}
.into()
@@ -599,12 +599,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.scale_x(),
value: Some(layer.transform.scale_x()),
label: "X".into(),
unit: "".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::ScaleX,
}
.into()
@@ -616,12 +616,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.transform.scale_y(),
value: Some(layer.transform.scale_y()),
label: "Y".into(),
unit: "".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::ScaleY,
}
.into()
@@ -641,12 +641,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.bounding_transform(font_cache).scale_x(),
value: Some(layer.bounding_transform(font_cache).scale_x()),
label: "W".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Width,
}
.into()
@@ -658,12 +658,12 @@ fn node_section_transform(layer: &Layer, font_cache: &FontCache) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.bounding_transform(font_cache).scale_y(),
value: Some(layer.bounding_transform(font_cache).scale_y()),
label: "H".into(),
unit: " px".into(),
on_update: WidgetCallback::new(|number_input: &NumberInput| {
PropertiesPanelMessage::ModifyTransform {
value: number_input.value,
value: number_input.value.unwrap(),
transform_op: TransformOp::Height,
}
.into()
@@ -765,7 +765,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: layer.size,
value: Some(layer.size),
min: Some(1.),
unit: " px".into(),
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
@@ -773,7 +773,7 @@ fn node_section_font(layer: &TextLayer) -> LayoutRow {
font_family: font_family.clone(),
font_style: font_style.clone(),
font_file: font_file.clone(),
size: number_input.value,
size: number_input.value.unwrap(),
}
.into()
}),
@@ -954,13 +954,13 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: stroke.weight() as f64,
value: Some(stroke.weight() as f64),
is_integer: false,
min: Some(0.),
unit: " px".into(),
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
PropertiesPanelMessage::ModifyStroke {
stroke: internal_stroke2.clone().with_weight(number_input.value),
stroke: internal_stroke2.clone().with_weight(number_input.value.unwrap()),
}
.into()
}),
@@ -1000,13 +1000,13 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: stroke.dash_offset() as f64,
value: Some(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),
stroke: internal_stroke4.clone().with_dash_offset(number_input.value.unwrap()),
}
.into()
}),
@@ -1120,13 +1120,13 @@ fn node_section_stroke(stroke: &Stroke) -> LayoutRow {
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: stroke.line_join_miter_limit() as f64,
value: Some(stroke.line_join_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_line_join_miter_limit(number_input.value),
stroke: internal_stroke5.clone().with_line_join_miter_limit(number_input.value.unwrap()),
}
.into()
}),
+29
View File
@@ -4,6 +4,7 @@ use graphene::LayerId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
pub type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
@@ -32,3 +33,31 @@ pub enum TargetDocument {
Artboard,
Artwork,
}
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum DocumentMode {
DesignMode,
SelectMode,
GuideMode,
}
impl fmt::Display for DocumentMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
DocumentMode::DesignMode => "Design Mode".to_string(),
DocumentMode::SelectMode => "Select Mode".to_string(),
DocumentMode::GuideMode => "Guide Mode".to_string(),
};
write!(f, "{}", text)
}
}
impl DocumentMode {
pub fn icon_name(&self) -> String {
match self {
DocumentMode::DesignMode => "ViewportDesignMode".to_string(),
DocumentMode::SelectMode => "ViewportSelectMode".to_string(),
DocumentMode::GuideMode => "ViewportGuideMode".to_string(),
}
}
}
+4 -4
View File
@@ -34,25 +34,25 @@ pub enum FrontendMessage {
// Update prefix: give the frontend a new value or state for it to use
UpdateActiveDocument { document_id: u64 },
UpdateActiveTool { tool_name: String },
UpdateCanvasRotation { angle_radians: f64 },
UpdateCanvasZoom { factor: f64 },
UpdateDialogDetails { layout_target: LayoutTarget, layout: SubLayout },
UpdateDocumentArtboards { svg: String },
UpdateDocumentArtwork { svg: String },
UpdateDocumentBarLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateDocumentLayer { data: LayerPanelEntry },
UpdateDocumentLayerDetails { data: LayerPanelEntry },
UpdateDocumentLayerTreeStructure { data_buffer: RawBuffer },
UpdateDocumentModeLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateDocumentOverlays { svg: String },
UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 },
UpdateDocumentScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
UpdateImageData { image_data: Vec<FrontendImageData> },
UpdateInputHints { hint_data: HintData },
UpdateLayerTreeOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateMouseCursor { cursor: MouseCursorIcon },
UpdateNodeGraphVisibility { visible: bool },
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },
UpdatePropertyPanelOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdatePropertyPanelSectionsLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateToolOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateToolShelfLayout { layout_target: LayoutTarget, layout: SubLayout },
UpdateWorkingColors { primary: Color, secondary: Color },
}
+5 -2
View File
@@ -17,9 +17,12 @@ pub enum LayoutMessage {
pub enum LayoutTarget {
DialogDetails,
DocumentBar,
PropertiesOptionsPanel,
PropertiesSectionsPanel,
DocumentMode,
LayerTreeOptions,
PropertiesOptions,
PropertiesSections,
ToolOptions,
ToolShelf,
// KEEP THIS ENUM LAST
// This is a marker that is used to define an array that is used to hold widgets
+24 -9
View File
@@ -12,30 +12,45 @@ pub struct LayoutMessageHandler {
}
impl LayoutMessageHandler {
#[remain::check]
fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque<Message>) {
let widget_layout = &self.layouts[layout_target as usize];
#[remain::sorted]
let message = match layout_target {
LayoutTarget::DialogDetails => FrontendMessage::UpdateDialogDetails {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::PropertiesOptionsPanel => FrontendMessage::UpdatePropertyPanelOptionsLayout {
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::PropertiesSectionsPanel => FrontendMessage::UpdatePropertyPanelSectionsLayout {
LayoutTarget::LayerTreeOptions => FrontendMessage::UpdateLayerTreeOptionsLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout {
layout_target,
layout: widget_layout.layout.clone(),
},
#[remain::unsorted]
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
};
responses.push_back(message.into());
@@ -72,8 +87,8 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
}
Widget::DropdownInput(dropdown_input) => {
let update_value = value.as_u64().expect("DropdownInput update was not of type: u64");
dropdown_input.selected_index = update_value as u32;
let callback_message = (dropdown_input.menu_entries.iter().flatten().nth(update_value as usize).unwrap().on_update.callback)(&());
dropdown_input.selected_index = Some(update_value as u32);
let callback_message = (dropdown_input.entries.iter().flatten().nth(update_value as usize).unwrap().on_update.callback)(&());
responses.push_back(callback_message);
}
Widget::FontInput(font_input) => {
@@ -102,7 +117,7 @@ impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
Widget::NumberInput(number_input) => match value {
Value::Number(num) => {
let update_value = num.as_f64().unwrap();
number_input.value = update_value;
number_input.value = Some(update_value);
let callback_message = (number_input.on_update.callback)(number_input);
responses.push_back(callback_message);
}
+28 -11
View File
@@ -52,8 +52,18 @@ pub type SubLayout = Vec<LayoutRow>;
#[remain::sorted]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum LayoutRow {
Row { widgets: Vec<WidgetHolder> },
Section { name: String, layout: SubLayout },
Column {
#[serde(rename = "columnWidgets")]
widgets: Vec<WidgetHolder>,
},
Row {
#[serde(rename = "rowWidgets")]
widgets: Vec<WidgetHolder>,
},
Section {
name: String,
layout: SubLayout,
},
}
#[derive(Debug, Default)]
@@ -72,6 +82,10 @@ impl<'a> Iterator for WidgetIter<'a> {
}
match self.stack.pop() {
Some(LayoutRow::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutRow::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
@@ -103,6 +117,10 @@ impl<'a> Iterator for WidgetIterMut<'a> {
};
match self.stack.pop() {
Some(LayoutRow::Column { widgets }) => {
self.current_slice = Some(widgets);
self.next()
}
Some(LayoutRow::Row { widgets }) => {
self.current_slice = Some(widgets);
self.next()
@@ -170,7 +188,7 @@ pub enum Widget {
#[derive(Clone, Serialize, Deserialize, Derivative)]
#[derivative(Debug, PartialEq, Default)]
pub struct NumberInput {
pub value: f64,
pub value: Option<f64>,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<NumberInput>,
@@ -285,6 +303,7 @@ pub struct IconButton {
#[serde(rename = "title")]
pub tooltip: String,
pub size: u32,
pub active: bool,
#[serde(rename = "gapAfter")]
pub gap_after: bool,
#[serde(skip)]
@@ -298,12 +317,12 @@ pub struct IconButton {
pub struct TextButton {
pub label: String,
pub emphasized: bool,
pub disabled: bool,
pub min_width: u32,
pub gap_after: bool,
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
pub on_update: WidgetCallback<TextButton>,
pub disabled: bool,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
@@ -342,16 +361,14 @@ pub struct PopoverButton {
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
#[derivative(Debug, PartialEq)]
pub struct DropdownInput {
#[serde(rename = "menuEntries")]
pub menu_entries: Vec<Vec<DropdownEntryData>>,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number
// TODO(mfish33): Replace with usize when using native UI
pub entries: Vec<Vec<DropdownEntryData>>,
// This uses `u32` instead of `usize` since it will be serialized as a normal JS number (replace with usize when we switch to a native UI)
#[serde(rename = "selectedIndex")]
pub selected_index: u32,
pub selected_index: Option<u32>,
#[serde(rename = "drawIcon")]
pub draw_icon: bool,
// `on_update` exists on the `DropdownEntryData`, not this parent `DropdownInput`
pub disabled: bool,
}
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
+200 -39
View File
@@ -2,7 +2,7 @@ use super::tools::*;
use crate::communication::message_handler::MessageHandler;
use crate::document::DocumentMessageHandler;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::widgets::PropertyHolder;
use crate::layout::widgets::{IconButton, LayoutRow, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
use crate::message_prelude::*;
use graphene::color::Color;
@@ -65,6 +65,43 @@ impl ToolData {
}
}
impl PropertyHolder for ToolData {
fn properties(&self) -> WidgetLayout {
let tool_groups_layout = ToolType::list_tools_in_groups()
.iter()
.flat_map(|group| {
let separator = std::iter::once(WidgetHolder::new(Widget::Separator(Separator {
direction: SeparatorDirection::Vertical,
separator_type: SeparatorType::Section,
})));
let buttons = group.iter().map(|tool_type| {
WidgetHolder::new(Widget::IconButton(IconButton {
icon: tool_type.icon_name(),
size: 32,
tooltip: tool_type.tooltip(),
active: self.active_tool_type == *tool_type,
on_update: WidgetCallback::new(|_| {
if !tool_type.tooltip().contains("Coming Soon") {
ToolMessage::ActivateTool { tool_type: *tool_type }.into()
} else {
DialogMessage::RequestComingSoonDialog { issue: None }.into()
}
}),
..Default::default()
}))
});
separator.chain(buttons)
})
// Skip the initial separator
.skip(1)
.collect();
WidgetLayout {
layout: vec![LayoutRow::Column { widgets: tool_groups_layout }],
}
}
}
#[derive(Debug)]
pub struct ToolFsmState {
pub document_tool_data: DocumentToolData,
@@ -126,19 +163,15 @@ impl ToolFsmState {
#[repr(usize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ToolType {
// General tool group
Select,
Artboard,
Navigate,
Eyedropper,
Text,
Fill,
Gradient,
Brush,
Heal,
Clone,
Patch,
Detail,
Relight,
// Vector tool group
Path,
Pen,
Freehand,
@@ -147,6 +180,115 @@ pub enum ToolType {
Rectangle,
Ellipse,
Shape,
Text,
// Raster tool group
Brush,
Heal,
Clone,
Patch,
Detail,
Relight,
}
impl ToolType {
/// List of all the tools in their conventional ordering and grouping.
pub fn list_tools_in_groups() -> [&'static [ToolType]; 3] {
[
&[
// General tool group
ToolType::Select,
ToolType::Artboard,
ToolType::Navigate,
ToolType::Eyedropper,
ToolType::Fill,
ToolType::Gradient,
],
&[
// Vector tool group
ToolType::Path,
ToolType::Pen,
ToolType::Freehand,
ToolType::Spline,
ToolType::Line,
ToolType::Rectangle,
ToolType::Ellipse,
ToolType::Shape,
ToolType::Text,
],
&[
// Raster tool group
ToolType::Brush,
ToolType::Heal,
ToolType::Clone,
ToolType::Patch,
ToolType::Detail,
ToolType::Relight,
],
]
}
pub fn icon_name(&self) -> String {
match self {
// General tool group
ToolType::Select => "GeneralSelectTool".into(),
ToolType::Artboard => "GeneralArtboardTool".into(),
ToolType::Navigate => "GeneralNavigateTool".into(),
ToolType::Eyedropper => "GeneralEyedropperTool".into(),
ToolType::Fill => "GeneralFillTool".into(),
ToolType::Gradient => "GeneralGradientTool".into(),
// Vector tool group
ToolType::Path => "VectorPathTool".into(),
ToolType::Pen => "VectorPenTool".into(),
ToolType::Freehand => "VectorFreehandTool".into(),
ToolType::Spline => "VectorSplineTool".into(),
ToolType::Line => "VectorLineTool".into(),
ToolType::Rectangle => "VectorRectangleTool".into(),
ToolType::Ellipse => "VectorEllipseTool".into(),
ToolType::Shape => "VectorShapeTool".into(),
ToolType::Text => "VectorTextTool".into(),
// Raster tool group
ToolType::Brush => "RasterBrushTool".into(),
ToolType::Heal => "RasterHealTool".into(),
ToolType::Clone => "RasterCloneTool".into(),
ToolType::Patch => "RasterPatchTool".into(),
ToolType::Detail => "RasterDetailTool".into(),
ToolType::Relight => "RasterRelightTool".into(),
}
}
pub fn tooltip(&self) -> String {
match self {
// General tool group
ToolType::Select => "Select Tool (V)".into(),
ToolType::Artboard => "Artboard Tool".into(),
ToolType::Navigate => "Navigate Tool (Z)".into(),
ToolType::Eyedropper => "Eyedropper Tool (I)".into(),
ToolType::Fill => "Fill Tool (F)".into(),
ToolType::Gradient => "Gradient Tool (H)".into(),
// Vector tool group
ToolType::Path => "Path Tool (A)".into(),
ToolType::Pen => "Pen Tool (P)".into(),
ToolType::Freehand => "Freehand Tool (N)".into(),
ToolType::Spline => "Spline Tool".into(),
ToolType::Line => "Line Tool (L)".into(),
ToolType::Rectangle => "Rectangle Tool (M)".into(),
ToolType::Ellipse => "Ellipse Tool (E)".into(),
ToolType::Shape => "Shape Tool (Y)".into(),
ToolType::Text => "Text Tool (T)".into(),
// Raster tool group
ToolType::Brush => "Coming Soon: Brush Tool (B)".into(),
ToolType::Heal => "Coming Soon: Heal Tool (J)".into(),
ToolType::Clone => "Coming Soon: Clone Tool (C)".into(),
ToolType::Patch => "Coming Soon: Patch Tool".into(),
ToolType::Detail => "Coming Soon: Detail Tool (D)".into(),
ToolType::Relight => "Coming Soon: Relight Tool (O)".into(),
}
}
}
impl fmt::Display for ToolType {
@@ -154,19 +296,15 @@ impl fmt::Display for ToolType {
use ToolType::*;
let name = match_variant_name!(match (self) {
// General tool group
Select,
Artboard,
Navigate,
Eyedropper,
Text,
Fill,
Gradient,
Brush,
Heal,
Clone,
Patch,
Detail,
Relight,
// Vector tool group
Path,
Pen,
Freehand,
@@ -174,7 +312,16 @@ impl fmt::Display for ToolType {
Line,
Rectangle,
Ellipse,
Shape
Shape,
Text,
// Raster tool group
Brush,
Heal,
Clone,
Patch,
Detail,
Relight,
});
formatter.write_str(name)
@@ -191,19 +338,15 @@ pub enum StandardToolMessageType {
pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) -> Option<ToolMessage> {
match message_type {
StandardToolMessageType::DocumentIsDirty => match tool {
// General tool group
ToolType::Select => Some(SelectToolMessage::DocumentIsDirty.into()),
ToolType::Artboard => Some(ArtboardToolMessage::DocumentIsDirty.into()),
ToolType::Navigate => None, // Some(NavigateToolMessage::DocumentIsDirty.into()),
ToolType::Eyedropper => None, // Some(EyedropperToolMessage::DocumentIsDirty.into()),
ToolType::Text => Some(TextMessage::DocumentIsDirty.into()),
ToolType::Fill => None, // Some(FillToolMessage::DocumentIsDirty.into()),
ToolType::Fill => None, // Some(FillToolMessage::DocumentIsDirty.into()),
ToolType::Gradient => Some(GradientToolMessage::DocumentIsDirty.into()),
ToolType::Brush => None, // Some(BrushMessage::DocumentIsDirty.into()),
ToolType::Heal => None, // Some(HealMessage::DocumentIsDirty.into()),
ToolType::Clone => None, // Some(CloneMessage::DocumentIsDirty.into()),
ToolType::Patch => None, // Some(PatchMessage::DocumentIsDirty.into()),
ToolType::Detail => None, // Some(DetailToolMessage::DocumentIsDirty.into()),
ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()),
// Vector tool group
ToolType::Path => Some(PathToolMessage::DocumentIsDirty.into()),
ToolType::Pen => Some(PenToolMessage::DocumentIsDirty.into()),
ToolType::Freehand => None, // Some(FreehandToolMessage::DocumentIsDirty.into()),
@@ -212,21 +355,26 @@ pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageTy
ToolType::Rectangle => None, // Some(RectangleToolMessage::DocumentIsDirty.into()),
ToolType::Ellipse => None, // Some(EllipseToolMessage::DocumentIsDirty.into()),
ToolType::Shape => None, // Some(ShapeToolMessage::DocumentIsDirty.into()),
ToolType::Text => Some(TextMessage::DocumentIsDirty.into()),
// Raster tool group
ToolType::Brush => None, // Some(BrushMessage::DocumentIsDirty.into()),
ToolType::Heal => None, // Some(HealMessage::DocumentIsDirty.into()),
ToolType::Clone => None, // Some(CloneMessage::DocumentIsDirty.into()),
ToolType::Patch => None, // Some(PatchMessage::DocumentIsDirty.into()),
ToolType::Detail => None, // Some(DetailToolMessage::DocumentIsDirty.into()),
ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()),
},
StandardToolMessageType::Abort => match tool {
// General tool group
ToolType::Select => Some(SelectToolMessage::Abort.into()),
ToolType::Artboard => Some(ArtboardToolMessage::Abort.into()),
ToolType::Navigate => Some(NavigateToolMessage::Abort.into()),
ToolType::Eyedropper => Some(EyedropperToolMessage::Abort.into()),
ToolType::Text => Some(TextMessage::Abort.into()),
ToolType::Fill => Some(FillToolMessage::Abort.into()),
ToolType::Gradient => Some(GradientToolMessage::Abort.into()),
// ToolType::Brush => Some(BrushMessage::Abort.into()),
// ToolType::Heal => Some(HealMessage::Abort.into()),
// ToolType::Clone => Some(CloneMessage::Abort.into()),
// ToolType::Patch => Some(PatchMessage::Abort.into()),
// ToolType::Detail => Some(DetailToolMessage::Abort.into()),
// ToolType::Relight => Some(RelightMessage::Abort.into()),
// Vector tool group
ToolType::Path => Some(PathToolMessage::Abort.into()),
ToolType::Pen => Some(PenToolMessage::Abort.into()),
ToolType::Freehand => Some(FreehandToolMessage::Abort.into()),
@@ -235,7 +383,15 @@ pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageTy
ToolType::Rectangle => Some(RectangleToolMessage::Abort.into()),
ToolType::Ellipse => Some(EllipseToolMessage::Abort.into()),
ToolType::Shape => Some(ShapeToolMessage::Abort.into()),
_ => None,
ToolType::Text => Some(TextMessage::Abort.into()),
// Raster tool group
ToolType::Brush => None, // Some(BrushMessage::Abort.into()),
ToolType::Heal => None, // Some(HealMessage::Abort.into()),
ToolType::Clone => None, // Some(CloneMessage::Abort.into()),
ToolType::Patch => None, // Some(PatchMessage::Abort.into()),
ToolType::Detail => None, // Some(DetailToolMessage::Abort.into()),
ToolType::Relight => None, // Some(RelightMessage::Abort.into()),
},
StandardToolMessageType::SelectionChanged => match tool {
ToolType::Path => Some(PathToolMessage::SelectionChanged.into()),
@@ -248,19 +404,15 @@ pub fn message_to_tool_type(message: &ToolMessage) -> ToolType {
use ToolMessage::*;
match message {
// General tool group
Select(_) => ToolType::Select,
Artboard(_) => ToolType::Artboard,
Navigate(_) => ToolType::Navigate,
Eyedropper(_) => ToolType::Eyedropper,
Text(_) => ToolType::Text,
Fill(_) => ToolType::Fill,
Gradient(_) => ToolType::Gradient,
// Brush(_) => ToolType::Brush,
// Heal(_) => ToolType::Heal,
// Clone(_) => ToolType::Clone,
// Patch(_) => ToolType::Patch,
// Detail(_) => ToolType::Detail,
// Relight(_) => ToolType::Relight,
// Vector tool group
Path(_) => ToolType::Path,
Pen(_) => ToolType::Pen,
Freehand(_) => ToolType::Freehand,
@@ -269,6 +421,15 @@ pub fn message_to_tool_type(message: &ToolMessage) -> ToolType {
Rectangle(_) => ToolType::Rectangle,
Ellipse(_) => ToolType::Ellipse,
Shape(_) => ToolType::Shape,
Text(_) => ToolType::Text,
// Raster tool group
// Brush(_) => ToolType::Brush,
// Heal(_) => ToolType::Heal,
// Clone(_) => ToolType::Clone,
// Patch(_) => ToolType::Patch,
// Detail(_) => ToolType::Detail,
// Relight(_) => ToolType::Relight,
_ => panic!("Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool"),
}
}
@@ -82,6 +82,7 @@ pub enum ToolMessage {
tool_type: ToolType,
},
DocumentIsDirty,
InitTools,
ResetColors,
SelectionChanged,
SelectPrimaryColor {
@@ -2,6 +2,7 @@ use super::tool::{message_to_tool_type, standard_tool_message, update_working_co
use crate::document::DocumentMessageHandler;
use crate::input::InputPreprocessorMessageHandler;
use crate::layout::layout_message::LayoutTarget;
use crate::layout::widgets::PropertyHolder;
use crate::message_prelude::*;
use graphene::color::Color;
@@ -69,12 +70,11 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
// Store the new active tool
tool_data.active_tool_type = tool_type;
// Notify the frontend about the new active tool to be displayed
let tool_name = tool_type.to_string();
responses.push_back(FrontendMessage::UpdateActiveTool { tool_name }.into());
// Send Properties to the frontend
tool_data.tools.get(&tool_type).unwrap().register_properties(responses, LayoutTarget::ToolOptions);
// Notify the frontend about the new active tool to be displayed
tool_data.register_properties(responses, LayoutTarget::ToolShelf);
}
DocumentIsDirty => {
// Send the DocumentIsDirty message to the active tool's sub-tool message handler
@@ -83,6 +83,21 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
responses.push_back(message.into());
}
}
InitTools => {
let tool_data = &mut self.tool_state.tool_data;
let document_data = &self.tool_state.document_tool_data;
let active_tool = &tool_data.active_tool_type;
// Register initial properties
tool_data.tools.get(active_tool).unwrap().register_properties(responses, LayoutTarget::ToolOptions);
// Notify the frontend about the initial active tool
tool_data.register_properties(responses, LayoutTarget::ToolShelf);
// Set initial hints and cursor
tool_data.active_tool_mut().process_action(ToolMessage::UpdateHints, (document, document_data, input), responses);
tool_data.active_tool_mut().process_action(ToolMessage::UpdateCursor, (document, document_data, input), responses);
}
ResetColors => {
let document_data = &mut self.tool_state.document_tool_data;
@@ -63,10 +63,10 @@ impl PropertyHolder for FreehandTool {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: self.options.line_weight as f64,
value: Some(self.options.line_weight as f64),
is_integer: false,
min: Some(1.),
on_update: WidgetCallback::new(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandToolMessageOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}])
+2 -2
View File
@@ -64,10 +64,10 @@ impl PropertyHolder for LineTool {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: self.options.line_weight as f64,
value: Some(self.options.line_weight as f64),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| LineToolMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}])
+2 -2
View File
@@ -73,10 +73,10 @@ impl PropertyHolder for PenTool {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: self.options.line_weight,
value: Some(self.options.line_weight),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}])
@@ -61,11 +61,11 @@ impl PropertyHolder for ShapeTool {
WidgetLayout::new(vec![LayoutRow::Row {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
label: "Sides".into(),
value: self.options.vertices as f64,
value: Some(self.options.vertices as f64),
is_integer: true,
min: Some(3.),
max: Some(256.),
on_update: WidgetCallback::new(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value as u8)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u8)).into()),
..NumberInput::default()
}))],
}])
@@ -67,10 +67,10 @@ impl PropertyHolder for SplineTool {
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Weight".into(),
value: self.options.line_weight,
value: Some(self.options.line_weight),
is_integer: false,
min: Some(0.),
on_update: WidgetCallback::new(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| SplineToolMessage::UpdateOptions(SplineOptionsUpdate::LineWeight(number_input.value.unwrap())).into()),
..NumberInput::default()
}))],
}])
+2 -2
View File
@@ -115,10 +115,10 @@ impl PropertyHolder for TextTool {
WidgetHolder::new(Widget::NumberInput(NumberInput {
unit: " px".into(),
label: "Size".into(),
value: self.options.font_size as f64,
value: Some(self.options.font_size as f64),
is_integer: true,
min: Some(1.),
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value.unwrap() as u32)).into()),
..NumberInput::default()
})),
],