Start on app menus
This commit is contained in:
parent
dffe0ea058
commit
631e264e3c
8 changed files with 272 additions and 116 deletions
|
@ -425,6 +425,10 @@ impl AppContext {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||||
|
self.platform.active_window()
|
||||||
|
}
|
||||||
|
|
||||||
/// Opens a new window with the given option and the root view returned by the given function.
|
/// Opens a new window with the given option and the root view returned by the given function.
|
||||||
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
|
/// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
|
||||||
/// functionality.
|
/// functionality.
|
||||||
|
@ -1015,6 +1019,42 @@ impl AppContext {
|
||||||
activate();
|
activate();
|
||||||
subscription
|
subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear_pending_keystrokes(&mut self) {
|
||||||
|
for window in self.windows() {
|
||||||
|
window
|
||||||
|
.update(self, |_, cx| {
|
||||||
|
cx.window
|
||||||
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.clear_pending_keystrokes()
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_action_available(&mut self, action: &dyn Action) -> bool {
|
||||||
|
if let Some(window) = self.active_window() {
|
||||||
|
let window_action_available = window
|
||||||
|
.update(self, |_, cx| {
|
||||||
|
if let Some(focus_id) = cx.window.focus {
|
||||||
|
cx.window
|
||||||
|
.current_frame
|
||||||
|
.dispatch_tree
|
||||||
|
.is_action_available(action, focus_id)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
if window_action_available {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.global_action_listeners
|
||||||
|
.contains_key(&action.as_any().type_id())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AppContext {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -82,13 +82,13 @@ impl DispatchTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_keystroke_matchers(&mut self) {
|
pub fn clear_pending_keystrokes(&mut self) {
|
||||||
self.keystroke_matchers.clear();
|
self.keystroke_matchers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Preserve keystroke matchers from previous frames to support multi-stroke
|
/// Preserve keystroke matchers from previous frames to support multi-stroke
|
||||||
/// bindings across multiple frames.
|
/// bindings across multiple frames.
|
||||||
pub fn preserve_keystroke_matchers(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
|
pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
|
||||||
if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
|
if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
|
||||||
let dispatch_path = self.dispatch_path(node_id);
|
let dispatch_path = self.dispatch_path(node_id);
|
||||||
|
|
||||||
|
@ -163,6 +163,22 @@ impl DispatchTree {
|
||||||
actions
|
actions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_action_available(&self, action: &dyn Action, target: FocusId) -> bool {
|
||||||
|
if let Some(node) = self.focusable_node_ids.get(&target) {
|
||||||
|
for node_id in self.dispatch_path(*node) {
|
||||||
|
let node = &self.nodes[node_id.0];
|
||||||
|
if node
|
||||||
|
.action_listeners
|
||||||
|
.iter()
|
||||||
|
.any(|listener| listener.action_type == action.as_any().type_id())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bindings_for_action(
|
pub fn bindings_for_action(
|
||||||
&self,
|
&self,
|
||||||
action: &dyn Action,
|
action: &dyn Action,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod app_menu;
|
||||||
mod keystroke;
|
mod keystroke;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod mac;
|
mod mac;
|
||||||
|
@ -32,6 +33,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub use app_menu::*;
|
||||||
pub use keystroke::*;
|
pub use keystroke::*;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub use mac::*;
|
pub use mac::*;
|
||||||
|
@ -59,7 +61,7 @@ pub trait Platform: 'static {
|
||||||
|
|
||||||
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
||||||
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
|
||||||
fn main_window(&self) -> Option<AnyWindowHandle>;
|
fn active_window(&self) -> Option<AnyWindowHandle>;
|
||||||
fn open_window(
|
fn open_window(
|
||||||
&self,
|
&self,
|
||||||
handle: AnyWindowHandle,
|
handle: AnyWindowHandle,
|
||||||
|
|
96
crates/gpui2/src/platform/app_menu.rs
Normal file
96
crates/gpui2/src/platform/app_menu.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use crate::{Action, AppContext, Platform};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
pub struct Menu<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub items: Vec<MenuItem<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum MenuItem<'a> {
|
||||||
|
Separator,
|
||||||
|
Submenu(Menu<'a>),
|
||||||
|
Action {
|
||||||
|
name: &'a str,
|
||||||
|
action: Box<dyn Action>,
|
||||||
|
os_action: Option<OsAction>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MenuItem<'a> {
|
||||||
|
pub fn separator() -> Self {
|
||||||
|
Self::Separator
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submenu(menu: Menu<'a>) -> Self {
|
||||||
|
Self::Submenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action(name: &'a str, action: impl Action) -> Self {
|
||||||
|
Self::Action {
|
||||||
|
name,
|
||||||
|
action: Box::new(action),
|
||||||
|
os_action: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
|
||||||
|
Self::Action {
|
||||||
|
name,
|
||||||
|
action: Box::new(action),
|
||||||
|
os_action: Some(os_action),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum OsAction {
|
||||||
|
Cut,
|
||||||
|
Copy,
|
||||||
|
Paste,
|
||||||
|
SelectAll,
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init(platform: &dyn Platform, cx: &mut AppContext) {
|
||||||
|
platform.on_will_open_menu(Box::new({
|
||||||
|
let cx = cx.to_async();
|
||||||
|
move || {
|
||||||
|
cx.update(|cx| cx.clear_pending_keystrokes()).ok();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
platform.on_validate_menu_command(Box::new({
|
||||||
|
let cx = cx.to_async();
|
||||||
|
move |action| {
|
||||||
|
cx.update(|cx| cx.is_action_available(action))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
platform.on_menu_command(Box::new({
|
||||||
|
let cx = cx.to_async();
|
||||||
|
move |action| {
|
||||||
|
cx.update(|cx| {
|
||||||
|
// if let Some(main_window) = cx.active_window() {
|
||||||
|
// let dispatched = main_window
|
||||||
|
// .update(&mut *cx, |cx| {
|
||||||
|
// if let Some(view_id) = cx.focused_view_id() {
|
||||||
|
// cx.dispatch_action(Some(view_id), action);
|
||||||
|
// true
|
||||||
|
// } else {
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .unwrap_or(false);
|
||||||
|
|
||||||
|
// if dispatched {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// cx.dispatch_global_action_any(action);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
|
@ -1,16 +1,17 @@
|
||||||
use super::BoolExt;
|
use super::BoolExt;
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
|
||||||
ForegroundExecutor, InputEvent, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem,
|
ForegroundExecutor, InputEvent, KeystrokeMatcher, MacDispatcher, MacDisplay, MacDisplayLinker,
|
||||||
MacWindow, PathPromptOptions, Platform, PlatformDisplay, PlatformTextSystem, PlatformWindow,
|
MacTextSystem, MacWindow, MenuItem, PathPromptOptions, Platform, PlatformDisplay,
|
||||||
Result, SemanticVersion, VideoTimestamp, WindowOptions,
|
PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{
|
appkit::{
|
||||||
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||||
NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
|
NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString,
|
||||||
|
NSSavePanel, NSWindow,
|
||||||
},
|
},
|
||||||
base::{id, nil, BOOL, YES},
|
base::{id, nil, BOOL, YES},
|
||||||
foundation::{
|
foundation::{
|
||||||
|
@ -237,13 +238,14 @@ impl MacPlatform {
|
||||||
// application_menu
|
// application_menu
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// unsafe fn create_menu_item(
|
unsafe fn create_menu_item(
|
||||||
// &self,
|
&self,
|
||||||
// item: MenuItem,
|
item: MenuItem,
|
||||||
// delegate: id,
|
delegate: id,
|
||||||
// actions: &mut Vec<Box<dyn Action>>,
|
actions: &mut Vec<Box<dyn Action>>,
|
||||||
// keystroke_matcher: &KeymapMatcher,
|
keystroke_matcher: &KeystrokeMatcher,
|
||||||
// ) -> id {
|
) -> id {
|
||||||
|
todo!()
|
||||||
// match item {
|
// match item {
|
||||||
// MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
// MenuItem::Separator => NSMenuItem::separatorItem(nil),
|
||||||
// MenuItem::Action {
|
// MenuItem::Action {
|
||||||
|
@ -344,7 +346,7 @@ impl MacPlatform {
|
||||||
// item
|
// item
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for MacPlatform {
|
impl Platform for MacPlatform {
|
||||||
|
@ -479,8 +481,8 @@ impl Platform for MacPlatform {
|
||||||
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_window(&self) -> Option<AnyWindowHandle> {
|
fn active_window(&self) -> Option<AnyWindowHandle> {
|
||||||
MacWindow::main_window()
|
MacWindow::active_window()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_window(
|
fn open_window(
|
||||||
|
|
|
@ -662,7 +662,7 @@ impl MacWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main_window() -> Option<AnyWindowHandle> {
|
pub fn active_window() -> Option<AnyWindowHandle> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app = NSApplication::sharedApplication(nil);
|
let app = NSApplication::sharedApplication(nil);
|
||||||
let main_window: id = msg_send![app, mainWindow];
|
let main_window: id = msg_send![app, mainWindow];
|
||||||
|
|
|
@ -127,7 +127,7 @@ impl Platform for TestPlatform {
|
||||||
self.displays().iter().find(|d| d.id() == id).cloned()
|
self.displays().iter().find(|d| d.id() == id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
|
fn active_window(&self) -> Option<crate::AnyWindowHandle> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -430,7 +430,7 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window
|
self.window
|
||||||
.current_frame
|
.current_frame
|
||||||
.dispatch_tree
|
.dispatch_tree
|
||||||
.clear_keystroke_matchers();
|
.clear_pending_keystrokes();
|
||||||
self.app.push_effect(Effect::FocusChanged {
|
self.app.push_effect(Effect::FocusChanged {
|
||||||
window_handle: self.window.handle,
|
window_handle: self.window.handle,
|
||||||
focused: Some(focus_id),
|
focused: Some(focus_id),
|
||||||
|
@ -1177,7 +1177,7 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window
|
self.window
|
||||||
.current_frame
|
.current_frame
|
||||||
.dispatch_tree
|
.dispatch_tree
|
||||||
.preserve_keystroke_matchers(
|
.preserve_pending_keystrokes(
|
||||||
&mut self.window.previous_frame.dispatch_tree,
|
&mut self.window.previous_frame.dispatch_tree,
|
||||||
self.window.focus,
|
self.window.focus,
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue