mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 05:18:19 +00:00
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:
parent
173398ad55
commit
9c2520111d
7 changed files with 137 additions and 78 deletions
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()];
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue