Minor Tooling and UI Improvements (#1314)

* [wip]fix: disable fill and gradient tools on bmp images
- aims to fix #1161

* [wip]fix: disable gradient tool for bmp images

* fix: disable gradient tool for bitmap images
- fixes https://github.com/GraphiteEditor/Graphite/issues/1161

* [wip]fix: disable menu bar elements if no open document

* [WIP]feat: render certain menubar elements if document is open

* fix: conditional menu bar rendering fixes
- update menu bar on certain document actions
- fix menu bar order on rendering all elements

* refactor: change conditional menu elements property
- use existing 'disabled' property

* feat: render menu dropdown items conditionally

* fix: revert formatter whitespace changes

* refactor: improve variable naming and message usage
- name menu bar bool flag better
- use SendLayout message and remove unneeded calls
This commit is contained in:
Dhruv 2023-06-24 01:49:00 +05:30 committed by Keavon Chambers
parent 173398ad55
commit 9c2520111d
7 changed files with 137 additions and 78 deletions

View file

@ -37,12 +37,14 @@ pub struct MenuBarEntry {
pub shortcut: Option<ActionKeys>,
pub action: WidgetHolder,
pub children: MenuBarEntryChildren,
pub disabled: bool,
}
impl MenuBarEntry {
pub fn new_root(label: String, children: MenuBarEntryChildren) -> Self {
pub fn new_root(label: String, disabled: bool, children: MenuBarEntryChildren) -> Self {
Self {
label,
disabled,
children,
..Default::default()
}
@ -67,6 +69,7 @@ impl Default for MenuBarEntry {
shortcut: None,
action: MenuBarEntry::no_action(),
children: MenuBarEntryChildren::empty(),
disabled: false,
}
}
}

View file

@ -6,12 +6,15 @@ use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
use crate::messages::prelude::*;
#[derive(Debug, Clone, Default)]
pub struct MenuBarMessageHandler {}
pub struct MenuBarMessageHandler {
no_active_document: bool,
}
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
impl MessageHandler<MenuBarMessage, bool> for MenuBarMessageHandler {
#[remain::check]
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, has_active_document: bool) {
use MenuBarMessage::*;
self.no_active_document = !has_active_document;
#[remain::sorted]
match message {
@ -26,75 +29,13 @@ impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
impl PropertyHolder for MenuBarMessageHandler {
fn properties(&self) -> Layout {
Layout::MenuLayout(MenuLayout::new(vec![
MenuBarEntry {
icon: Some("GraphiteLogo".into()),
action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()),
..Default::default()
},
MenuBarEntry::new_root(
"File".into(),
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "New…".into(),
icon: Some("File".into()),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog),
children: MenuBarEntryChildren::empty(),
},
MenuBarEntry {
label: "Open…".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Close".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
action: MenuBarEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Close All".into(),
shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
action: MenuBarEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Save".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
..MenuBarEntry::default()
}],
vec![
MenuBarEntry {
label: "Import…".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::Import),
action: MenuBarEntry::create_action(|_| PortfolioMessage::Import.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Export…".into(),
shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Preferences…".into(),
icon: Some("Settings".into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()),
..MenuBarEntry::default()
}],
]),
),
let no_active_document = self.no_active_document;
// enable these only if there's an active document
let conditional_menu_entries = vec![
MenuBarEntry::new_root(
"Edit".into(),
no_active_document,
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
@ -136,6 +77,7 @@ impl PropertyHolder for MenuBarMessageHandler {
),
MenuBarEntry::new_root(
"Layer".into(),
no_active_document,
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
@ -213,6 +155,7 @@ impl PropertyHolder for MenuBarMessageHandler {
),
MenuBarEntry::new_root(
"Document".into(),
no_active_document,
MenuBarEntryChildren(vec![vec![MenuBarEntry {
label: "Clear Artboards".into(),
action: MenuBarEntry::create_action(|_| ArtboardMessage::ClearArtboards.into()),
@ -221,6 +164,7 @@ impl PropertyHolder for MenuBarMessageHandler {
),
MenuBarEntry::new_root(
"View".into(),
no_active_document,
MenuBarEntryChildren(vec![vec![
MenuBarEntry {
label: "Zoom to Selected".into(),
@ -248,8 +192,84 @@ impl PropertyHolder for MenuBarMessageHandler {
},
]]),
),
];
let mut menu_bar_entries = vec![
MenuBarEntry {
icon: Some("GraphiteLogo".into()),
action: MenuBarEntry::create_action(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.rs".into() }.into()),
..Default::default()
},
MenuBarEntry::new_root(
"File".into(),
false,
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "New…".into(),
icon: Some("File".into()),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestNewDocumentDialog.into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestNewDocumentDialog),
children: MenuBarEntryChildren::empty(),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Open…".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::OpenDocument),
action: MenuBarEntry::create_action(|_| PortfolioMessage::OpenDocument.into()),
..MenuBarEntry::default()
},
],
vec![
MenuBarEntry {
label: "Close".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::CloseActiveDocumentWithConfirmation),
action: MenuBarEntry::create_action(|_| PortfolioMessage::CloseActiveDocumentWithConfirmation.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Close All".into(),
shortcut: action_keys!(DialogMessageDiscriminant::CloseAllDocumentsWithConfirmation),
action: MenuBarEntry::create_action(|_| DialogMessage::CloseAllDocumentsWithConfirmation.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Save".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::SaveDocument),
action: MenuBarEntry::create_action(|_| DocumentMessage::SaveDocument.into()),
disabled: no_active_document,
..MenuBarEntry::default()
}],
vec![
MenuBarEntry {
label: "Import…".into(),
shortcut: action_keys!(PortfolioMessageDiscriminant::Import),
action: MenuBarEntry::create_action(|_| PortfolioMessage::Import.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Export…".into(),
shortcut: action_keys!(DialogMessageDiscriminant::RequestExportDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestExportDialog.into()),
disabled: no_active_document,
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Preferences…".into(),
icon: Some("Settings".into()),
shortcut: action_keys!(DialogMessageDiscriminant::RequestPreferencesDialog),
action: MenuBarEntry::create_action(|_| DialogMessage::RequestPreferencesDialog.into()),
..MenuBarEntry::default()
}],
]),
),
MenuBarEntry::new_root(
"Help".into(),
true,
MenuBarEntryChildren(vec![
vec![MenuBarEntry {
label: "About Graphite".into(),
@ -327,6 +347,11 @@ impl PropertyHolder for MenuBarMessageHandler {
],
]),
),
]))
];
if !no_active_document {
menu_bar_entries.splice(2..2, conditional_menu_entries);
}
Layout::MenuLayout(MenuLayout::new(menu_bar_entries))
}
}

View file

@ -33,11 +33,13 @@ pub struct PortfolioMessageHandler {
impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)> for PortfolioMessageHandler {
#[remain::check]
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, (ipp, preferences): (&InputPreprocessorMessageHandler, &PreferencesMessageHandler)) {
let has_active_document = self.active_document_id.is_some();
#[remain::sorted]
match message {
// Sub-messages
#[remain::unsorted]
PortfolioMessage::MenuBar(message) => self.menu_bar_message_handler.process_message(message, responses, ()),
PortfolioMessage::MenuBar(message) => self.menu_bar_message_handler.process_message(message, responses, has_active_document),
#[remain::unsorted]
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
@ -109,6 +111,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
let hint_data = HintData(vec![HintGroup(vec![])]);
responses.add(FrontendMessage::UpdateInputHints { hint_data });
}
// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)
responses.add(PortfolioMessage::DeleteDocument { document_id });
@ -179,6 +182,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
if self.document_ids.is_empty() {
self.active_document_id = None;
responses.add(MenuBarMessage::SendLayout);
} else if self.active_document_id.is_some() {
let document_id = if document_index == self.document_ids.len() {
// If we closed the last document take the one previous (same as last)
@ -195,6 +199,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
self.documents.clear();
self.document_ids.clear();
self.active_document_id = None;
responses.add(MenuBarMessage::SendLayout);
}
PortfolioMessage::FontLoaded {
font_family,
@ -462,7 +467,10 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
}
PortfolioMessage::SetActiveDocument { document_id } => self.active_document_id = Some(document_id),
PortfolioMessage::SetActiveDocument { document_id } => {
self.active_document_id = Some(document_id);
responses.add(MenuBarMessage::SendLayout);
}
PortfolioMessage::SetImageBlobUrl {
document_id,
layer_path,

View file

@ -7,6 +7,7 @@ use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHan
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::layers::style::Fill;
use glam::DVec2;
@ -103,6 +104,17 @@ impl Fsm for FillToolFsmState {
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
if let Some(path) = document.document_legacy.intersects_quad_root(quad, render_data).last() {
let is_bitmap = document
.document_legacy
.layer(path)
.ok()
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap {
return self;
}
let color = match lmb_or_rmb {
LeftPointerDown => global_tool_data.primary_color,
RightPointerDown => global_tool_data.secondary_color,

View file

@ -11,6 +11,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad;
use document_legacy::layers::layer_info::Layer;
use document_legacy::layers::layer_layer::CachedOutputData;
use document_legacy::layers::style::{Fill, Gradient, GradientType, PathStyle, RenderData, Stroke};
use document_legacy::LayerId;
use document_legacy::Operation;
@ -420,9 +421,6 @@ impl Fsm for GradientToolFsmState {
}
for path in document.selected_visible_layers() {
if !document.document_legacy.multiply_transforms(path).unwrap().inverse().is_finite() {
continue;
}
let layer = document.document_legacy.layer(path).unwrap();
if let Ok(Fill::Gradient(gradient)) = layer.style().map(|style| style.fill()) {
@ -568,7 +566,18 @@ impl Fsm for GradientToolFsmState {
let quad = Quad::from_box([input.mouse.position - tolerance, input.mouse.position + tolerance]);
let intersection = document.document_legacy.intersects_quad_root(quad, render_data).pop();
// the intersection is the layer where the gradient is being applied
if let Some(intersection) = intersection {
let is_bitmap = document
.document_legacy
.layer(&intersection)
.ok()
.and_then(|layer| layer.as_layer().ok())
.map_or(false, |layer| matches!(layer.cached_output_data, CachedOutputData::BlobURL(_) | CachedOutputData::SurfaceId(_)));
if is_bitmap {
return self;
}
if !document.selected_layers_contains(&intersection) {
let replacement_selected_layers = vec![intersection.clone()];

View file

@ -63,7 +63,7 @@
// New fields in `MenuListEntry`
shortcutRequiresLock: entry.shortcut ? shortcutRequiresLock(entry.shortcut.keys) : undefined,
value: undefined,
disabled: undefined,
disabled: entry.disabled ?? undefined,
font: undefined,
ref: undefined,
});

View file

@ -774,6 +774,7 @@ type MenuEntryCommon = {
export type MenuBarEntry = MenuEntryCommon & {
action: Widget;
children?: MenuBarEntry[][];
disabled?: boolean,
};
// An entry in the all-encompassing MenuList component which defines all types of menus ranging from `MenuBarInput` to `DropdownInput` widgets
@ -1300,6 +1301,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
...entry,
action: hoistWidgetHolders([entry.action])[0],
children: entry.children ? createMenuLayoutRecursive(entry.children) : undefined,
disabled: entry.disabled ?? false,
}))
);
}