From 3247f499546e5e1c0a8004d477670de4bc589f0b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 12 Apr 2021 14:09:49 -0700 Subject: [PATCH] Allow menu items to specify arguments for their commands Co-Authored-By: Nathan Sobo --- gpui/src/app.rs | 12 +-- gpui/src/platform/mac/platform.rs | 30 ++++--- gpui/src/platform/mac/window.rs | 20 ++++- gpui/src/platform/mod.rs | 8 +- gpui/src/platform/test.rs | 11 ++- zed/src/lib.rs | 2 +- zed/src/main.rs | 43 +++++----- zed/src/menus.rs | 125 ++++++++++++++++-------------- 8 files changed, 149 insertions(+), 102 deletions(-) diff --git a/gpui/src/app.rs b/gpui/src/app.rs index e5f40c6ba5..9a8c00788d 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -69,7 +69,7 @@ pub trait UpdateView { pub struct Menu<'a> { pub name: &'a str, - pub items: &'a [MenuItem<'a>], + pub items: Vec>, } pub enum MenuItem<'a> { @@ -77,6 +77,7 @@ pub enum MenuItem<'a> { name: &'a str, keystroke: Option<&'a str>, action: &'a str, + arg: Option>, }, Separator, } @@ -171,14 +172,14 @@ impl App { pub fn on_menu_command(self, mut callback: F) -> Self where - F: 'static + FnMut(&str, &mut MutableAppContext), + F: 'static + FnMut(&str, Option<&dyn Any>, &mut MutableAppContext), { let ctx = self.0.clone(); self.0 .borrow() .platform - .on_menu_command(Box::new(move |command| { - callback(command, &mut *ctx.borrow_mut()) + .on_menu_command(Box::new(move |command, arg| { + callback(command, arg, &mut *ctx.borrow_mut()) })); self } @@ -197,7 +198,7 @@ impl App { self } - pub fn set_menus(&self, menus: &[Menu]) { + pub fn set_menus(&self, menus: Vec) { self.0.borrow().platform.set_menus(menus); } @@ -742,6 +743,7 @@ impl MutableAppContext { fn open_platform_window(&mut self, window_id: usize) { match self.platform.open_window( + window_id, WindowOptions { bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)), title: "Zed".into(), diff --git a/gpui/src/platform/mac/platform.rs b/gpui/src/platform/mac/platform.rs index 4201100a6d..cc70487d77 100644 --- a/gpui/src/platform/mac/platform.rs +++ b/gpui/src/platform/mac/platform.rs @@ -20,6 +20,7 @@ use objc::{ }; use ptr::null_mut; use std::{ + any::Any, cell::RefCell, ffi::{c_void, CStr}, os::raw::c_char, @@ -76,7 +77,7 @@ pub struct MacPlatform { dispatcher: Arc, fonts: Arc, callbacks: RefCell, - menu_item_actions: RefCell>, + menu_item_actions: RefCell>)>>, } #[derive(Default)] @@ -84,7 +85,7 @@ struct Callbacks { become_active: Option>, resign_active: Option>, event: Option bool>>, - menu_command: Option>, + menu_command: Option)>>, open_files: Option)>>, finish_launching: Option ()>>, } @@ -99,7 +100,7 @@ impl MacPlatform { } } - unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id { + unsafe fn create_menu_bar(&self, menus: Vec) -> id { let menu_bar = NSMenu::new(nil).autorelease(); let mut menu_item_actions = self.menu_item_actions.borrow_mut(); menu_item_actions.clear(); @@ -107,8 +108,9 @@ impl MacPlatform { for menu_config in menus { let menu_bar_item = NSMenuItem::new(nil).autorelease(); let menu = NSMenu::new(nil).autorelease(); + let menu_name = menu_config.name; - menu.setTitle_(ns_string(menu_config.name)); + menu.setTitle_(ns_string(menu_name)); for item_config in menu_config.items { let item; @@ -121,12 +123,13 @@ impl MacPlatform { name, keystroke, action, + arg, } => { if let Some(keystroke) = keystroke { let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| { panic!( "Invalid keystroke for menu item {}:{} - {:?}", - menu_config.name, name, err + menu_name, name, err ) }); @@ -161,7 +164,7 @@ impl MacPlatform { let tag = menu_item_actions.len() as NSInteger; let _: () = msg_send![item, setTag: tag]; - menu_item_actions.push(action.to_string()); + menu_item_actions.push((action.to_string(), arg)); } } @@ -189,7 +192,7 @@ impl platform::Platform for MacPlatform { self.callbacks.borrow_mut().event = Some(callback); } - fn on_menu_command(&self, callback: Box) { + fn on_menu_command(&self, callback: Box)>) { self.callbacks.borrow_mut().menu_command = Some(callback); } @@ -231,10 +234,15 @@ impl platform::Platform for MacPlatform { fn open_window( &self, + id: usize, options: platform::WindowOptions, executor: Rc, ) -> Result> { - Ok(Box::new(Window::open(options, executor, self.fonts())?)) + Ok(Box::new(Window::open(id, options, executor, self.fonts())?)) + } + + fn key_window_id(&self) -> Option { + Window::key_window_id() } fn prompt_for_paths( @@ -292,7 +300,7 @@ impl platform::Platform for MacPlatform { } } - fn set_menus(&self, menus: &[Menu]) { + fn set_menus(&self, menus: Vec) { unsafe { let app: id = msg_send![APP_CLASS, sharedApplication]; app.setMainMenu_(self.create_menu_bar(menus)); @@ -375,8 +383,8 @@ extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() { let tag: NSInteger = msg_send![item, tag]; let index = tag as usize; - if let Some(action) = platform.menu_item_actions.borrow().get(index) { - callback(&action); + if let Some((action, arg)) = platform.menu_item_actions.borrow().get(index) { + callback(action, arg.as_ref().map(Box::as_ref)); } } } diff --git a/gpui/src/platform/mac/window.rs b/gpui/src/platform/mac/window.rs index e9e6318b5d..940c00686c 100644 --- a/gpui/src/platform/mac/window.rs +++ b/gpui/src/platform/mac/window.rs @@ -7,8 +7,8 @@ use crate::{ use anyhow::{anyhow, Result}; use cocoa::{ appkit::{ - NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, - NSWindow, NSWindowStyleMask, + NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, + NSViewWidthSizable, NSWindow, NSWindowStyleMask, }, base::{id, nil}, foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString}, @@ -118,6 +118,7 @@ unsafe fn build_classes() { pub struct Window(Rc>); struct WindowState { + id: usize, native_window: id, event_callback: Option>, resize_callback: Option>, @@ -131,6 +132,7 @@ struct WindowState { impl Window { pub fn open( + id: usize, options: platform::WindowOptions, executor: Rc, fonts: Arc, @@ -180,6 +182,7 @@ impl Window { } let window = Self(Rc::new(RefCell::new(WindowState { + id, native_window, event_callback: None, resize_callback: None, @@ -230,6 +233,19 @@ impl Window { Ok(window) } } + + pub fn key_window_id() -> Option { + unsafe { + let app = NSApplication::sharedApplication(nil); + let key_window: id = msg_send![app, keyWindow]; + if key_window.is_null() { + None + } else { + let id = get_window_state(&*key_window).borrow().id; + Some(id) + } + } + } } impl Drop for Window { diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index 39825c941e..76d93c03be 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -20,10 +20,10 @@ use crate::{ use anyhow::Result; use async_task::Runnable; pub use event::Event; -use std::{ops::Range, path::PathBuf, rc::Rc, sync::Arc}; +use std::{any::Any, ops::Range, path::PathBuf, rc::Rc, sync::Arc}; pub trait Platform { - fn on_menu_command(&self, callback: Box); + fn on_menu_command(&self, callback: Box)>); fn on_become_active(&self, callback: Box); fn on_resign_active(&self, callback: Box); fn on_event(&self, callback: Box bool>); @@ -36,13 +36,15 @@ pub trait Platform { fn activate(&self, ignoring_other_apps: bool); fn open_window( &self, + id: usize, options: WindowOptions, executor: Rc, ) -> Result>; + fn key_window_id(&self) -> Option; fn prompt_for_paths(&self, options: PathPromptOptions) -> Option>; fn quit(&self); fn copy(&self, text: &str); - fn set_menus(&self, menus: &[Menu]); + fn set_menus(&self, menus: Vec); } pub trait Dispatcher: Send + Sync { diff --git a/gpui/src/platform/test.rs b/gpui/src/platform/test.rs index f1d6bead66..fb120a8eb5 100644 --- a/gpui/src/platform/test.rs +++ b/gpui/src/platform/test.rs @@ -1,6 +1,6 @@ use pathfinder_geometry::vector::Vector2F; -use std::rc::Rc; use std::sync::Arc; +use std::{any::Any, rc::Rc}; struct Platform { dispatcher: Arc, @@ -27,7 +27,7 @@ impl Platform { } impl super::Platform for Platform { - fn on_menu_command(&self, _: Box) {} + fn on_menu_command(&self, _: Box)>) {} fn on_become_active(&self, _: Box) {} @@ -53,13 +53,18 @@ impl super::Platform for Platform { fn open_window( &self, + _: usize, options: super::WindowOptions, _executor: Rc, ) -> anyhow::Result> { Ok(Box::new(Window::new(options.bounds.size()))) } - fn set_menus(&self, _menus: &[crate::Menu]) {} + fn key_window_id(&self) -> Option { + None + } + + fn set_menus(&self, _menus: Vec) {} fn quit(&self) {} diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 752df470c5..14c2369258 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -10,6 +10,6 @@ mod test; mod time; mod timer; mod util; -mod watch; +pub mod watch; pub mod workspace; mod worktree; diff --git a/zed/src/main.rs b/zed/src/main.rs index 407b4952c1..980262d197 100644 --- a/zed/src/main.rs +++ b/zed/src/main.rs @@ -4,7 +4,9 @@ use log::LevelFilter; use simplelog::SimpleLogger; use std::{fs, path::PathBuf}; use zed::{ - assets, editor, file_finder, menus, settings, + assets, editor, file_finder, menus, + settings::{self, Settings}, + watch::Receiver, workspace::{self, OpenParams}, }; @@ -13,27 +15,28 @@ fn main() { let app = gpui::App::new(assets::Assets).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); - app.set_menus(menus::MENUS); - app.on_menu_command({ - let settings_rx = settings_rx.clone(); - move |command, ctx| match command { - "app:open" => { - if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions { - files: true, - directories: true, - multiple: true, - }) { - ctx.dispatch_global_action( - "workspace:open_paths", - OpenParams { - paths, - settings: settings_rx.clone(), - }, - ); - } + app.set_menus(menus::menus(settings_rx.clone())); + app.on_menu_command(move |command, arg, ctx| match command { + "app:open" => { + if let Some(paths) = ctx.platform().prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }) { + ctx.dispatch_global_action( + "workspace:open_paths", + OpenParams { + paths, + settings: arg + .unwrap() + .downcast_ref::>() + .unwrap() + .clone(), + }, + ); } - _ => ctx.dispatch_global_action(command, ()), } + _ => ctx.dispatch_global_action(command, ()), }) .run(move |ctx| { workspace::init(ctx); diff --git a/zed/src/menus.rs b/zed/src/menus.rs index cda749c30e..7da690972e 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -1,60 +1,71 @@ +use crate::{settings::Settings, watch::Receiver}; use gpui::{Menu, MenuItem}; #[cfg(target_os = "macos")] -pub const MENUS: &'static [Menu] = &[ - Menu { - name: "Zed", - items: &[ - MenuItem::Action { - name: "About Zed…", - keystroke: None, - action: "app:about-zed", - }, - MenuItem::Separator, - MenuItem::Action { - name: "Quit", - keystroke: Some("cmd-q"), - action: "app:quit", - }, - ], - }, - Menu { - name: "File", - items: &[MenuItem::Action { - name: "Open…", - keystroke: Some("cmd-o"), - action: "app:open", - }], - }, - Menu { - name: "Edit", - items: &[ - MenuItem::Action { - name: "Undo", - keystroke: Some("cmd-z"), - action: "editor:undo", - }, - MenuItem::Action { - name: "Redo", - keystroke: Some("cmd-Z"), - action: "editor:redo", - }, - MenuItem::Separator, - MenuItem::Action { - name: "Cut", - keystroke: Some("cmd-x"), - action: "editor:cut", - }, - MenuItem::Action { - name: "Copy", - keystroke: Some("cmd-c"), - action: "editor:copy", - }, - MenuItem::Action { - name: "Paste", - keystroke: Some("cmd-v"), - action: "editor:paste", - }, - ], - }, -]; +pub fn menus(settings: Receiver) -> Vec> { + vec![ + Menu { + name: "Zed", + items: vec![ + MenuItem::Action { + name: "About Zed…", + keystroke: None, + action: "app:about-zed", + arg: None, + }, + MenuItem::Separator, + MenuItem::Action { + name: "Quit", + keystroke: Some("cmd-q"), + action: "app:quit", + arg: None, + }, + ], + }, + Menu { + name: "File", + items: vec![MenuItem::Action { + name: "Open…", + keystroke: Some("cmd-o"), + action: "app:open", + arg: Some(Box::new(settings)), + }], + }, + Menu { + name: "Edit", + items: vec![ + MenuItem::Action { + name: "Undo", + keystroke: Some("cmd-z"), + action: "editor:undo", + arg: None, + }, + MenuItem::Action { + name: "Redo", + keystroke: Some("cmd-Z"), + action: "editor:redo", + arg: None, + }, + MenuItem::Separator, + MenuItem::Action { + name: "Cut", + keystroke: Some("cmd-x"), + action: "editor:cut", + arg: None, + }, + MenuItem::Action { + name: "Copy", + keystroke: Some("cmd-c"), + action: "editor:copy", + arg: None, + }, + MenuItem::Action { + name: "Paste", + keystroke: Some("cmd-v"), + action: "editor:paste", + arg: None, + }, + ], + }, + ] +}