mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Major overhaul of input and communication systems
* Add input manager * WIP lifetime hell * Hell yeah, dark lifetime magic * Replace events with actions in tools * Fix borrow in GlobalEventHandler * Fix typo in response-handler * Distribute dispatch structure * Add translation from events to actions * Port default key actions to input_mapper * Split actions macro * Add handling for Ambiguous Mouse events * Fix warnings and clippy lints * Add actions macro * WIP rework * Add AsMessage macro * Add implementation for derived enums * Add macro implementation for top level message * Add #[child] attribute to indicate derivation * Replace some mentions of Actions and Responses with Message * It compiles !!! * Add functionality to some message handlers * Add document rendering * ICE * Rework the previous code while keeping basic functionality * Reduce parent-top-level macro args to only two * Add workaround for ICE * Fix cyclic reference in document.rs * Make derive_transitive_child a bit more powerful This addresses the todo that was left, enabling arbitrary expressions to be passed as the last parameter of a #[parent] attribute * Adapt frontend message format * Make responses use VecDeque Our responses are a queue so we should use a queue type for them * Move traits to sensible location * Are we rectangle yet? * Simplify, improve & document `derive_discriminant` * Change `child` to `sub_discriminant` This only applies to `ToDiscriminant`. Code using `#[impl_message]` continues to work. * Add docs for `derive_transitive_child` * Finish docs and improve macros The improvements are that impl_message now uses trait resolution to obtain the parent's discriminant and that derive_as_message now allows for non-unit variants (which we don't use but it's nice to have, just in case) * Remove logging call * Move files around and cleanup structure * Fix proc macro doc tests * Improve actions_fn!() macro * Add ellipse tool * Pass populated actions list to the input mapper * Add KeyState bitvector * Merge mouse buttons into "keyboard" * Add macro for initialization of key mapper table * Add syntactic sugar for the macro * Implement mapping function * Translate the remaining tools * Fix shape tool * Add keybindings for line and pen tool * Fix modifiers * Cleanup * Add doc comments for the actions macro * Fix formatting * Rename MouseMove to PointerMove * Add keybinds for tools * Apply review suggestions * Rename KeyMappings -> KeyMappingEntries * Apply review changes Co-authored-by: T0mstone <realt0mstone@gmail.com> Co-authored-by: Paul Kupper <kupper.pa@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
c08b2d4b31
commit
4b19a459b7
73
core/editor/src/communication/dispatcher.rs
Normal file
73
core/editor/src/communication/dispatcher.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::{frontend::FrontendMessageHandler, message_prelude::*, Callback, EditorError};
|
||||
|
||||
pub use crate::document::DocumentMessageHandler;
|
||||
pub use crate::input::{InputMapper, InputPreprocessor};
|
||||
pub use crate::tool::ToolMessageHandler;
|
||||
|
||||
use crate::global::GlobalMessageHandler;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub struct Dispatcher {
|
||||
frontend_message_handler: FrontendMessageHandler,
|
||||
input_preprocessor: InputPreprocessor,
|
||||
input_mapper: InputMapper,
|
||||
global_message_handler: GlobalMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
document_message_handler: DocumentMessageHandler,
|
||||
messages: VecDeque<Message>,
|
||||
}
|
||||
|
||||
impl Dispatcher {
|
||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
|
||||
let message = message.into();
|
||||
use Message::*;
|
||||
if !matches!(
|
||||
message,
|
||||
Message::InputPreprocessor(_) | Message::InputMapper(_) | Message::Tool(ToolMessage::Rectangle(RectangleMessage::MouseMove))
|
||||
) {
|
||||
log::trace!("Message: {}", message.to_discriminant().global_name());
|
||||
}
|
||||
match message {
|
||||
NoOp => (),
|
||||
Document(message) => self.document_message_handler.process_action(message, (), &mut self.messages),
|
||||
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
|
||||
Tool(message) => self
|
||||
.tool_message_handler
|
||||
.process_action(message, (&self.document_message_handler.active_document().document, &self.input_preprocessor), &mut self.messages),
|
||||
Frontend(message) => self.frontend_message_handler.process_action(message, (), &mut self.messages),
|
||||
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
|
||||
InputMapper(message) => {
|
||||
let actions = self.collect_actions();
|
||||
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
|
||||
}
|
||||
}
|
||||
if let Some(message) = self.messages.pop_front() {
|
||||
self.handle_message(message)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn collect_actions(&self) -> ActionList {
|
||||
//TODO: reduce the number of heap allocations
|
||||
let mut list = Vec::new();
|
||||
list.extend(self.frontend_message_handler.actions());
|
||||
list.extend(self.input_preprocessor.actions());
|
||||
list.extend(self.input_mapper.actions());
|
||||
list.extend(self.global_message_handler.actions());
|
||||
list.extend(self.tool_message_handler.actions());
|
||||
list.extend(self.document_message_handler.actions());
|
||||
list
|
||||
}
|
||||
|
||||
pub fn new(callback: Callback) -> Dispatcher {
|
||||
Dispatcher {
|
||||
frontend_message_handler: FrontendMessageHandler::new(callback),
|
||||
input_preprocessor: InputPreprocessor::default(),
|
||||
global_message_handler: GlobalMessageHandler::new(),
|
||||
input_mapper: InputMapper::default(),
|
||||
document_message_handler: DocumentMessageHandler::default(),
|
||||
tool_message_handler: ToolMessageHandler::default(),
|
||||
messages: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
30
core/editor/src/communication/message.rs
Normal file
30
core/editor/src/communication/message.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::message_prelude::*;
|
||||
use graphite_proc_macros::*;
|
||||
|
||||
pub trait AsMessage: TransitiveChild
|
||||
where
|
||||
Self::TopParent: TransitiveChild<Parent = Self::TopParent, TopParent = Self::TopParent> + AsMessage,
|
||||
{
|
||||
fn local_name(self) -> String;
|
||||
fn global_name(self) -> String {
|
||||
<Self as Into<Self::TopParent>>::into(self).local_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_message]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum Message {
|
||||
NoOp,
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
#[child]
|
||||
Global(GlobalMessage),
|
||||
#[child]
|
||||
Tool(ToolMessage),
|
||||
#[child]
|
||||
Frontend(FrontendMessage),
|
||||
#[child]
|
||||
InputPreprocessor(InputPreprocessorMessage),
|
||||
#[child]
|
||||
InputMapper(InputMapperMessage),
|
||||
}
|
||||
23
core/editor/src/communication/mod.rs
Normal file
23
core/editor/src/communication/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
pub mod dispatcher;
|
||||
pub mod message;
|
||||
use crate::message_prelude::*;
|
||||
pub use dispatcher::*;
|
||||
|
||||
pub use crate::input::InputPreprocessor;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub type ActionList = Vec<Vec<MessageDiscriminant>>;
|
||||
|
||||
// TODO: Add Send + Sync requirement
|
||||
// Use something like rw locks for synchronization
|
||||
pub trait MessageHandlerData {}
|
||||
|
||||
pub trait MessageHandler<A: ToDiscriminant, T>
|
||||
where
|
||||
A::Discriminant: AsMessage,
|
||||
<A::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <A::Discriminant as TransitiveChild>::TopParent, TopParent = <A::Discriminant as TransitiveChild>::TopParent> + AsMessage,
|
||||
{
|
||||
/// Return true if the Action is consumed.
|
||||
fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque<Message>);
|
||||
fn actions(&self) -> ActionList;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use super::{Event, EventHandler, Operation, Response};
|
||||
use crate::tools::{DocumentToolData, ToolData};
|
||||
use crate::Document;
|
||||
|
||||
pub struct DocumentEventHandler {}
|
||||
|
||||
impl DocumentEventHandler {
|
||||
fn pre_process_event(&mut self, editor_state: &Document, tool_data: &mut DocumentToolData, events: &mut Vec<Event>, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) {}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
use crate::tools::ToolType;
|
||||
use crate::Color;
|
||||
use bitflags::bitflags;
|
||||
|
||||
use document_core::LayerId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_core::DocumentResponse;
|
||||
|
||||
use std::{
|
||||
fmt,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[repr(C)]
|
||||
pub enum Event {
|
||||
SelectTool(ToolType),
|
||||
SelectPrimaryColor(Color),
|
||||
SelectSecondaryColor(Color),
|
||||
SelectLayer(Vec<LayerId>),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
AddLayer(Vec<LayerId>),
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
SwapColors,
|
||||
ResetColors,
|
||||
AmbiguousMouseDown(MouseState),
|
||||
AmbiguousMouseUp(MouseState),
|
||||
LmbDown(MouseState),
|
||||
RmbDown(MouseState),
|
||||
MmbDown(MouseState),
|
||||
LmbUp(MouseState),
|
||||
RmbUp(MouseState),
|
||||
MmbUp(MouseState),
|
||||
MouseMove(ViewportPosition),
|
||||
KeyUp(Key),
|
||||
KeyDown(Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub enum ToolResponse {
|
||||
// These may not have the same names as any of the DocumentResponses
|
||||
SetActiveTool { tool_name: String },
|
||||
UpdateCanvas { document: String },
|
||||
}
|
||||
|
||||
impl fmt::Display for ToolResponse {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ToolResponse::*;
|
||||
|
||||
let name = match_variant_name!(match (self) {
|
||||
SetActiveTool,
|
||||
UpdateCanvas,
|
||||
});
|
||||
|
||||
formatter.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
// TODO - Make Copy when possible
|
||||
pub enum Response {
|
||||
Tool(ToolResponse),
|
||||
Document(DocumentResponse),
|
||||
}
|
||||
|
||||
impl From<ToolResponse> for Response {
|
||||
fn from(response: ToolResponse) -> Self {
|
||||
Response::Tool(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentResponse> for Response {
|
||||
fn from(response: DocumentResponse) -> Self {
|
||||
Response::Document(response)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Response {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
use Response::*;
|
||||
|
||||
let name = match_variant_name!(match (self) {
|
||||
Tool,
|
||||
Document
|
||||
});
|
||||
let appendix = match self {
|
||||
Tool(t) => t.to_string(),
|
||||
Document(d) => d.to_string(),
|
||||
};
|
||||
|
||||
formatter.write_str(format!("{}::{}", name, appendix).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Trace(Vec<TracePoint>);
|
||||
|
||||
impl Deref for Trace {
|
||||
type Target = Vec<TracePoint>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Trace {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Trace {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
// origin is top left
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub struct ViewportPosition {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
impl ViewportPosition {
|
||||
pub fn distance(&self, other: &Self) -> f64 {
|
||||
let x_diff = other.x as f64 - self.x as f64;
|
||||
let y_diff = other.y as f64 - self.y as f64;
|
||||
f64::sqrt(x_diff * x_diff + y_diff * y_diff)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub struct TracePoint {
|
||||
pub mouse_state: MouseState,
|
||||
pub mod_keys: ModKeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub struct MouseState {
|
||||
pub position: ViewportPosition,
|
||||
pub mouse_keys: MouseKeys,
|
||||
}
|
||||
|
||||
impl MouseState {
|
||||
pub fn new() -> MouseState {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn from_pos(x: u32, y: u32) -> MouseState {
|
||||
MouseState {
|
||||
position: ViewportPosition { x, y },
|
||||
mouse_keys: MouseKeys::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Key {
|
||||
UnknownKey,
|
||||
KeyR,
|
||||
KeyM,
|
||||
KeyE,
|
||||
KeyL,
|
||||
KeyP,
|
||||
KeyV,
|
||||
KeyX,
|
||||
KeyZ,
|
||||
KeyY,
|
||||
KeyEnter,
|
||||
Key0,
|
||||
Key1,
|
||||
Key2,
|
||||
Key3,
|
||||
Key4,
|
||||
Key5,
|
||||
Key6,
|
||||
Key7,
|
||||
Key8,
|
||||
Key9,
|
||||
KeyShift,
|
||||
KeyControl,
|
||||
KeyAlt,
|
||||
KeyEscape,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct ModKeys: u8 {
|
||||
const CONTROL = 0b0000_0001;
|
||||
const SHIFT = 0b0000_0010;
|
||||
const ALT = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct MouseKeys: u8 {
|
||||
const LEFT = 0b0000_0001;
|
||||
const RIGHT = 0b0000_0010;
|
||||
const MIDDLE = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use super::{input_manager::InputManager, Event, EventHandler, Operation, Response};
|
||||
use crate::tools::{DocumentToolData, ToolData, ToolSettings};
|
||||
use document_core::document::Document;
|
||||
|
||||
pub struct GlobalEventHandler {}
|
||||
|
||||
impl GlobalEventHandler {
|
||||
fn new(tool_data: ToolData) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn pre_process_event(&mut self, input: &InputManager, events: &mut Vec<Event>, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
pub mod events;
|
||||
use crate::{tools::ToolType, Color, Document, EditorError, EditorState};
|
||||
use document_core::Operation;
|
||||
use events::{DocumentResponse, Event, Key, Response, ToolResponse};
|
||||
|
||||
pub type Callback = Box<dyn Fn(Response)>;
|
||||
pub struct Dispatcher {
|
||||
callback: Callback,
|
||||
}
|
||||
|
||||
impl Dispatcher {
|
||||
pub fn handle_event(&self, editor_state: &mut EditorState, event: &Event) -> Result<(), EditorError> {
|
||||
log::trace!("{:?}", event);
|
||||
|
||||
match event {
|
||||
Event::SelectTool(tool_name) => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = *tool_name;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool { tool_name: tool_name.to_string() });
|
||||
}
|
||||
Event::SelectPrimaryColor(color) => {
|
||||
editor_state.tool_state.document_tool_data.primary_color = *color;
|
||||
}
|
||||
Event::SelectSecondaryColor(color) => {
|
||||
editor_state.tool_state.document_tool_data.secondary_color = *color;
|
||||
}
|
||||
Event::SwapColors => {
|
||||
editor_state.tool_state.swap_colors();
|
||||
}
|
||||
Event::ResetColors => {
|
||||
editor_state.tool_state.document_tool_data.primary_color = Color::BLACK;
|
||||
editor_state.tool_state.document_tool_data.secondary_color = Color::WHITE;
|
||||
}
|
||||
Event::LmbDown(mouse_state) | Event::RmbDown(mouse_state) | Event::MmbDown(mouse_state) | Event::LmbUp(mouse_state) | Event::RmbUp(mouse_state) | Event::MmbUp(mouse_state) => {
|
||||
editor_state.tool_state.document_tool_data.mouse_state = *mouse_state;
|
||||
}
|
||||
Event::MouseMove(pos) => {
|
||||
editor_state.tool_state.document_tool_data.mouse_state.position = *pos;
|
||||
}
|
||||
Event::ToggleLayerVisibility(path) => {
|
||||
let document_responses = self.dispatch_operations(&mut editor_state.document, vec![Operation::ToggleVisibility { path: path.clone() }]);
|
||||
self.dispatch_response(ToolResponse::UpdateCanvas {
|
||||
document: editor_state.document.render_root(),
|
||||
});
|
||||
self.dispatch_responses(document_responses);
|
||||
}
|
||||
Event::KeyUp(_key) => (),
|
||||
Event::KeyDown(key) => {
|
||||
log::trace!("pressed key {:?}", key);
|
||||
log::debug!("pressed key {:?}", key);
|
||||
|
||||
match key {
|
||||
Key::Key0 => {
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
log::debug!("set log verbosity to info");
|
||||
}
|
||||
Key::Key1 => {
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
log::debug!("set log verbosity to debug");
|
||||
}
|
||||
Key::Key2 => {
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
log::debug!("set log verbosity to trace");
|
||||
}
|
||||
Key::KeyV => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Select;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool {
|
||||
tool_name: ToolType::Select.to_string(),
|
||||
});
|
||||
}
|
||||
Key::KeyL => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Line;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool {
|
||||
tool_name: ToolType::Line.to_string(),
|
||||
});
|
||||
}
|
||||
Key::KeyP => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Pen;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool { tool_name: ToolType::Pen.to_string() });
|
||||
}
|
||||
Key::KeyM => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Rectangle;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool {
|
||||
tool_name: ToolType::Rectangle.to_string(),
|
||||
});
|
||||
}
|
||||
Key::KeyY => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Shape;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool {
|
||||
tool_name: ToolType::Shape.to_string(),
|
||||
});
|
||||
}
|
||||
Key::KeyE => {
|
||||
editor_state.tool_state.tool_data.active_tool_type = ToolType::Ellipse;
|
||||
self.dispatch_response(ToolResponse::SetActiveTool {
|
||||
tool_name: ToolType::Ellipse.to_string(),
|
||||
});
|
||||
}
|
||||
Key::KeyX => {
|
||||
editor_state.tool_state.swap_colors();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
_ => todo!("Implement layer handling"),
|
||||
}
|
||||
|
||||
let (mut tool_responses, operations) = editor_state
|
||||
.tool_state
|
||||
.tool_data
|
||||
.active_tool()?
|
||||
.handle_input(event, &editor_state.document, &editor_state.tool_state.document_tool_data);
|
||||
|
||||
let mut document_responses = self.dispatch_operations(&mut editor_state.document, operations);
|
||||
//let changes = document_responses.drain_filter(|x| x == DocumentResponse::DocumentChanged);
|
||||
let mut canvas_dirty = false;
|
||||
let mut i = 0;
|
||||
while i < document_responses.len() {
|
||||
if matches!(document_responses[i], DocumentResponse::DocumentChanged) {
|
||||
canvas_dirty = true;
|
||||
document_responses.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if canvas_dirty {
|
||||
tool_responses.push(ToolResponse::UpdateCanvas {
|
||||
document: editor_state.document.render_root(),
|
||||
})
|
||||
}
|
||||
self.dispatch_responses(tool_responses);
|
||||
self.dispatch_responses(document_responses);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_operations<I: IntoIterator<Item = Operation>>(&self, document: &mut Document, operations: I) -> Vec<DocumentResponse> {
|
||||
let mut responses = vec![];
|
||||
for operation in operations {
|
||||
match self.dispatch_operation(document, operation) {
|
||||
Ok(Some(mut res)) => {
|
||||
responses.append(&mut res);
|
||||
}
|
||||
Ok(None) => (),
|
||||
Err(error) => log::error!("{}", error),
|
||||
}
|
||||
}
|
||||
responses
|
||||
}
|
||||
|
||||
fn dispatch_operation(&self, document: &mut Document, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, EditorError> {
|
||||
Ok(document.handle_operation(operation)?)
|
||||
}
|
||||
|
||||
pub fn dispatch_responses<T: Into<Response>, I: IntoIterator<Item = T>>(&self, responses: I) {
|
||||
for response in responses {
|
||||
self.dispatch_response(response);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dispatch_response<T: Into<Response>>(&self, response: T) {
|
||||
let func = &self.callback;
|
||||
let response: Response = response.into();
|
||||
log::trace!("Sending {} Response", response);
|
||||
func(response)
|
||||
}
|
||||
|
||||
pub fn new(callback: Callback) -> Dispatcher {
|
||||
Dispatcher { callback }
|
||||
}
|
||||
}
|
||||
7
core/editor/src/document/document_file.rs
Normal file
7
core/editor/src/document/document_file.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
use document_core::document::Document as InteralDocument;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Document {
|
||||
pub document: InteralDocument,
|
||||
pub name: String,
|
||||
}
|
||||
100
core/editor/src/document/document_message_handler.rs
Normal file
100
core/editor/src/document/document_message_handler.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::message_prelude::*;
|
||||
use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
|
||||
use crate::document::Document;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[impl_message(Message, Document)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum DocumentMessage {
|
||||
DispatchOperation(DocumentOperation),
|
||||
SelectLayer(Vec<LayerId>),
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
AddFolder(Vec<LayerId>),
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
SelectDocument(usize),
|
||||
RenderDocument,
|
||||
Undo,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
fn from(operation: DocumentOperation) -> DocumentMessage {
|
||||
Self::DispatchOperation(operation)
|
||||
}
|
||||
}
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(operation).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentMessageHandler {
|
||||
documents: Vec<Document>,
|
||||
active_document: usize,
|
||||
}
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
pub fn active_document(&self) -> &Document {
|
||||
&self.documents[self.active_document]
|
||||
}
|
||||
pub fn active_document_mut(&mut self) -> &mut Document {
|
||||
&mut self.documents[self.active_document]
|
||||
}
|
||||
fn filter_document_responses(&self, document_responses: &mut Vec<DocumentResponse>) -> bool {
|
||||
let len = document_responses.len();
|
||||
document_responses.retain(|response| !matches!(response, DocumentResponse::DocumentChanged));
|
||||
document_responses.len() != len
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DocumentMessageHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
documents: vec![Document::default()],
|
||||
active_document: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
|
||||
fn process_action(&mut self, message: DocumentMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
match message {
|
||||
DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()),
|
||||
AddFolder(path) => responses.push_back(DocumentOperation::AddFolder { path }.into()),
|
||||
SelectDocument(id) => {
|
||||
assert!(id < self.documents.len(), "Tried to select a document that was not initialized");
|
||||
self.active_document = id;
|
||||
}
|
||||
ToggleLayerVisibility(path) => {
|
||||
responses.push_back(DocumentOperation::ToggleVisibility { path }.into());
|
||||
}
|
||||
Undo => {
|
||||
// this is a temporary fix and will be addressed by #123
|
||||
if let Some(id) = self.active_document().document.root.list_layers().last() {
|
||||
responses.push_back(DocumentOperation::DeleteLayer { path: vec![*id] }.into())
|
||||
}
|
||||
}
|
||||
DispatchOperation(op) => {
|
||||
if let Ok(Some(mut document_responses)) = self.active_document_mut().document.handle_operation(op) {
|
||||
let canvas_dirty = self.filter_document_responses(&mut document_responses);
|
||||
responses.extend(document_responses.into_iter().map(Into::into));
|
||||
if canvas_dirty {
|
||||
responses.push_back(RenderDocument.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderDocument => responses.push_back(
|
||||
FrontendMessage::UpdateCanvas {
|
||||
document: self.active_document_mut().document.render_root(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
|
||||
}
|
||||
}
|
||||
advertise_actions!(DocumentMessageDiscriminant; Undo, RenderDocument);
|
||||
}
|
||||
8
core/editor/src/document/mod.rs
Normal file
8
core/editor/src/document/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod document_file;
|
||||
mod document_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_file::Document;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::{DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler};
|
||||
59
core/editor/src/frontend/frontend_message_handler.rs
Normal file
59
core/editor/src/frontend/frontend_message_handler.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use crate::message_prelude::*;
|
||||
use document_core::{response::LayerPanelEntry, DocumentResponse, LayerId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type Callback = Box<dyn Fn(FrontendMessage)>;
|
||||
|
||||
#[impl_message(Message, Frontend)]
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
pub enum FrontendMessage {
|
||||
CollapseFolder { path: Vec<LayerId> },
|
||||
ExpandFolder { path: Vec<LayerId>, children: Vec<LayerPanelEntry> },
|
||||
SetActiveTool { tool_name: String },
|
||||
UpdateCanvas { document: String },
|
||||
EnableTextInput,
|
||||
DisableTextInput,
|
||||
}
|
||||
|
||||
impl From<DocumentResponse> for Message {
|
||||
fn from(response: DocumentResponse) -> Self {
|
||||
let frontend: FrontendMessage = response.into();
|
||||
frontend.into()
|
||||
}
|
||||
}
|
||||
impl From<DocumentResponse> for FrontendMessage {
|
||||
fn from(response: DocumentResponse) -> Self {
|
||||
match response {
|
||||
DocumentResponse::ExpandFolder { path, children } => Self::ExpandFolder { path, children },
|
||||
DocumentResponse::CollapseFolder { path } => Self::CollapseFolder { path },
|
||||
_ => unimplemented!("The frontend does not handle {:?}", response),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrontendMessageHandler {
|
||||
callback: crate::Callback,
|
||||
}
|
||||
|
||||
impl FrontendMessageHandler {
|
||||
pub fn new(callback: Callback) -> Self {
|
||||
Self { callback }
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<FrontendMessage, ()> for FrontendMessageHandler {
|
||||
fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque<Message>) {
|
||||
log::trace!("Sending {} Response", message.to_discriminant().global_name());
|
||||
(self.callback)(message)
|
||||
}
|
||||
advertise_actions!(
|
||||
FrontendMessageDiscriminant;
|
||||
|
||||
CollapseFolder,
|
||||
ExpandFolder,
|
||||
SetActiveTool,
|
||||
UpdateCanvas,
|
||||
EnableTextInput,
|
||||
DisableTextInput,
|
||||
);
|
||||
}
|
||||
3
core/editor/src/frontend/mod.rs
Normal file
3
core/editor/src/frontend/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod frontend_message_handler;
|
||||
|
||||
pub use frontend_message_handler::{Callback, FrontendMessage, FrontendMessageDiscriminant, FrontendMessageHandler};
|
||||
40
core/editor/src/global/global_message_handler.rs
Normal file
40
core/editor/src/global/global_message_handler.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::message_prelude::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[impl_message(Message, Global)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum GlobalMessage {
|
||||
LogInfo,
|
||||
LogDebug,
|
||||
LogTrace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GlobalMessageHandler {}
|
||||
|
||||
impl GlobalMessageHandler {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<GlobalMessage, ()> for GlobalMessageHandler {
|
||||
fn process_action(&mut self, message: GlobalMessage, _data: (), _responses: &mut VecDeque<Message>) {
|
||||
use GlobalMessage::*;
|
||||
match message {
|
||||
LogInfo => {
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
log::info!("set log verbosity to info");
|
||||
}
|
||||
LogDebug => {
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
log::info!("set log verbosity to debug");
|
||||
}
|
||||
LogTrace => {
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
log::info!("set log verbosity to trace");
|
||||
}
|
||||
}
|
||||
}
|
||||
advertise_actions!(GlobalMessageDiscriminant; LogInfo, LogDebug, LogTrace);
|
||||
}
|
||||
3
core/editor/src/global/mod.rs
Normal file
3
core/editor/src/global/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod global_message_handler;
|
||||
|
||||
pub use global_message_handler::{GlobalMessage, GlobalMessageDiscriminant, GlobalMessageHandler};
|
||||
@@ -1,5 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait Hint {
|
||||
fn hints(&self) -> HashMap<String, String>;
|
||||
}
|
||||
191
core/editor/src/input/input_mapper.rs
Normal file
191
core/editor/src/input/input_mapper.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolType;
|
||||
|
||||
use super::{
|
||||
keyboard::{Key, KeyStates, NUMBER_OF_KEYS},
|
||||
InputPreprocessor,
|
||||
};
|
||||
|
||||
#[impl_message(Message, InputMapper)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum InputMapperMessage {
|
||||
PointerMove,
|
||||
KeyUp(Key),
|
||||
KeyDown(Key),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
struct MappingEntry {
|
||||
trigger: InputMapperMessage,
|
||||
modifiers: KeyStates,
|
||||
action: Message,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct KeyMappingEntries(Vec<MappingEntry>);
|
||||
|
||||
impl KeyMappingEntries {
|
||||
fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
for entry in self.0.iter() {
|
||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
return Some(entry.action.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn push(&mut self, entry: MappingEntry) {
|
||||
self.0.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Mapping {
|
||||
up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pointer_move: KeyMappingEntries,
|
||||
}
|
||||
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
),*
|
||||
state
|
||||
}};
|
||||
}
|
||||
macro_rules! entry {
|
||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
||||
}};
|
||||
{action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?}
|
||||
}};
|
||||
{action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}
|
||||
}};
|
||||
}
|
||||
macro_rules! mapping {
|
||||
//[$(<action=$action:expr; message=$key:expr; $(modifiers=[$($m:ident),* $(,)?];)?>)*] => {{
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut up: [KeyMappingEntries; NUMBER_OF_KEYS] = Default::default();
|
||||
let mut down: [KeyMappingEntries; NUMBER_OF_KEYS] = Default::default();
|
||||
let mut pointer_move: KeyMappingEntries = Default::default();
|
||||
$(
|
||||
let arr = match $entry.trigger {
|
||||
InputMapperMessage::KeyDown(key) => &mut down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut up[key as usize],
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
};
|
||||
arr.push($entry);
|
||||
)*
|
||||
(up, down, pointer_move)
|
||||
}};
|
||||
}
|
||||
|
||||
impl Default for Mapping {
|
||||
fn default() -> Self {
|
||||
let (up, down, pointer_move) = mapping![
|
||||
// Rectangle
|
||||
entry! {action=RectangleMessage::Center, key_down=KeyAlt},
|
||||
entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt},
|
||||
entry! {action=RectangleMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=RectangleMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=RectangleMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=Rmb},
|
||||
entry! {action=RectangleMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=RectangleMessage::LockAspectRatio, key_down=KeyShift},
|
||||
entry! {action=RectangleMessage::UnlockAspectRatio, key_up=KeyShift},
|
||||
entry! {action=RectangleMessage::LockAspectRatio, key_down=KeyCaps},
|
||||
entry! {action=RectangleMessage::UnlockAspectRatio, key_up=KeyCaps},
|
||||
// Ellipse
|
||||
entry! {action=EllipseMessage::Center, key_down=KeyAlt},
|
||||
entry! {action=EllipseMessage::UnCenter, key_up=KeyAlt},
|
||||
entry! {action=EllipseMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=EllipseMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=EllipseMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=Rmb},
|
||||
entry! {action=EllipseMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=EllipseMessage::LockAspectRatio, key_down=KeyShift},
|
||||
entry! {action=EllipseMessage::UnlockAspectRatio, key_up=KeyShift},
|
||||
entry! {action=EllipseMessage::LockAspectRatio, key_down=KeyCaps},
|
||||
entry! {action=EllipseMessage::UnlockAspectRatio, key_up=KeyCaps},
|
||||
// Shape
|
||||
entry! {action=ShapeMessage::Center, key_down=KeyAlt},
|
||||
entry! {action=ShapeMessage::UnCenter, key_up=KeyAlt},
|
||||
entry! {action=ShapeMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=ShapeMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=ShapeMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=Rmb},
|
||||
entry! {action=ShapeMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=ShapeMessage::LockAspectRatio, key_down=KeyShift},
|
||||
entry! {action=ShapeMessage::UnlockAspectRatio, key_up=KeyShift},
|
||||
entry! {action=ShapeMessage::LockAspectRatio, key_down=KeyCaps},
|
||||
entry! {action=ShapeMessage::UnlockAspectRatio, key_up=KeyCaps},
|
||||
// Line
|
||||
entry! {action=LineMessage::Center, key_down=KeyAlt},
|
||||
entry! {action=LineMessage::UnCenter, key_up=KeyAlt},
|
||||
entry! {action=LineMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=LineMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=LineMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=LineMessage::Abort, key_down=Rmb},
|
||||
entry! {action=LineMessage::Abort, key_down=KeyEscape},
|
||||
entry! {action=LineMessage::LockAngle, key_down=KeyControl},
|
||||
entry! {action=LineMessage::UnlockAngle, key_up=KeyControl},
|
||||
entry! {action=LineMessage::SnapToAngle, key_down=KeyShift},
|
||||
entry! {action=LineMessage::UnSnapToAngle, key_up=KeyShift},
|
||||
entry! {action=LineMessage::SnapToAngle, key_down=KeyCaps},
|
||||
entry! {action=LineMessage::UnSnapToAngle, key_up=KeyCaps},
|
||||
// Pen
|
||||
entry! {action=PenMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=PenMessage::DragStart, key_down=Lmb},
|
||||
entry! {action=PenMessage::DragStop, key_up=Lmb},
|
||||
entry! {action=PenMessage::Confirm, key_down=Rmb},
|
||||
entry! {action=PenMessage::Confirm, key_down=KeyEscape},
|
||||
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
|
||||
// Document Actions
|
||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||
// Tool Actions
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Line), key_down=KeyL},
|
||||
entry! {action=ToolMessage::SelectTool(ToolType::Shape), key_down=KeyY},
|
||||
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
||||
// Global Actions
|
||||
entry! {action=GlobalMessage::LogInfo, key_down=Key1},
|
||||
entry! {action=GlobalMessage::LogDebug, key_down=Key2},
|
||||
entry! {action=GlobalMessage::LogTrace, key_down=Key3},
|
||||
];
|
||||
Self { up, down, pointer_move }
|
||||
}
|
||||
}
|
||||
|
||||
impl Mapping {
|
||||
fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
use InputMapperMessage::*;
|
||||
let list = match message {
|
||||
KeyDown(key) => &self.down[key as usize],
|
||||
KeyUp(key) => &self.up[key as usize],
|
||||
PointerMove => &self.pointer_move,
|
||||
};
|
||||
list.match_mapping(keys, actions)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputMapper {
|
||||
mapping: Mapping,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessor, ActionList)> for InputMapper {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessor, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, actions) = data;
|
||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
74
core/editor/src/input/input_preprocessor.rs
Normal file
74
core/editor/src/input/input_preprocessor.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{MouseKeys, MouseState, ViewportPosition};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_core::DocumentResponse;
|
||||
|
||||
#[impl_message(Message, InputPreprocessor)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum InputPreprocessorMessage {
|
||||
MouseDown(MouseState),
|
||||
MouseUp(MouseState),
|
||||
MouseMove(ViewportPosition),
|
||||
KeyUp(Key),
|
||||
KeyDown(Key),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputPreprocessor {
|
||||
pub keyboard: KeyStates,
|
||||
pub mouse: MouseState,
|
||||
}
|
||||
|
||||
enum KeyPosition {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
let response = match message {
|
||||
InputPreprocessorMessage::MouseMove(pos) => {
|
||||
self.mouse.position = pos;
|
||||
InputMapperMessage::PointerMove.into()
|
||||
}
|
||||
InputPreprocessorMessage::MouseDown(state) => self.translate_mouse_event(state, KeyPosition::Pressed),
|
||||
InputPreprocessorMessage::MouseUp(state) => self.translate_mouse_event(state, KeyPosition::Released),
|
||||
InputPreprocessorMessage::KeyDown(key) => {
|
||||
self.keyboard.set(key as usize);
|
||||
InputMapperMessage::KeyDown(key).into()
|
||||
}
|
||||
InputPreprocessorMessage::KeyUp(key) => {
|
||||
self.keyboard.unset(key as usize);
|
||||
InputMapperMessage::KeyUp(key).into()
|
||||
}
|
||||
};
|
||||
responses.push_back(response)
|
||||
}
|
||||
// clean user input and if possible reconstruct it
|
||||
// store the changes in the keyboard if it is a key event
|
||||
// transform canvas coordinates to document coordinates
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl InputPreprocessor {
|
||||
fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Message {
|
||||
// Calculate the difference between the two key states (binary xor)
|
||||
let diff = self.mouse.mouse_keys ^ new_state.mouse_keys;
|
||||
self.mouse = new_state;
|
||||
let key = match diff {
|
||||
MouseKeys::LEFT => Key::Lmb,
|
||||
MouseKeys::RIGHT => Key::Rmb,
|
||||
MouseKeys::MIDDLE => Key::Mmb,
|
||||
_ => {
|
||||
log::warn!("The number of buttons modified at the same time was not equal to 1. Modification: {:#010b}", diff);
|
||||
Key::UnknownKey
|
||||
}
|
||||
};
|
||||
match position {
|
||||
KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(),
|
||||
KeyPosition::Released => InputMapperMessage::KeyUp(key).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
143
core/editor/src/input/keyboard.rs
Normal file
143
core/editor/src/input/keyboard.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
// Edit this to specify the storage type used
|
||||
// TODO: Increase size of type
|
||||
pub type StorageType = u8;
|
||||
const STORAGE_SIZE: u32 = std::mem::size_of::<usize>() as u32 * 8 + 2 - std::mem::size_of::<StorageType>().leading_zeros();
|
||||
const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE;
|
||||
const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE;
|
||||
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Key {
|
||||
UnknownKey,
|
||||
// MouseKeys
|
||||
Lmb,
|
||||
Rmb,
|
||||
Mmb,
|
||||
|
||||
// Keyboard keys
|
||||
KeyR,
|
||||
KeyM,
|
||||
KeyE,
|
||||
KeyL,
|
||||
KeyP,
|
||||
KeyV,
|
||||
KeyX,
|
||||
KeyZ,
|
||||
KeyY,
|
||||
KeyEnter,
|
||||
Key0,
|
||||
Key1,
|
||||
Key2,
|
||||
Key3,
|
||||
Key4,
|
||||
Key5,
|
||||
Key6,
|
||||
Key7,
|
||||
Key8,
|
||||
Key9,
|
||||
KeyShift,
|
||||
KeyCaps,
|
||||
KeyControl,
|
||||
KeyAlt,
|
||||
KeyEscape,
|
||||
|
||||
// This has to be the last element in the enum.
|
||||
NumKeys,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BitVector<const LENGTH: usize>([StorageType; LENGTH]);
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign},
|
||||
usize,
|
||||
};
|
||||
|
||||
impl<const LENGTH: usize> BitVector<LENGTH> {
|
||||
#[inline]
|
||||
fn convert_index(bitvector_index: usize) -> (usize, StorageType) {
|
||||
let bit = 1 << (bitvector_index & (STORAGE_SIZE_BITS as StorageType - 1) as usize);
|
||||
let offset = bitvector_index >> STORAGE_SIZE;
|
||||
(offset, bit)
|
||||
}
|
||||
pub const fn new() -> Self {
|
||||
Self([0; LENGTH])
|
||||
}
|
||||
pub fn set(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] |= bit;
|
||||
}
|
||||
pub fn unset(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] &= !bit;
|
||||
}
|
||||
pub fn toggle(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] ^= bit;
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let mut result = 0;
|
||||
for storage in self.0.iter() {
|
||||
result |= storage;
|
||||
}
|
||||
result == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Default for BitVector<LENGTH> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for storage in self.0.iter().rev() {
|
||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! bit_ops {
|
||||
($(($op:ident, $func:ident)),* $(,)?) => {
|
||||
$(
|
||||
impl<const LENGTH: usize> $op for BitVector<LENGTH> {
|
||||
type Output = Self;
|
||||
fn $func(self, right: Self) -> Self::Output {
|
||||
let mut result = Self::new();
|
||||
for ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {
|
||||
*new = $op::$func(left, right);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
impl<const LENGTH: usize> $op for &BitVector<LENGTH> {
|
||||
type Output = BitVector<LENGTH>;
|
||||
fn $func(self, right: Self) -> Self::Output {
|
||||
let mut result = BitVector::<LENGTH>::new();
|
||||
for ((left, right), new) in self.0.iter().zip(right.0.iter()).zip(result.0.iter_mut()) {
|
||||
*new = $op::$func(left, right);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
macro_rules! bit_ops_assign {
|
||||
($(($op:ident, $func:ident)),* $(,)?) => {
|
||||
$(impl<const LENGTH: usize> $op for BitVector<LENGTH> {
|
||||
fn $func(&mut self, right: Self) {
|
||||
for (left, right) in self.0.iter_mut().zip(right.0.iter()) {
|
||||
$op::$func(left, right);
|
||||
}
|
||||
}
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
bit_ops!((BitAnd, bitand), (BitOr, bitor), (BitXor, bitxor));
|
||||
bit_ops_assign!((BitAndAssign, bitand_assign), (BitOrAssign, bitor_assign), (BitXorAssign, bitxor_assign));
|
||||
9
core/editor/src/input/mod.rs
Normal file
9
core/editor/src/input/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod input_mapper;
|
||||
pub mod input_preprocessor;
|
||||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
||||
pub use {
|
||||
input_mapper::{InputMapper, InputMapperMessage, InputMapperMessageDiscriminant},
|
||||
input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant},
|
||||
};
|
||||
48
core/editor/src/input/mouse.rs
Normal file
48
core/editor/src/input/mouse.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use bitflags::bitflags;
|
||||
|
||||
// origin is top left
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub struct ViewportPosition {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
}
|
||||
|
||||
impl ViewportPosition {
|
||||
pub fn distance(&self, other: &Self) -> f64 {
|
||||
let x_diff = other.x as i64 - self.x as i64;
|
||||
let y_diff = other.y as i64 - self.y as i64;
|
||||
f64::sqrt((x_diff * x_diff + y_diff * y_diff) as f64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
|
||||
pub struct MouseState {
|
||||
pub position: ViewportPosition,
|
||||
pub mouse_keys: MouseKeys,
|
||||
}
|
||||
|
||||
impl MouseState {
|
||||
pub fn new() -> MouseState {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn from_pos(x: u32, y: u32) -> MouseState {
|
||||
MouseState {
|
||||
position: ViewportPosition { x, y },
|
||||
mouse_keys: MouseKeys::default(),
|
||||
}
|
||||
}
|
||||
pub fn from_u8_pos(keys: u8, position: ViewportPosition) -> Self {
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys");
|
||||
Self { position, mouse_keys }
|
||||
}
|
||||
}
|
||||
bitflags! {
|
||||
#[derive(Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct MouseKeys: u8 {
|
||||
const LEFT = 0b0000_0001;
|
||||
const RIGHT = 0b0000_0010;
|
||||
const MIDDLE = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
// since our policy is tabs, we want to stop clippy from warning about that
|
||||
#![allow(clippy::tabs_in_doc_comments)]
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
extern crate graphite_proc_macros;
|
||||
|
||||
mod dispatcher;
|
||||
mod error;
|
||||
pub mod hint;
|
||||
pub mod tools;
|
||||
pub mod workspace;
|
||||
mod communication;
|
||||
#[macro_use]
|
||||
pub mod misc;
|
||||
mod document;
|
||||
mod frontend;
|
||||
mod global;
|
||||
pub mod input;
|
||||
pub mod tool;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use error::EditorError;
|
||||
pub use misc::EditorError;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_core::color::Color;
|
||||
@@ -20,41 +22,49 @@ pub use document_core::color::Color;
|
||||
pub use document_core::LayerId;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use dispatcher::events;
|
||||
pub use document_core::document::Document as SvgDocument;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use dispatcher::Callback;
|
||||
|
||||
use dispatcher::Dispatcher;
|
||||
use document_core::document::Document;
|
||||
use tools::ToolFsmState;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub struct EditorState {
|
||||
tool_state: ToolFsmState,
|
||||
workspace: Workspace,
|
||||
document: Document,
|
||||
}
|
||||
pub use frontend::Callback;
|
||||
|
||||
use communication::dispatcher::Dispatcher;
|
||||
// TODO: serialize with serde to save the current editor state
|
||||
pub struct Editor {
|
||||
state: EditorState,
|
||||
dispatcher: Dispatcher,
|
||||
}
|
||||
|
||||
use message_prelude::*;
|
||||
|
||||
impl Editor {
|
||||
pub fn new(callback: Callback) -> Self {
|
||||
Self {
|
||||
state: EditorState {
|
||||
tool_state: ToolFsmState::new(),
|
||||
workspace: Workspace::new(),
|
||||
document: Document::default(),
|
||||
},
|
||||
dispatcher: Dispatcher::new(callback),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: events::Event) -> Result<(), EditorError> {
|
||||
self.dispatcher.handle_event(&mut self.state, &event)
|
||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
|
||||
self.dispatcher.handle_message(message)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod message_prelude {
|
||||
pub use super::communication::message::{AsMessage, Message, MessageDiscriminant};
|
||||
pub use super::communication::{ActionList, MessageHandler};
|
||||
pub use super::document::{DocumentMessage, DocumentMessageDiscriminant};
|
||||
pub use super::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
pub use super::global::{GlobalMessage, GlobalMessageDiscriminant};
|
||||
pub use super::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||
pub use super::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild};
|
||||
pub use super::tool::tool_messages::*;
|
||||
pub use super::tool::tools::crop::{CropMessage, CropMessageDiscriminant};
|
||||
pub use super::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
|
||||
pub use super::tool::tools::line::{LineMessage, LineMessageDiscriminant};
|
||||
pub use super::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
|
||||
pub use super::tool::tools::path::{PathMessage, PathMessageDiscriminant};
|
||||
pub use super::tool::tools::pen::{PenMessage, PenMessageDiscriminant};
|
||||
pub use super::tool::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant};
|
||||
pub use super::tool::tools::select::{SelectMessage, SelectMessageDiscriminant};
|
||||
pub use super::tool::tools::shape::{ShapeMessage, ShapeMessageDiscriminant};
|
||||
pub use graphite_proc_macros::*;
|
||||
pub use std::collections::VecDeque;
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/// Counts args in the macro invocation by adding `+ 1` for every arg.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = count_args!(("example1"), (10), (25));
|
||||
/// assert_eq!(x, 3);
|
||||
/// ```
|
||||
/// expands to
|
||||
/// ```ignore
|
||||
/// let x = 0 + 1 + 1 + 1;
|
||||
/// assert_eq!(x, 3);
|
||||
/// ```
|
||||
macro_rules! count_args {
|
||||
(@one $($t:tt)*) => { 1 };
|
||||
($(($($x:tt)*)),*$(,)?) => {
|
||||
0 $(+ count_args!(@one $($x)*))*
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a [`std::collections::HashMap`] for `ToolState`'s `tools` variable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let tools = gen_tools_hash_map! {
|
||||
/// Select => select::Select,
|
||||
/// Crop => crop::Crop,
|
||||
/// };
|
||||
/// ```
|
||||
/// expands to
|
||||
/// ```ignore
|
||||
/// let tools = {
|
||||
/// let mut hash_map: std::collections::HashMap<crate::tools::ToolType, Box<dyn crate::tools::Tool>> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */));
|
||||
///
|
||||
/// hash_map.insert(crate::tools::ToolType::Select, Box::new(select::Select::default()));
|
||||
/// hash_map.insert(crate::tools::ToolType::Crop, Box::new(crop::Crop::default()));
|
||||
///
|
||||
/// hash_map
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! gen_tools_hash_map {
|
||||
($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::tools::ToolType, ::std::boxed::Box<dyn $crate::tools::Tool>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
$(hash_map.insert($crate::tools::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)*
|
||||
|
||||
hash_map
|
||||
}};
|
||||
}
|
||||
|
||||
/// Creates a string representation of an enum value that exactly matches the given name of each enum variant
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// enum E {
|
||||
/// A(u8),
|
||||
/// B
|
||||
/// }
|
||||
///
|
||||
/// // this line is important
|
||||
/// use E::*;
|
||||
///
|
||||
/// let a = E::A(7);
|
||||
/// let s = match_variant_name!(match (a) { A, B });
|
||||
/// ```
|
||||
///
|
||||
/// expands to
|
||||
///
|
||||
/// ```ignore
|
||||
/// // ...
|
||||
///
|
||||
/// let s = match a {
|
||||
/// A { .. } => "A",
|
||||
/// B { .. } => "B"
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! match_variant_name {
|
||||
(match ($e:expr) { $($v:ident),* $(,)? }) => {
|
||||
match $e {
|
||||
$(
|
||||
$v { .. } => stringify!($v)
|
||||
),*
|
||||
}
|
||||
};
|
||||
}
|
||||
18
core/editor/src/misc/derivable_custom_traits.rs
Normal file
18
core/editor/src/misc/derivable_custom_traits.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
//! Traits that can be derived using macros from `graphite-proc-macros`
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub trait Hint {
|
||||
fn hints(&self) -> HashMap<String, String>;
|
||||
}
|
||||
|
||||
pub trait ToDiscriminant {
|
||||
type Discriminant;
|
||||
|
||||
fn to_discriminant(&self) -> Self::Discriminant;
|
||||
}
|
||||
|
||||
pub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {
|
||||
type TopParent;
|
||||
type Parent;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::events::Event;
|
||||
use crate::Color;
|
||||
use document_core::DocumentError;
|
||||
use thiserror::Error;
|
||||
@@ -8,8 +7,6 @@ use thiserror::Error;
|
||||
pub enum EditorError {
|
||||
#[error("Failed to execute operation: {0}")]
|
||||
InvalidOperation(String),
|
||||
#[error("Failed to dispatch event: {0}")]
|
||||
InvalidEvent(String),
|
||||
#[error("{0}")]
|
||||
Misc(String),
|
||||
#[error("Tried to construct an invalid color {0:?}")]
|
||||
@@ -33,5 +30,4 @@ macro_rules! derive_from {
|
||||
derive_from!(&str, Misc);
|
||||
derive_from!(String, Misc);
|
||||
derive_from!(Color, Color);
|
||||
derive_from!(Event, InvalidEvent);
|
||||
derive_from!(DocumentError, Document);
|
||||
138
core/editor/src/misc/macros.rs
Normal file
138
core/editor/src/misc/macros.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
/// Counts args in the macro invocation by adding `+ 1` for every arg.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let x = count_args!(("example1"), (10), (25));
|
||||
/// assert_eq!(x, 3);
|
||||
/// ```
|
||||
/// expands to
|
||||
/// ```ignore
|
||||
/// let x = 0 + 1 + 1 + 1;
|
||||
/// assert_eq!(x, 3);
|
||||
/// ```
|
||||
macro_rules! count_args {
|
||||
(@one $($t:tt)*) => { 1 };
|
||||
($(($($x:tt)*)),*$(,)?) => {
|
||||
0 $(+ count_args!(@one $($x)*))*
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a [`std::collections::HashMap`] for `ToolState`'s `tools` variable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let tools = gen_tools_hash_map! {
|
||||
/// Select => select::Select,
|
||||
/// Crop => crop::Crop,
|
||||
/// };
|
||||
/// ```
|
||||
/// expands to
|
||||
/// ```ignore
|
||||
/// let tools = {
|
||||
/// let mut hash_map: std::collections::HashMap<crate::tool::ToolType, Box<dyn crate::tool::Tool>> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */));
|
||||
///
|
||||
/// hash_map.insert(crate::tool::ToolType::Select, Box::new(select::Select::default()));
|
||||
/// hash_map.insert(crate::tool::ToolType::Crop, Box::new(crop::Crop::default()));
|
||||
///
|
||||
/// hash_map
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! gen_tools_hash_map {
|
||||
($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::tool::ToolType, ::std::boxed::Box<dyn for<'a> $crate::message_prelude::MessageHandler<$crate::tool::tool_messages::ToolMessage,$crate::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
$(hash_map.insert($crate::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)*
|
||||
|
||||
hash_map
|
||||
}};
|
||||
}
|
||||
|
||||
/// Creates a string representation of an enum value that exactly matches the given name of each enum variant
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// enum E {
|
||||
/// A(u8),
|
||||
/// B
|
||||
/// }
|
||||
///
|
||||
/// // this line is important
|
||||
/// use E::*;
|
||||
///
|
||||
/// let a = E::A(7);
|
||||
/// let s = match_variant_name!(match (a) { A, B });
|
||||
/// ```
|
||||
///
|
||||
/// expands to
|
||||
///
|
||||
/// ```ignore
|
||||
/// // ...
|
||||
///
|
||||
/// let s = match a {
|
||||
/// A { .. } => "A",
|
||||
/// B { .. } => "B"
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! match_variant_name {
|
||||
(match ($e:expr) { $($v:ident),* $(,)? }) => {
|
||||
match $e {
|
||||
$(
|
||||
$v { .. } => stringify!($v)
|
||||
),*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Syntax sugar for initializing an `ActionList`
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// actions!(DocumentMessage::Undo, DocumentMessage::Redo);
|
||||
/// ```
|
||||
///
|
||||
/// expands to:
|
||||
/// ```ignore
|
||||
/// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];
|
||||
/// ```
|
||||
///
|
||||
/// and
|
||||
/// ```ignore
|
||||
/// actions!(DocumentMessage; Undo, Redo);
|
||||
/// ```
|
||||
///
|
||||
/// expands to:
|
||||
/// ```ignore
|
||||
/// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];
|
||||
/// ```
|
||||
///
|
||||
macro_rules! actions {
|
||||
($($v:expr),* $(,)?) => {{
|
||||
vec![$(vec![$v.into()]),*]
|
||||
}};
|
||||
($name:ident; $($v:ident),* $(,)?) => {{
|
||||
vec![vec![$(($name::$v).into()),*]]
|
||||
}};
|
||||
}
|
||||
|
||||
/// Does the same thing as the `actions!` macro but wraps everything in:
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn actions(&self) -> ActionList {
|
||||
/// actions!(…)
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! advertise_actions {
|
||||
($($v:expr),* $(,)?) => {
|
||||
fn actions(&self) -> $crate::communication::ActionList {
|
||||
actions!($($v),*)
|
||||
}
|
||||
};
|
||||
($name:ident; $($v:ident),* $(,)?) => {
|
||||
fn actions(&self) -> $crate::communication::ActionList {
|
||||
actions!($name; $($v),*)
|
||||
}
|
||||
}
|
||||
}
|
||||
7
core/editor/src/misc/mod.rs
Normal file
7
core/editor/src/misc/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[macro_use]
|
||||
pub mod macros;
|
||||
pub mod derivable_custom_traits;
|
||||
mod error;
|
||||
|
||||
pub use error::EditorError;
|
||||
pub use macros::*;
|
||||
@@ -1,63 +1,79 @@
|
||||
mod crop;
|
||||
mod ellipse;
|
||||
mod eyedropper;
|
||||
mod line;
|
||||
mod navigate;
|
||||
mod path;
|
||||
mod pen;
|
||||
mod rectangle;
|
||||
mod select;
|
||||
mod shape;
|
||||
pub mod tool_message_handler;
|
||||
pub mod tool_settings;
|
||||
pub mod tools;
|
||||
|
||||
use crate::events::{Event, ModKeys, MouseState, ToolResponse, Trace, TracePoint};
|
||||
use crate::Color;
|
||||
use crate::Document;
|
||||
use crate::EditorError;
|
||||
use document_core::Operation;
|
||||
use std::{collections::HashMap, fmt};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use crate::SvgDocument;
|
||||
use crate::{
|
||||
communication::{message::Message, MessageHandler},
|
||||
Color,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
pub use tool_message_handler::ToolMessageHandler;
|
||||
use tool_settings::ToolSettings;
|
||||
pub use tool_settings::*;
|
||||
use tools::*;
|
||||
|
||||
pub trait Tool {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>);
|
||||
pub mod tool_messages {
|
||||
pub use super::tool_message_handler::{ToolMessage, ToolMessageDiscriminant};
|
||||
pub use super::tools::ellipse::{EllipseMessage, EllipseMessageDiscriminant};
|
||||
pub use super::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant};
|
||||
}
|
||||
|
||||
pub type ToolActionHandlerData<'a> = (&'a SvgDocument, &'a DocumentToolData, &'a InputPreprocessor);
|
||||
|
||||
pub trait Fsm {
|
||||
type ToolData;
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self;
|
||||
|
||||
fn transition(self, message: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, messages: &mut VecDeque<Message>) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentToolData {
|
||||
pub mouse_state: MouseState,
|
||||
pub mod_keys: ModKeys,
|
||||
pub primary_color: Color,
|
||||
pub secondary_color: Color,
|
||||
}
|
||||
|
||||
pub struct ToolData {
|
||||
pub active_tool_type: ToolType,
|
||||
pub tools: HashMap<ToolType, Box<dyn Tool>>,
|
||||
tool_settings: HashMap<ToolType, ToolSettings>,
|
||||
}
|
||||
|
||||
impl ToolData {
|
||||
pub fn active_tool(&mut self) -> Result<&mut Box<dyn Tool>, EditorError> {
|
||||
self.tools.get_mut(&self.active_tool_type).ok_or(EditorError::UnknownTool)
|
||||
type SubToolMessageHandler = dyn for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>>;
|
||||
pub struct ToolData {
|
||||
pub active_tool_type: ToolType,
|
||||
pub tools: HashMap<ToolType, Box<SubToolMessageHandler>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ToolData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ToolData").field("active_tool_type", &self.active_tool_type).field("tool_settings", &"[…]").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolData {
|
||||
pub fn active_tool_mut(&mut self) -> &mut Box<SubToolMessageHandler> {
|
||||
self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized")
|
||||
}
|
||||
pub fn active_tool(&self) -> &SubToolMessageHandler {
|
||||
self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolFsmState {
|
||||
pub document_tool_data: DocumentToolData,
|
||||
pub tool_data: ToolData,
|
||||
pub trace: Trace,
|
||||
}
|
||||
|
||||
impl Default for ToolFsmState {
|
||||
fn default() -> Self {
|
||||
ToolFsmState {
|
||||
trace: Trace::new(),
|
||||
tool_data: ToolData {
|
||||
active_tool_type: ToolType::Select,
|
||||
tools: gen_tools_hash_map! {
|
||||
Rectangle => rectangle::Rectangle,
|
||||
Select => select::Select,
|
||||
Crop => crop::Crop,
|
||||
Navigate => navigate::Navigate,
|
||||
@@ -65,17 +81,14 @@ impl Default for ToolFsmState {
|
||||
Path => path::Path,
|
||||
Pen => pen::Pen,
|
||||
Line => line::Line,
|
||||
Rectangle => rectangle::Rectangle,
|
||||
Ellipse => ellipse::Ellipse,
|
||||
Shape => shape::Shape,
|
||||
Ellipse => ellipse::Ellipse,
|
||||
},
|
||||
tool_settings: default_tool_settings(),
|
||||
},
|
||||
document_tool_data: DocumentToolData {
|
||||
mouse_state: MouseState::default(),
|
||||
mod_keys: ModKeys::default(),
|
||||
primary_color: Color::BLACK,
|
||||
secondary_color: Color::WHITE,
|
||||
tool_settings: default_tool_settings(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -86,13 +99,6 @@ impl ToolFsmState {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn record_trace_point(&mut self) {
|
||||
self.trace.push(TracePoint {
|
||||
mouse_state: self.document_tool_data.mouse_state,
|
||||
mod_keys: self.document_tool_data.mod_keys,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn swap_colors(&mut self) {
|
||||
std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color);
|
||||
}
|
||||
@@ -178,24 +184,3 @@ impl ToolType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ToolSettings {
|
||||
Select { append_mode: SelectAppendMode },
|
||||
Ellipse,
|
||||
Shape { shape: Shape },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum SelectAppendMode {
|
||||
New,
|
||||
Add,
|
||||
Subtract,
|
||||
Intersect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Shape {
|
||||
Star { vertices: u32 },
|
||||
Polygon { vertices: u32 },
|
||||
}
|
||||
101
core/editor/src/tool/tool_message_handler.rs
Normal file
101
core/editor/src/tool/tool_message_handler.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use crate::message_prelude::*;
|
||||
use document_core::color::Color;
|
||||
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::{
|
||||
tool::{ToolFsmState, ToolType},
|
||||
SvgDocument,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[impl_message(Message, Tool)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ToolMessage {
|
||||
SelectTool(ToolType),
|
||||
SelectPrimaryColor(Color),
|
||||
SelectSecondaryColor(Color),
|
||||
SwapColors,
|
||||
ResetColors,
|
||||
#[child]
|
||||
Rectangle(RectangleMessage),
|
||||
#[child]
|
||||
Ellipse(EllipseMessage),
|
||||
#[child]
|
||||
Select(SelectMessage),
|
||||
#[child]
|
||||
Line(LineMessage),
|
||||
#[child]
|
||||
Crop(CropMessage),
|
||||
#[child]
|
||||
Eyedropper(EyedropperMessage),
|
||||
#[child]
|
||||
Navigate(NavigateMessage),
|
||||
#[child]
|
||||
Path(PathMessage),
|
||||
#[child]
|
||||
Pen(PenMessage),
|
||||
#[child]
|
||||
Shape(ShapeMessage),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ToolMessageHandler {
|
||||
tool_state: ToolFsmState,
|
||||
}
|
||||
impl MessageHandler<ToolMessage, (&SvgDocument, &InputPreprocessor)> for ToolMessageHandler {
|
||||
fn process_action(&mut self, message: ToolMessage, data: (&SvgDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
let (document, input) = data;
|
||||
use ToolMessage::*;
|
||||
match message {
|
||||
SelectPrimaryColor(c) => self.tool_state.document_tool_data.primary_color = c,
|
||||
SelectSecondaryColor(c) => self.tool_state.document_tool_data.secondary_color = c,
|
||||
SelectTool(tool) => {
|
||||
let mut reset = |tool| match tool {
|
||||
ToolType::Ellipse => responses.push_back(EllipseMessage::Abort.into()),
|
||||
ToolType::Rectangle => responses.push_back(RectangleMessage::Abort.into()),
|
||||
ToolType::Shape => responses.push_back(ShapeMessage::Abort.into()),
|
||||
ToolType::Line => responses.push_back(LineMessage::Abort.into()),
|
||||
ToolType::Pen => responses.push_back(PenMessage::Abort.into()),
|
||||
_ => (),
|
||||
};
|
||||
reset(tool);
|
||||
reset(self.tool_state.tool_data.active_tool_type);
|
||||
self.tool_state.tool_data.active_tool_type = tool;
|
||||
|
||||
responses.push_back(FrontendMessage::SetActiveTool { tool_name: tool.to_string() }.into())
|
||||
}
|
||||
SwapColors => {
|
||||
let doc_data = &mut self.tool_state.document_tool_data;
|
||||
std::mem::swap(&mut doc_data.primary_color, &mut doc_data.secondary_color);
|
||||
}
|
||||
ResetColors => {
|
||||
let doc_data = &mut self.tool_state.document_tool_data;
|
||||
doc_data.primary_color = Color::WHITE;
|
||||
doc_data.secondary_color = Color::BLACK;
|
||||
}
|
||||
message => {
|
||||
let tool_type = match message {
|
||||
Rectangle(_) => ToolType::Rectangle,
|
||||
Ellipse(_) => ToolType::Ellipse,
|
||||
Shape(_) => ToolType::Shape,
|
||||
Line(_) => ToolType::Line,
|
||||
Pen(_) => ToolType::Pen,
|
||||
Select(_) => ToolType::Select,
|
||||
Crop(_) => ToolType::Crop,
|
||||
Eyedropper(_) => ToolType::Eyedropper,
|
||||
Navigate(_) => ToolType::Navigate,
|
||||
Path(_) => ToolType::Path,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if let Some(tool) = self.tool_state.tool_data.tools.get_mut(&tool_type) {
|
||||
tool.process_action(message, (&document, &self.tool_state.document_tool_data, input), responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, SelectTool);
|
||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||
list
|
||||
}
|
||||
}
|
||||
20
core/editor/src/tool/tool_settings.rs
Normal file
20
core/editor/src/tool/tool_settings.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum ToolSettings {
|
||||
Select { append_mode: SelectAppendMode },
|
||||
Ellipse,
|
||||
Shape { shape: Shape },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum SelectAppendMode {
|
||||
New,
|
||||
Add,
|
||||
Subtract,
|
||||
Intersect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum Shape {
|
||||
Star { vertices: u32 },
|
||||
Polygon { vertices: u32 },
|
||||
}
|
||||
18
core/editor/src/tool/tools/crop.rs
Normal file
18
core/editor/src/tool/tools/crop.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Crop;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Crop)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum CropMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Crop {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
173
core/editor/src/tool/tools/ellipse.rs
Normal file
173
core/editor/src/tool/tools/ellipse.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ellipse {
|
||||
fsm_state: EllipseToolFsmState,
|
||||
data: EllipseToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Ellipse)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum EllipseMessage {
|
||||
Undo,
|
||||
DragStart,
|
||||
DragStop,
|
||||
MouseMove,
|
||||
Abort,
|
||||
Center,
|
||||
UnCenter,
|
||||
LockAspectRatio,
|
||||
UnlockAspectRatio,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Ellipse {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
use EllipseToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(EllipseMessageDiscriminant; Undo, DragStart, Center, UnCenter, LockAspectRatio, UnlockAspectRatio),
|
||||
Dragging => actions!(EllipseMessageDiscriminant; DragStop, Center, UnCenter, LockAspectRatio, UnlockAspectRatio, MouseMove, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum EllipseToolFsmState {
|
||||
Ready,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
impl Default for EllipseToolFsmState {
|
||||
fn default() -> Self {
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct EllipseToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_circle: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for EllipseToolFsmState {
|
||||
type ToolData = EllipseToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
use EllipseMessage::*;
|
||||
use EllipseToolFsmState::*;
|
||||
if let ToolMessage::Ellipse(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
data.drag_start = input.mouse.position;
|
||||
data.drag_current = input.mouse.position;
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(Dragging, Abort) => {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
|
||||
Ready
|
||||
}
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, false, tool_data, data, responses, Dragging),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state_no_op(state: &mut bool, value: bool, new_state: EllipseToolFsmState) -> EllipseToolFsmState {
|
||||
*state = value;
|
||||
new_state
|
||||
}
|
||||
|
||||
fn update_state(
|
||||
state: fn(&mut EllipseToolData) -> &mut bool,
|
||||
value: bool,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut EllipseToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: EllipseToolFsmState,
|
||||
) -> EllipseToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(&data, tool_data));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
if data.constrain_to_circle {
|
||||
let (cx, cy, r) = if data.center_around_cursor {
|
||||
(x0, y0, f64::hypot(x1 - x0, y1 - y0))
|
||||
} else {
|
||||
let diameter = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
let (x2, y2) = (x0 + (x1 - x0).signum() * diameter, y0 + (y1 - y0).signum() * diameter);
|
||||
((x0 + x2) * 0.5, (y0 + y2) * 0.5, diameter * 0.5)
|
||||
};
|
||||
Operation::AddCircle {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
} else {
|
||||
let (cx, cy, r_scale) = if data.center_around_cursor { (x0, y0, 1.0) } else { ((x0 + x1) * 0.5, (y0 + y1) * 0.5, 0.5) };
|
||||
let (rx, ry) = ((x1 - x0).abs() * r_scale, (y1 - y0).abs() * r_scale);
|
||||
Operation::AddEllipse {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
rx,
|
||||
ry,
|
||||
rot: 0.0,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
18
core/editor/src/tool/tools/eyedropper.rs
Normal file
18
core/editor/src/tool/tools/eyedropper.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Eyedropper;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Eyedropper)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum EyedropperMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Eyedropper {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
184
core/editor/src/tool/tools/line.rs
Normal file
184
core/editor/src/tool/tools/line.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Line {
|
||||
fsm_state: LineToolFsmState,
|
||||
data: LineToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Line)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum LineMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
MouseMove,
|
||||
Abort,
|
||||
Center,
|
||||
UnCenter,
|
||||
LockAngle,
|
||||
UnlockAngle,
|
||||
SnapToAngle,
|
||||
UnSnapToAngle,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
use LineToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(LineMessageDiscriminant; DragStart, Center, UnCenter, LockAngle, UnlockAngle, SnapToAngle, UnSnapToAngle),
|
||||
Dragging => actions!(LineMessageDiscriminant; DragStop, MouseMove, Abort, Center, UnCenter, LockAngle, UnlockAngle, SnapToAngle, UnSnapToAngle),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum LineToolFsmState {
|
||||
Ready,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
impl Default for LineToolFsmState {
|
||||
fn default() -> Self {
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct LineToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
angle: f64,
|
||||
snap_angle: bool,
|
||||
lock_angle: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for LineToolFsmState {
|
||||
type ToolData = LineToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
use LineMessage::*;
|
||||
use LineToolFsmState::*;
|
||||
if let ToolMessage::Line(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
data.drag_start = input.mouse.position;
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(Dragging, Abort) => {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
|
||||
Ready
|
||||
}
|
||||
(Ready, LockAngle) => update_state_no_op(&mut data.lock_angle, true, Ready),
|
||||
(Ready, UnlockAngle) => update_state_no_op(&mut data.lock_angle, false, Ready),
|
||||
(Dragging, LockAngle) => update_state(|data| &mut data.lock_angle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAngle) => update_state(|data| &mut data.lock_angle, false, tool_data, data, responses, Dragging),
|
||||
|
||||
(Ready, SnapToAngle) => update_state_no_op(&mut data.snap_angle, true, Ready),
|
||||
(Ready, UnSnapToAngle) => update_state_no_op(&mut data.snap_angle, false, Ready),
|
||||
(Dragging, SnapToAngle) => update_state(|data| &mut data.snap_angle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnSnapToAngle) => update_state(|data| &mut data.snap_angle, false, tool_data, data, responses, Dragging),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state_no_op(state: &mut bool, value: bool, new_state: LineToolFsmState) -> LineToolFsmState {
|
||||
*state = value;
|
||||
new_state
|
||||
}
|
||||
|
||||
fn update_state(
|
||||
state: fn(&mut LineToolData) -> &mut bool,
|
||||
value: bool,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut LineToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: LineToolFsmState,
|
||||
) -> LineToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (dx, dy) = (x1 - x0, y1 - y0);
|
||||
let mut angle = f64::atan2(dx, dy);
|
||||
|
||||
if data.lock_angle {
|
||||
angle = data.angle
|
||||
};
|
||||
|
||||
if data.snap_angle {
|
||||
let snap_resolution = 12.0;
|
||||
angle = (angle * snap_resolution / PI).round() / snap_resolution * PI;
|
||||
}
|
||||
|
||||
data.angle = angle;
|
||||
|
||||
let (dir_x, dir_y) = (f64::sin(angle), f64::cos(angle));
|
||||
let projected_length = dx * dir_x + dy * dir_y;
|
||||
let (x1, y1) = (x0 + dir_x * projected_length, y0 + dir_y * projected_length);
|
||||
|
||||
let (x0, y0) = if data.center_around_cursor { (x0 - (x1 - x0), y0 - (y1 - y0)) } else { (x0, y0) };
|
||||
|
||||
Operation::AddLine {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), None),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
13
core/editor/src/tool/tools/mod.rs
Normal file
13
core/editor/src/tool/tools/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// already implemented
|
||||
pub mod ellipse;
|
||||
pub mod line;
|
||||
pub mod pen;
|
||||
pub mod rectangle;
|
||||
pub mod shape;
|
||||
|
||||
// not implemented yet
|
||||
pub mod crop;
|
||||
pub mod eyedropper;
|
||||
pub mod navigate;
|
||||
pub mod path;
|
||||
pub mod select;
|
||||
18
core/editor/src/tool/tools/navigate.rs
Normal file
18
core/editor/src/tool/tools/navigate.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Navigate;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Navigate)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum NavigateMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
18
core/editor/src/tool/tools/path.rs
Normal file
18
core/editor/src/tool/tools/path.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Path;
|
||||
|
||||
#[impl_message(Message, ToolMessage, Path)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum PathMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
130
core/editor/src/tool/tools/pen.rs
Normal file
130
core/editor/src/tool/tools/pen.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pen {
|
||||
fsm_state: PenToolFsmState,
|
||||
data: PenToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Pen)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum PenMessage {
|
||||
Undo,
|
||||
DragStart,
|
||||
DragStop,
|
||||
MouseMove,
|
||||
Confirm,
|
||||
Abort,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PenToolFsmState {
|
||||
Ready,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Pen {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
use PenToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(PenMessageDiscriminant; Undo, DragStart, DragStop, Confirm, Abort),
|
||||
Dragging => actions!(PenMessageDiscriminant; DragStop, MouseMove, Confirm, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PenToolFsmState {
|
||||
fn default() -> Self {
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PenToolData {
|
||||
points: Vec<ViewportPosition>,
|
||||
next_point: ViewportPosition,
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
use PenMessage::*;
|
||||
use PenToolFsmState::*;
|
||||
if let ToolMessage::Pen(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
|
||||
data.points.push(input.mouse.position);
|
||||
data.next_point = input.mouse.position;
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.points.last() != Some(&input.mouse.position) {
|
||||
data.points.push(input.mouse.position);
|
||||
data.next_point = input.mouse.position;
|
||||
}
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data, true));
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.next_point = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data, true));
|
||||
|
||||
Dragging
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(Dragging, Confirm) => {
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
|
||||
if data.points.len() >= 2 {
|
||||
responses.push_back(make_operation(data, tool_data, false));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
}
|
||||
|
||||
data.points.clear();
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, Abort) => {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
data.points.clear();
|
||||
|
||||
Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message {
|
||||
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x as f64, p.y as f64)).collect();
|
||||
if show_preview {
|
||||
points.push((data.next_point.x as f64, data.next_point.y as f64))
|
||||
}
|
||||
Operation::AddPen {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), Some(style::Fill::none())),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
171
core/editor/src/tool/tools/rectangle.rs
Normal file
171
core/editor/src/tool/tools/rectangle.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Rectangle {
|
||||
fsm_state: RectangleToolFsmState,
|
||||
data: RectangleToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Rectangle)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum RectangleMessage {
|
||||
DragStart,
|
||||
DragStop,
|
||||
MouseMove,
|
||||
Abort,
|
||||
Center,
|
||||
UnCenter,
|
||||
LockAspectRatio,
|
||||
UnlockAspectRatio,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Rectangle {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
use RectangleToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(RectangleMessageDiscriminant; DragStart, Center, UnCenter, LockAspectRatio, UnlockAspectRatio),
|
||||
Dragging => actions!(RectangleMessageDiscriminant; DragStop, Center, UnCenter, LockAspectRatio, UnlockAspectRatio, MouseMove, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum RectangleToolFsmState {
|
||||
Ready,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
impl Default for RectangleToolFsmState {
|
||||
fn default() -> Self {
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct RectangleToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_square: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for RectangleToolFsmState {
|
||||
type ToolData = RectangleToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
use RectangleMessage::*;
|
||||
use RectangleToolFsmState::*;
|
||||
if let ToolMessage::Rectangle(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
data.drag_start = input.mouse.position;
|
||||
data.drag_current = input.mouse.position;
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(Dragging, Abort) => {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
|
||||
Ready
|
||||
}
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state_no_op(state: &mut bool, value: bool, new_state: RectangleToolFsmState) -> RectangleToolFsmState {
|
||||
*state = value;
|
||||
new_state
|
||||
}
|
||||
|
||||
fn update_state(
|
||||
state: fn(&mut RectangleToolData) -> &mut bool,
|
||||
value: bool,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut RectangleToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: RectangleToolFsmState,
|
||||
) -> RectangleToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (x0, y0, x1, y1) = if data.constrain_to_square {
|
||||
let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum());
|
||||
let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
if data.center_around_cursor {
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
} else {
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
}
|
||||
} else {
|
||||
let (x0, y0) = if data.center_around_cursor {
|
||||
let delta_x = x1 - x0;
|
||||
let delta_y = y1 - y0;
|
||||
|
||||
(x0 - delta_x, y0 - delta_y)
|
||||
} else {
|
||||
(x0, y0)
|
||||
};
|
||||
(x0, y0, x1, y1)
|
||||
};
|
||||
|
||||
Operation::AddRect {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
60
core/editor/src/tool/tools/select.rs
Normal file
60
core/editor/src/tool/tools/select.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Select {
|
||||
fsm_state: SelectToolFsmState,
|
||||
data: SelectToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Select)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum SelectMessage {
|
||||
MouseMove,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Select {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum SelectToolFsmState {
|
||||
Ready,
|
||||
}
|
||||
|
||||
impl Default for SelectToolFsmState {
|
||||
fn default() -> Self {
|
||||
SelectToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SelectToolData;
|
||||
|
||||
impl Fsm for SelectToolFsmState {
|
||||
type ToolData = SelectToolData;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_document: &SvgDocument,
|
||||
_tool_data: &DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
_input: &InputPreprocessor,
|
||||
_responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use SelectMessage::*;
|
||||
use SelectToolFsmState::*;
|
||||
if let ToolMessage::Select(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, MouseMove) => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
175
core/editor/src/tool/tools/shape.rs
Normal file
175
core/editor/src/tool/tools/shape.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Shape {
|
||||
fsm_state: ShapeToolFsmState,
|
||||
data: ShapeToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Shape)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum ShapeMessage {
|
||||
Undo,
|
||||
DragStart,
|
||||
DragStop,
|
||||
MouseMove,
|
||||
Abort,
|
||||
Center,
|
||||
UnCenter,
|
||||
LockAspectRatio,
|
||||
UnlockAspectRatio,
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Shape {
|
||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
use ShapeToolFsmState::*;
|
||||
match self.fsm_state {
|
||||
Ready => actions!(ShapeMessageDiscriminant; Undo, DragStart, Center, UnCenter, LockAspectRatio, UnlockAspectRatio),
|
||||
Dragging => actions!(ShapeMessageDiscriminant; DragStop, Center, UnCenter, LockAspectRatio, UnlockAspectRatio, MouseMove, Abort),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ShapeToolFsmState {
|
||||
Ready,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
impl Default for ShapeToolFsmState {
|
||||
fn default() -> Self {
|
||||
ShapeToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ShapeToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_square: bool,
|
||||
center_around_cursor: bool,
|
||||
sides: u8,
|
||||
}
|
||||
|
||||
impl Fsm for ShapeToolFsmState {
|
||||
type ToolData = ShapeToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
use ShapeMessage::*;
|
||||
use ShapeToolFsmState::*;
|
||||
if let ToolMessage::Shape(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
data.drag_start = input.mouse.position;
|
||||
data.drag_current = input.mouse.position;
|
||||
|
||||
data.sides = 6;
|
||||
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
Ready
|
||||
}
|
||||
(Dragging, Abort) => {
|
||||
responses.push_back(Operation::DiscardWorkingFolder.into());
|
||||
|
||||
Ready
|
||||
}
|
||||
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_state_no_op(state: &mut bool, value: bool, new_state: ShapeToolFsmState) -> ShapeToolFsmState {
|
||||
*state = value;
|
||||
new_state
|
||||
}
|
||||
|
||||
fn update_state(
|
||||
state: fn(&mut ShapeToolData) -> &mut bool,
|
||||
value: bool,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut ShapeToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: ShapeToolFsmState,
|
||||
) -> ShapeToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (x0, y0, x1, y1) = if data.constrain_to_square {
|
||||
let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum());
|
||||
let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
if data.center_around_cursor {
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
} else {
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
}
|
||||
} else {
|
||||
let (x0, y0) = if data.center_around_cursor {
|
||||
let delta_x = x1 - x0;
|
||||
let delta_y = y1 - y0;
|
||||
|
||||
(x0 - delta_x, y0 - delta_y)
|
||||
} else {
|
||||
(x0, y0)
|
||||
};
|
||||
(x0, y0, x1, y1)
|
||||
};
|
||||
|
||||
Operation::AddShape {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
sides: data.sides,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::tools::Tool;
|
||||
use crate::Document;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Crop;
|
||||
|
||||
impl Tool for Crop {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?}", module_path!(), event, document, tool_data)
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::events::{Key, ViewportPosition};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
use document_core::layers::style;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ellipse {
|
||||
fsm_state: EllipseToolFsmState,
|
||||
data: EllipseToolData,
|
||||
}
|
||||
|
||||
impl Tool for Ellipse {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum EllipseToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
}
|
||||
|
||||
impl Default for EllipseToolFsmState {
|
||||
fn default() -> Self {
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct EllipseToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_circle: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for EllipseToolFsmState {
|
||||
type ToolData = EllipseToolData;
|
||||
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(EllipseToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||
data.drag_start = mouse_state.position;
|
||||
data.drag_current = mouse_state.position;
|
||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||
EllipseToolFsmState::LmbDown
|
||||
}
|
||||
(EllipseToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => {
|
||||
if let Some(id) = document.root.list_layers().last() {
|
||||
operations.push(Operation::DeleteLayer { path: vec![*id] })
|
||||
}
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
(EllipseToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||
data.drag_current = *mouse_state;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
|
||||
EllipseToolFsmState::LmbDown
|
||||
}
|
||||
(EllipseToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||
data.drag_current = mouse_state.position;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
operations.push(make_operation(data, tool_data));
|
||||
operations.push(Operation::CommitTransaction);
|
||||
}
|
||||
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(EllipseToolFsmState::LmbDown, Event::KeyUp(Key::KeyEscape)) | (EllipseToolFsmState::LmbDown, Event::RmbDown(_)) => {
|
||||
operations.push(Operation::DiscardWorkingFolder);
|
||||
|
||||
EllipseToolFsmState::Ready
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyShift)) => {
|
||||
data.constrain_to_circle = true;
|
||||
|
||||
if state == EllipseToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyShift)) => {
|
||||
data.constrain_to_circle = false;
|
||||
|
||||
if state == EllipseToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = true;
|
||||
|
||||
if state == EllipseToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = false;
|
||||
|
||||
if state == EllipseToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Operation {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
if data.constrain_to_circle {
|
||||
let (cx, cy, r) = if data.center_around_cursor {
|
||||
(x0, y0, f64::hypot(x1 - x0, y1 - y0))
|
||||
} else {
|
||||
let diameter = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
let (x2, y2) = (x0 + (x1 - x0).signum() * diameter, y0 + (y1 - y0).signum() * diameter);
|
||||
((x0 + x2) * 0.5, (y0 + y2) * 0.5, diameter * 0.5)
|
||||
};
|
||||
Operation::AddCircle {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
} else {
|
||||
let (cx, cy, r_scale) = if data.center_around_cursor { (x0, y0, 1.0) } else { ((x0 + x1) * 0.5, (y0 + y1) * 0.5, 0.5) };
|
||||
let (rx, ry) = ((x1 - x0).abs() * r_scale, (y1 - y0).abs() * r_scale);
|
||||
Operation::AddEllipse {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
rx,
|
||||
ry,
|
||||
rot: 0.0,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::tools::Tool;
|
||||
use crate::Document;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Eyedropper;
|
||||
|
||||
impl Tool for Eyedropper {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?}", module_path!(), event, document, tool_data)
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::events::{Key, ViewportPosition};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
use document_core::layers::style;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Line {
|
||||
fsm_state: LineToolFsmState,
|
||||
data: LineToolData,
|
||||
}
|
||||
|
||||
impl Tool for Line {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum LineToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
}
|
||||
|
||||
impl Default for LineToolFsmState {
|
||||
fn default() -> Self {
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct LineToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
angle: f64,
|
||||
snap_angle: bool,
|
||||
lock_angle: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for LineToolFsmState {
|
||||
type ToolData = LineToolData;
|
||||
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(LineToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||
data.drag_start = mouse_state.position;
|
||||
data.drag_current = mouse_state.position;
|
||||
|
||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||
|
||||
LineToolFsmState::LmbDown
|
||||
}
|
||||
(LineToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => {
|
||||
if let Some(id) = document.root.list_layers().last() {
|
||||
operations.push(Operation::DeleteLayer { path: vec![*id] })
|
||||
}
|
||||
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
(LineToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||
data.drag_current = *mouse_state;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
|
||||
LineToolFsmState::LmbDown
|
||||
}
|
||||
(LineToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||
data.drag_current = mouse_state.position;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
operations.push(make_operation(data, tool_data));
|
||||
operations.push(Operation::CommitTransaction);
|
||||
}
|
||||
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(LineToolFsmState::LmbDown, Event::KeyUp(Key::KeyEscape)) | (LineToolFsmState::LmbDown, Event::RmbDown(_)) => {
|
||||
operations.push(Operation::DiscardWorkingFolder);
|
||||
|
||||
LineToolFsmState::Ready
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyShift)) => {
|
||||
data.snap_angle = true;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyShift)) => {
|
||||
data.snap_angle = false;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyControl)) => {
|
||||
data.lock_angle = true;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyControl)) => {
|
||||
data.lock_angle = false;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = true;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = false;
|
||||
|
||||
if state == LineToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Operation {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (dx, dy) = (x1 - x0, y1 - y0);
|
||||
let mut angle = f64::atan2(dx, dy);
|
||||
|
||||
if data.lock_angle {
|
||||
angle = data.angle
|
||||
};
|
||||
|
||||
if data.snap_angle {
|
||||
let snap_resolution = 12.0;
|
||||
angle = (angle * snap_resolution / PI).round() / snap_resolution * PI;
|
||||
}
|
||||
|
||||
data.angle = angle;
|
||||
|
||||
let (dir_x, dir_y) = (f64::sin(angle), f64::cos(angle));
|
||||
let projected_length = dx * dir_x + dy * dir_y;
|
||||
let (x1, y1) = (x0 + dir_x * projected_length, y0 + dir_y * projected_length);
|
||||
|
||||
let (x0, y0) = if data.center_around_cursor { (x0 - (x1 - x0), y0 - (y1 - y0)) } else { (x0, y0) };
|
||||
|
||||
Operation::AddLine {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), None),
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::tools::Tool;
|
||||
use crate::Document;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Navigate;
|
||||
|
||||
impl Tool for Navigate {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?}", module_path!(), event, document, tool_data)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::tools::Tool;
|
||||
use crate::Document;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Path;
|
||||
|
||||
impl Tool for Path {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?}", module_path!(), event, document, tool_data)
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::events::{Key, ViewportPosition};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
|
||||
use document_core::layers::style;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pen {
|
||||
fsm_state: PenToolFsmState,
|
||||
data: PenToolData,
|
||||
}
|
||||
|
||||
impl Tool for Pen {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum PenToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
}
|
||||
|
||||
impl Default for PenToolFsmState {
|
||||
fn default() -> Self {
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PenToolData {
|
||||
points: Vec<ViewportPosition>,
|
||||
next_point: ViewportPosition,
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(PenToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||
|
||||
data.points.push(mouse_state.position);
|
||||
data.next_point = mouse_state.position;
|
||||
|
||||
PenToolFsmState::LmbDown
|
||||
}
|
||||
(PenToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => {
|
||||
if let Some(id) = document.root.list_layers().last() {
|
||||
operations.push(Operation::DeleteLayer { path: vec![*id] })
|
||||
}
|
||||
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
(PenToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.points.last() != Some(&mouse_state.position) {
|
||||
data.points.push(mouse_state.position);
|
||||
data.next_point = mouse_state.position;
|
||||
}
|
||||
|
||||
PenToolFsmState::LmbDown
|
||||
}
|
||||
(PenToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||
data.next_point = *mouse_state;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data, true));
|
||||
|
||||
PenToolFsmState::LmbDown
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(PenToolFsmState::LmbDown, Event::KeyDown(Key::KeyEnter)) | (PenToolFsmState::LmbDown, Event::KeyDown(Key::KeyEscape)) | (PenToolFsmState::LmbDown, Event::RmbDown(_)) => {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
|
||||
if data.points.len() >= 2 {
|
||||
operations.push(make_operation(data, tool_data, false));
|
||||
operations.push(Operation::CommitTransaction);
|
||||
} else {
|
||||
operations.push(Operation::DiscardWorkingFolder);
|
||||
}
|
||||
|
||||
data.points.clear();
|
||||
|
||||
PenToolFsmState::Ready
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Operation {
|
||||
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x as f64, p.y as f64)).collect();
|
||||
if show_preview {
|
||||
points.push((data.next_point.x as f64, data.next_point.y as f64))
|
||||
}
|
||||
Operation::AddPen {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), Some(style::Fill::none())),
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::events::{Key, ViewportPosition};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
use document_core::layers::style;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Rectangle {
|
||||
fsm_state: RectangleToolFsmState,
|
||||
data: RectangleToolData,
|
||||
}
|
||||
|
||||
impl Tool for Rectangle {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum RectangleToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
}
|
||||
|
||||
impl Default for RectangleToolFsmState {
|
||||
fn default() -> Self {
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct RectangleToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_square: bool,
|
||||
center_around_cursor: bool,
|
||||
}
|
||||
|
||||
impl Fsm for RectangleToolFsmState {
|
||||
type ToolData = RectangleToolData;
|
||||
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(RectangleToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||
data.drag_start = mouse_state.position;
|
||||
data.drag_current = mouse_state.position;
|
||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||
RectangleToolFsmState::LmbDown
|
||||
}
|
||||
(RectangleToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => {
|
||||
if let Some(id) = document.root.list_layers().last() {
|
||||
operations.push(Operation::DeleteLayer { path: vec![*id] })
|
||||
}
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
(RectangleToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||
data.drag_current = *mouse_state;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
|
||||
RectangleToolFsmState::LmbDown
|
||||
}
|
||||
(RectangleToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||
data.drag_current = mouse_state.position;
|
||||
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
operations.push(make_operation(data, tool_data));
|
||||
operations.push(Operation::CommitTransaction);
|
||||
}
|
||||
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(RectangleToolFsmState::LmbDown, Event::KeyUp(Key::KeyEscape)) | (RectangleToolFsmState::LmbDown, Event::RmbDown(_)) => {
|
||||
operations.push(Operation::DiscardWorkingFolder);
|
||||
|
||||
RectangleToolFsmState::Ready
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyShift)) => {
|
||||
data.constrain_to_square = true;
|
||||
|
||||
if state == RectangleToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyShift)) => {
|
||||
data.constrain_to_square = false;
|
||||
|
||||
if state == RectangleToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = true;
|
||||
|
||||
if state == RectangleToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = false;
|
||||
|
||||
if state == RectangleToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Operation {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (x0, y0, x1, y1) = if data.constrain_to_square {
|
||||
let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum());
|
||||
let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
if data.center_around_cursor {
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
} else {
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
}
|
||||
} else {
|
||||
let (x0, y0) = if data.center_around_cursor {
|
||||
let delta_x = x1 - x0;
|
||||
let delta_y = y1 - y0;
|
||||
|
||||
(x0 - delta_x, y0 - delta_y)
|
||||
} else {
|
||||
(x0, y0)
|
||||
};
|
||||
(x0, y0, x1, y1)
|
||||
};
|
||||
|
||||
Operation::AddRect {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Select {
|
||||
fsm_state: SelectToolFsmState,
|
||||
data: SelectToolData,
|
||||
}
|
||||
|
||||
impl Tool for Select {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum SelectToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
TransformSelected,
|
||||
}
|
||||
|
||||
impl Default for SelectToolFsmState {
|
||||
fn default() -> Self {
|
||||
SelectToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SelectToolData;
|
||||
|
||||
impl Fsm for SelectToolFsmState {
|
||||
type ToolData = SelectToolData;
|
||||
|
||||
fn transition(self, event: &Event, _document: &Document, _tool_data: &DocumentToolData, _data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, _operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(SelectToolFsmState::Ready, Event::LmbDown(_mouse_state)) => SelectToolFsmState::LmbDown,
|
||||
|
||||
(SelectToolFsmState::LmbDown, Event::LmbUp(_mouse_state)) => SelectToolFsmState::Ready,
|
||||
|
||||
(SelectToolFsmState::LmbDown, Event::MouseMove(_mouse_state)) => SelectToolFsmState::TransformSelected,
|
||||
|
||||
(SelectToolFsmState::TransformSelected, Event::MouseMove(_mouse_state)) => self,
|
||||
|
||||
(SelectToolFsmState::TransformSelected, Event::LmbUp(_mouse_state)) => SelectToolFsmState::Ready,
|
||||
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
use crate::events::{Event, ToolResponse};
|
||||
use crate::events::{Key, ViewportPosition};
|
||||
use crate::tools::{Fsm, Tool};
|
||||
use crate::Document;
|
||||
use document_core::layers::style;
|
||||
use document_core::Operation;
|
||||
|
||||
use super::DocumentToolData;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Shape {
|
||||
fsm_state: ShapeToolFsmState,
|
||||
data: ShapeToolData,
|
||||
}
|
||||
|
||||
impl Tool for Shape {
|
||||
fn handle_input(&mut self, event: &Event, document: &Document, tool_data: &DocumentToolData) -> (Vec<ToolResponse>, Vec<Operation>) {
|
||||
let mut responses = Vec::new();
|
||||
let mut operations = Vec::new();
|
||||
self.fsm_state = self.fsm_state.transition(event, document, tool_data, &mut self.data, &mut responses, &mut operations);
|
||||
|
||||
(responses, operations)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ShapeToolFsmState {
|
||||
Ready,
|
||||
LmbDown,
|
||||
}
|
||||
|
||||
impl Default for ShapeToolFsmState {
|
||||
fn default() -> Self {
|
||||
ShapeToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ShapeToolData {
|
||||
drag_start: ViewportPosition,
|
||||
drag_current: ViewportPosition,
|
||||
constrain_to_square: bool,
|
||||
center_around_cursor: bool,
|
||||
sides: u8,
|
||||
}
|
||||
|
||||
impl Fsm for ShapeToolFsmState {
|
||||
type ToolData = ShapeToolData;
|
||||
|
||||
fn transition(self, event: &Event, document: &Document, tool_data: &DocumentToolData, data: &mut Self::ToolData, _responses: &mut Vec<ToolResponse>, operations: &mut Vec<Operation>) -> Self {
|
||||
match (self, event) {
|
||||
(ShapeToolFsmState::Ready, Event::LmbDown(mouse_state)) => {
|
||||
data.drag_start = mouse_state.position;
|
||||
data.drag_current = mouse_state.position;
|
||||
|
||||
data.sides = 6;
|
||||
|
||||
operations.push(Operation::MountWorkingFolder { path: vec![] });
|
||||
ShapeToolFsmState::LmbDown
|
||||
}
|
||||
(ShapeToolFsmState::Ready, Event::KeyDown(Key::KeyZ)) => {
|
||||
if let Some(id) = document.root.list_layers().last() {
|
||||
operations.push(Operation::DeleteLayer { path: vec![*id] })
|
||||
}
|
||||
ShapeToolFsmState::Ready
|
||||
}
|
||||
(ShapeToolFsmState::LmbDown, Event::MouseMove(mouse_state)) => {
|
||||
data.drag_current = *mouse_state;
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
|
||||
ShapeToolFsmState::LmbDown
|
||||
}
|
||||
(ShapeToolFsmState::LmbDown, Event::LmbUp(mouse_state)) => {
|
||||
data.drag_current = mouse_state.position;
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
operations.push(make_operation(data, tool_data));
|
||||
operations.push(Operation::CommitTransaction);
|
||||
}
|
||||
|
||||
ShapeToolFsmState::Ready
|
||||
}
|
||||
// TODO - simplify with or_patterns when rust 1.53.0 is stable (https://github.com/rust-lang/rust/issues/54883)
|
||||
(ShapeToolFsmState::LmbDown, Event::KeyUp(Key::KeyEscape)) | (ShapeToolFsmState::LmbDown, Event::RmbDown(_)) => {
|
||||
operations.push(Operation::DiscardWorkingFolder);
|
||||
|
||||
ShapeToolFsmState::Ready
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyShift)) => {
|
||||
data.constrain_to_square = true;
|
||||
|
||||
if state == ShapeToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyShift)) => {
|
||||
data.constrain_to_square = false;
|
||||
|
||||
if state == ShapeToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyDown(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = true;
|
||||
|
||||
if state == ShapeToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(state, Event::KeyUp(Key::KeyAlt)) => {
|
||||
data.center_around_cursor = false;
|
||||
|
||||
if state == ShapeToolFsmState::LmbDown {
|
||||
operations.push(Operation::ClearWorkingFolder);
|
||||
operations.push(make_operation(data, tool_data));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Operation {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (x0, y0, x1, y1) = if data.constrain_to_square {
|
||||
let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum());
|
||||
let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
if data.center_around_cursor {
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
} else {
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
}
|
||||
} else {
|
||||
let (x0, y0) = if data.center_around_cursor {
|
||||
let delta_x = x1 - x0;
|
||||
let delta_y = y1 - y0;
|
||||
|
||||
(x0 - delta_x, y0 - delta_y)
|
||||
} else {
|
||||
(x0, y0)
|
||||
};
|
||||
(x0, y0, x1, y1)
|
||||
};
|
||||
|
||||
Operation::AddShape {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
sides: data.sides,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub type PanelId = u32;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Workspace {
|
||||
pub hovered_panel: PanelId,
|
||||
pub root: PanelGroup,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
pub fn new() -> Workspace {
|
||||
Workspace {
|
||||
hovered_panel: 0,
|
||||
root: PanelGroup::new(),
|
||||
}
|
||||
}
|
||||
// add panel / panel group
|
||||
// delete panel / panel group
|
||||
// move panel / panel group
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PanelGroup {
|
||||
pub contents: Vec<Contents>,
|
||||
pub layout_direction: LayoutDirection,
|
||||
}
|
||||
|
||||
impl Default for PanelGroup {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PanelGroup {
|
||||
fn new() -> PanelGroup {
|
||||
PanelGroup {
|
||||
contents: vec![],
|
||||
layout_direction: LayoutDirection::Horizontal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum Contents {
|
||||
PanelArea(PanelArea),
|
||||
Group(PanelGroup),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PanelArea {
|
||||
pub panels: Vec<PanelId>,
|
||||
pub active: PanelId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum LayoutDirection {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
Reference in New Issue
Block a user