mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-02 04:22:15 +00:00
Clean up MenuList types and fix many Vue and Clippy warnings
Also remove hard-coded-in-Vue Graphite logo in the menu bar in favor of a Rust definition.
This commit is contained in:
parent
1a90a4db86
commit
3a84de32ac
27 changed files with 361 additions and 374 deletions
|
@ -1,7 +1,7 @@
|
||||||
use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon};
|
use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon};
|
||||||
use crate::messages::layout::utility_types::layout_widget::SubLayout;
|
use crate::messages::layout::utility_types::layout_widget::SubLayout;
|
||||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||||
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuColumn;
|
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuBarEntry;
|
||||||
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer};
|
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::HintData;
|
use crate::messages::tool::utility_types::HintData;
|
||||||
|
@ -141,7 +141,7 @@ pub enum FrontendMessage {
|
||||||
UpdateMenuBarLayout {
|
UpdateMenuBarLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
layout: Vec<MenuColumn>,
|
layout: Vec<MenuBarEntry>,
|
||||||
},
|
},
|
||||||
UpdateMouseCursor {
|
UpdateMouseCursor {
|
||||||
cursor: MouseCursorIcon,
|
cursor: MouseCursorIcon,
|
||||||
|
|
|
@ -30,18 +30,31 @@ impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage
|
||||||
self.send_layout(layout_target, responses, &action_input_mapping);
|
self.send_layout(layout_target, responses, &action_input_mapping);
|
||||||
}
|
}
|
||||||
UpdateLayout { layout_target, widget_id, value } => {
|
UpdateLayout { layout_target, widget_id, value } => {
|
||||||
let layout = &mut self.layouts[layout_target as usize];
|
// Look up the layout
|
||||||
let widget_holder = layout.iter_mut().find(|widget| widget.widget_id == widget_id);
|
let layout = if let Some(layout) = self.layouts.get_mut(layout_target as usize) {
|
||||||
if widget_holder.is_none() {
|
layout
|
||||||
log::trace!(
|
} else {
|
||||||
"Could not find widget_id:{} on layout_target:{:?}. This could be an indication of a problem or just a user clicking off of an actively edited layer",
|
log::warn!(
|
||||||
|
"UpdateLayout was called referencing an invalid layout. `widget_id: {}`, `layout_target: {:?}`",
|
||||||
widget_id,
|
widget_id,
|
||||||
layout_target
|
layout_target
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let widget_holder = if let Some(widget_holder) = layout.iter_mut().find(|widget| widget.widget_id == widget_id) {
|
||||||
|
widget_holder
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"UpdateLayout was called referencing an invalid widget ID, although the layout target was valid. `widget_id: {}`, `layout_target: {:?}`",
|
||||||
|
widget_id,
|
||||||
|
layout_target
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
match &mut widget_holder.unwrap().widget {
|
match &mut widget_holder.widget {
|
||||||
Widget::CheckboxInput(checkbox_input) => {
|
Widget::CheckboxInput(checkbox_input) => {
|
||||||
let update_value = value.as_bool().expect("CheckboxInput update was not of type: bool");
|
let update_value = value.as_bool().expect("CheckboxInput update was not of type: bool");
|
||||||
checkbox_input.checked = update_value;
|
checkbox_input.checked = update_value;
|
||||||
|
|
|
@ -9,9 +9,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use super::input_widgets::InvisibleStandinInput;
|
use super::input_widgets::InvisibleStandinInput;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||||
pub struct MenuEntryGroups(pub Vec<Vec<MenuEntry>>);
|
pub struct MenuBarEntryChildren(pub Vec<Vec<MenuBarEntry>>);
|
||||||
|
|
||||||
impl MenuEntryGroups {
|
impl MenuBarEntryChildren {
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self(Vec::new())
|
Self(Vec::new())
|
||||||
}
|
}
|
||||||
|
@ -31,15 +31,23 @@ impl MenuEntryGroups {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
pub struct MenuEntry {
|
pub struct MenuBarEntry {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub icon: Option<String>,
|
pub icon: Option<String>,
|
||||||
pub children: MenuEntryGroups,
|
|
||||||
pub action: WidgetHolder,
|
|
||||||
pub shortcut: Option<ActionKeys>,
|
pub shortcut: Option<ActionKeys>,
|
||||||
|
pub action: WidgetHolder,
|
||||||
|
pub children: MenuBarEntryChildren,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuEntry {
|
impl MenuBarEntry {
|
||||||
|
pub fn new_root(label: String, children: MenuBarEntryChildren) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder {
|
pub fn create_action(callback: impl Fn(&()) -> Message + 'static) -> WidgetHolder {
|
||||||
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput {
|
WidgetHolder::new(Widget::InvisibleStandinInput(InvisibleStandinInput {
|
||||||
on_update: WidgetCallback::new(callback),
|
on_update: WidgetCallback::new(callback),
|
||||||
|
@ -47,54 +55,46 @@ impl MenuEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_action() -> WidgetHolder {
|
pub fn no_action() -> WidgetHolder {
|
||||||
MenuEntry::create_action(|_| Message::NoOp)
|
MenuBarEntry::create_action(|_| Message::NoOp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MenuEntry {
|
impl Default for MenuBarEntry {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestComingSoonDialog { issue: None }.into()),
|
|
||||||
label: "".into(),
|
label: "".into(),
|
||||||
icon: None,
|
icon: None,
|
||||||
children: MenuEntryGroups::empty(),
|
|
||||||
shortcut: None,
|
shortcut: None,
|
||||||
|
action: MenuBarEntry::no_action(),
|
||||||
|
children: MenuBarEntryChildren::empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
|
|
||||||
pub struct MenuColumn {
|
|
||||||
pub label: String,
|
|
||||||
pub children: MenuEntryGroups,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MenuLayout {
|
pub struct MenuLayout {
|
||||||
pub layout: Vec<MenuColumn>,
|
pub layout: Vec<MenuBarEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuLayout {
|
impl MenuLayout {
|
||||||
pub fn new(layout: Vec<MenuColumn>) -> Self {
|
pub fn new(layout: Vec<MenuBarEntry>) -> Self {
|
||||||
Self { layout }
|
Self { layout }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
pub fn iter(&self) -> impl Iterator<Item = &WidgetHolder> + '_ {
|
||||||
MenuLayoutIter {
|
MenuLayoutIter { stack: self.layout.iter().collect() }
|
||||||
stack: self.layout.iter().flat_map(|column| column.children.0.iter()).flat_map(|group| group.iter()).collect(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut WidgetHolder> + '_ {
|
||||||
MenuLayoutIterMut {
|
MenuLayoutIterMut {
|
||||||
stack: self.layout.iter_mut().flat_map(|column| column.children.0.iter_mut()).flat_map(|group| group.iter_mut()).collect(),
|
stack: self.layout.iter_mut().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MenuLayoutIter<'a> {
|
pub struct MenuLayoutIter<'a> {
|
||||||
pub stack: Vec<&'a MenuEntry>,
|
pub stack: Vec<&'a MenuBarEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for MenuLayoutIter<'a> {
|
impl<'a> Iterator for MenuLayoutIter<'a> {
|
||||||
|
@ -114,7 +114,7 @@ impl<'a> Iterator for MenuLayoutIter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MenuLayoutIterMut<'a> {
|
pub struct MenuLayoutIterMut<'a> {
|
||||||
pub stack: Vec<&'a mut MenuEntry>,
|
pub stack: Vec<&'a mut MenuBarEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for MenuLayoutIterMut<'a> {
|
impl<'a> Iterator for MenuLayoutIterMut<'a> {
|
||||||
|
|
|
@ -47,6 +47,7 @@ impl LayerMetadata {
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct LayerPanelEntry {
|
pub struct LayerPanelEntry {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub tooltip: String,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
#[serde(rename = "layerType")]
|
#[serde(rename = "layerType")]
|
||||||
pub layer_type: LayerDataTypeDiscriminant,
|
pub layer_type: LayerDataTypeDiscriminant,
|
||||||
|
@ -59,9 +60,16 @@ pub struct LayerPanelEntry {
|
||||||
impl LayerPanelEntry {
|
impl LayerPanelEntry {
|
||||||
pub fn new(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>, font_cache: &FontCache) -> Self {
|
pub fn new(layer_metadata: &LayerMetadata, transform: DAffine2, layer: &Layer, path: Vec<LayerId>, font_cache: &FontCache) -> Self {
|
||||||
let name = layer.name.clone().unwrap_or_else(|| String::from(""));
|
let name = layer.name.clone().unwrap_or_else(|| String::from(""));
|
||||||
|
|
||||||
|
let tooltip = if cfg!(debug_assertions) {
|
||||||
|
let joined = &path.iter().map(|id| id.to_string()).collect::<Vec<_>>().join(" / ");
|
||||||
|
name.clone() + "\nLayer Path: " + joined.as_str()
|
||||||
|
} else {
|
||||||
|
name.clone()
|
||||||
|
};
|
||||||
|
|
||||||
let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
let arr = layer.data.bounding_box(transform, font_cache).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||||
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
|
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
|
||||||
|
|
||||||
let mut thumbnail = String::new();
|
let mut thumbnail = String::new();
|
||||||
let mut svg_defs = String::new();
|
let mut svg_defs = String::new();
|
||||||
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
|
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
|
||||||
|
@ -84,6 +92,7 @@ impl LayerPanelEntry {
|
||||||
|
|
||||||
LayerPanelEntry {
|
LayerPanelEntry {
|
||||||
name,
|
name,
|
||||||
|
tooltip,
|
||||||
visible: layer.visible,
|
visible: layer.visible,
|
||||||
layer_type: (&layer.data).into(),
|
layer_type: (&layer.data).into(),
|
||||||
layer_metadata: *layer_metadata,
|
layer_metadata: *layer_metadata,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
use crate::messages::layout::utility_types::layout_widget::{Layout, PropertyHolder};
|
use crate::messages::layout::utility_types::layout_widget::{Layout, PropertyHolder};
|
||||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||||
use crate::messages::layout::utility_types::widgets::menu_widgets::{MenuColumn, MenuEntry, MenuEntryGroups, MenuLayout};
|
use crate::messages::layout::utility_types::widgets::menu_widgets::{MenuBarEntry, MenuBarEntryChildren, MenuLayout};
|
||||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
@ -27,295 +27,300 @@ impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
|
||||||
impl PropertyHolder for MenuBarMessageHandler {
|
impl PropertyHolder for MenuBarMessageHandler {
|
||||||
fn properties(&self) -> Layout {
|
fn properties(&self) -> Layout {
|
||||||
Layout::MenuLayout(MenuLayout::new(vec![
|
Layout::MenuLayout(MenuLayout::new(vec![
|
||||||
MenuColumn {
|
MenuBarEntry {
|
||||||
label: "File".into(),
|
icon: Some("GraphiteLogo".into()),
|
||||||
children: MenuEntryGroups(vec![
|
action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
MenuBarEntry::new_root(
|
||||||
|
"File".into(),
|
||||||
|
MenuBarEntryChildren(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "New…".into(),
|
label: "New…".into(),
|
||||||
icon: Some("File".into()),
|
icon: Some("File".into()),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
|
action: MenuBarEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
|
||||||
shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog),
|
shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog),
|
||||||
children: MenuEntryGroups::empty(),
|
children: MenuBarEntryChildren::empty(),
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Open…".into(),
|
label: "Open…".into(),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Close".into(),
|
label: "Close".into(),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Close All".into(),
|
label: "Close All".into(),
|
||||||
shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
|
shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
|
action: MenuBarEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Save".into(),
|
label: "Save".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Import…".into(),
|
label: "Import…".into(),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::Import),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::Import),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::Import.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::Import.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Export…".into(),
|
label: "Export…".into(),
|
||||||
shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog),
|
shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
|
action: MenuBarEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
},
|
),
|
||||||
MenuColumn {
|
MenuBarEntry::new_root(
|
||||||
label: "Edit".into(),
|
"Edit".into(),
|
||||||
children: MenuEntryGroups(vec![
|
MenuBarEntryChildren(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Undo".into(),
|
label: "Undo".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::Undo),
|
shortcut: action_keys!(DocumentMessageDiscriminant::Undo),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::Undo.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::Undo.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Redo".into(),
|
label: "Redo".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::Redo),
|
shortcut: action_keys!(DocumentMessageDiscriminant::Redo),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::Redo.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::Redo.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Cut".into(),
|
label: "Cut".into(),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::Cut),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::Cut),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Copy".into(),
|
label: "Copy".into(),
|
||||||
icon: Some("Copy".into()),
|
icon: Some("Copy".into()),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::Copy),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::Copy),
|
||||||
action: MenuEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Paste".into(),
|
label: "Paste".into(),
|
||||||
icon: Some("Paste".into()),
|
icon: Some("Paste".into()),
|
||||||
shortcut: action_keys!(FrontendMessageDiscriminant::TriggerPaste),
|
shortcut: action_keys!(FrontendMessageDiscriminant::TriggerPaste),
|
||||||
action: MenuEntry::create_action(|_| FrontendMessage::TriggerPaste.into()),
|
action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerPaste.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
},
|
),
|
||||||
MenuColumn {
|
MenuBarEntry::new_root(
|
||||||
label: "Layer".into(),
|
"Layer".into(),
|
||||||
children: MenuEntryGroups(vec![
|
MenuBarEntryChildren(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Select All".into(),
|
label: "Select All".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectAllLayers),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectAllLayers),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectAllLayers.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Deselect All".into(),
|
label: "Deselect All".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers),
|
shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Delete Selected".into(),
|
label: "Delete Selected".into(),
|
||||||
icon: Some("Trash".into()),
|
icon: Some("Trash".into()),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
|
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Grab Selected".into(),
|
label: "Grab Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
|
||||||
action: MenuEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Rotate Selected".into(),
|
label: "Rotate Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
|
||||||
action: MenuEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Scale Selected".into(),
|
label: "Scale Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
|
||||||
action: MenuEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Order".into(),
|
label: "Order".into(),
|
||||||
action: MenuEntry::no_action(),
|
action: MenuBarEntry::no_action(),
|
||||||
children: MenuEntryGroups(vec![vec![
|
children: MenuBarEntryChildren(vec![vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Raise To Front".into(),
|
label: "Raise To Front".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Raise".into(),
|
label: "Raise".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Lower".into(),
|
label: "Lower".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Lower to Back".into(),
|
label: "Lower to Back".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
]]),
|
]]),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
]),
|
]),
|
||||||
},
|
),
|
||||||
MenuColumn {
|
MenuBarEntry::new_root(
|
||||||
label: "Document".into(),
|
"Document".into(),
|
||||||
children: MenuEntryGroups(vec![vec![MenuEntry {
|
MenuBarEntryChildren(vec![vec![MenuBarEntry {
|
||||||
label: "Clear Artboards".into(),
|
label: "Clear Artboards".into(),
|
||||||
action: MenuEntry::create_action(|_| ArtboardMessage::ClearArtboards.into()),
|
action: MenuBarEntry::create_action(|_| ArtboardMessage::ClearArtboards.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}]]),
|
}]]),
|
||||||
},
|
),
|
||||||
MenuColumn {
|
MenuBarEntry::new_root(
|
||||||
label: "View".into(),
|
"View".into(),
|
||||||
children: MenuEntryGroups(vec![
|
MenuBarEntryChildren(vec![
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Zoom to Fit".into(),
|
label: "Zoom to Fit".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
|
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Zoom to 100%".into(),
|
label: "Zoom to 100%".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
|
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Zoom to 200%".into(),
|
label: "Zoom to 200%".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
|
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Node Graph (In Development)".into(),
|
label: "Node Graph (In Development)".into(),
|
||||||
action: MenuEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
|
action: MenuBarEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
]),
|
]),
|
||||||
},
|
),
|
||||||
MenuColumn {
|
MenuBarEntry::new_root(
|
||||||
label: "Help".into(),
|
"Help".into(),
|
||||||
children: MenuEntryGroups(vec![
|
MenuBarEntryChildren(vec![
|
||||||
vec![MenuEntry {
|
vec![MenuBarEntry {
|
||||||
label: "About Graphite".into(),
|
label: "About Graphite".into(),
|
||||||
icon: Some("GraphiteLogo".into()),
|
icon: Some("GraphiteLogo".into()),
|
||||||
action: MenuEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
|
action: MenuBarEntry::create_action(|_| DialogMessage::RequestAboutGraphiteDialog.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Report a Bug".into(),
|
label: "Report a Bug".into(),
|
||||||
action: MenuEntry::create_action(|_| {
|
action: MenuBarEntry::create_action(|_| {
|
||||||
FrontendMessage::TriggerVisitLink {
|
FrontendMessage::TriggerVisitLink {
|
||||||
url: "https://github.com/GraphiteEditor/Graphite/issues/new".into(),
|
url: "https://github.com/GraphiteEditor/Graphite/issues/new".into(),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Visit on GitHub".into(),
|
label: "Visit on GitHub".into(),
|
||||||
action: MenuEntry::create_action(|_| {
|
action: MenuBarEntry::create_action(|_| {
|
||||||
FrontendMessage::TriggerVisitLink {
|
FrontendMessage::TriggerVisitLink {
|
||||||
url: "https://github.com/GraphiteEditor/Graphite".into(),
|
url: "https://github.com/GraphiteEditor/Graphite".into(),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}),
|
}),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Debug: Print Messages".into(),
|
label: "Debug: Print Messages".into(),
|
||||||
action: MenuEntry::no_action(),
|
action: MenuBarEntry::no_action(),
|
||||||
children: MenuEntryGroups(vec![vec![
|
children: MenuBarEntryChildren(vec![vec![
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Off".into(),
|
label: "Off".into(),
|
||||||
// icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode
|
// icon: Some("Checkmark".into()), // TODO: Find a way to set this icon on the active mode
|
||||||
shortcut: action_keys!(DebugMessageDiscriminant::MessageOff),
|
shortcut: action_keys!(DebugMessageDiscriminant::MessageOff),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::MessageOff.into()),
|
action: MenuBarEntry::create_action(|_| DebugMessage::MessageOff.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Only Names".into(),
|
label: "Only Names".into(),
|
||||||
shortcut: action_keys!(DebugMessageDiscriminant::MessageNames),
|
shortcut: action_keys!(DebugMessageDiscriminant::MessageNames),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::MessageNames.into()),
|
action: MenuBarEntry::create_action(|_| DebugMessage::MessageNames.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Full Contents".into(),
|
label: "Full Contents".into(),
|
||||||
shortcut: action_keys!(DebugMessageDiscriminant::MessageContents),
|
shortcut: action_keys!(DebugMessageDiscriminant::MessageContents),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::MessageContents.into()),
|
action: MenuBarEntry::create_action(|_| DebugMessage::MessageContents.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
]]),
|
]]),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Debug: Print Trace Logs".into(),
|
label: "Debug: Print Trace Logs".into(),
|
||||||
icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
icon: Some(if let log::LevelFilter::Trace = log::max_level() { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||||
shortcut: action_keys!(DebugMessageDiscriminant::ToggleTraceLogs),
|
shortcut: action_keys!(DebugMessageDiscriminant::ToggleTraceLogs),
|
||||||
action: MenuEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
action: MenuBarEntry::create_action(|_| DebugMessage::ToggleTraceLogs.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Debug: Print Document".into(),
|
label: "Debug: Print Document".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::DebugPrintDocument),
|
shortcut: action_keys!(DocumentMessageDiscriminant::DebugPrintDocument),
|
||||||
action: MenuEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::DebugPrintDocument.into()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuEntry {
|
MenuBarEntry {
|
||||||
label: "Debug: Panic (DANGER)".into(),
|
label: "Debug: Panic (DANGER)".into(),
|
||||||
action: MenuEntry::create_action(|_| panic!()),
|
action: MenuBarEntry::create_action(|_| panic!()),
|
||||||
..MenuEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]),
|
]),
|
||||||
},
|
),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<template>
|
|
||||||
<div style="display: flex">
|
|
||||||
<div tabindex="0" style="width: 50%; outline: none">
|
|
||||||
<App />
|
|
||||||
</div>
|
|
||||||
<div tabindex="0" style="width: 50%; outline: none">
|
|
||||||
<App />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
import App from "@/App.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { App },
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -44,8 +44,8 @@
|
||||||
:open="entry.ref?.open || false"
|
:open="entry.ref?.open || false"
|
||||||
:direction="'TopRight'"
|
:direction="'TopRight'"
|
||||||
:entries="entry.children"
|
:entries="entry.children"
|
||||||
v-bind="{ defaultAction, minWidth, drawIcon, scrollableY }"
|
v-bind="{ minWidth, drawIcon, scrollableY }"
|
||||||
:ref="(ref: typeof FloatingMenu) => ref && (entry.ref = ref)"
|
:ref="(ref: MenuListInstance) => ref && (entry.ref = ref)"
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</template>
|
</template>
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { MenuListEntry, SectionsOfMenuListEntries, MenuListEntryData } from "@/wasm-communication/messages";
|
import type { MenuListEntry } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
import FloatingMenu, { MenuDirection } from "@/components/floating-menus/FloatingMenu.vue";
|
import FloatingMenu, { MenuDirection } from "@/components/floating-menus/FloatingMenu.vue";
|
||||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||||
|
@ -169,10 +169,13 @@ import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import Separator from "@/components/widgets/labels/Separator.vue";
|
import Separator from "@/components/widgets/labels/Separator.vue";
|
||||||
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
import UserInputLabel from "@/components/widgets/labels/UserInputLabel.vue";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
type MenuListInstance = InstanceType<typeof MenuList>;
|
||||||
|
|
||||||
const MenuList = defineComponent({
|
const MenuList = defineComponent({
|
||||||
emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
emits: ["update:open", "update:activeEntry", "naturalWidth"],
|
||||||
props: {
|
props: {
|
||||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
entries: { type: Array as PropType<MenuListEntry[][]>, required: true },
|
||||||
activeEntry: { type: Object as PropType<MenuListEntry>, required: false },
|
activeEntry: { type: Object as PropType<MenuListEntry>, required: false },
|
||||||
open: { type: Boolean as PropType<boolean>, required: true },
|
open: { type: Boolean as PropType<boolean>, required: true },
|
||||||
direction: { type: String as PropType<MenuDirection>, default: "Bottom" },
|
direction: { type: String as PropType<MenuDirection>, default: "Bottom" },
|
||||||
|
@ -181,7 +184,6 @@ const MenuList = defineComponent({
|
||||||
interactive: { type: Boolean as PropType<boolean>, default: false },
|
interactive: { type: Boolean as PropType<boolean>, default: false },
|
||||||
scrollableY: { type: Boolean as PropType<boolean>, default: false },
|
scrollableY: { type: Boolean as PropType<boolean>, default: false },
|
||||||
virtualScrollingEntryHeight: { type: Number as PropType<number>, default: 0 },
|
virtualScrollingEntryHeight: { type: Number as PropType<number>, default: 0 },
|
||||||
defaultAction: { type: Function as PropType<() => void>, required: false },
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -209,33 +211,32 @@ const MenuList = defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onEntryClick(menuEntry: MenuListEntry): void {
|
onEntryClick(menuListEntry: MenuListEntry): void {
|
||||||
// Call the action, or a default, if either are provided
|
// Call the action if available
|
||||||
if (menuEntry.action) menuEntry.action();
|
if (menuListEntry.action) menuListEntry.action();
|
||||||
else if (this.defaultAction) this.defaultAction();
|
|
||||||
|
|
||||||
// Emit the clicked entry as the new active entry
|
// Emit the clicked entry as the new active entry
|
||||||
this.$emit("update:activeEntry", menuEntry);
|
this.$emit("update:activeEntry", menuListEntry);
|
||||||
|
|
||||||
// Close the containing menu
|
// Close the containing menu
|
||||||
if (menuEntry.ref) menuEntry.ref.isOpen = false;
|
if (menuListEntry.ref) menuListEntry.ref.isOpen = false;
|
||||||
this.$emit("update:open", false);
|
this.$emit("update:open", false);
|
||||||
this.isOpen = false; // TODO: This is a hack for MenuBarInput submenus, remove it when we get rid of using `ref`
|
this.isOpen = false; // TODO: This is a hack for MenuBarInput submenus, remove it when we get rid of using `ref`
|
||||||
},
|
},
|
||||||
onEntryPointerEnter(menuEntry: MenuListEntry): void {
|
onEntryPointerEnter(menuListEntry: MenuListEntry): void {
|
||||||
if (!menuEntry.children?.length) return;
|
if (!menuListEntry.children?.length) return;
|
||||||
|
|
||||||
if (menuEntry.ref) menuEntry.ref.isOpen = true;
|
if (menuListEntry.ref) menuListEntry.ref.isOpen = true;
|
||||||
else this.$emit("update:open", true);
|
else this.$emit("update:open", true);
|
||||||
},
|
},
|
||||||
onEntryPointerLeave(menuEntry: MenuListEntry): void {
|
onEntryPointerLeave(menuListEntry: MenuListEntry): void {
|
||||||
if (!menuEntry.children?.length) return;
|
if (!menuListEntry.children?.length) return;
|
||||||
|
|
||||||
if (menuEntry.ref) menuEntry.ref.isOpen = false;
|
if (menuListEntry.ref) menuListEntry.ref.isOpen = false;
|
||||||
else this.$emit("update:open", false);
|
else this.$emit("update:open", false);
|
||||||
},
|
},
|
||||||
isEntryOpen(menuEntry: MenuListEntry): boolean {
|
isEntryOpen(menuListEntry: MenuListEntry): boolean {
|
||||||
if (!menuEntry.children?.length) return false;
|
if (!menuListEntry.children?.length) return false;
|
||||||
|
|
||||||
return this.open;
|
return this.open;
|
||||||
},
|
},
|
||||||
|
@ -249,7 +250,7 @@ const MenuList = defineComponent({
|
||||||
const flatEntries = this.entries.flat().filter((entry) => !entry.disabled);
|
const flatEntries = this.entries.flat().filter((entry) => !entry.disabled);
|
||||||
const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.isOpen);
|
const openChild = flatEntries.findIndex((entry) => entry.children?.length && entry.ref?.isOpen);
|
||||||
|
|
||||||
const openSubmenu = (highlighted: MenuListEntry<string>): void => {
|
const openSubmenu = (highlighted: MenuListEntry): void => {
|
||||||
if (highlighted.ref && highlighted.children?.length) {
|
if (highlighted.ref && highlighted.children?.length) {
|
||||||
highlighted.ref.isOpen = true;
|
highlighted.ref.isOpen = true;
|
||||||
|
|
||||||
|
@ -316,7 +317,7 @@ const MenuList = defineComponent({
|
||||||
// By default, keep the menu stack open
|
// By default, keep the menu stack open
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
setHighlighted(newHighlight: MenuListEntry<string> | undefined) {
|
setHighlighted(newHighlight: MenuListEntry | undefined) {
|
||||||
this.highlighted = newHighlight;
|
this.highlighted = newHighlight;
|
||||||
// Interactive menus should keep the active entry the same as the highlighted one
|
// Interactive menus should keep the active entry the same as the highlighted one
|
||||||
if (this.interactive && newHighlight?.value !== this.activeEntry?.value) this.$emit("update:activeEntry", newHighlight);
|
if (this.interactive && newHighlight?.value !== this.activeEntry?.value) this.$emit("update:activeEntry", newHighlight);
|
||||||
|
@ -327,14 +328,6 @@ const MenuList = defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
entriesWithoutRefs(): MenuListEntryData[][] {
|
|
||||||
return this.entries.map((menuListEntries) =>
|
|
||||||
menuListEntries.map((entry) => {
|
|
||||||
const { ref, ...entryWithoutRef } = entry;
|
|
||||||
return entryWithoutRef;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
virtualScrollingTotalHeight() {
|
virtualScrollingTotalHeight() {
|
||||||
return this.entries[0].length * this.virtualScrollingEntryHeight;
|
return this.entries[0].length * this.virtualScrollingEntryHeight;
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<WidgetLayout :layout="layerTreeOptionsLayout" />
|
<WidgetLayout :layout="layerTreeOptionsLayout" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="layer-tree-rows" :scrollableY="true">
|
<LayoutRow class="layer-tree-rows" :scrollableY="true">
|
||||||
<LayoutCol class="list" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="(e) => draggable && updateInsertLine(e)" @dragend="() => draggable && drop()">
|
<LayoutCol class="list" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="(e: DragEvent) => draggable && updateInsertLine(e)" @dragend="() => draggable && drop()">
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="layer-row"
|
class="layer-row"
|
||||||
v-for="(listing, index) in layers"
|
v-for="(listing, index) in layers"
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
>
|
>
|
||||||
<LayoutRow class="visibility">
|
<LayoutRow class="visibility">
|
||||||
<IconButton
|
<IconButton
|
||||||
:action="(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
|
:action="(e: MouseEvent) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())"
|
||||||
:size="24"
|
:size="24"
|
||||||
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
|
:icon="listing.entry.visible ? 'EyeVisible' : 'EyeHidden'"
|
||||||
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
|
:title="listing.entry.visible ? 'Visible' : 'Hidden'"
|
||||||
|
@ -34,15 +34,15 @@
|
||||||
:data-index="index"
|
:data-index="index"
|
||||||
:title="listing.entry.tooltip"
|
:title="listing.entry.tooltip"
|
||||||
:draggable="draggable"
|
:draggable="draggable"
|
||||||
@dragstart="(e) => draggable && dragStart(e, listing)"
|
@dragstart="(e: DragEvent) => draggable && dragStart(e, listing)"
|
||||||
@click.exact="(e) => selectLayer(false, false, false, listing, e)"
|
@click.exact="(e: MouseEvent) => selectLayer(false, false, false, listing, e)"
|
||||||
@click.shift.exact="(e) => selectLayer(false, false, true, listing, e)"
|
@click.shift.exact="(e: MouseEvent) => selectLayer(false, false, true, listing, e)"
|
||||||
@click.ctrl.exact="(e) => selectLayer(true, false, false, listing, e)"
|
@click.ctrl.exact="(e: MouseEvent) => selectLayer(true, false, false, listing, e)"
|
||||||
@click.ctrl.shift.exact="(e) => selectLayer(true, false, true, listing, e)"
|
@click.ctrl.shift.exact="(e: MouseEvent) => selectLayer(true, false, true, listing, e)"
|
||||||
@click.meta.exact="(e) => selectLayer(false, true, false, listing, e)"
|
@click.meta.exact="(e: MouseEvent) => selectLayer(false, true, false, listing, e)"
|
||||||
@click.meta.shift.exact="(e) => selectLayer(false, true, true, listing, e)"
|
@click.meta.shift.exact="(e: MouseEvent) => selectLayer(false, true, true, listing, e)"
|
||||||
@click.ctrl.meta="(e) => e.stopPropagation()"
|
@click.ctrl.meta="(e: MouseEvent) => e.stopPropagation()"
|
||||||
@click.alt="(e) => e.stopPropagation()"
|
@click.alt="(e: MouseEvent) => e.stopPropagation()"
|
||||||
>
|
>
|
||||||
<LayoutRow class="layer-type-icon">
|
<LayoutRow class="layer-type-icon">
|
||||||
<IconLabel v-if="listing.entry.layerType === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
|
<IconLabel v-if="listing.entry.layerType === 'Folder'" :icon="'NodeFolder'" :iconStyle="'Node'" title="Folder" />
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
<LayoutRow class="options-bar"></LayoutRow>
|
<LayoutRow class="options-bar"></LayoutRow>
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="graph"
|
class="graph"
|
||||||
@wheel="(e) => scroll(e)"
|
@wheel="(e: WheelEvent) => scroll(e)"
|
||||||
ref="graph"
|
ref="graph"
|
||||||
@pointerdown="(e) => pointerDown(e)"
|
@pointerdown="(e: PointerEvent) => pointerDown(e)"
|
||||||
@pointermove="(e) => pointerMove(e)"
|
@pointermove="(e: PointerEvent) => pointerMove(e)"
|
||||||
@pointerup="(e) => pointerUp(e)"
|
@pointerup="(e: PointerEvent) => pointerUp(e)"
|
||||||
:style="`--grid-spacing: ${gridSpacing}px; --grid-offset-x: ${transform.x * transform.scale}px; --grid-offset-y: ${transform.y * transform.scale}px; --dot-radius: ${dotRadius}px`"
|
:style="`--grid-spacing: ${gridSpacing}px; --grid-offset-x: ${transform.x * transform.scale}px; --grid-offset-y: ${transform.y * transform.scale}px; --dot-radius: ${dotRadius}px`"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -12,12 +12,7 @@
|
||||||
v-model:open="open"
|
v-model:open="open"
|
||||||
@update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)"
|
@update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)"
|
||||||
/>
|
/>
|
||||||
<FontInput
|
<FontInput v-if="component.props.kind === 'FontInput'" v-bind="component.props" v-model:open="open" @changeFont="(value: unknown) => updateLayout(component.widgetId, value)" />
|
||||||
v-if="component.props.kind === 'FontInput'"
|
|
||||||
v-bind="component.props"
|
|
||||||
v-model:open="open"
|
|
||||||
@changeFont="(value: { name: string, style: string, file: string }) => updateLayout(component.widgetId, value)"
|
|
||||||
/>
|
|
||||||
<IconButton v-if="component.props.kind === 'IconButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, null)" />
|
<IconButton v-if="component.props.kind === 'IconButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, null)" />
|
||||||
<IconLabel v-if="component.props.kind === 'IconLabel'" v-bind="component.props" />
|
<IconLabel v-if="component.props.kind === 'IconLabel'" v-bind="component.props" />
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
@ -29,8 +24,8 @@
|
||||||
/>
|
/>
|
||||||
<OptionalInput v-if="component.props.kind === 'OptionalInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" />
|
<OptionalInput v-if="component.props.kind === 'OptionalInput'" v-bind="component.props" @update:checked="(value: boolean) => updateLayout(component.widgetId, value)" />
|
||||||
<PopoverButton v-if="component.props.kind === 'PopoverButton'" v-bind="component.props">
|
<PopoverButton v-if="component.props.kind === 'PopoverButton'" v-bind="component.props">
|
||||||
<h3>{{ component.props.header }}</h3>
|
<h3>{{ (component.props as any).header }}</h3>
|
||||||
<p>{{ component.props.text }}</p>
|
<p>{{ (component.props as any).text }}</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<RadioInput v-if="component.props.kind === 'RadioInput'" v-bind="component.props" @update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)" />
|
<RadioInput v-if="component.props.kind === 'RadioInput'" v-bind="component.props" @update:selectedIndex="(value: number) => updateLayout(component.widgetId, value)" />
|
||||||
<Separator v-if="component.props.kind === 'Separator'" v-bind="component.props" />
|
<Separator v-if="component.props.kind === 'Separator'" v-bind="component.props" />
|
||||||
|
@ -38,7 +33,7 @@
|
||||||
<TextAreaInput v-if="component.props.kind === 'TextAreaInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" />
|
<TextAreaInput v-if="component.props.kind === 'TextAreaInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" />
|
||||||
<TextButton v-if="component.props.kind === 'TextButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, null)" />
|
<TextButton v-if="component.props.kind === 'TextButton'" v-bind="component.props" :action="() => updateLayout(component.widgetId, null)" />
|
||||||
<TextInput v-if="component.props.kind === 'TextInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" />
|
<TextInput v-if="component.props.kind === 'TextInput'" v-bind="component.props" @commitText="(value: string) => updateLayout(component.widgetId, value)" />
|
||||||
<TextLabel v-if="component.props.kind === 'TextLabel'" v-bind="withoutValue(component.props)">{{ component.props.value }}</TextLabel>
|
<TextLabel v-if="component.props.kind === 'TextLabel'" v-bind="withoutValue(component.props)">{{ (component.props as any).value }}</TextLabel>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -124,7 +119,8 @@ export default defineComponent({
|
||||||
updateLayout(widgetId: bigint, value: unknown) {
|
updateLayout(widgetId: bigint, value: unknown) {
|
||||||
this.editor.instance.updateLayout(this.layoutTarget, widgetId, value);
|
this.editor.instance.updateLayout(this.layoutTarget, widgetId, value);
|
||||||
},
|
},
|
||||||
withoutValue(props: Record<string, unknown>): Record<string, unknown> {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
withoutValue(props: Record<string, any>): Record<string, unknown> {
|
||||||
const { value: _, ...rest } = props;
|
const { value: _, ...rest } = props;
|
||||||
return rest;
|
return rest;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutRow class="color-input" :title="tooltip">
|
<LayoutRow class="color-input" :title="tooltip">
|
||||||
<OptionalInput v-if="!noTransparency" :icon="'CloseX'" :checked="Boolean(value)" @update:checked="(val) => updateEnabled(val)"></OptionalInput>
|
<OptionalInput v-if="!noTransparency" :icon="'CloseX'" :checked="Boolean(value)" @update:checked="(state: boolean) => updateEnabled(state)"></OptionalInput>
|
||||||
<TextInput :value="displayValue" :label="label" :disabled="disabled || !value" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
<TextInput :value="displayValue" :label="label" :disabled="disabled || !value" @commitText="(value: string) => textInputUpdated(value)" :center="true" />
|
||||||
<Separator :type="'Related'" />
|
<Separator :type="'Related'" />
|
||||||
<LayoutRow class="swatch">
|
<LayoutRow class="swatch">
|
||||||
<button class="swatch-button" :class="{ 'disabled-swatch': !value }" :style="`--swatch-color: #${value}`" @click="() => $emit('update:open', true)"></button>
|
<button class="swatch-button" :class="{ 'disabled-swatch': !value }" :style="`--swatch-color: #${value}`" @click="() => $emit('update:open', true)"></button>
|
||||||
<FloatingMenu v-model:open="isOpen" :type="'Popover'" :direction="'Bottom'">
|
<FloatingMenu v-model:open="isOpen" :type="'Popover'" :direction="'Bottom'">
|
||||||
<ColorPicker @update:color="(color) => colorPickerUpdated(color)" :color="color" />
|
<ColorPicker @update:color="(color: RGBA) => colorPickerUpdated(color)" :color="color" />
|
||||||
</FloatingMenu>
|
</FloatingMenu>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
:style="{ minWidth: `${minWidth}px` }"
|
:style="{ minWidth: `${minWidth}px` }"
|
||||||
@click="() => !disabled && (open = true)"
|
@click="() => !disabled && (open = true)"
|
||||||
@blur="(e: FocusEvent) => blur(e)"
|
@blur="(e: FocusEvent) => blur(e)"
|
||||||
@keydown="(e) => keydown(e)"
|
@keydown="(e: KeyboardEvent) => keydown(e)"
|
||||||
ref="dropdownBox"
|
ref="dropdownBox"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
data-hover-menu-spawner
|
data-hover-menu-spawner
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType, toRaw } from "vue";
|
import { defineComponent, PropType, toRaw } from "vue";
|
||||||
|
|
||||||
import { MenuListEntry, SectionsOfMenuListEntries } from "@/wasm-communication/messages";
|
import { MenuListEntry } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
import MenuList from "@/components/floating-menus/MenuList.vue";
|
import MenuList from "@/components/floating-menus/MenuList.vue";
|
||||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||||
|
@ -110,7 +110,7 @@ const DASH_ENTRY = { label: "-" };
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ["update:selectedIndex"],
|
emits: ["update:selectedIndex"],
|
||||||
props: {
|
props: {
|
||||||
entries: { type: Array as PropType<SectionsOfMenuListEntries>, required: true },
|
entries: { type: Array as PropType<MenuListEntry[][]>, required: true },
|
||||||
selectedIndex: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
|
selectedIndex: { type: Number as PropType<number>, required: false }, // When not provided, a dash is displayed
|
||||||
drawIcon: { type: Boolean as PropType<boolean>, default: false },
|
drawIcon: { type: Boolean as PropType<boolean>, default: false },
|
||||||
interactive: { type: Boolean as PropType<boolean>, default: true },
|
interactive: { type: Boolean as PropType<boolean>, default: true },
|
||||||
|
|
|
@ -1,31 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="menu-bar-input" data-menu-bar-input>
|
<div class="menu-bar-input" data-menu-bar-input>
|
||||||
<div class="entry-container">
|
|
||||||
<button @click="() => visitWebsite('https://graphite.rs')" class="entry">
|
|
||||||
<IconLabel :icon="'GraphiteLogo'" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="entry-container" v-for="(entry, index) in entries" :key="index">
|
<div class="entry-container" v-for="(entry, index) in entries" :key="index">
|
||||||
<div
|
<div
|
||||||
@click="(e) => onClick(entry, e.target)"
|
@click="(e: MouseEvent) => onClick(entry, e.target)"
|
||||||
tabindex="0"
|
@blur="(e: FocusEvent) => blur(entry, e.target)"
|
||||||
@blur="(e: FocusEvent) => blur(e,entry)"
|
@keydown="(e: KeyboardEvent) => entry.ref?.keydown(e, false)"
|
||||||
@keydown="entry.ref?.keydown"
|
|
||||||
class="entry"
|
class="entry"
|
||||||
:class="{ open: entry.ref?.isOpen }"
|
:class="{ open: entry.ref?.isOpen }"
|
||||||
|
tabindex="0"
|
||||||
data-hover-menu-spawner
|
data-hover-menu-spawner
|
||||||
>
|
>
|
||||||
<IconLabel v-if="entry.icon" :icon="entry.icon" />
|
<IconLabel v-if="entry.icon" :icon="entry.icon" />
|
||||||
<span v-if="entry.label">{{ entry.label }}</span>
|
<span v-if="entry.label">{{ entry.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
<MenuList
|
<MenuList
|
||||||
|
v-if="entry.children && entry.children.length > 0"
|
||||||
:open="entry.ref?.open || false"
|
:open="entry.ref?.open || false"
|
||||||
:entries="entry.children || []"
|
:entries="entry.children || []"
|
||||||
:direction="'Bottom'"
|
:direction="'Bottom'"
|
||||||
:minWidth="240"
|
:minWidth="240"
|
||||||
:drawIcon="true"
|
:drawIcon="true"
|
||||||
:defaultAction="() => editor.instance.requestComingSoonDialog()"
|
:ref="(ref: MenuListInstance) => ref && (entry.ref = ref)"
|
||||||
:ref="(ref: typeof MenuList) => ref && (entry.ref = ref)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -73,11 +68,14 @@
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { platformIsMac } from "@/utility-functions/platform";
|
import { platformIsMac } from "@/utility-functions/platform";
|
||||||
import { MenuEntry, UpdateMenuBarLayout, MenuListEntry, KeyRaw, KeysGroup } from "@/wasm-communication/messages";
|
import { MenuBarEntry, UpdateMenuBarLayout, MenuListEntry, KeyRaw, KeysGroup } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
import MenuList from "@/components/floating-menus/MenuList.vue";
|
import MenuList from "@/components/floating-menus/MenuList.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
type MenuListInstance = InstanceType<typeof MenuList>;
|
||||||
|
|
||||||
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
|
// TODO: Apparently, Safari does not support the Keyboard.lock() API but does relax its authority over certain keyboard shortcuts in fullscreen mode, which we should take advantage of
|
||||||
const accelKey = platformIsMac() ? "Command" : "Control";
|
const accelKey = platformIsMac() ? "Command" : "Control";
|
||||||
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
|
const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
|
||||||
|
@ -88,12 +86,6 @@ const LOCK_REQUIRING_SHORTCUTS: KeyRaw[][] = [
|
||||||
[accelKey, "Shift", "KeyT"],
|
[accelKey, "Shift", "KeyT"],
|
||||||
];
|
];
|
||||||
|
|
||||||
type FrontendMenuColumn = {
|
|
||||||
label: string;
|
|
||||||
children: FrontendMenuEntry[][];
|
|
||||||
};
|
|
||||||
type FrontendMenuEntry = Omit<MenuEntry, "action" | "children"> & { shortcutRequiresLock: boolean | undefined; action: () => void; children: FrontendMenuEntry[][] | undefined };
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inject: ["editor"],
|
inject: ["editor"],
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -106,39 +98,47 @@ export default defineComponent({
|
||||||
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
|
return LOCK_REQUIRING_SHORTCUTS.some((lockKeyCombo) => arraysEqual(shortcutKeys, lockKeyCombo));
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuEntryToFrontendMenuEntry = (subLayout: MenuEntry[][]): FrontendMenuEntry[][] =>
|
const menuBarEntryToMenuListEntry = (entry: MenuBarEntry): MenuListEntry => ({
|
||||||
subLayout.map((group) =>
|
// From `MenuEntryCommon`
|
||||||
group.map((entry) => ({
|
...entry,
|
||||||
...entry,
|
|
||||||
children: entry.children ? menuEntryToFrontendMenuEntry(entry.children) : undefined,
|
|
||||||
action: (): void => this.editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
|
||||||
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
this.entries = updateMenuBarLayout.layout.map((column) => ({ ...column, children: menuEntryToFrontendMenuEntry(column.children) }));
|
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
|
||||||
|
action: (): void => this.editor.instance.updateLayout(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
||||||
|
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
|
||||||
|
|
||||||
|
// New fields in `MenuListEntry`
|
||||||
|
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
|
||||||
|
value: undefined,
|
||||||
|
disabled: undefined,
|
||||||
|
font: undefined,
|
||||||
|
ref: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.entries = updateMenuBarLayout.layout.map(menuBarEntryToMenuListEntry);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onClick(menuEntry: MenuListEntry, target: EventTarget | null) {
|
onClick(menuListEntry: MenuListEntry, target: EventTarget | null) {
|
||||||
|
// If there's no menu to open, trigger the action but don't try to open its non-existant children
|
||||||
|
if (!menuListEntry.children || menuListEntry.children.length === 0) {
|
||||||
|
if (menuListEntry.action && !menuListEntry.disabled) menuListEntry.action();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Focus the target so that keyboard inputs are sent to the dropdown
|
// Focus the target so that keyboard inputs are sent to the dropdown
|
||||||
(target as HTMLElement)?.focus();
|
(target as HTMLElement)?.focus();
|
||||||
|
|
||||||
if (menuEntry.ref) menuEntry.ref.isOpen = true;
|
if (menuListEntry.ref) menuListEntry.ref.isOpen = true;
|
||||||
else throw new Error("The menu bar floating menu has no associated ref");
|
else throw new Error("The menu bar floating menu has no associated ref");
|
||||||
},
|
},
|
||||||
blur(e: FocusEvent, menuEntry: MenuListEntry) {
|
blur(menuListEntry: MenuListEntry, target: EventTarget | null) {
|
||||||
if ((e.target as HTMLElement).closest("[data-menu-bar-input]") !== this.$el && menuEntry.ref) menuEntry.ref.isOpen = false;
|
if ((target as HTMLElement)?.closest("[data-menu-bar-input]") !== this.$el && menuListEntry.ref) menuListEntry.ref.isOpen = false;
|
||||||
},
|
|
||||||
// TODO: Move to backend
|
|
||||||
visitWebsite(url: string) {
|
|
||||||
// This method is required because `window` isn't accessible from the Vue component HTML
|
|
||||||
window.open(url, "_blank");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
entries: [] as FrontendMenuColumn[],
|
entries: [] as MenuListEntry[],
|
||||||
open: false,
|
open: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutRow class="optional-input">
|
<LayoutRow class="optional-input">
|
||||||
<CheckboxInput :checked="checked" @input="(e) => $emit('update:checked', (e.target as HTMLInputElement).checked)" :icon="icon" :tooltip="tooltip" />
|
<CheckboxInput :checked="checked" @input="(e: Event) => $emit('update:checked', (e.target as HTMLInputElement).checked)" :icon="icon" :tooltip="tooltip" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -77,11 +77,11 @@ export default defineComponent({
|
||||||
selectedIndex: { type: Number as PropType<number>, required: true },
|
selectedIndex: { type: Number as PropType<number>, required: true },
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleEntryClick(menuEntry: RadioEntryData) {
|
handleEntryClick(radioEntryData: RadioEntryData) {
|
||||||
const index = this.entries.indexOf(menuEntry);
|
const index = this.entries.indexOf(radioEntryData);
|
||||||
this.$emit("update:selectedIndex", index);
|
this.$emit("update:selectedIndex", index);
|
||||||
|
|
||||||
menuEntry.action?.();
|
radioEntryData.action?.();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
data-tab
|
data-tab
|
||||||
v-for="(tabLabel, tabIndex) in tabLabels"
|
v-for="(tabLabel, tabIndex) in tabLabels"
|
||||||
:key="tabIndex"
|
:key="tabIndex"
|
||||||
@click="(e) => (e?.stopPropagation(), clickAction?.(tabIndex))"
|
@click="(e: MouseEvent) => (e?.stopPropagation(), clickAction?.(tabIndex))"
|
||||||
@click.middle="(e) => (e?.stopPropagation(), closeAction?.(tabIndex))"
|
@click.middle="(e: MouseEvent) => (e?.stopPropagation(), closeAction?.(tabIndex))"
|
||||||
>
|
>
|
||||||
<span>{{ tabLabel }}</span>
|
<span>{{ tabLabel }}</span>
|
||||||
<IconButton :action="(e) => (e?.stopPropagation(), closeAction?.(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
<IconButton :action="(e: MouseEvent) => (e?.stopPropagation(), closeAction?.(tabIndex))" :icon="'CloseX'" :size="16" v-if="tabCloseButtons" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<PopoverButton :icon="'VerticalEllipsis'">
|
<PopoverButton :icon="'VerticalEllipsis'">
|
||||||
|
|
|
@ -8,23 +8,23 @@
|
||||||
:tabCloseButtons="true"
|
:tabCloseButtons="true"
|
||||||
:tabMinWidths="true"
|
:tabMinWidths="true"
|
||||||
:tabLabels="portfolio.state.documents.map((doc) => doc.displayName)"
|
:tabLabels="portfolio.state.documents.map((doc) => doc.displayName)"
|
||||||
:clickAction="(tabIndex) => editor.instance.selectDocument(portfolio.state.documents[tabIndex].id)"
|
:clickAction="(tabIndex: number) => editor.instance.selectDocument(portfolio.state.documents[tabIndex].id)"
|
||||||
:closeAction="(tabIndex) => editor.instance.closeDocumentWithConfirmation(portfolio.state.documents[tabIndex].id)"
|
:closeAction="(tabIndex: number) => editor.instance.closeDocumentWithConfirmation(portfolio.state.documents[tabIndex].id)"
|
||||||
:tabActiveIndex="portfolio.state.activeDocumentIndex"
|
:tabActiveIndex="portfolio.state.activeDocumentIndex"
|
||||||
ref="documentsPanel"
|
ref="documentsPanel"
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="workspace-grid-resize-gutter" @pointerdown="(e) => resizePanel(e)" v-if="nodeGraphVisible"></LayoutRow>
|
<LayoutRow class="workspace-grid-resize-gutter" @pointerdown="(e: PointerEvent) => resizePanel(e)" v-if="nodeGraphVisible"></LayoutRow>
|
||||||
<LayoutRow class="workspace-grid-subdivision" v-if="nodeGraphVisible">
|
<LayoutRow class="workspace-grid-subdivision" v-if="nodeGraphVisible">
|
||||||
<Panel :panelType="'NodeGraph'" :tabLabels="['Node Graph']" :tabActiveIndex="0" />
|
<Panel :panelType="'NodeGraph'" :tabLabels="['Node Graph']" :tabActiveIndex="0" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="workspace-grid-resize-gutter" @pointerdown="(e) => resizePanel(e)"></LayoutCol>
|
<LayoutCol class="workspace-grid-resize-gutter" @pointerdown="(e: PointerEvent) => resizePanel(e)"></LayoutCol>
|
||||||
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 0.17">
|
<LayoutCol class="workspace-grid-subdivision" style="flex-grow: 0.17">
|
||||||
<LayoutRow class="workspace-grid-subdivision" style="flex-grow: 402">
|
<LayoutRow class="workspace-grid-subdivision" style="flex-grow: 402">
|
||||||
<Panel :panelType="'Properties'" :tabLabels="['Properties']" :tabActiveIndex="0" />
|
<Panel :panelType="'Properties'" :tabLabels="['Properties']" :tabActiveIndex="0" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="workspace-grid-resize-gutter" @pointerdown="(e) => resizePanel(e)"></LayoutRow>
|
<LayoutRow class="workspace-grid-resize-gutter" @pointerdown="(e: PointerEvent) => resizePanel(e)"></LayoutRow>
|
||||||
<LayoutRow class="workspace-grid-subdivision" style="flex-grow: 590">
|
<LayoutRow class="workspace-grid-subdivision" style="flex-grow: 590">
|
||||||
<Panel :panelType="'LayerTree'" :tabLabels="['Layer Tree']" :tabActiveIndex="0" />
|
<Panel :panelType="'LayerTree'" :tabLabels="['Layer Tree']" :tabActiveIndex="0" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
|
|
|
@ -244,7 +244,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
|
||||||
if (editor.instance.hasCrashed()) return;
|
if (editor.instance.hasCrashed()) return;
|
||||||
|
|
||||||
// Skip the message during development, since it's annoying when testing
|
// Skip the message during development, since it's annoying when testing
|
||||||
if (process.env.NODE_ENV === "development") return;
|
if (editor.instance.inDevelopmentMode()) return;
|
||||||
|
|
||||||
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
||||||
if (!allDocumentsSaved) {
|
if (!allDocumentsSaved) {
|
||||||
|
|
23
frontend/src/volar.d.ts
vendored
23
frontend/src/volar.d.ts
vendored
|
@ -1,23 +0,0 @@
|
||||||
import { type RGBA as RGBA_ } from "@/wasm-communication/messages";
|
|
||||||
|
|
||||||
import FloatingMenu from "@/components/floating-menus/FloatingMenu.vue";
|
|
||||||
import MenuList from "@/components/floating-menus/MenuList.vue";
|
|
||||||
|
|
||||||
// TODO: When a Volar bug is fixed (likely in v0.34.16):
|
|
||||||
// TODO: - Uncomment this block
|
|
||||||
// TODO: - Remove the `MenuList` and `FloatingMenu` lines from the `declare global` section below
|
|
||||||
// TODO: - And possibly add the empty export line of code `export {};` to the bottom of this file, for some reason
|
|
||||||
// declare module "vue" {
|
|
||||||
// interface ComponentCustomProperties {
|
|
||||||
// const MenuList: MenuList;
|
|
||||||
// const FloatingMenu: FloatingMenu;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Satisfies Volar
|
|
||||||
// TODO: Move this back into `DropdownInput.vue` and `SwatchPairInput.vue` after https://github.com/johnsoncodehk/volar/issues/1321 is fixed
|
|
||||||
declare global {
|
|
||||||
const MenuList: MenuList;
|
|
||||||
const FloatingMenu: FloatingMenu;
|
|
||||||
type RGBA = RGBA_;
|
|
||||||
}
|
|
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
import { Transform, Type, plainToClass } from "class-transformer";
|
import { Transform, Type, plainToClass } from "class-transformer";
|
||||||
|
|
||||||
import { IconName, IconSize, IconStyle } from "@/utility-functions/icons";
|
import type { IconName, IconSize, IconStyle } from "@/utility-functions/icons";
|
||||||
import type { WasmEditorInstance, WasmRawInstance } from "@/wasm-communication/editor";
|
import type { WasmEditorInstance, WasmRawInstance } from "@/wasm-communication/editor";
|
||||||
|
|
||||||
|
import type MenuList from "@/components/floating-menus/MenuList.vue";
|
||||||
|
|
||||||
export class JsMessage {
|
export class JsMessage {
|
||||||
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
||||||
static readonly jsMessageMarker = true;
|
static readonly jsMessageMarker = true;
|
||||||
|
@ -324,6 +326,8 @@ export class UpdateDocumentLayerDetails extends JsMessage {
|
||||||
export class LayerPanelEntry {
|
export class LayerPanelEntry {
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
|
tooltip!: string;
|
||||||
|
|
||||||
visible!: boolean;
|
visible!: boolean;
|
||||||
|
|
||||||
layerType!: LayerType;
|
layerType!: LayerType;
|
||||||
|
@ -410,36 +414,32 @@ export class ColorInput extends WidgetProps {
|
||||||
tooltip!: string;
|
tooltip!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MenuColumn = {
|
type MenuEntryCommon = {
|
||||||
label: string;
|
label: string;
|
||||||
children: MenuEntry[][];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MenuEntry = {
|
|
||||||
shortcut: ActionKeys | undefined;
|
|
||||||
action: Widget;
|
|
||||||
label: string;
|
|
||||||
icon: string | undefined;
|
|
||||||
children: undefined | MenuEntry[][];
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface MenuListEntryData<Value = string> {
|
|
||||||
value?: Value;
|
|
||||||
label?: string;
|
|
||||||
icon?: IconName;
|
icon?: IconName;
|
||||||
font?: URL;
|
|
||||||
shortcut?: ActionKeys;
|
shortcut?: ActionKeys;
|
||||||
shortcutRequiresLock?: boolean;
|
};
|
||||||
disabled?: boolean;
|
|
||||||
|
// The entry in the expanded menu or a sub-menu as received from the Rust backend
|
||||||
|
export type MenuBarEntry = MenuEntryCommon & {
|
||||||
|
action: Widget;
|
||||||
|
children?: MenuBarEntry[][];
|
||||||
|
};
|
||||||
|
|
||||||
|
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
|
||||||
|
export type MenuListEntry = MenuEntryCommon & {
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
children?: SectionsOfMenuListEntries;
|
children?: MenuListEntry[][];
|
||||||
}
|
|
||||||
export type MenuListEntry<Value = string> = MenuListEntryData<Value> & { ref?: typeof FloatingMenu | typeof MenuList };
|
shortcutRequiresLock?: boolean;
|
||||||
export type MenuListEntries<Value = string> = MenuListEntry<Value>[];
|
value?: string;
|
||||||
export type SectionsOfMenuListEntries<Value = string> = MenuListEntries<Value>[];
|
disabled?: boolean;
|
||||||
|
font?: URL;
|
||||||
|
ref?: InstanceType<typeof MenuList>;
|
||||||
|
};
|
||||||
|
|
||||||
export class DropdownInput extends WidgetProps {
|
export class DropdownInput extends WidgetProps {
|
||||||
entries!: SectionsOfMenuListEntries;
|
entries!: MenuListEntry[][];
|
||||||
|
|
||||||
selectedIndex!: number | undefined;
|
selectedIndex!: number | undefined;
|
||||||
|
|
||||||
|
@ -793,16 +793,19 @@ export class UpdateMenuBarLayout extends JsMessage {
|
||||||
// TODO: Replace `any` with correct typing
|
// TODO: Replace `any` with correct typing
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@Transform(({ value }: { value: any }) => createMenuLayout(value))
|
@Transform(({ value }: { value: any }) => createMenuLayout(value))
|
||||||
layout!: MenuColumn[];
|
layout!: MenuBarEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function createMenuLayout(menuLayout: any[]): MenuColumn[] {
|
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
|
||||||
return menuLayout.map((column) => ({ ...column, children: createMenuLayoutRecursive(column.children) }));
|
return menuBarEntry.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
children: createMenuLayoutRecursive(entry.children),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function createMenuLayoutRecursive(subLayout: any[][]): MenuEntry[][] {
|
function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
|
||||||
return subLayout.map((groups) =>
|
return children.map((groups) =>
|
||||||
groups.map((entry) => ({
|
groups.map((entry) => ({
|
||||||
...entry,
|
...entry,
|
||||||
action: hoistWidgetHolders([entry.action])[0],
|
action: hoistWidgetHolders([entry.action])[0],
|
||||||
|
|
|
@ -98,11 +98,11 @@ License information is required on production builds. Aborting.`);
|
||||||
let licenses = (rustLicenses || []).map((rustLicense) => ({
|
let licenses = (rustLicenses || []).map((rustLicense) => ({
|
||||||
licenseName: htmlDecode(rustLicense.licenseName),
|
licenseName: htmlDecode(rustLicense.licenseName),
|
||||||
licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)),
|
licenseText: trimBlankLines(htmlDecode(rustLicense.licenseText)),
|
||||||
packages: rustLicense.packages.map((package) => ({
|
packages: rustLicense.packages.map((packageInfo) => ({
|
||||||
name: htmlDecode(package.name),
|
name: htmlDecode(packageInfo.name),
|
||||||
version: htmlDecode(package.version),
|
version: htmlDecode(packageInfo.version),
|
||||||
author: htmlDecode(package.author).replace(/\[(.*), \]/, "$1"),
|
author: htmlDecode(packageInfo.author).replace(/\[(.*), \]/, "$1"),
|
||||||
repository: htmlDecode(package.repository),
|
repository: htmlDecode(packageInfo.repository),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ License information is required on production builds. Aborting.`);
|
||||||
|
|
||||||
// Delete the internal Graphite crates, which are not third-party and belong elsewhere
|
// Delete the internal Graphite crates, which are not third-party and belong elsewhere
|
||||||
licenses = licenses.filter((license) => {
|
licenses = licenses.filter((license) => {
|
||||||
license.packages = license.packages.filter((package) => !(package.repository && package.repository.includes("github.com/GraphiteEditor/Graphite")));
|
license.packages = license.packages.filter((packageInfo) => !(packageInfo.repository && packageInfo.repository.includes("github.com/GraphiteEditor/Graphite")));
|
||||||
return license.packages.length > 0;
|
return license.packages.length > 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,8 +151,8 @@ License information is required on production builds. Aborting.`);
|
||||||
|
|
||||||
licenses.forEach((license) => {
|
licenses.forEach((license) => {
|
||||||
let packagesWithSameLicense = "";
|
let packagesWithSameLicense = "";
|
||||||
license.packages.forEach((package) => {
|
license.packages.forEach((packageInfo) => {
|
||||||
const { name, version, author, repository } = package;
|
const { name, version, author, repository } = packageInfo;
|
||||||
packagesWithSameLicense += `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}\n`;
|
packagesWithSameLicense += `${name} ${version}${author ? ` - ${author}` : ""}${repository ? ` - ${repository}` : ""}\n`;
|
||||||
});
|
});
|
||||||
packagesWithSameLicense = packagesWithSameLicense.trim();
|
packagesWithSameLicense = packagesWithSameLicense.trim();
|
||||||
|
|
|
@ -137,6 +137,12 @@ impl JsEditorHandle {
|
||||||
EDITOR_HAS_CRASHED.load(Ordering::SeqCst)
|
EDITOR_HAS_CRASHED.load(Ordering::SeqCst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Answer whether or not the editor is in development mode
|
||||||
|
#[wasm_bindgen(js_name = inDevelopmentMode)]
|
||||||
|
pub fn in_development_mode(&self) -> bool {
|
||||||
|
cfg!(debug_assertions)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the constant `FILE_SAVE_SUFFIX`
|
/// Get the constant `FILE_SAVE_SUFFIX`
|
||||||
#[wasm_bindgen(js_name = fileSaveSuffix)]
|
#[wasm_bindgen(js_name = fileSaveSuffix)]
|
||||||
pub fn file_save_suffix(&self) -> String {
|
pub fn file_save_suffix(&self) -> String {
|
||||||
|
@ -218,12 +224,6 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(js_name = requestComingSoonDialog)]
|
|
||||||
pub fn request_coming_soon_dialog(&self, issue: Option<i32>) {
|
|
||||||
let message = DialogMessage::RequestComingSoonDialog { issue };
|
|
||||||
self.dispatch(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send new bounds when document panel viewports get resized or moved within the editor
|
/// Send new bounds when document panel viewports get resized or moved within the editor
|
||||||
/// [left, top, right, bottom]...
|
/// [left, top, right, bottom]...
|
||||||
#[wasm_bindgen(js_name = boundsOfViewports)]
|
#[wasm_bindgen(js_name = boundsOfViewports)]
|
||||||
|
@ -468,6 +468,7 @@ impl JsEditorHandle {
|
||||||
|
|
||||||
impl Drop for JsEditorHandle {
|
impl Drop for JsEditorHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
// Consider removing after https://github.com/rustwasm/wasm-bindgen/pull/2984 is merged and released
|
||||||
EDITOR_INSTANCES.with(|instances| instances.borrow_mut().remove(&self.editor_id));
|
EDITOR_INSTANCES.with(|instances| instances.borrow_mut().remove(&self.editor_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,6 +148,7 @@ impl Subpath {
|
||||||
// do row operations to eliminate `a` coefficients
|
// do row operations to eliminate `a` coefficients
|
||||||
c[0] /= -b[0];
|
c[0] /= -b[0];
|
||||||
d[0] /= -b[0];
|
d[0] /= -b[0];
|
||||||
|
#[allow(clippy::assign_op_pattern)]
|
||||||
for i in 1..n {
|
for i in 1..n {
|
||||||
b[i] += c[i - 1];
|
b[i] += c[i - 1];
|
||||||
// for some reason the below line makes the borrow checker mad
|
// for some reason the below line makes the borrow checker mad
|
||||||
|
@ -160,6 +161,7 @@ impl Subpath {
|
||||||
// at this point b[i] == -a[i + 1], a[i] == 0,
|
// at this point b[i] == -a[i + 1], a[i] == 0,
|
||||||
// do row operations to eliminate 'c' coefficients and solve
|
// do row operations to eliminate 'c' coefficients and solve
|
||||||
d[n - 1] *= -1.0;
|
d[n - 1] *= -1.0;
|
||||||
|
#[allow(clippy::assign_op_pattern)]
|
||||||
for i in (0..n - 1).rev() {
|
for i in (0..n - 1).rev() {
|
||||||
d[i] = d[i] - (c[i] * d[i + 1]);
|
d[i] = d[i] - (c[i] * d[i + 1]);
|
||||||
d[i] *= -1.0; //d[i] /= b[i]
|
d[i] *= -1.0; //d[i] /= b[i]
|
||||||
|
|
|
@ -42,6 +42,7 @@ impl Bezier {
|
||||||
|
|
||||||
// TODO: Consider removing this function
|
// TODO: Consider removing this function
|
||||||
/// Create a cubic bezier using the provided coordinates as the start, handles, and end points.
|
/// Create a cubic bezier using the provided coordinates as the start, handles, and end points.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {
|
pub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {
|
||||||
Bezier {
|
Bezier {
|
||||||
start: DVec2::new(x1, y1),
|
start: DVec2::new(x1, y1),
|
||||||
|
|
|
@ -52,7 +52,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_node() {
|
fn map_node() {
|
||||||
let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
// let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
||||||
(&GrayscaleNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.));
|
(&GrayscaleNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.));
|
||||||
/*let map = ForEachNode(MutWrapper(GrayscaleNode));
|
/*let map = ForEachNode(MutWrapper(GrayscaleNode));
|
||||||
(&map).eval(array.iter_mut());
|
(&map).eval(array.iter_mut());
|
||||||
|
|
|
@ -91,10 +91,10 @@ impl<'n, N: RefNode<Any<'n>, Output = Any<'n>> + 'n> DynNodeOwned<'n> for N {}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use graphene_core::ops::{AddNode, IdNode};
|
use graphene_core::ops::AddNode;
|
||||||
use graphene_core::value::ValueNode;
|
use graphene_core::value::ValueNode;
|
||||||
/*#[test]
|
/*#[test]
|
||||||
pub fn dyn_input_compositon() {
|
pub fn dyn_input_composition() {
|
||||||
use graphene_core::structural::After;
|
use graphene_core::structural::After;
|
||||||
use graphene_core::structural::ComposeNode;
|
use graphene_core::structural::ComposeNode;
|
||||||
let id: DynAnyNode<_, u32> = DynAnyNode::new(IdNode);
|
let id: DynAnyNode<_, u32> = DynAnyNode::new(IdNode);
|
||||||
|
|
|
@ -34,6 +34,13 @@ pub enum WasmMaximizeArcs {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WasmBezier(Bezier);
|
pub struct WasmBezier(Bezier);
|
||||||
|
|
||||||
|
impl Drop for WasmBezier {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Is it correct to keep this empty?
|
||||||
|
// Consider removing after https://github.com/rustwasm/wasm-bindgen/pull/2984 is merged and released
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a `DVec2` into a `Point`.
|
/// Convert a `DVec2` into a `Point`.
|
||||||
fn vec_to_point(p: &DVec2) -> Point {
|
fn vec_to_point(p: &DVec2) -> Point {
|
||||||
Point { x: p.x, y: p.y }
|
Point { x: p.x, y: p.y }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue