diff --git a/client/web/.eslintrc.js b/client/web/.eslintrc.js
index 879500821..04749764d 100644
--- a/client/web/.eslintrc.js
+++ b/client/web/.eslintrc.js
@@ -21,7 +21,7 @@ module.exports = {
},
},
rules: {
- indent: ["error", "tab"],
+ indent: ["error", "tab", { SwitchCase: 1 }],
quotes: ["error", "double"],
"linebreak-style": ["error", "unix"],
"eol-last": ["error", "always"],
@@ -29,7 +29,7 @@ module.exports = {
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"max-len": ["error", { code: 200, tabWidth: 4 }],
"@typescript-eslint/camelcase": "off",
- camelcase: ["error", { ignoreImports: true, ignoreDestructuring: true }],
+ camelcase: ["error", { allow: ["^(?:[a-z]+_)*[a-z]+$"] }],
"prettier-vue/prettier": [
"error",
{
diff --git a/client/web/src/components/panels/Document.vue b/client/web/src/components/panels/Document.vue
index 4c9887c71..31fee8210 100644
--- a/client/web/src/components/panels/Document.vue
+++ b/client/web/src/components/panels/Document.vue
@@ -180,7 +180,7 @@
diff --git a/client/web/src/response-handler.ts b/client/web/src/response-handler.ts
index 45be02395..b9e6c2465 100644
--- a/client/web/src/response-handler.ts
+++ b/client/web/src/response-handler.ts
@@ -1,4 +1,4 @@
-type ResponseCallback = (responseData: string) => void;
+type ResponseCallback = (responseData: Response) => void;
type ResponseMap = {
[response: string]: ResponseCallback | undefined;
};
@@ -9,10 +9,10 @@ declare global {
}
export enum ResponseType {
- "Tool::UpdateCanvas" = "Tool::UpdateCanvas",
- "Document::ExpandFolder" = "Document::ExpandFolder",
- "Document::CollapseFolder" = "Document::CollapseFolder",
- "Tool::SetActiveTool" = "Tool::SetActiveTool",
+ UpdateCanvas = "UpdateCanvas",
+ ExpandFolder = "ExpandFolder",
+ CollapseFolder = "CollapseFolder",
+ SetActiveTool = "SetActiveTool",
}
export function attachResponseHandlerToPage() {
@@ -24,12 +24,82 @@ export function registerResponseHandler(responseType: ResponseType, callback: Re
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export function handleResponse(responseType: ResponseType, responseData: any) {
- const callback = window.responseMap[responseType];
+function parseResponse(origin: string, responseType: string, data: any): Response {
+ type OriginNames = "Document" | "Tool";
- if (callback) {
- callback(responseData);
+ const originHandlers = {
+ Document: () => {
+ switch (responseType) {
+ case "DocumentChanged":
+ return (data.Document.DocumentChanged as DocumentChanged) as Response;
+ case "CollapseFolder":
+ return (data.Document.CollapseFolder as CollapseFolder) as Response;
+ case "ExpandFolder":
+ return (data.Document.ExpandFolder as ExpandFolder) as Response;
+ default:
+ return undefined;
+ }
+ },
+ Tool: () => {
+ switch (responseType) {
+ case "SetActiveTool":
+ return (data.Tool.SetActiveTool as SetActiveTool) as Response;
+ case "UpdateCanvas":
+ return (data.Tool.UpdateCanvas as UpdateCanvas) as Response;
+ default:
+ return undefined;
+ }
+ },
+ };
+
+ // TODO: Optional chaining would be nice here when we can upgrade to Webpack 5: https://github.com/webpack/webpack/issues/10227
+ // const response = originHandlers[origin as OriginNames]?.();
+ const response = originHandlers[origin as OriginNames] && originHandlers[origin as OriginNames]();
+ if (!response) throw new Error("ResponseType not recognized.");
+ return response;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function handleResponse(responseIdentifier: string, responseData: any) {
+ const [origin, responesType] = responseIdentifier.split("::", 2);
+ const callback = window.responseMap[responesType];
+ const data = parseResponse(origin, responesType, responseData);
+
+ if (callback && data) {
+ callback(data);
+ } else if (data) {
+ console.error(`Received a Response of type "${responseIdentifier}" but no handler was registered for it from the client.`);
} else {
- console.error(`Received a Response of type "${responseType}" but no handler was registered for it from the client.`);
+ console.error(`Received a Response of type "${responseIdentifier}" but but was not able to parse the data.`);
}
}
+
+export type Response = SetActiveTool | UpdateCanvas | DocumentChanged | CollapseFolder | ExpandFolder;
+
+export interface SetActiveTool {
+ tool_name: string;
+}
+export interface UpdateCanvas {
+ document: string;
+}
+export type DocumentChanged = {};
+export interface CollapseFolder {
+ path: Array;
+}
+export interface ExpandFolder {
+ path: Array;
+ children: Array;
+}
+
+export interface LayerPanelEntry {
+ name: string;
+ visible: boolean;
+ layer_type: LayerType;
+ collapsed: boolean;
+ path: Array;
+}
+
+export enum LayerType {
+ Folder,
+ Shape,
+}
diff --git a/client/web/wasm/src/document.rs b/client/web/wasm/src/document.rs
index 58c08985a..f0187490b 100644
--- a/client/web/wasm/src/document.rs
+++ b/client/web/wasm/src/document.rs
@@ -1,7 +1,7 @@
use crate::shims::Error;
use crate::wrappers::{translate_key, translate_tool, Color};
use crate::EDITOR_STATE;
-use editor_core::events;
+use editor_core::{events, LayerId};
use wasm_bindgen::prelude::*;
fn convert_error(err: editor_core::EditorError) -> JsValue {
@@ -32,7 +32,14 @@ mod mouse_state {
(false, 1) => Event::LmbUp(state),
(false, 2) => Event::RmbUp(state),
(false, 4) => Event::MmbUp(state),
- _ => panic!("two buttons where modified at the same time. modification: {:#010b}", diff),
+ (down, _) => {
+ log::warn!("two buttons where modified at the same time. Modification: {:#010b}", diff);
+ if down {
+ Event::AmbiguousMouseDown(state)
+ } else {
+ Event::AmbiguousMouseUp(state)
+ }
+ }
}
}
}
@@ -118,3 +125,45 @@ pub fn swap_colors() -> Result<(), JsValue> {
pub fn reset_colors() -> Result<(), JsValue> {
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::ResetColors)).map_err(convert_error)
}
+
+/// Select a layer from the layer list
+#[wasm_bindgen]
+pub fn select_layer(path: Vec) -> Result<(), JsValue> {
+ EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::SelectLayer(path))).map_err(convert_error)
+}
+
+/// Toggle visibility of a layer from the layer list
+#[wasm_bindgen]
+pub fn toggle_layer_visibility(path: Vec) -> Result<(), JsValue> {
+ EDITOR_STATE
+ .with(|editor| editor.borrow_mut().handle_event(events::Event::ToggleLayerVisibility(path)))
+ .map_err(convert_error)
+}
+
+/// Toggle expansions state of a layer from the layer list
+#[wasm_bindgen]
+pub fn toggle_layer_expansion(path: Vec) -> Result<(), JsValue> {
+ EDITOR_STATE
+ .with(|editor| editor.borrow_mut().handle_event(events::Event::ToggleLayerExpansion(path)))
+ .map_err(convert_error)
+}
+
+/// Renames a layer from the layer list
+#[wasm_bindgen]
+pub fn rename_layer(path: Vec, new_name: String) -> Result<(), JsValue> {
+ EDITOR_STATE
+ .with(|editor| editor.borrow_mut().handle_event(events::Event::RenameLayer(path, new_name)))
+ .map_err(convert_error)
+}
+
+/// Deletes a layer from the layer list
+#[wasm_bindgen]
+pub fn delete_layer(path: Vec) -> Result<(), JsValue> {
+ EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::DeleteLayer(path))).map_err(convert_error)
+}
+
+/// Requests the backend to add a layer to the layer list
+#[wasm_bindgen]
+pub fn add_layer(path: Vec) -> Result<(), JsValue> {
+ EDITOR_STATE.with(|editor| editor.borrow_mut().handle_event(events::Event::AddLayer(path))).map_err(convert_error)
+}
diff --git a/core/document/src/document.rs b/core/document/src/document.rs
index 7e5799f46..4a128d26f 100644
--- a/core/document/src/document.rs
+++ b/core/document/src/document.rs
@@ -1,6 +1,6 @@
use crate::{
layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, PolyLine, Rect, Shape},
- response::{LayerPanelEntry, LayerType},
+ response::LayerPanelEntry,
DocumentError, DocumentResponse, LayerId, Operation,
};
@@ -172,16 +172,12 @@ impl Document {
/// any actual data, but rather metadata such as visibility and names of the layers.
pub fn layer_panel(&self, path: &[LayerId]) -> Result, DocumentError> {
let folder = self.document_folder(path)?;
- let l_type = |layer: &LayerDataTypes| match layer {
- LayerDataTypes::Folder(_) => LayerType::Folder,
- _ => LayerType::Shape,
- };
- let translate = |layer: &Layer| LayerPanelEntry {
- name: layer.name.clone().unwrap_or_else(|| String::from("UnnamedFolder")),
- visible: layer.visible,
- layer_type: l_type(&layer.data),
- };
- let entries = folder.layers().iter().map(|layer| translate(layer)).collect();
+ let entries = folder
+ .layers()
+ .iter()
+ .zip(folder.layer_ids.iter())
+ .map(|(layer, id)| LayerPanelEntry::from_layer(layer, [path, &[*id]].concat()))
+ .collect();
Ok(entries)
}
diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs
index 71896e2ca..e47978afc 100644
--- a/core/document/src/layers/folder.rs
+++ b/core/document/src/layers/folder.rs
@@ -9,6 +9,7 @@ pub struct Folder {
next_assignment_id: LayerId,
pub layer_ids: Vec,
layers: Vec,
+ pub collapsed: bool,
}
impl LayerData for Folder {
@@ -87,6 +88,7 @@ impl Default for Folder {
layer_ids: vec![],
layers: vec![],
next_assignment_id: 0,
+ collapsed: false,
}
}
}
diff --git a/core/document/src/response.rs b/core/document/src/response.rs
index 2aa0a5917..31d85eba5 100644
--- a/core/document/src/response.rs
+++ b/core/document/src/response.rs
@@ -1,4 +1,7 @@
-use crate::LayerId;
+use crate::{
+ layers::{Layer, LayerDataTypes},
+ LayerId,
+};
use serde::{Deserialize, Serialize};
use std::fmt;
@@ -7,12 +10,19 @@ pub struct LayerPanelEntry {
pub name: String,
pub visible: bool,
pub layer_type: LayerType,
+ pub collapsed: bool,
+ pub path: Vec,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LayerType {
Folder,
Shape,
+ Circle,
+ Rect,
+ Line,
+ PolyLine,
+ Ellipse,
}
impl fmt::Display for LayerType {
@@ -20,12 +30,47 @@ impl fmt::Display for LayerType {
let name = match self {
LayerType::Folder => "folder",
LayerType::Shape => "shape",
+ LayerType::Rect => "rect",
+ LayerType::Line => "line",
+ LayerType::Circle => "circle",
+ LayerType::PolyLine => "poly line",
+ LayerType::Ellipse => "ellipse",
};
formatter.write_str(name)
}
}
+impl From<&LayerDataTypes> for LayerType {
+ fn from(data: &LayerDataTypes) -> Self {
+ use LayerDataTypes::*;
+ match data {
+ Folder(_) => LayerType::Folder,
+ Shape(_) => LayerType::Shape,
+ Circle(_) => LayerType::Circle,
+ Rect(_) => LayerType::Rect,
+ Line(_) => LayerType::Line,
+ PolyLine(_) => LayerType::PolyLine,
+ Ellipse(_) => LayerType::Ellipse,
+ }
+ }
+}
+
+impl LayerPanelEntry {
+ pub fn from_layer(layer: &Layer, path: Vec) -> Self {
+ let layer_type: LayerType = (&layer.data).into();
+ let name = layer.name.clone().unwrap_or_else(|| format!("Unnamed {}", layer_type));
+ let collapsed = if let LayerDataTypes::Folder(f) = &layer.data { f.collapsed } else { true };
+ Self {
+ name,
+ visible: layer.visible,
+ layer_type,
+ collapsed,
+ path,
+ }
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[repr(C)]
// TODO - Make Copy when possible
diff --git a/core/document/src/shape_points.rs b/core/document/src/shape_points.rs
index 3ff34122f..21258394b 100644
--- a/core/document/src/shape_points.rs
+++ b/core/document/src/shape_points.rs
@@ -1,7 +1,6 @@
use std::{fmt, ops::Add};
use kurbo::{PathEl, Point, Vec2};
-use log::info;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShapePoints {
@@ -47,7 +46,6 @@ impl std::fmt::Display for ShapePoints {
let sine = theta.sin();
Vec2::new(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine)
}
- info!("sides{}", self.sides);
for i in 0..self.sides {
let radians = self.apothem_offset_angle() * ((i * 2 + (self.sides % 2)) as f64);
let offset = rotate(&self.extent, radians);
diff --git a/core/editor/src/dispatcher/document_event_handler.rs b/core/editor/src/dispatcher/document_event_handler.rs
new file mode 100644
index 000000000..c2504096a
--- /dev/null
+++ b/core/editor/src/dispatcher/document_event_handler.rs
@@ -0,0 +1,9 @@
+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, responses: &mut Vec, operations: &mut Vec) {}
+}
diff --git a/core/editor/src/dispatcher/events.rs b/core/editor/src/dispatcher/events.rs
index e6428e503..8fde796f4 100644
--- a/core/editor/src/dispatcher/events.rs
+++ b/core/editor/src/dispatcher/events.rs
@@ -2,6 +2,7 @@ use crate::tools::ToolType;
use crate::Color;
use bitflags::bitflags;
+use document_core::LayerId;
use serde::{Deserialize, Serialize};
#[doc(inline)]
@@ -18,8 +19,16 @@ pub enum Event {
SelectTool(ToolType),
SelectPrimaryColor(Color),
SelectSecondaryColor(Color),
+ SelectLayer(Vec),
+ ToggleLayerVisibility(Vec),
+ ToggleLayerExpansion(Vec),
+ DeleteLayer(Vec),
+ AddLayer(Vec),
+ RenameLayer(Vec, String),
SwapColors,
ResetColors,
+ AmbiguousMouseDown(MouseState),
+ AmbiguousMouseUp(MouseState),
LmbDown(MouseState),
RmbDown(MouseState),
MmbDown(MouseState),
@@ -34,6 +43,7 @@ pub enum Event {
#[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 },
}
diff --git a/core/editor/src/dispatcher/global_event_handler.rs b/core/editor/src/dispatcher/global_event_handler.rs
new file mode 100644
index 000000000..ec7144725
--- /dev/null
+++ b/core/editor/src/dispatcher/global_event_handler.rs
@@ -0,0 +1,15 @@
+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, responses: &mut Vec, operations: &mut Vec) -> bool {
+ false
+ }
+}
diff --git a/core/editor/src/dispatcher/mod.rs b/core/editor/src/dispatcher/mod.rs
index d275e4570..84cced139 100644
--- a/core/editor/src/dispatcher/mod.rs
+++ b/core/editor/src/dispatcher/mod.rs
@@ -90,6 +90,7 @@ impl Dispatcher {
_ => (),
}
}
+ _ => todo!("Implement layer handling"),
}
let (mut tool_responses, operations) = editor_state
diff --git a/core/editor/src/workspace/mod.rs b/core/editor/src/workspace/mod.rs
index eedb8a832..c792f232f 100644
--- a/core/editor/src/workspace/mod.rs
+++ b/core/editor/src/workspace/mod.rs
@@ -1,5 +1,8 @@
-pub type PanelId = usize;
+use serde::{Deserialize, Serialize};
+pub type PanelId = u32;
+
+#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Workspace {
pub hovered_panel: PanelId,
pub root: PanelGroup,
@@ -15,14 +18,20 @@ impl Workspace {
// add panel / panel group
// delete panel / panel group
// move panel / panel group
- // get_serialized_layout()
}
+#[derive(Debug, Serialize, Deserialize)]
pub struct PanelGroup {
pub contents: Vec,
pub layout_direction: LayoutDirection,
}
+impl Default for PanelGroup {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl PanelGroup {
fn new() -> PanelGroup {
PanelGroup {
@@ -32,16 +41,19 @@ impl PanelGroup {
}
}
+#[derive(Debug, Serialize, Deserialize)]
pub enum Contents {
PanelArea(PanelArea),
Group(PanelGroup),
}
+#[derive(Debug, Serialize, Deserialize)]
pub struct PanelArea {
pub panels: Vec,
pub active: PanelId,
}
+#[derive(Debug, Serialize, Deserialize)]
pub enum LayoutDirection {
Horizontal,
Vertical,