Add FSM and related tool plumbing (Fix #54) (#62)

- Added FSM trait
- Added FSM to select and ellipse tools
- Moved circle stamping code to ellipse tool
This commit is contained in:
George Atkinson 2021-04-05 23:57:33 +01:00 committed by Keavon Chambers
parent 273aa652df
commit 0d6d6fb81b
20 changed files with 272 additions and 199 deletions

1
Cargo.lock generated
View File

@ -49,6 +49,7 @@ name = "graphite-document-core"
version = "0.1.0"
dependencies = [
"kurbo",
"log",
]
[[package]]

View File

@ -605,95 +605,6 @@
"tslint": "^5.20.1",
"webpack": "^4.0.0",
"yorkie": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"@vue/cli-plugin-vuex": {
@ -5475,6 +5386,95 @@
}
}
},
"fork-ts-checker-webpack-plugin-v5": {
"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
"dev": true,
"optional": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@types/json-schema": "^7.0.5",
"chalk": "^4.1.0",
"cosmiconfig": "^6.0.0",
"deepmerge": "^4.2.2",
"fs-extra": "^9.0.0",
"memfs": "^3.1.2",
"minimatch": "^3.0.4",
"schema-utils": "2.7.0",
"semver": "^7.3.2",
"tapable": "^1.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"optional": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
@ -7655,9 +7655,9 @@
"dev": true
},
"memfs": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz",
"integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.1.tgz",
"integrity": "sha512-Y5vcpQzWTime4fBTr/fEnxXUxEYUgKbDlty1WX0gaa4ae14I6KmvK1S8HtXOX0elKAE6ENZJctkGtbTFYcRIUw==",
"dev": true,
"optional": true,
"requires": {

View File

@ -18,7 +18,7 @@ pub fn select_tool(tool: String) -> Result<(), JsValue> {
#[wasm_bindgen]
pub fn on_mouse_move(x: u32, y: u32) -> Result<(), JsValue> {
// TODO: Convert these screenspace viewport coordinates to canvas coordinates based on the current zoom and pan
let ev = events::Event::MouseMovement(events::ViewportPosition { x, y });
let ev = events::Event::MouseMove(events::ViewportPosition { x, y });
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(ev)).map_err(|err| Error::new(&err.to_string()).into())
}

View File

@ -9,4 +9,5 @@ repository = "https://github.com/GraphiteEditor/Graphite"
license = "Apache-2.0"
[dependencies]
log = "0.4"
kurbo = "0.8.0"

View File

@ -27,4 +27,17 @@ impl Document {
pub fn render(&self) -> String {
self.svg.iter().map(|element| element.render()).collect::<Vec<_>>().join("\n")
}
pub fn handle_operation<F: Fn(String)>(&mut self, operation: &Operation, update_frontend: F) {
match *operation {
Operation::AddCircle { cx, cy, r } => {
self.svg.push(SvgElement::Circle(Circle {
center: Point { x: cx, y: cy },
radius: r,
}));
update_frontend(self.render());
}
}
}
}

View File

@ -1,3 +1,3 @@
pub enum Operation {
AddCircle((f64, f64), f64),
AddCircle { cx: f64, cy: f64, r: f64 },
}

View File

@ -13,14 +13,14 @@ pub enum Event {
ResetColors,
MouseDown(MouseState),
MouseUp(MouseState),
MouseMovement(ViewportPosition),
ModifierKeyDown(ModKeys),
ModifierKeyUp(ModKeys),
KeyPress(Key),
MouseMove(ViewportPosition),
KeyUp(Key),
KeyDown(Key),
}
#[derive(Debug, Clone)]
#[repr(C)]
// TODO - Make Copy when possible
pub enum Response {
UpdateCanvas { document: String },
}

View File

@ -1,6 +1,6 @@
pub mod events;
use crate::{Color, EditorError, EditorState};
use document_core::{Circle, Point, SvgElement};
use crate::{Color, Document, EditorError, EditorState};
use document_core::Operation;
use events::{Event, Response};
pub type Callback = Box<dyn Fn(Response)>;
@ -9,82 +9,69 @@ pub struct Dispatcher {
}
impl Dispatcher {
pub fn handle_event(&self, state: &mut EditorState, event: Event) -> Result<(), EditorError> {
pub fn handle_event(&self, editor_state: &mut EditorState, event: &Event) -> Result<(), EditorError> {
log::trace!("{:?}", event);
match event {
Event::SelectTool(tool_type) => {
state.tool_state.active_tool_type = tool_type;
Ok(())
editor_state.tool_state.active_tool_type = *tool_type;
}
Event::SelectPrimaryColor(color) => {
state.tool_state.primary_color = color;
Ok(())
editor_state.tool_state.primary_color = *color;
}
Event::SelectSecondaryColor(color) => {
state.tool_state.secondary_color = color;
Ok(())
editor_state.tool_state.secondary_color = *color;
}
Event::SwapColors => {
std::mem::swap(&mut state.tool_state.primary_color, &mut state.tool_state.secondary_color);
Ok(())
std::mem::swap(&mut editor_state.tool_state.primary_color, &mut editor_state.tool_state.secondary_color);
}
Event::ResetColors => {
state.tool_state.primary_color = Color::BLACK;
state.tool_state.secondary_color = Color::WHITE;
Ok(())
editor_state.tool_state.primary_color = Color::BLACK;
editor_state.tool_state.secondary_color = Color::WHITE;
}
Event::MouseDown(mouse_state) => {
state.tool_state.mouse_state = mouse_state;
state.tool_state.active_tool()?.handle_input(event);
Ok(())
editor_state.tool_state.mouse_state = *mouse_state;
}
Event::MouseUp(mouse_state) => {
state.tool_state.mouse_state = mouse_state;
state.document.svg.push(SvgElement::Circle(Circle {
center: Point {
x: mouse_state.position.x as f64,
y: mouse_state.position.y as f64,
},
radius: 10.0,
}));
self.emit_response(Response::UpdateCanvas { document: state.document.render() });
state.tool_state.active_tool()?.handle_input(event);
Ok(())
editor_state.tool_state.mouse_state = *mouse_state;
}
Event::MouseMovement(pos) => {
state.tool_state.mouse_state.position = pos;
state.tool_state.active_tool()?.handle_input(event);
Ok(())
Event::MouseMove(pos) => {
editor_state.tool_state.mouse_state.position = *pos;
}
Event::ModifierKeyDown(mod_keys) => {
state.tool_state.mod_keys = mod_keys;
state.tool_state.active_tool()?.handle_input(event);
Event::KeyUp(key) => todo!(),
Event::KeyDown(key) => todo!(),
}
Ok(())
}
Event::ModifierKeyUp(mod_keys) => {
state.tool_state.mod_keys = mod_keys;
state.tool_state.active_tool()?.handle_input(event);
let (responses, operations) = editor_state.tool_state.active_tool()?.handle_input(event, &editor_state.document);
Ok(())
}
Event::KeyPress(key) => todo!(),
self.dispatch_operations(&mut editor_state.document, &operations);
// TODO - Dispatch Responses
Ok(())
}
fn dispatch_operations(&self, document: &mut Document, operations: &[Operation]) {
for operation in operations {
self.dispatch_operation(document, operation);
}
}
pub fn emit_response(&self, response: Response) {
fn dispatch_operation(&self, document: &mut Document, operation: &Operation) {
document.handle_operation(operation, |svg: String| {
self.dispatch_response(Response::UpdateCanvas { document: svg });
});
}
pub fn dispatch_responses(&self, responses: &[Response]) {
for response in responses {
// TODO - Remove clone when Response is Copy
self.dispatch_response(response.clone());
}
}
pub fn dispatch_response(&self, response: Response) {
let func = &self.callback;
// TODO - Remove clone if possible
func(response)
}

View File

@ -48,6 +48,6 @@ impl Editor {
}
pub fn handle_event(&mut self, event: events::Event) -> Result<(), EditorError> {
self.dispatcher.handle_event(&mut self.state, event)
self.dispatcher.handle_event(&mut self.state, &event)
}
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Crop;
impl Tool for Crop {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -1,12 +1,54 @@
use crate::events::Event;
use crate::tools::Tool;
use crate::events::MouseKeys;
use crate::events::{Event, Response};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Ellipse;
pub struct Ellipse {
state: EllipseToolState,
}
impl Tool for Ellipse {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
todo!();
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.state = self.state.transition(event, document, &mut responses, &mut operations);
(responses, operations)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EllipseToolState {
Ready,
LmbDown,
TransformSelected,
}
impl Default for EllipseToolState {
fn default() -> Self {
EllipseToolState::Ready
}
}
impl Fsm for EllipseToolState {
fn transition(self, event: &Event, document: &Document, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(EllipseToolState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => EllipseToolState::LmbDown,
// TODO - Check for left mouse button
(EllipseToolState::LmbDown, Event::MouseUp(mouse_state)) => {
operations.push(Operation::AddCircle {
cx: mouse_state.position.x as f64,
cy: mouse_state.position.y as f64,
r: 10.0,
});
EllipseToolState::Ready
}
_ => self,
}
}
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Line;
impl Tool for Line {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -9,14 +9,19 @@ mod sample;
mod select;
mod shape;
use crate::events::{Event, ModKeys, MouseState, Trace, TracePoint};
use crate::events::{Event, ModKeys, MouseState, Response, Trace, TracePoint};
use crate::Color;
use crate::Document;
use crate::EditorError;
use document_core::Operation;
use std::collections::HashMap;
pub trait Tool {
fn handle_input(&mut self, event: Event) -> Vec<Operation>;
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>);
}
pub trait Fsm {
fn transition(self, event: &Event, document: &Document, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self;
}
pub struct ToolState {
@ -38,7 +43,7 @@ impl Default for ToolState {
trace: Trace::new(),
primary_color: Color::BLACK,
secondary_color: Color::WHITE,
active_tool_type: ToolType::Select,
active_tool_type: ToolType::Ellipse,
tools: gen_tools_hash_map! {
Select => select::Select,
Crop => crop::Crop,

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Navigate;
impl Tool for Navigate {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Path;
impl Tool for Path {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Pen;
impl Tool for Pen {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -1,12 +1,18 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Rectangle;
impl Tool for Rectangle {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}
enum RectangleToolState {
Ready,
Dragging,
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Sample;
impl Tool for Sample {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}

View File

@ -1,40 +1,51 @@
use crate::events::Event;
use crate::events::MouseKeys;
use crate::tools::Tool;
use crate::events::{Event, Response};
use crate::tools::{Fsm, Tool};
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Select(Fsm);
pub struct Select {
state: SelectToolState,
}
impl Tool for Select {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
match event {
Event::MouseDown(state) => {
if state.mouse_keys.contains(MouseKeys::LEFT) {
self.0 = Fsm::LmbDown;
}
}
Event::MouseUp(state) => {
if self.0 == Fsm::LmbDown && state.mouse_keys.contains(MouseKeys::LEFT) {
self.0 = Fsm::SelectedObject;
}
}
_ => {}
}
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
let mut responses = Vec::new();
let mut operations = Vec::new();
self.state = self.state.transition(event, document, &mut responses, &mut operations);
Vec::new()
(responses, operations)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Fsm {
enum SelectToolState {
Ready,
LmbDown,
SelectedObject,
TransformSelected,
}
impl Default for Fsm {
impl Default for SelectToolState {
fn default() -> Self {
Fsm::Ready
SelectToolState::Ready
}
}
impl Fsm for SelectToolState {
fn transition(self, event: &Event, document: &Document, responses: &mut Vec<Response>, operations: &mut Vec<Operation>) -> Self {
match (self, event) {
(SelectToolState::Ready, Event::MouseDown(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => SelectToolState::LmbDown,
(SelectToolState::LmbDown, Event::MouseUp(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => SelectToolState::Ready,
(SelectToolState::LmbDown, Event::MouseMove(mouse_state)) => SelectToolState::TransformSelected,
(SelectToolState::TransformSelected, Event::MouseMove(mouse_state)) => self,
(SelectToolState::TransformSelected, Event::MouseUp(mouse_state)) if mouse_state.mouse_keys.contains(MouseKeys::LEFT) => SelectToolState::Ready,
_ => self,
}
}
}

View File

@ -1,12 +1,13 @@
use crate::events::Event;
use crate::events::{Event, Response};
use crate::tools::Tool;
use crate::Document;
use document_core::Operation;
#[derive(Default)]
pub struct Shape;
impl Tool for Shape {
fn handle_input(&mut self, event: Event) -> Vec<Operation> {
fn handle_input(&mut self, event: &Event, document: &Document) -> (Vec<Response>, Vec<Operation>) {
todo!();
}
}