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

* Fix two-axis scrollbars in scrollable regions on Firefox

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

* Port the Layer Tree options bar layout to the backend

* Port the tool shelf to the backend

* Clean up initialization and wasm wrapper

* Fix crash

* Fix missing document bar

* Remove unused functions in api.rs

* Code review

* Tool initalisation

* Remove some frontend functions

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

* Remove blend_mode and opacity from LayerPanelEntry

Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2022-05-17 13:12:52 -07:00
co-authored by 0hypercube
parent 95435d8bf1
commit a8f09da5a2
50 changed files with 1034 additions and 978 deletions
+18 -102
View File
@@ -2,17 +2,13 @@
// It serves as a thin wrapper over the editor backend API that relies
// on the dispatcher messaging system and more complex Rust data types.
use crate::helpers::Error;
use crate::type_translators::{translate_blend_mode, translate_key, translate_tool_type};
use crate::helpers::{translate_key, Error};
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES, JS_EDITOR_HANDLES};
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
use editor::input::input_preprocessor::ModifierKeys;
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
use editor::message_prelude::*;
use editor::misc::EditorError;
use editor::viewport_tools::tool::ToolType;
use editor::viewport_tools::tools;
use editor::Color;
use editor::Editor;
use editor::LayerId;
@@ -96,19 +92,6 @@ impl JsEditorHandle {
self.dispatch(WorkspaceMessage::NodeGraphToggleVisibility);
}
/// Modify the currently selected tool in the document state store
pub fn select_tool(&self, tool: String) -> Result<(), JsValue> {
match translate_tool_type(&tool) {
Some(tool_type) => {
let message = ToolMessage::ActivateTool { tool_type };
self.dispatch(message);
Ok(())
}
None => Err(Error::new(&format!("Couldn't select {} because it was not recognized as a valid tool", tool)).into()),
}
}
/// Update layout of a given UI
pub fn update_layout(&self, layout_target: JsValue, widget_id: u64, value: JsValue) -> Result<(), JsValue> {
match (from_value(layout_target), from_value(value)) {
@@ -121,29 +104,6 @@ impl JsEditorHandle {
}
}
/// Send a message to a given tool
pub fn send_tool_message(&self, tool: String, message: &JsValue) -> Result<(), JsValue> {
let tool_message = match translate_tool_type(&tool) {
Some(tool) => match tool {
ToolType::Select => match serde_wasm_bindgen::from_value::<tools::select_tool::SelectToolMessage>(message.clone()) {
Ok(select_message) => Ok(ToolMessage::Select(select_message)),
Err(err) => Err(Error::new(&format!("Invalid message for {}: {}", tool, err)).into()),
},
_ => Err(Error::new(&format!("Tool message sending not implemented for {}", tool)).into()),
},
None => Err(Error::new(&format!("Couldn't send message for {} because it was not recognized as a valid tool", tool)).into()),
};
match tool_message {
Ok(message) => {
self.dispatch(message);
Ok(())
}
Err(err) => Err(err),
}
}
pub fn select_document(&self, document_id: u64) {
let message = PortfolioMessage::SelectDocument { document_id };
self.dispatch(message);
@@ -442,12 +402,6 @@ impl JsEditorHandle {
self.dispatch(message);
}
/// Delete all selected layers
pub fn delete_selected_layers(&self) {
let message = DocumentMessage::DeleteSelectedLayers;
self.dispatch(message);
}
/// Reorder selected layer
pub fn reorder_selected_layers(&self, relative_index_offset: isize) {
let message = DocumentMessage::ReorderSelectedLayers { relative_index_offset };
@@ -470,25 +424,6 @@ impl JsEditorHandle {
self.dispatch(message);
}
/// Set the blend mode for the selected layers
pub fn set_blend_mode_for_selected_layers(&self, blend_mode_svg_style_name: String) -> Result<(), JsValue> {
if let Some(blend_mode) = translate_blend_mode(blend_mode_svg_style_name.as_str()) {
let message = DocumentMessage::SetBlendModeForSelectedLayers { blend_mode };
self.dispatch(message);
Ok(())
} else {
Err(Error::new(&EditorError::Misc("UnknownBlendMode".to_string()).to_string()).into())
}
}
/// Set the opacity for the selected layers
pub fn set_opacity_for_selected_layers(&self, opacity_percent: f64) {
let opacity = opacity_percent / 100.;
let message = DocumentMessage::SetOpacityForSelectedLayers { opacity };
self.dispatch(message);
}
/// Export the document
pub fn export_document(&self) {
let message = DialogMessage::RequestExportDialog;
@@ -507,13 +442,6 @@ impl JsEditorHandle {
self.dispatch(message);
}
/// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX
pub fn select_layers(&self, paths: Vec<LayerId>) {
let replacement_selected_layers = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect();
let message = DocumentMessage::SetSelectedLayers { replacement_selected_layers };
self.dispatch(message);
}
/// Sends the blob url generated by js
pub fn set_image_blob_url(&self, path: Vec<LayerId>, blob_url: String, width: f64, height: f64) {
let dimensions = (width, height);
@@ -540,29 +468,14 @@ impl JsEditorHandle {
self.dispatch(message);
}
/// Renames a layer from the layer list
pub fn rename_layer(&self, layer_path: Vec<LayerId>, new_name: String) {
let message = DocumentMessage::RenameLayer { layer_path, new_name };
// TODO: Replace with initialization system, issue #524
pub fn init_app(&self) {
let message = PortfolioMessage::UpdateDocumentWidgets;
self.dispatch(message);
}
/// Deletes a layer from the layer list
pub fn delete_layer(&self, layer_path: Vec<LayerId>) {
let message = DocumentMessage::DeleteLayer { layer_path };
let message = ToolMessage::InitTools;
self.dispatch(message);
}
/// Creates an empty folder at the document root
pub fn create_empty_folder(&self) {
let message = DocumentMessage::CreateEmptyFolder { container_path: vec![] };
self.dispatch(message);
}
// TODO(mfish33): Replace with initialization system Issue:#524
pub fn init_document_bar(&self) {
let message = PortfolioMessage::UpdateDocumentBar;
self.dispatch(message)
}
}
// Needed to make JsEditorHandle functions pub to rust. Do not fully
@@ -579,10 +492,11 @@ impl Drop for JsEditorHandle {
}
}
/// Access a handle to WASM memory
/// Set the random seed used by the editor by calling this from JS upon initialization.
/// This is necessary because WASM doesn't have a random number generator.
#[wasm_bindgen]
pub fn wasm_memory() -> JsValue {
wasm_bindgen::memory()
pub fn set_random_seed(seed: u64) {
editor::communication::set_uuid_seed(seed)
}
/// Intentionally panic for debugging purposes
@@ -591,17 +505,24 @@ pub fn intentional_panic() {
panic!();
}
/// Get the constant FILE_SAVE_SUFFIX
/// Access a handle to WASM memory
#[wasm_bindgen]
pub fn wasm_memory() -> JsValue {
wasm_bindgen::memory()
}
/// Get the constant `FILE_SAVE_SUFFIX`
#[wasm_bindgen]
pub fn file_save_suffix() -> String {
FILE_SAVE_SUFFIX.into()
}
/// Get the constant FILE_SAVE_SUFFIX
/// Get the constant `GRAPHITE_DOCUMENT_VERSION`
#[wasm_bindgen]
pub fn graphite_version() -> String {
GRAPHITE_DOCUMENT_VERSION.to_string()
}
/// Get the constant `i32::MAX`
#[wasm_bindgen]
pub fn i32_max() -> i32 {
@@ -613,8 +534,3 @@ pub fn i32_max() -> i32 {
pub fn i32_min() -> i32 {
i32::MIN
}
#[wasm_bindgen]
pub fn set_random_seed(seed: u64) {
editor::communication::set_uuid_seed(seed)
}
+129 -11
View File
@@ -1,5 +1,27 @@
use crate::JS_EDITOR_HANDLES;
use editor::{input::keyboard::Key, message_prelude::FrontendMessage};
use std::panic;
use wasm_bindgen::prelude::*;
/// When a panic occurs, notify the user and log the error to the JS console before the backend dies
pub fn panic_hook(info: &panic::PanicInfo) {
let panic_info = info.to_string();
let title = "The editor crashed — sorry about that".to_string();
let description = "An internal error occurred. Reload the editor to continue. Please report this by filing an issue on GitHub.".to_string();
log::error!("{}", info);
JS_EDITOR_HANDLES.with(|instances| {
instances.borrow_mut().values_mut().for_each(|instance| {
instance.handle_response_rust_proxy(FrontendMessage::DisplayDialogPanic {
panic_info: panic_info.clone(),
title: title.clone(),
description: description.clone(),
})
})
});
}
/// The JavaScript `Error` type
#[wasm_bindgen]
extern "C" {
@@ -10,15 +32,111 @@ extern "C" {
pub fn new(msg: &str) -> Error;
}
/// Takes a string and matches it to its equivalently-named enum variant (useful for simple type translations)
macro_rules! match_string_to_enum {
(match ($e:expr) {$($var:ident),* $(,)?}) => {
match $e {
$(
stringify!($var) => Some($var),
)*
_ => None
}
};
/// Logging to the JS console
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn info(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str, format: &str);
}
#[derive(Default)]
pub struct WasmLog;
impl log::Log for WasmLog {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
let (log, name, color): (fn(&str, &str), &str, &str) = match record.level() {
log::Level::Trace => (log, "trace", "color:plum"),
log::Level::Debug => (log, "debug", "color:cyan"),
log::Level::Warn => (warn, "warn", "color:goldenrod"),
log::Level::Info => (info, "info", "color:mediumseagreen"),
log::Level::Error => (error, "error", "color:red"),
};
let msg = &format!("%c{}\t{}", name, record.args());
log(msg, color)
}
fn flush(&self) {}
}
/// Translate a keyboard key from its JS name to its Rust `Key` enum
pub fn translate_key(name: &str) -> Key {
use Key::*;
log::trace!("Key event received: {}", name);
match name.to_lowercase().as_str() {
"a" => KeyA,
"b" => KeyB,
"c" => KeyC,
"d" => KeyD,
"e" => KeyE,
"f" => KeyF,
"g" => KeyG,
"h" => KeyH,
"i" => KeyI,
"j" => KeyJ,
"k" => KeyK,
"l" => KeyL,
"m" => KeyM,
"n" => KeyN,
"o" => KeyO,
"p" => KeyP,
"q" => KeyQ,
"r" => KeyR,
"s" => KeyS,
"t" => KeyT,
"u" => KeyU,
"v" => KeyV,
"w" => KeyW,
"x" => KeyX,
"y" => KeyY,
"z" => KeyZ,
"0" => Key0,
"1" => Key1,
"2" => Key2,
"3" => Key3,
"4" => Key4,
"5" => Key5,
"6" => Key6,
"7" => Key7,
"8" => Key8,
"9" => Key9,
"enter" => KeyEnter,
"=" => KeyEquals,
"+" => KeyPlus,
"-" => KeyMinus,
"shift" => KeyShift,
// When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps
"capslock" => KeyShift,
" " => KeySpace,
"control" => KeyControl,
"delete" => KeyDelete,
"backspace" => KeyBackspace,
"alt" => KeyAlt,
"escape" => KeyEscape,
"tab" => KeyTab,
"arrowup" => KeyArrowUp,
"arrowdown" => KeyArrowDown,
"arrowleft" => KeyArrowLeft,
"arrowright" => KeyArrowRight,
"[" => KeyLeftBracket,
"]" => KeyRightBracket,
"{" => KeyLeftCurlyBracket,
"}" => KeyRightCurlyBracket,
"pageup" => KeyPageUp,
"pagedown" => KeyPageDown,
"," => KeyComma,
"." => KeyPeriod,
_ => UnknownKey,
}
}
pub(crate) use match_string_to_enum;
+2 -23
View File
@@ -1,11 +1,7 @@
pub mod api;
pub mod helpers;
pub mod logging;
pub mod type_translators;
use editor::message_prelude::*;
use logging::WasmLog;
use helpers::{panic_hook, WasmLog};
use std::cell::RefCell;
use std::collections::HashMap;
use std::panic;
@@ -20,7 +16,7 @@ thread_local! {
pub static JS_EDITOR_HANDLES: RefCell<HashMap<u64, api::JsEditorHandle>> = RefCell::new(HashMap::new());
}
// Initialize the backend
/// Initialize the backend
#[wasm_bindgen(start)]
pub fn init() {
panic::set_hook(Box::new(panic_hook));
@@ -28,20 +24,3 @@ pub fn init() {
log::set_logger(&LOGGER).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Debug);
}
// When a panic occurs, close up shop before the backend dies
fn panic_hook(info: &panic::PanicInfo) {
let panic_info = info.to_string();
let title = "The editor crashed — sorry about that".to_string();
let description = "An internal error occurred. Reload the editor to continue. Please report this by filing an issue on GitHub.".to_string();
log::error!("{}", info);
JS_EDITOR_HANDLES.with(|instances| {
instances.borrow_mut().values_mut().for_each(|instance| {
instance.handle_response_rust_proxy(FrontendMessage::DisplayDialogPanic {
panic_info: panic_info.clone(),
title: title.clone(),
description: description.clone(),
})
})
});
}
-36
View File
@@ -1,36 +0,0 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn info(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str, format: &str);
}
#[derive(Default)]
pub struct WasmLog;
impl log::Log for WasmLog {
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::Level::Info
}
fn log(&self, record: &log::Record) {
let (log, name, color): (fn(&str, &str), &str, &str) = match record.level() {
log::Level::Trace => (log, "trace", "color:plum"),
log::Level::Debug => (log, "debug", "color:cyan"),
log::Level::Warn => (warn, "warn", "color:goldenrod"),
log::Level::Info => (info, "info", "color:mediumseagreen"),
log::Level::Error => (error, "error", "color:red"),
};
let msg = &format!("%c{}\t{}", name, record.args());
log(msg, color)
}
fn flush(&self) {}
}
-131
View File
@@ -1,131 +0,0 @@
use crate::helpers::match_string_to_enum;
use editor::input::keyboard::Key;
use editor::viewport_tools::tool::ToolType;
use graphene::layers::blend_mode::BlendMode;
pub fn translate_tool_type(name: &str) -> Option<ToolType> {
use ToolType::*;
match_string_to_enum!(match (name) {
Select,
Artboard,
Navigate,
Eyedropper,
Text,
Fill,
Gradient,
Brush,
Heal,
Clone,
Patch,
Detail,
Relight,
Path,
Pen,
Freehand,
Spline,
Line,
Rectangle,
Ellipse,
Shape
})
}
pub fn translate_blend_mode(blend_mode_svg_style_name: &str) -> Option<BlendMode> {
use BlendMode::*;
let blend_mode = match blend_mode_svg_style_name {
"Normal" => Normal,
"Multiply" => Multiply,
"Darken" => Darken,
"ColorBurn" => ColorBurn,
"Screen" => Screen,
"Lighten" => Lighten,
"ColorDodge" => ColorDodge,
"Overlay" => Overlay,
"SoftLight" => SoftLight,
"HardLight" => HardLight,
"Difference" => Difference,
"Exclusion" => Exclusion,
"Hue" => Hue,
"Saturation" => Saturation,
"Color" => Color,
"Luminosity" => Luminosity,
_ => return None,
};
Some(blend_mode)
}
pub fn translate_key(name: &str) -> Key {
use Key::*;
log::trace!("Key event received: {}", name);
match name.to_lowercase().as_str() {
"a" => KeyA,
"b" => KeyB,
"c" => KeyC,
"d" => KeyD,
"e" => KeyE,
"f" => KeyF,
"g" => KeyG,
"h" => KeyH,
"i" => KeyI,
"j" => KeyJ,
"k" => KeyK,
"l" => KeyL,
"m" => KeyM,
"n" => KeyN,
"o" => KeyO,
"p" => KeyP,
"q" => KeyQ,
"r" => KeyR,
"s" => KeyS,
"t" => KeyT,
"u" => KeyU,
"v" => KeyV,
"w" => KeyW,
"x" => KeyX,
"y" => KeyY,
"z" => KeyZ,
"0" => Key0,
"1" => Key1,
"2" => Key2,
"3" => Key3,
"4" => Key4,
"5" => Key5,
"6" => Key6,
"7" => Key7,
"8" => Key8,
"9" => Key9,
"enter" => KeyEnter,
"=" => KeyEquals,
"+" => KeyPlus,
"-" => KeyMinus,
"shift" => KeyShift,
// When using linux + chrome + the neo keyboard layout, the shift key is recognized as caps
"capslock" => KeyShift,
" " => KeySpace,
"control" => KeyControl,
"delete" => KeyDelete,
"backspace" => KeyBackspace,
"alt" => KeyAlt,
"escape" => KeyEscape,
"tab" => KeyTab,
"arrowup" => KeyArrowUp,
"arrowdown" => KeyArrowDown,
"arrowleft" => KeyArrowLeft,
"arrowright" => KeyArrowRight,
"[" => KeyLeftBracket,
"]" => KeyRightBracket,
"{" => KeyLeftCurlyBracket,
"}" => KeyRightCurlyBracket,
"pageup" => KeyPageUp,
"pagedown" => KeyPageDown,
"," => KeyComma,
"." => KeyPeriod,
_ => UnknownKey,
}
}