mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 08:18:14 +08:00
Layout system implementation and applied to tool options bar (#499)
* initial layout system with tool options * cargo fmt * cargo fmt again * document bar defined on the backend * cargo fmt * removed RC<RefCell> * cargo fmt * - fix increment behavior - removed hashmap from layout message handler - removed no op message from layoutMessage * cargo fmt * only send documentBar when zoom or rotation is updated * ctrl-0 changes zoom properly * Code review changes Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
121a68ad3c
commit
96d3ef2650
@@ -1,6 +1,7 @@
|
||||
use crate::document::PortfolioMessageHandler;
|
||||
use crate::global::GlobalMessageHandler;
|
||||
use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler};
|
||||
use crate::layout::layout_message_handler::LayoutMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool_message_handler::ToolMessageHandler;
|
||||
|
||||
@@ -19,6 +20,7 @@ struct DispatcherMessageHandlers {
|
||||
global_message_handler: GlobalMessageHandler,
|
||||
input_mapper_message_handler: InputMapperMessageHandler,
|
||||
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
||||
layout_message_handler: LayoutMessageHandler,
|
||||
portfolio_message_handler: PortfolioMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
}
|
||||
@@ -76,6 +78,7 @@ impl Dispatcher {
|
||||
InputPreprocessor(message) => {
|
||||
self.message_handlers.input_preprocessor_message_handler.process_action(message, (), &mut self.message_queue);
|
||||
}
|
||||
Layout(message) => self.message_handlers.layout_message_handler.process_action(message, (), &mut self.message_queue),
|
||||
Portfolio(message) => {
|
||||
self.message_handlers
|
||||
.portfolio_message_handler
|
||||
|
||||
@@ -31,6 +31,8 @@ pub enum Message {
|
||||
#[child]
|
||||
InputPreprocessor(InputPreprocessorMessage),
|
||||
#[child]
|
||||
Layout(LayoutMessage),
|
||||
#[child]
|
||||
Portfolio(PortfolioMessage),
|
||||
#[child]
|
||||
Tool(ToolMessage),
|
||||
|
||||
@@ -7,6 +7,10 @@ use crate::consts::{
|
||||
ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
|
||||
};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{
|
||||
IconButton, LayoutRow, NumberInput, NumberInputIncrementBehavior, OptionalInput, PopoverButton, PropertyHolder, RadioEntryData, RadioInput, Separator, SeparatorDirection, SeparatorType, Widget,
|
||||
WidgetCallback, WidgetHolder, WidgetLayout,
|
||||
};
|
||||
use crate::message_prelude::*;
|
||||
use crate::EditorError;
|
||||
|
||||
@@ -459,6 +463,154 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyHolder for DocumentMessageHandler {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
||||
checked: self.snapping_enabled,
|
||||
icon: "Snapping".into(),
|
||||
tooltip: "Snapping".into(),
|
||||
on_update: WidgetCallback::new(|updated_optional_input| DocumentMessage::SetSnapping { snap: updated_optional_input.checked }.into()),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Snapping".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
||||
checked: true,
|
||||
icon: "Grid".into(),
|
||||
tooltip: "Grid".into(),
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(318) }.into()),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Grid".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::OptionalInput(OptionalInput {
|
||||
checked: self.overlays_visible,
|
||||
icon: "Overlays".into(),
|
||||
tooltip: "Overlays".into(),
|
||||
on_update: WidgetCallback::new(|updated_optional_input| {
|
||||
DocumentMessage::SetOverlaysVisibility {
|
||||
visible: updated_optional_input.checked,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Overlays".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::RadioInput(RadioInput {
|
||||
selected_index: if self.view_mode == ViewMode::Normal { 0 } else { 1 },
|
||||
entries: vec![
|
||||
RadioEntryData {
|
||||
value: "normal".into(),
|
||||
icon: "ViewModeNormal".into(),
|
||||
tooltip: "View Mode: Normal".into(),
|
||||
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Normal }.into()),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
value: "outline".into(),
|
||||
icon: "ViewModeOutline".into(),
|
||||
tooltip: "View Mode: Outline".into(),
|
||||
on_update: WidgetCallback::new(|_| DocumentMessage::SetViewMode { view_mode: ViewMode::Outline }.into()),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
RadioEntryData {
|
||||
value: "pixels".into(),
|
||||
icon: "ViewModePixels".into(),
|
||||
tooltip: "View Mode: Pixels".into(),
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(320) }.into()),
|
||||
..RadioEntryData::default()
|
||||
},
|
||||
],
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "View Mode".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Section,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: "°".into(),
|
||||
value: self.movement_handler.tilt / (std::f64::consts::PI / 180.),
|
||||
increment_factor: 15.,
|
||||
on_update: WidgetCallback::new(|number_input| {
|
||||
MovementMessage::SetCanvasRotation {
|
||||
angle_radians: number_input.value * (std::f64::consts::PI / 180.),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Section,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 24,
|
||||
icon: "ZoomIn".into(),
|
||||
tooltip: "Zoom In".into(),
|
||||
on_update: WidgetCallback::new(|_| MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 24,
|
||||
icon: "ZoomOut".into(),
|
||||
tooltip: "Zoom Out".into(),
|
||||
on_update: WidgetCallback::new(|_| MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
size: 24,
|
||||
icon: "ZoomReset".into(),
|
||||
tooltip: "Zoom to 100%".into(),
|
||||
on_update: WidgetCallback::new(|_| MovementMessage::SetCanvasZoom { zoom_factor: 1. }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
separator_type: SeparatorType::Related,
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: "%".into(),
|
||||
value: self.movement_handler.zoom * 100.,
|
||||
min: Some(0.000001),
|
||||
max: Some(1000000.),
|
||||
on_update: WidgetCallback::new(|number_input| {
|
||||
MovementMessage::SetCanvasZoom {
|
||||
zoom_factor: number_input.value / 100.,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
increment_behavior: NumberInputIncrementBehavior::Callback,
|
||||
increment_callback_decrease: WidgetCallback::new(|_| MovementMessage::DecreaseCanvasZoom { center_on_mouse: false }.into()),
|
||||
increment_callback_increase: WidgetCallback::new(|_| MovementMessage::IncreaseCanvasZoom { center_on_mouse: false }.into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for DocumentMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
@@ -674,7 +826,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
|
||||
responses.extend([LayerChanged { affected_layer_path }.into(), DocumentStructureChanged.into()]);
|
||||
}
|
||||
GroupSelectedLayers => {
|
||||
let mut new_folder_path: Vec<u64> = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
||||
let mut new_folder_path = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
||||
|
||||
// Required for grouping parent folders with their own children
|
||||
if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) {
|
||||
|
||||
@@ -156,6 +156,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
||||
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());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
}
|
||||
IncreaseCanvasZoom { center_on_mouse } => {
|
||||
@@ -245,12 +246,14 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
|
||||
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());
|
||||
}
|
||||
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());
|
||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||
}
|
||||
TransformCanvasEnd => {
|
||||
|
||||
@@ -60,5 +60,6 @@ pub enum PortfolioMessage {
|
||||
SelectDocument {
|
||||
document_id: u64,
|
||||
},
|
||||
UpdateDocumentBar,
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use super::DocumentMessageHandler;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
use crate::frontend::utility_types::FrontendDocumentDetails;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
@@ -369,6 +371,11 @@ 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());
|
||||
}
|
||||
UpdateDocumentBar => {
|
||||
let active_document = self.active_document();
|
||||
active_document.register_properties(responses, LayoutTarget::DocumentBar)
|
||||
}
|
||||
UpdateOpenDocumentsList => {
|
||||
// Send the list of document tab names
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||
use crate::document::layer_panel::{LayerPanelEntry, RawBuffer};
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::SubLayout;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::HintData;
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::Color;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -15,6 +16,7 @@ pub enum FrontendMessage {
|
||||
DisplayConfirmationToCloseAllDocuments,
|
||||
DisplayConfirmationToCloseDocument { document_id: u64 },
|
||||
DisplayDialogAboutGraphite,
|
||||
DisplayDialogComingSoon { issue: Option<i32> },
|
||||
DisplayDialogError { title: String, description: String },
|
||||
DisplayDialogPanic { panic_info: String, title: String, description: String },
|
||||
DisplayDocumentLayerTreeStructure { data_buffer: RawBuffer },
|
||||
@@ -30,11 +32,12 @@ 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, tool_options: Option<ToolOptions> },
|
||||
UpdateActiveTool { tool_name: String },
|
||||
UpdateCanvasRotation { angle_radians: f64 },
|
||||
UpdateCanvasZoom { factor: f64 },
|
||||
UpdateDocumentArtboards { svg: String },
|
||||
UpdateDocumentArtwork { svg: String },
|
||||
UpdateDocumentBarLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
UpdateDocumentLayer { data: LayerPanelEntry },
|
||||
UpdateDocumentOverlays { svg: String },
|
||||
UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 },
|
||||
@@ -42,5 +45,6 @@ pub enum FrontendMessage {
|
||||
UpdateInputHints { hint_data: HintData },
|
||||
UpdateMouseCursor { cursor: MouseCursorIcon },
|
||||
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },
|
||||
UpdateToolOptionsLayout { layout_target: LayoutTarget, layout: SubLayout },
|
||||
UpdateWorkingColors { primary: Color, secondary: Color },
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use super::widgets::WidgetLayout;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Layout)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
pub enum LayoutMessage {
|
||||
SendLayout { layout: WidgetLayout, layout_target: LayoutTarget },
|
||||
UpdateLayout { layout_target: LayoutTarget, widget_id: u64, value: serde_json::Value },
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug, Hash, Eq, Copy)]
|
||||
#[repr(u8)]
|
||||
pub enum LayoutTarget {
|
||||
DocumentBar,
|
||||
ToolOptions,
|
||||
|
||||
// KEEP THIS ENUM LAST
|
||||
// This is a marker that is used to define an array that is used to hold widgets
|
||||
#[remain::unsorted]
|
||||
LayoutTargetLength,
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use super::layout_message::LayoutTarget;
|
||||
use super::widgets::WidgetLayout;
|
||||
use crate::layout::widgets::Widget;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct LayoutMessageHandler {
|
||||
layouts: [WidgetLayout; LayoutTarget::LayoutTargetLength as usize],
|
||||
}
|
||||
|
||||
impl LayoutMessageHandler {
|
||||
fn send_layout(&self, layout_target: LayoutTarget, responses: &mut VecDeque<Message>) {
|
||||
let widget_layout = &self.layouts[layout_target as usize];
|
||||
let message = match layout_target {
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout {
|
||||
layout_target,
|
||||
layout: widget_layout.layout.clone(),
|
||||
},
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout {
|
||||
layout_target,
|
||||
layout: widget_layout.layout.clone(),
|
||||
},
|
||||
LayoutTarget::LayoutTargetLength => panic!("`LayoutTargetLength` is not a valid Layout Target and is used for array indexing"),
|
||||
};
|
||||
responses.push_back(message.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<LayoutMessage, ()> for LayoutMessageHandler {
|
||||
fn process_action(&mut self, action: LayoutMessage, _data: (), responses: &mut std::collections::VecDeque<crate::message_prelude::Message>) {
|
||||
use LayoutMessage::*;
|
||||
match action {
|
||||
SendLayout { layout, layout_target } => {
|
||||
self.layouts[layout_target as usize] = layout;
|
||||
|
||||
self.send_layout(layout_target, responses);
|
||||
}
|
||||
UpdateLayout { layout_target, widget_id, value } => {
|
||||
let layout = &mut self.layouts[layout_target as usize];
|
||||
let widget_holder = layout.iter_mut().find(|widget| widget.widget_id == widget_id).expect("Received invalid widget_id from the frontend");
|
||||
match &mut widget_holder.widget {
|
||||
Widget::NumberInput(number_input) => match value {
|
||||
Value::Number(num) => {
|
||||
let update_value = num.as_f64().unwrap();
|
||||
number_input.value = update_value;
|
||||
let callback_message = (number_input.on_update.callback)(number_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Value::String(str) => match str.as_str() {
|
||||
"Increment" => responses.push_back((number_input.increment_callback_increase.callback)(number_input)),
|
||||
"Decrement" => responses.push_back((number_input.increment_callback_decrease.callback)(number_input)),
|
||||
_ => {
|
||||
panic!("Invalid string found when updating `NumberInput`")
|
||||
}
|
||||
},
|
||||
_ => panic!("Invalid type found when updating `NumberInput`"),
|
||||
},
|
||||
Widget::Separator(_) => {}
|
||||
Widget::IconButton(icon_button) => {
|
||||
let callback_message = (icon_button.on_update.callback)(icon_button);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::PopoverButton(_) => {}
|
||||
Widget::OptionalInput(optional_input) => {
|
||||
let update_value = value.as_bool().expect("OptionalInput update was not of type: bool");
|
||||
optional_input.checked = update_value;
|
||||
let callback_message = (optional_input.on_update.callback)(optional_input);
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
Widget::RadioInput(radio_input) => {
|
||||
let update_value = value.as_u64().expect("OptionalInput update was not of type: u64");
|
||||
radio_input.selected_index = update_value as u32;
|
||||
let callback_message = (radio_input.entries[update_value as usize].on_update.callback)(&());
|
||||
responses.push_back(callback_message);
|
||||
}
|
||||
};
|
||||
self.send_layout(layout_target, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> crate::message_prelude::ActionList {
|
||||
actions!()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod layout_message;
|
||||
pub mod layout_message_handler;
|
||||
pub mod widgets;
|
||||
|
||||
pub use layout_message::{LayoutMessage, LayoutMessageDiscriminant};
|
||||
@@ -0,0 +1,283 @@
|
||||
use super::layout_message::LayoutTarget;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use derivative::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait PropertyHolder {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::default()
|
||||
}
|
||||
|
||||
fn register_properties(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
||||
responses.push_back(
|
||||
LayoutMessage::SendLayout {
|
||||
layout: self.properties(),
|
||||
layout_target,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WidgetLayout {
|
||||
pub layout: SubLayout,
|
||||
}
|
||||
|
||||
impl WidgetLayout {
|
||||
pub fn new(layout: SubLayout) -> Self {
|
||||
Self { layout }
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> WidgetIter<'_> {
|
||||
WidgetIter {
|
||||
stack: self.layout.iter().collect(),
|
||||
current_slice: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
|
||||
WidgetIterMut {
|
||||
stack: self.layout.iter_mut().collect(),
|
||||
current_slice: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type SubLayout = Vec<LayoutRow>;
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum LayoutRow {
|
||||
Row { name: String, widgets: Vec<WidgetHolder> },
|
||||
Section { name: String, layout: SubLayout },
|
||||
}
|
||||
|
||||
impl LayoutRow {
|
||||
pub fn widgets(&self) -> Vec<WidgetHolder> {
|
||||
match &self {
|
||||
Self::Row { name: _, widgets } => widgets.to_vec(),
|
||||
Self::Section { name: _, layout } => layout.iter().flat_map(|row| row.widgets()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WidgetIter<'a> {
|
||||
pub stack: Vec<&'a LayoutRow>,
|
||||
pub current_slice: Option<&'a [WidgetHolder]>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WidgetIter<'a> {
|
||||
type Item = &'a WidgetHolder;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(item) = self.current_slice.map(|slice| slice.first()).flatten() {
|
||||
self.current_slice = Some(&self.current_slice.unwrap()[1..]);
|
||||
return Some(item);
|
||||
}
|
||||
|
||||
match self.stack.pop() {
|
||||
Some(LayoutRow::Row { name: _, widgets }) => {
|
||||
self.current_slice = Some(widgets);
|
||||
self.next()
|
||||
}
|
||||
Some(LayoutRow::Section { name: _, layout }) => {
|
||||
for layout_row in layout {
|
||||
self.stack.push(layout_row);
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WidgetIterMut<'a> {
|
||||
pub stack: Vec<&'a mut LayoutRow>,
|
||||
pub current_slice: Option<&'a mut [WidgetHolder]>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WidgetIterMut<'a> {
|
||||
type Item = &'a mut WidgetHolder;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((first, rest)) = self.current_slice.take().map(|slice| slice.split_first_mut()).flatten() {
|
||||
self.current_slice = Some(rest);
|
||||
return Some(first);
|
||||
};
|
||||
|
||||
match self.stack.pop() {
|
||||
Some(LayoutRow::Row { name: _, widgets }) => {
|
||||
self.current_slice = Some(widgets);
|
||||
self.next()
|
||||
}
|
||||
Some(LayoutRow::Section { name: _, layout }) => {
|
||||
for layout_row in layout {
|
||||
self.stack.push(layout_row);
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct WidgetHolder {
|
||||
pub widget_id: u64,
|
||||
pub widget: Widget,
|
||||
}
|
||||
|
||||
impl WidgetHolder {
|
||||
pub fn new(widget: Widget) -> Self {
|
||||
Self { widget_id: generate_uuid(), widget }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WidgetCallback<T> {
|
||||
pub callback: fn(&T) -> Message,
|
||||
}
|
||||
|
||||
impl<T> WidgetCallback<T> {
|
||||
pub fn new(callback: fn(&T) -> Message) -> Self {
|
||||
Self { callback }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for WidgetCallback<T> {
|
||||
fn default() -> Self {
|
||||
Self { callback: |_| Message::NoOp }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Widget {
|
||||
IconButton(IconButton),
|
||||
NumberInput(NumberInput),
|
||||
OptionalInput(OptionalInput),
|
||||
PopoverButton(PopoverButton),
|
||||
RadioInput(RadioInput),
|
||||
Separator(Separator),
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug, PartialEq, Default)]
|
||||
pub struct NumberInput {
|
||||
pub value: f64,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<NumberInput>,
|
||||
pub min: Option<f64>,
|
||||
pub max: Option<f64>,
|
||||
#[serde(rename = "isInteger")]
|
||||
pub is_integer: bool,
|
||||
#[serde(rename = "incrementBehavior")]
|
||||
pub increment_behavior: NumberInputIncrementBehavior,
|
||||
#[serde(rename = "incrementFactor")]
|
||||
#[derivative(Default(value = "1."))]
|
||||
pub increment_factor: f64,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_increase: WidgetCallback<NumberInput>,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub increment_callback_decrease: WidgetCallback<NumberInput>,
|
||||
pub label: String,
|
||||
pub unit: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum NumberInputIncrementBehavior {
|
||||
Add,
|
||||
Multiply,
|
||||
Callback,
|
||||
}
|
||||
|
||||
impl Default for NumberInputIncrementBehavior {
|
||||
fn default() -> Self {
|
||||
Self::Add
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Separator {
|
||||
pub direction: SeparatorDirection,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub separator_type: SeparatorType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SeparatorDirection {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum SeparatorType {
|
||||
Related,
|
||||
Unrelated,
|
||||
Section,
|
||||
List,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct IconButton {
|
||||
pub icon: String,
|
||||
#[serde(rename = "title")]
|
||||
pub tooltip: String,
|
||||
pub size: u32,
|
||||
#[serde(rename = "gapAfter")]
|
||||
pub gap_after: bool,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<IconButton>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct OptionalInput {
|
||||
pub checked: bool,
|
||||
pub icon: String,
|
||||
#[serde(rename = "title")]
|
||||
pub tooltip: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<OptionalInput>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct PopoverButton {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioInput {
|
||||
pub entries: Vec<RadioEntryData>,
|
||||
|
||||
// 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
|
||||
#[serde(rename = "selectedIndex")]
|
||||
pub selected_index: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Derivative, Default)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct RadioEntryData {
|
||||
pub value: String,
|
||||
pub label: String,
|
||||
pub icon: String,
|
||||
pub tooltip: String,
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
pub on_update: WidgetCallback<()>,
|
||||
}
|
||||
@@ -8,6 +8,7 @@ pub mod document;
|
||||
pub mod frontend;
|
||||
pub mod global;
|
||||
pub mod input;
|
||||
pub mod layout;
|
||||
pub mod viewport_tools;
|
||||
|
||||
#[doc(inline)]
|
||||
@@ -67,6 +68,7 @@ pub mod message_prelude {
|
||||
pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
pub use crate::global::{GlobalMessage, GlobalMessageDiscriminant};
|
||||
pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||
pub use crate::layout::{LayoutMessage, LayoutMessageDiscriminant};
|
||||
pub use crate::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild};
|
||||
pub use crate::viewport_tools::tool_message::{ToolMessage, ToolMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::crop::{CropMessage, CropMessageDiscriminant};
|
||||
|
||||
@@ -41,7 +41,7 @@ macro_rules! count_args {
|
||||
/// ```
|
||||
macro_rules! gen_tools_hash_map {
|
||||
($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::viewport_tools::tool::ToolType, ::std::boxed::Box<dyn for<'a> $crate::message_prelude::MessageHandler<$crate::viewport_tools::tool_message::ToolMessage,$crate::viewport_tools::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::viewport_tools::tool::ToolType, ::std::boxed::Box<$crate::viewport_tools::tool::Tool>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
$(hash_map.insert($crate::viewport_tools::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)*
|
||||
|
||||
hash_map
|
||||
|
||||
@@ -2,5 +2,4 @@ pub mod snapping;
|
||||
pub mod tool;
|
||||
pub mod tool_message;
|
||||
pub mod tool_message_handler;
|
||||
pub mod tool_options;
|
||||
pub mod tools;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use super::tool_options::{SelectAppendMode, ShapeType, ToolOptions};
|
||||
use super::tools::*;
|
||||
use crate::communication::message_handler::MessageHandler;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
@@ -15,6 +15,7 @@ pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentTo
|
||||
|
||||
pub trait Fsm {
|
||||
type ToolData;
|
||||
type ToolOptions;
|
||||
|
||||
#[must_use]
|
||||
fn transition(
|
||||
@@ -23,6 +24,7 @@ pub trait Fsm {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self;
|
||||
@@ -35,14 +37,16 @@ pub trait Fsm {
|
||||
pub struct DocumentToolData {
|
||||
pub primary_color: Color,
|
||||
pub secondary_color: Color,
|
||||
pub tool_options: HashMap<ToolType, ToolOptions>,
|
||||
}
|
||||
|
||||
type SubToolMessageHandler = dyn for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>>;
|
||||
pub trait ToolCommon: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder {}
|
||||
impl<T> ToolCommon for T where T: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder {}
|
||||
|
||||
type Tool = dyn ToolCommon;
|
||||
|
||||
pub struct ToolData {
|
||||
pub active_tool_type: ToolType,
|
||||
pub tools: HashMap<ToolType, Box<SubToolMessageHandler>>,
|
||||
pub tools: HashMap<ToolType, Box<Tool>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ToolData {
|
||||
@@ -52,10 +56,11 @@ impl fmt::Debug for ToolData {
|
||||
}
|
||||
|
||||
impl ToolData {
|
||||
pub fn active_tool_mut(&mut self) -> &mut Box<SubToolMessageHandler> {
|
||||
pub fn active_tool_mut(&mut self) -> &mut Box<Tool> {
|
||||
self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized")
|
||||
}
|
||||
pub fn active_tool(&self) -> &SubToolMessageHandler {
|
||||
|
||||
pub fn active_tool(&self) -> &Tool {
|
||||
self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized")
|
||||
}
|
||||
}
|
||||
@@ -98,7 +103,6 @@ impl Default for ToolFsmState {
|
||||
document_tool_data: DocumentToolData {
|
||||
primary_color: Color::BLACK,
|
||||
secondary_color: Color::WHITE,
|
||||
tool_options: default_tool_options(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -114,35 +118,6 @@ impl ToolFsmState {
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
|
||||
let tool_init = |tool: ToolType| (tool, tool.default_options());
|
||||
[
|
||||
tool_init(ToolType::Select),
|
||||
tool_init(ToolType::Crop),
|
||||
tool_init(ToolType::Navigate),
|
||||
tool_init(ToolType::Eyedropper),
|
||||
tool_init(ToolType::Text),
|
||||
tool_init(ToolType::Fill),
|
||||
tool_init(ToolType::Gradient),
|
||||
tool_init(ToolType::Brush),
|
||||
tool_init(ToolType::Heal),
|
||||
tool_init(ToolType::Clone),
|
||||
tool_init(ToolType::Patch),
|
||||
tool_init(ToolType::BlurSharpen),
|
||||
tool_init(ToolType::Relight),
|
||||
tool_init(ToolType::Path),
|
||||
tool_init(ToolType::Pen),
|
||||
tool_init(ToolType::Freehand),
|
||||
tool_init(ToolType::Spline),
|
||||
tool_init(ToolType::Line),
|
||||
tool_init(ToolType::Rectangle),
|
||||
tool_init(ToolType::Ellipse),
|
||||
tool_init(ToolType::Shape),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolType {
|
||||
@@ -201,36 +176,6 @@ impl fmt::Display for ToolType {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolType {
|
||||
fn default_options(&self) -> ToolOptions {
|
||||
match self {
|
||||
ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New },
|
||||
ToolType::Crop => ToolOptions::Crop {},
|
||||
ToolType::Navigate => ToolOptions::Navigate {},
|
||||
ToolType::Eyedropper => ToolOptions::Eyedropper {},
|
||||
ToolType::Text => ToolOptions::Text { font_size: 14 },
|
||||
ToolType::Fill => ToolOptions::Fill {},
|
||||
ToolType::Gradient => ToolOptions::Gradient {},
|
||||
ToolType::Brush => ToolOptions::Brush {},
|
||||
ToolType::Heal => ToolOptions::Heal {},
|
||||
ToolType::Clone => ToolOptions::Clone {},
|
||||
ToolType::Patch => ToolOptions::Patch {},
|
||||
ToolType::BlurSharpen => ToolOptions::BlurSharpen {},
|
||||
ToolType::Relight => ToolOptions::Relight {},
|
||||
ToolType::Path => ToolOptions::Path {},
|
||||
ToolType::Pen => ToolOptions::Pen { weight: 5 },
|
||||
ToolType::Freehand => ToolOptions::Freehand { weight: 5 },
|
||||
ToolType::Spline => ToolOptions::Spline {},
|
||||
ToolType::Line => ToolOptions::Line { weight: 5 },
|
||||
ToolType::Rectangle => ToolOptions::Rectangle {},
|
||||
ToolType::Ellipse => ToolOptions::Ellipse {},
|
||||
ToolType::Shape => ToolOptions::Shape {
|
||||
shape_type: ShapeType::Polygon { vertices: 6 },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum StandardToolMessageType {
|
||||
Abort,
|
||||
DocumentIsDirty,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use super::tool::ToolType;
|
||||
use super::tool_options::ToolOptions;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
@@ -92,10 +91,6 @@ pub enum ToolMessage {
|
||||
SelectSecondaryColor {
|
||||
color: Color,
|
||||
},
|
||||
SetToolOptions {
|
||||
tool_type: ToolType,
|
||||
tool_options: ToolOptions,
|
||||
},
|
||||
SwapColors,
|
||||
UpdateCursor,
|
||||
UpdateHints,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::tool::{message_to_tool_type, standard_tool_message, update_working_colors, StandardToolMessageType, ToolFsmState};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
@@ -60,8 +61,10 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
|
||||
// Notify the frontend about the new active tool to be displayed
|
||||
let tool_name = tool_type.to_string();
|
||||
let tool_options = self.tool_state.document_tool_data.tool_options.get(&tool_type).copied();
|
||||
responses.push_back(FrontendMessage::UpdateActiveTool { tool_name, tool_options }.into());
|
||||
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);
|
||||
}
|
||||
DocumentIsDirty => {
|
||||
// Send the DocumentIsDirty message to the active tool's sub-tool message handler
|
||||
@@ -90,11 +93,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
|
||||
update_working_colors(document_data, responses);
|
||||
}
|
||||
SetToolOptions { tool_type, tool_options } => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
document_data.tool_options.insert(tool_type, tool_options);
|
||||
}
|
||||
SwapColors => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
@@ -123,7 +121,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMes
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool, SetToolOptions);
|
||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool);
|
||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||
|
||||
list
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub enum ToolOptions {
|
||||
Select { append_mode: SelectAppendMode },
|
||||
Crop {},
|
||||
Navigate {},
|
||||
Eyedropper {},
|
||||
Text { font_size: u32 },
|
||||
Fill {},
|
||||
Gradient {},
|
||||
Brush {},
|
||||
Heal {},
|
||||
Clone {},
|
||||
Patch {},
|
||||
BlurSharpen {},
|
||||
Relight {},
|
||||
Path {},
|
||||
Pen { weight: u32 },
|
||||
Freehand { weight: u32 },
|
||||
Spline {},
|
||||
Line { weight: u32 },
|
||||
Rectangle {},
|
||||
Ellipse {},
|
||||
Shape { shape_type: ShapeType },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub enum SelectAppendMode {
|
||||
New,
|
||||
Add,
|
||||
Subtract,
|
||||
Intersect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub enum ShapeType {
|
||||
Star { vertices: u32 },
|
||||
Polygon { vertices: u32 },
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool::ToolActionHandlerData;
|
||||
|
||||
@@ -25,3 +26,5 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Crop {
|
||||
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl PropertyHolder for Crop {}
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -36,6 +37,8 @@ pub enum EllipseMessage {
|
||||
},
|
||||
}
|
||||
|
||||
impl PropertyHolder for Ellipse {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Ellipse {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -48,7 +51,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Ellipse {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -86,6 +89,7 @@ struct EllipseToolData {
|
||||
|
||||
impl Fsm for EllipseToolFsmState {
|
||||
type ToolData = EllipseToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -93,6 +97,7 @@ impl Fsm for EllipseToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -32,6 +33,8 @@ pub enum EyedropperMessage {
|
||||
RightMouseDown,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Eyedropper {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Eyedropper {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -44,7 +47,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Eyedropper {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -72,6 +75,7 @@ struct EyedropperToolData {}
|
||||
|
||||
impl Fsm for EyedropperToolFsmState {
|
||||
type ToolData = EyedropperToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -79,6 +83,7 @@ impl Fsm for EyedropperToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -32,6 +33,8 @@ pub enum FillMessage {
|
||||
RightMouseDown,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Fill {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -44,7 +47,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -72,6 +75,7 @@ struct FillToolData {}
|
||||
|
||||
impl Fsm for FillToolFsmState {
|
||||
type ToolData = FillToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -79,6 +83,7 @@ impl Fsm for FillToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -2,10 +2,10 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
@@ -17,6 +17,17 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Freehand {
|
||||
fsm_state: FreehandToolFsmState,
|
||||
data: FreehandToolData,
|
||||
options: FreehandOptions,
|
||||
}
|
||||
|
||||
pub struct FreehandOptions {
|
||||
line_weight: u32,
|
||||
}
|
||||
|
||||
impl Default for FreehandOptions {
|
||||
fn default() -> Self {
|
||||
Self { line_weight: 5 }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -31,6 +42,13 @@ pub enum FreehandMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
UpdateOptions(FreehandMessageOptionsUpdate),
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum FreehandMessageOptionsUpdate {
|
||||
LineWeight(u32),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -39,6 +57,23 @@ enum FreehandToolFsmState {
|
||||
Drawing,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Freehand {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Weight".into(),
|
||||
value: self.options.line_weight as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input| FreehandMessage::UpdateOptions(FreehandMessageOptionsUpdate::LineWeight(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Freehand {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -51,7 +86,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Freehand {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
if let ToolMessage::Freehand(FreehandMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
FreehandMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -84,6 +126,7 @@ struct FreehandToolData {
|
||||
|
||||
impl Fsm for FreehandToolFsmState {
|
||||
type ToolData = FreehandToolData;
|
||||
type ToolOptions = FreehandOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -91,6 +134,7 @@ impl Fsm for FreehandToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -110,10 +154,7 @@ impl Fsm for FreehandToolFsmState {
|
||||
|
||||
data.points.push(pos);
|
||||
|
||||
data.weight = match tool_data.tool_options.get(&ToolType::Freehand) {
|
||||
Some(&ToolOptions::Freehand { weight }) => weight,
|
||||
_ => 5,
|
||||
};
|
||||
data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
@@ -20,6 +20,17 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Line {
|
||||
fsm_state: LineToolFsmState,
|
||||
data: LineToolData,
|
||||
options: LineOptions,
|
||||
}
|
||||
|
||||
pub struct LineOptions {
|
||||
line_weight: u32,
|
||||
}
|
||||
|
||||
impl Default for LineOptions {
|
||||
fn default() -> Self {
|
||||
Self { line_weight: 5 }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -38,6 +49,30 @@ pub enum LineMessage {
|
||||
lock_angle: Key,
|
||||
snap_angle: Key,
|
||||
},
|
||||
UpdateOptions(LineOptionsUpdate),
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum LineOptionsUpdate {
|
||||
LineWeight(u32),
|
||||
}
|
||||
|
||||
impl PropertyHolder for Line {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Weight".into(),
|
||||
value: self.options.line_weight as f64,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
on_update: WidgetCallback::new(|number_input| LineMessage::UpdateOptions(LineOptionsUpdate::LineWeight(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
||||
@@ -52,7 +87,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
if let ToolMessage::Line(LineMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -95,6 +137,7 @@ struct LineToolData {
|
||||
|
||||
impl Fsm for LineToolFsmState {
|
||||
type ToolData = LineToolData;
|
||||
type ToolOptions = LineOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -102,6 +145,7 @@ impl Fsm for LineToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -118,10 +162,7 @@ impl Fsm for LineToolFsmState {
|
||||
data.path = Some(vec![generate_uuid()]);
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
data.weight = match tool_data.tool_options.get(&ToolType::Line) {
|
||||
Some(&ToolOptions::Line { weight }) => weight,
|
||||
_ => 5,
|
||||
};
|
||||
data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddLine {
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -37,6 +38,8 @@ pub enum NavigateMessage {
|
||||
ZoomCanvasBegin,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Navigate {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -49,7 +52,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -89,6 +92,7 @@ struct NavigateToolData {
|
||||
|
||||
impl Fsm for NavigateToolFsmState {
|
||||
type ToolData = NavigateToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -96,6 +100,7 @@ impl Fsm for NavigateToolFsmState {
|
||||
_document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -38,6 +39,8 @@ pub enum PathMessage {
|
||||
PointerMove,
|
||||
}
|
||||
|
||||
impl PropertyHolder for Path {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -50,7 +53,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -105,6 +108,7 @@ struct PathToolSelection {
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
type ToolData = PathToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -112,6 +116,7 @@ impl Fsm for PathToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -2,11 +2,11 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
@@ -18,6 +18,17 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Pen {
|
||||
fsm_state: PenToolFsmState,
|
||||
data: PenToolData,
|
||||
options: PenOptions,
|
||||
}
|
||||
|
||||
pub struct PenOptions {
|
||||
line_weight: u32,
|
||||
}
|
||||
|
||||
impl Default for PenOptions {
|
||||
fn default() -> Self {
|
||||
Self { line_weight: 5 }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -34,6 +45,7 @@ pub enum PenMessage {
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Undo,
|
||||
UpdateOptions(PenOptionsUpdate),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -42,6 +54,29 @@ enum PenToolFsmState {
|
||||
Drawing,
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum PenOptionsUpdate {
|
||||
LineWeight(u32),
|
||||
}
|
||||
|
||||
impl PropertyHolder for Pen {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Weight".into(),
|
||||
value: self.options.line_weight as f64,
|
||||
is_integer: true,
|
||||
min: Some(0.),
|
||||
on_update: WidgetCallback::new(|number_input| PenMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Pen {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -54,7 +89,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Pen {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
if let ToolMessage::Pen(PenMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -89,6 +131,7 @@ struct PenToolData {
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
type ToolOptions = PenOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -96,6 +139,7 @@ impl Fsm for PenToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -119,10 +163,7 @@ impl Fsm for PenToolFsmState {
|
||||
data.points.push(pos);
|
||||
data.next_point = pos;
|
||||
|
||||
data.weight = match tool_data.tool_options.get(&ToolType::Pen) {
|
||||
Some(&ToolOptions::Pen { weight }) => weight,
|
||||
_ => 5,
|
||||
};
|
||||
data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(make_operation(data, tool_data, true));
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
@@ -36,6 +37,8 @@ pub enum RectangleMessage {
|
||||
},
|
||||
}
|
||||
|
||||
impl PropertyHolder for Rectangle {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Rectangle {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -48,7 +51,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Rectangle {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -85,6 +88,7 @@ struct RectangleToolData {
|
||||
|
||||
impl Fsm for RectangleToolFsmState {
|
||||
type ToolData = RectangleToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -92,6 +96,7 @@ impl Fsm for RectangleToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{IconButton, LayoutRow, PopoverButton, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
@@ -51,6 +52,179 @@ pub enum SelectMessage {
|
||||
},
|
||||
}
|
||||
|
||||
impl PropertyHolder for Select {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignLeft".into(),
|
||||
tooltip: "Align Left".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::X,
|
||||
aggregate: AlignAggregate::Min,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignHorizontalCenter".into(),
|
||||
tooltip: "Align Horizontal Center".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::X,
|
||||
aggregate: AlignAggregate::Center,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignRight".into(),
|
||||
tooltip: "Align Right".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::X,
|
||||
aggregate: AlignAggregate::Max,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Unrelated,
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignTop".into(),
|
||||
tooltip: "Align Top".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::Y,
|
||||
aggregate: AlignAggregate::Min,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignVerticalCenter".into(),
|
||||
tooltip: "Align Vertical Center".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::Y,
|
||||
aggregate: AlignAggregate::Center,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "AlignBottom".into(),
|
||||
tooltip: "Align Bottom".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| {
|
||||
DocumentMessage::AlignSelectedLayers {
|
||||
axis: AlignAxis::Y,
|
||||
aggregate: AlignAggregate::Max,
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Align".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Section,
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "FlipHorizontal".into(),
|
||||
tooltip: "Flip Horizontal".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| SelectMessage::FlipHorizontal.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "FlipVertical".into(),
|
||||
tooltip: "Flip Vertical".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| SelectMessage::FlipVertical.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Flip".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Section,
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "BooleanUnion".into(),
|
||||
tooltip: "Boolean Union".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(197) }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "BooleanSubtractFront".into(),
|
||||
tooltip: "Boolean Subtract Front".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(197) }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "BooleanSubtractBack".into(),
|
||||
tooltip: "Boolean Subtract Back".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(197) }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "BooleanIntersect".into(),
|
||||
tooltip: "Boolean Intersect".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(197) }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::IconButton(IconButton {
|
||||
icon: "BooleanDifference".into(),
|
||||
tooltip: "Boolean Difference".into(),
|
||||
size: 24,
|
||||
on_update: WidgetCallback::new(|_| FrontendMessage::DisplayDialogComingSoon { issue: Some(197) }.into()),
|
||||
..IconButton::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
|
||||
title: "Boolean".into(),
|
||||
text: "The contents of this popover menu are coming soon".into(),
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
if action == ToolMessage::UpdateHints {
|
||||
@@ -63,7 +237,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &(), data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -141,6 +315,7 @@ fn transform_from_box(pos1: DVec2, pos2: DVec2) -> [f64; 6] {
|
||||
|
||||
impl Fsm for SelectToolFsmState {
|
||||
type ToolData = SelectToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -148,6 +323,7 @@ impl Fsm for SelectToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -3,10 +3,10 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::{ShapeType, ToolOptions};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
@@ -18,6 +18,17 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Shape {
|
||||
fsm_state: ShapeToolFsmState,
|
||||
data: ShapeToolData,
|
||||
options: ShapeOptions,
|
||||
}
|
||||
|
||||
pub struct ShapeOptions {
|
||||
vertices: u8,
|
||||
}
|
||||
|
||||
impl Default for ShapeOptions {
|
||||
fn default() -> Self {
|
||||
Self { vertices: 6 }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -35,6 +46,30 @@ pub enum ShapeMessage {
|
||||
center: Key,
|
||||
lock_ratio: Key,
|
||||
},
|
||||
UpdateOptions(ShapeOptionsUpdate),
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum ShapeOptionsUpdate {
|
||||
Vertices(u8),
|
||||
}
|
||||
|
||||
impl PropertyHolder for Shape {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
label: "Sides".into(),
|
||||
value: self.options.vertices as f64,
|
||||
is_integer: true,
|
||||
min: Some(3.),
|
||||
max: Some(256.),
|
||||
on_update: WidgetCallback::new(|number_input| ShapeMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value as u8)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Shape {
|
||||
@@ -49,7 +84,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Shape {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
if let ToolMessage::Shape(ShapeMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -87,6 +129,7 @@ struct ShapeToolData {
|
||||
|
||||
impl Fsm for ShapeToolFsmState {
|
||||
type ToolData = ShapeToolData;
|
||||
type ToolOptions = ShapeOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -94,6 +137,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -109,12 +153,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(vec![generate_uuid()]);
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
data.sides = match tool_data.tool_options.get(&ToolType::Shape) {
|
||||
Some(&ToolOptions::Shape {
|
||||
shape_type: ShapeType::Polygon { vertices },
|
||||
}) => vertices as u8,
|
||||
_ => 6,
|
||||
};
|
||||
data.sides = tool_options.vertices;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNgon {
|
||||
|
||||
@@ -3,10 +3,10 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::intersection::Quad;
|
||||
@@ -19,6 +19,17 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Text {
|
||||
fsm_state: TextToolFsmState,
|
||||
data: TextToolData,
|
||||
options: TextOptions,
|
||||
}
|
||||
|
||||
pub struct TextOptions {
|
||||
font_size: u32,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
fn default() -> Self {
|
||||
Self { font_size: 14 }
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
@@ -41,6 +52,30 @@ pub enum TextMessage {
|
||||
UpdateBounds {
|
||||
new_text: String,
|
||||
},
|
||||
UpdateOptions(TextOptionsUpdate),
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum TextOptionsUpdate {
|
||||
FontSize(u32),
|
||||
}
|
||||
|
||||
impl PropertyHolder for Text {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
name: "".into(),
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Font Size".into(),
|
||||
value: self.options.font_size as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Text {
|
||||
@@ -55,7 +90,14 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Text {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let new_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, &self.options, data.2, responses);
|
||||
|
||||
if self.fsm_state != new_state {
|
||||
self.fsm_state = new_state;
|
||||
@@ -137,6 +179,7 @@ fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, r
|
||||
|
||||
impl Fsm for TextToolFsmState {
|
||||
type ToolData = TextToolData;
|
||||
type ToolOptions = TextOptions;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
@@ -144,6 +187,7 @@ impl Fsm for TextToolFsmState {
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -200,10 +244,7 @@ impl Fsm for TextToolFsmState {
|
||||
// Creating new text
|
||||
else if state == TextToolFsmState::Ready {
|
||||
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
|
||||
let font_size = match tool_data.tool_options.get(&ToolType::Text) {
|
||||
Some(&ToolOptions::Text { font_size }) => font_size,
|
||||
_ => 14,
|
||||
};
|
||||
let font_size = tool_options.font_size;
|
||||
data.path = vec![generate_uuid()];
|
||||
|
||||
responses.push_back(
|
||||
|
||||
Reference in New Issue
Block a user