Merge pull request #14 from zed-industries/menu-commands

Make the application menu dispatch commands on the focused view
This commit is contained in:
Max Brunsfeld 2021-04-12 16:40:48 -07:00 committed by GitHub
commit 0d69b632b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 250 deletions

View file

@ -69,7 +69,7 @@ pub trait UpdateView {
pub struct Menu<'a> { pub struct Menu<'a> {
pub name: &'a str, pub name: &'a str,
pub items: &'a [MenuItem<'a>], pub items: Vec<MenuItem<'a>>,
} }
pub enum MenuItem<'a> { pub enum MenuItem<'a> {
@ -77,6 +77,7 @@ pub enum MenuItem<'a> {
name: &'a str, name: &'a str,
keystroke: Option<&'a str>, keystroke: Option<&'a str>,
action: &'a str, action: &'a str,
arg: Option<Box<dyn Any + 'static>>,
}, },
Separator, Separator,
} }
@ -127,9 +128,27 @@ impl App {
let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?); let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
let app = Self(Rc::new(RefCell::new(MutableAppContext::new( let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
foreground, foreground,
platform, platform.clone(),
asset_source, asset_source,
)))); ))));
let ctx = app.0.clone();
platform.on_menu_command(Box::new(move |command, arg| {
let mut ctx = ctx.borrow_mut();
if let Some(key_window_id) = ctx.platform.key_window_id() {
if let Some((presenter, _)) =
ctx.presenters_and_platform_windows.get(&key_window_id)
{
let presenter = presenter.clone();
let path = presenter.borrow().dispatch_path(ctx.as_ref());
if ctx.dispatch_action_any(key_window_id, &path, command, arg.unwrap_or(&())) {
return;
}
}
}
ctx.dispatch_global_action_any(command, arg.unwrap_or(&()));
}));
app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0));
Ok(app) Ok(app)
} }
@ -169,20 +188,6 @@ impl App {
self self
} }
pub fn on_menu_command<F>(self, mut callback: F) -> Self
where
F: 'static + FnMut(&str, &mut MutableAppContext),
{
let ctx = self.0.clone();
self.0
.borrow()
.platform
.on_menu_command(Box::new(move |command| {
callback(command, &mut *ctx.borrow_mut())
}));
self
}
pub fn on_open_files<F>(self, mut callback: F) -> Self pub fn on_open_files<F>(self, mut callback: F) -> Self
where where
F: 'static + FnMut(Vec<PathBuf>, &mut MutableAppContext), F: 'static + FnMut(Vec<PathBuf>, &mut MutableAppContext),
@ -197,10 +202,6 @@ impl App {
self self
} }
pub fn set_menus(&self, menus: &[Menu]) {
self.0.borrow().platform.set_menus(menus);
}
pub fn run<F>(self, on_finish_launching: F) pub fn run<F>(self, on_finish_launching: F)
where where
F: 'static + FnOnce(&mut MutableAppContext), F: 'static + FnOnce(&mut MutableAppContext),
@ -383,8 +384,8 @@ pub struct MutableAppContext {
subscriptions: HashMap<usize, Vec<Subscription>>, subscriptions: HashMap<usize, Vec<Subscription>>,
observations: HashMap<usize, Vec<Observation>>, observations: HashMap<usize, Vec<Observation>>,
window_invalidations: HashMap<usize, WindowInvalidation>, window_invalidations: HashMap<usize, WindowInvalidation>,
invalidation_callbacks: presenters_and_platform_windows:
HashMap<usize, Box<dyn FnMut(WindowInvalidation, &mut MutableAppContext)>>, HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>, debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
foreground: Rc<executor::Foreground>, foreground: Rc<executor::Foreground>,
future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>, future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
@ -422,7 +423,7 @@ impl MutableAppContext {
subscriptions: HashMap::new(), subscriptions: HashMap::new(),
observations: HashMap::new(), observations: HashMap::new(),
window_invalidations: HashMap::new(), window_invalidations: HashMap::new(),
invalidation_callbacks: HashMap::new(), presenters_and_platform_windows: HashMap::new(),
debug_elements_callbacks: HashMap::new(), debug_elements_callbacks: HashMap::new(),
foreground, foreground,
future_handlers: Default::default(), future_handlers: Default::default(),
@ -454,15 +455,6 @@ impl MutableAppContext {
&self.ctx.background &self.ctx.background
} }
pub fn on_window_invalidated<F>(&mut self, window_id: usize, callback: F)
where
F: 'static + FnMut(WindowInvalidation, &mut MutableAppContext),
{
self.invalidation_callbacks
.insert(window_id, Box::new(callback));
self.update_windows();
}
pub fn on_debug_elements<F>(&mut self, window_id: usize, callback: F) pub fn on_debug_elements<F>(&mut self, window_id: usize, callback: F)
where where
F: 'static + Fn(&AppContext) -> crate::json::Value, F: 'static + Fn(&AppContext) -> crate::json::Value,
@ -573,6 +565,10 @@ impl MutableAppContext {
result result
} }
pub fn set_menus(&self, menus: Vec<Menu>) {
self.platform.set_menus(menus);
}
pub fn dispatch_action<T: 'static + Any>( pub fn dispatch_action<T: 'static + Any>(
&mut self, &mut self,
window_id: usize, window_id: usize,
@ -634,7 +630,7 @@ impl MutableAppContext {
} }
if !halted_dispatch { if !halted_dispatch {
self.dispatch_global_action_with_dyn_arg(name, arg); self.dispatch_global_action_any(name, arg);
} }
self.flush_effects(); self.flush_effects();
@ -642,10 +638,10 @@ impl MutableAppContext {
} }
pub fn dispatch_global_action<T: 'static + Any>(&mut self, name: &str, arg: T) { pub fn dispatch_global_action<T: 'static + Any>(&mut self, name: &str, arg: T) {
self.dispatch_global_action_with_dyn_arg(name, Box::new(arg).as_ref()); self.dispatch_global_action_any(name, Box::new(arg).as_ref());
} }
fn dispatch_global_action_with_dyn_arg(&mut self, name: &str, arg: &dyn Any) { fn dispatch_global_action_any(&mut self, name: &str, arg: &dyn Any) {
if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) { if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) {
self.pending_flushes += 1; self.pending_flushes += 1;
for handler in handlers.iter_mut().rev() { for handler in handlers.iter_mut().rev() {
@ -741,87 +737,75 @@ impl MutableAppContext {
} }
fn open_platform_window(&mut self, window_id: usize) { fn open_platform_window(&mut self, window_id: usize) {
match self.platform.open_window( let mut window = self.platform.open_window(
window_id,
WindowOptions { WindowOptions {
bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)), bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)),
title: "Zed".into(), title: "Zed".into(),
}, },
self.foreground.clone(), self.foreground.clone(),
) { );
Err(e) => log::error!("error opening window: {}", e), let text_layout_cache = TextLayoutCache::new(self.platform.fonts());
Ok(mut window) => { let presenter = Rc::new(RefCell::new(Presenter::new(
let text_layout_cache = TextLayoutCache::new(self.platform.fonts()); window_id,
let presenter = Rc::new(RefCell::new(Presenter::new( self.font_cache.clone(),
window_id, text_layout_cache,
self.font_cache.clone(), self.assets.clone(),
text_layout_cache, self,
self.assets.clone(), )));
self,
)));
{ {
let mut app = self.upgrade(); let mut app = self.upgrade();
let presenter = presenter.clone(); let presenter = presenter.clone();
window.on_event(Box::new(move |event| { window.on_event(Box::new(move |event| {
app.update(|ctx| { app.update(|ctx| {
if let Event::KeyDown { keystroke, .. } = &event { if let Event::KeyDown { keystroke, .. } = &event {
if ctx if ctx
.dispatch_keystroke( .dispatch_keystroke(
window_id, window_id,
presenter.borrow().dispatch_path(ctx.as_ref()), presenter.borrow().dispatch_path(ctx.as_ref()),
keystroke, keystroke,
) )
.unwrap() .unwrap()
{ {
return; return;
} }
} }
let actions = let actions = presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
presenter.borrow_mut().dispatch_event(event, ctx.as_ref()); for action in actions {
for action in actions { ctx.dispatch_action_any(
ctx.dispatch_action_any( window_id,
window_id, &action.path,
&action.path, action.name,
action.name, action.arg.as_ref(),
action.arg.as_ref(), );
); }
} })
}) }));
}));
}
{
let mut app = self.upgrade();
let presenter = presenter.clone();
window.on_resize(Box::new(move |window| {
app.update(|ctx| {
let scene = presenter.borrow_mut().build_scene(
window.size(),
window.scale_factor(),
ctx,
);
window.present_scene(scene);
})
}));
}
{
let presenter = presenter.clone();
self.on_window_invalidated(window_id, move |invalidation, ctx| {
let mut presenter = presenter.borrow_mut();
presenter.invalidate(invalidation, ctx.as_ref());
let scene =
presenter.build_scene(window.size(), window.scale_factor(), ctx);
window.present_scene(scene);
});
}
self.on_debug_elements(window_id, move |ctx| {
presenter.borrow().debug_elements(ctx).unwrap()
});
}
} }
{
let mut app = self.upgrade();
let presenter = presenter.clone();
window.on_resize(Box::new(move |window| {
app.update(|ctx| {
let scene = presenter.borrow_mut().build_scene(
window.size(),
window.scale_factor(),
ctx,
);
window.present_scene(scene);
})
}));
}
self.presenters_and_platform_windows
.insert(window_id, (presenter.clone(), window));
self.on_debug_elements(window_id, move |ctx| {
presenter.borrow().debug_elements(ctx).unwrap()
});
} }
pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T> pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
@ -922,9 +906,17 @@ impl MutableAppContext {
std::mem::swap(&mut invalidations, &mut self.window_invalidations); std::mem::swap(&mut invalidations, &mut self.window_invalidations);
for (window_id, invalidation) in invalidations { for (window_id, invalidation) in invalidations {
if let Some(mut callback) = self.invalidation_callbacks.remove(&window_id) { if let Some((presenter, mut window)) =
callback(invalidation, self); self.presenters_and_platform_windows.remove(&window_id)
self.invalidation_callbacks.insert(window_id, callback); {
{
let mut presenter = presenter.borrow_mut();
presenter.invalidate(invalidation, self.as_ref());
let scene = presenter.build_scene(window.size(), window.scale_factor(), self);
window.present_scene(scene);
}
self.presenters_and_platform_windows
.insert(window_id, (presenter, window));
} }
} }
} }

View file

@ -1,6 +1,5 @@
use super::{BoolExt as _, Dispatcher, FontSystem, Window}; use super::{BoolExt as _, Dispatcher, FontSystem, Window};
use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem}; use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
use anyhow::Result;
use cocoa::{ use cocoa::{
appkit::{ appkit::{
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
@ -20,6 +19,7 @@ use objc::{
}; };
use ptr::null_mut; use ptr::null_mut;
use std::{ use std::{
any::Any,
cell::RefCell, cell::RefCell,
ffi::{c_void, CStr}, ffi::{c_void, CStr},
os::raw::c_char, os::raw::c_char,
@ -76,7 +76,7 @@ pub struct MacPlatform {
dispatcher: Arc<Dispatcher>, dispatcher: Arc<Dispatcher>,
fonts: Arc<FontSystem>, fonts: Arc<FontSystem>,
callbacks: RefCell<Callbacks>, callbacks: RefCell<Callbacks>,
menu_item_actions: RefCell<Vec<String>>, menu_item_actions: RefCell<Vec<(String, Option<Box<dyn Any>>)>>,
} }
#[derive(Default)] #[derive(Default)]
@ -84,7 +84,7 @@ struct Callbacks {
become_active: Option<Box<dyn FnMut()>>, become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>, resign_active: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(crate::Event) -> bool>>, event: Option<Box<dyn FnMut(crate::Event) -> bool>>,
menu_command: Option<Box<dyn FnMut(&str)>>, menu_command: Option<Box<dyn FnMut(&str, Option<&dyn Any>)>>,
open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>, open_files: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
finish_launching: Option<Box<dyn FnOnce() -> ()>>, finish_launching: Option<Box<dyn FnOnce() -> ()>>,
} }
@ -99,7 +99,7 @@ impl MacPlatform {
} }
} }
unsafe fn create_menu_bar(&self, menus: &[Menu]) -> id { unsafe fn create_menu_bar(&self, menus: Vec<Menu>) -> id {
let menu_bar = NSMenu::new(nil).autorelease(); let menu_bar = NSMenu::new(nil).autorelease();
let mut menu_item_actions = self.menu_item_actions.borrow_mut(); let mut menu_item_actions = self.menu_item_actions.borrow_mut();
menu_item_actions.clear(); menu_item_actions.clear();
@ -107,8 +107,9 @@ impl MacPlatform {
for menu_config in menus { for menu_config in menus {
let menu_bar_item = NSMenuItem::new(nil).autorelease(); let menu_bar_item = NSMenuItem::new(nil).autorelease();
let menu = NSMenu::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 { for item_config in menu_config.items {
let item; let item;
@ -121,12 +122,13 @@ impl MacPlatform {
name, name,
keystroke, keystroke,
action, action,
arg,
} => { } => {
if let Some(keystroke) = keystroke { if let Some(keystroke) = keystroke {
let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| { let keystroke = Keystroke::parse(keystroke).unwrap_or_else(|err| {
panic!( panic!(
"Invalid keystroke for menu item {}:{} - {:?}", "Invalid keystroke for menu item {}:{} - {:?}",
menu_config.name, name, err menu_name, name, err
) )
}); });
@ -161,7 +163,7 @@ impl MacPlatform {
let tag = menu_item_actions.len() as NSInteger; let tag = menu_item_actions.len() as NSInteger;
let _: () = msg_send![item, setTag: tag]; let _: () = msg_send![item, setTag: tag];
menu_item_actions.push(action.to_string()); menu_item_actions.push((action.to_string(), arg));
} }
} }
@ -189,7 +191,7 @@ impl platform::Platform for MacPlatform {
self.callbacks.borrow_mut().event = Some(callback); self.callbacks.borrow_mut().event = Some(callback);
} }
fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>) { fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>) {
self.callbacks.borrow_mut().menu_command = Some(callback); self.callbacks.borrow_mut().menu_command = Some(callback);
} }
@ -231,10 +233,15 @@ impl platform::Platform for MacPlatform {
fn open_window( fn open_window(
&self, &self,
id: usize,
options: platform::WindowOptions, options: platform::WindowOptions,
executor: Rc<executor::Foreground>, executor: Rc<executor::Foreground>,
) -> Result<Box<dyn platform::Window>> { ) -> Box<dyn platform::Window> {
Ok(Box::new(Window::open(options, executor, self.fonts())?)) Box::new(Window::open(id, options, executor, self.fonts()))
}
fn key_window_id(&self) -> Option<usize> {
Window::key_window_id()
} }
fn prompt_for_paths( fn prompt_for_paths(
@ -292,7 +299,7 @@ impl platform::Platform for MacPlatform {
} }
} }
fn set_menus(&self, menus: &[Menu]) { fn set_menus(&self, menus: Vec<Menu>) {
unsafe { unsafe {
let app: id = msg_send![APP_CLASS, sharedApplication]; let app: id = msg_send![APP_CLASS, sharedApplication];
app.setMainMenu_(self.create_menu_bar(menus)); app.setMainMenu_(self.create_menu_bar(menus));
@ -375,8 +382,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() { if let Some(callback) = platform.callbacks.borrow_mut().menu_command.as_mut() {
let tag: NSInteger = msg_send![item, tag]; let tag: NSInteger = msg_send![item, tag];
let index = tag as usize; let index = tag as usize;
if let Some(action) = platform.menu_item_actions.borrow().get(index) { if let Some((action, arg)) = platform.menu_item_actions.borrow().get(index) {
callback(&action); callback(action, arg.as_ref().map(Box::as_ref));
} }
} }
} }

View file

@ -9,7 +9,6 @@ use crate::{
scene::Layer, scene::Layer,
Scene, Scene,
}; };
use anyhow::{anyhow, Result};
use cocoa::foundation::NSUInteger; use cocoa::foundation::NSUInteger;
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange}; use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
use shaders::{ToFloat2 as _, ToUchar4 as _}; use shaders::{ToFloat2 as _, ToUchar4 as _};
@ -41,10 +40,10 @@ impl Renderer {
device: metal::Device, device: metal::Device,
pixel_format: metal::MTLPixelFormat, pixel_format: metal::MTLPixelFormat,
fonts: Arc<dyn platform::FontSystem>, fonts: Arc<dyn platform::FontSystem>,
) -> Result<Self> { ) -> Self {
let library = device let library = device
.new_library_with_data(SHADERS_METALLIB) .new_library_with_data(SHADERS_METALLIB)
.map_err(|message| anyhow!("error building metal library: {}", message))?; .expect("error building metal library");
let unit_vertices = [ let unit_vertices = [
(0., 0.).to_float2(), (0., 0.).to_float2(),
@ -73,7 +72,7 @@ impl Renderer {
"quad_vertex", "quad_vertex",
"quad_fragment", "quad_fragment",
pixel_format, pixel_format,
)?; );
let shadow_pipeline_state = build_pipeline_state( let shadow_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -81,7 +80,7 @@ impl Renderer {
"shadow_vertex", "shadow_vertex",
"shadow_fragment", "shadow_fragment",
pixel_format, pixel_format,
)?; );
let sprite_pipeline_state = build_pipeline_state( let sprite_pipeline_state = build_pipeline_state(
&device, &device,
&library, &library,
@ -89,7 +88,7 @@ impl Renderer {
"sprite_vertex", "sprite_vertex",
"sprite_fragment", "sprite_fragment",
pixel_format, pixel_format,
)?; );
let path_atlas_pipeline_state = build_path_atlas_pipeline_state( let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
&device, &device,
&library, &library,
@ -97,8 +96,8 @@ impl Renderer {
"path_atlas_vertex", "path_atlas_vertex",
"path_atlas_fragment", "path_atlas_fragment",
MTLPixelFormat::R8Unorm, MTLPixelFormat::R8Unorm,
)?; );
Ok(Self { Self {
sprite_cache, sprite_cache,
path_atlases, path_atlases,
quad_pipeline_state, quad_pipeline_state,
@ -107,7 +106,7 @@ impl Renderer {
path_atlas_pipeline_state, path_atlas_pipeline_state,
unit_vertices, unit_vertices,
instances, instances,
}) }
} }
pub fn render( pub fn render(
@ -713,13 +712,13 @@ fn build_pipeline_state(
vertex_fn_name: &str, vertex_fn_name: &str,
fragment_fn_name: &str, fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat, pixel_format: metal::MTLPixelFormat,
) -> Result<metal::RenderPipelineState> { ) -> metal::RenderPipelineState {
let vertex_fn = library let vertex_fn = library
.get_function(vertex_fn_name, None) .get_function(vertex_fn_name, None)
.map_err(|message| anyhow!("error locating vertex function: {}", message))?; .expect("error locating vertex function");
let fragment_fn = library let fragment_fn = library
.get_function(fragment_fn_name, None) .get_function(fragment_fn_name, None)
.map_err(|message| anyhow!("error locating fragment function: {}", message))?; .expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new(); let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label); descriptor.set_label(label);
@ -737,7 +736,7 @@ fn build_pipeline_state(
device device
.new_render_pipeline_state(&descriptor) .new_render_pipeline_state(&descriptor)
.map_err(|message| anyhow!("could not create render pipeline state: {}", message)) .expect("could not create render pipeline state")
} }
fn build_path_atlas_pipeline_state( fn build_path_atlas_pipeline_state(
@ -747,13 +746,13 @@ fn build_path_atlas_pipeline_state(
vertex_fn_name: &str, vertex_fn_name: &str,
fragment_fn_name: &str, fragment_fn_name: &str,
pixel_format: metal::MTLPixelFormat, pixel_format: metal::MTLPixelFormat,
) -> Result<metal::RenderPipelineState> { ) -> metal::RenderPipelineState {
let vertex_fn = library let vertex_fn = library
.get_function(vertex_fn_name, None) .get_function(vertex_fn_name, None)
.map_err(|message| anyhow!("error locating vertex function: {}", message))?; .expect("error locating vertex function");
let fragment_fn = library let fragment_fn = library
.get_function(fragment_fn_name, None) .get_function(fragment_fn_name, None)
.map_err(|message| anyhow!("error locating fragment function: {}", message))?; .expect("error locating fragment function");
let descriptor = metal::RenderPipelineDescriptor::new(); let descriptor = metal::RenderPipelineDescriptor::new();
descriptor.set_label(label); descriptor.set_label(label);
@ -771,7 +770,7 @@ fn build_path_atlas_pipeline_state(
device device
.new_render_pipeline_state(&descriptor) .new_render_pipeline_state(&descriptor)
.map_err(|message| anyhow!("could not create render pipeline state: {}", message)) .expect("could not create render pipeline state")
} }
mod shaders { mod shaders {

View file

@ -4,11 +4,10 @@ use crate::{
platform::{self, Event, WindowContext}, platform::{self, Event, WindowContext},
Scene, Scene,
}; };
use anyhow::{anyhow, Result};
use cocoa::{ use cocoa::{
appkit::{ appkit::{
NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
NSWindow, NSWindowStyleMask, NSViewWidthSizable, NSWindow, NSWindowStyleMask,
}, },
base::{id, nil}, base::{id, nil},
foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString}, foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString},
@ -118,6 +117,7 @@ unsafe fn build_classes() {
pub struct Window(Rc<RefCell<WindowState>>); pub struct Window(Rc<RefCell<WindowState>>);
struct WindowState { struct WindowState {
id: usize,
native_window: id, native_window: id,
event_callback: Option<Box<dyn FnMut(Event)>>, event_callback: Option<Box<dyn FnMut(Event)>>,
resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>, resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>,
@ -131,10 +131,11 @@ struct WindowState {
impl Window { impl Window {
pub fn open( pub fn open(
id: usize,
options: platform::WindowOptions, options: platform::WindowOptions,
executor: Rc<executor::Foreground>, executor: Rc<executor::Foreground>,
fonts: Arc<dyn platform::FontSystem>, fonts: Arc<dyn platform::FontSystem>,
) -> Result<Self> { ) -> Self {
const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm; const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
unsafe { unsafe {
@ -153,13 +154,10 @@ impl Window {
NSBackingStoreBuffered, NSBackingStoreBuffered,
NO, NO,
); );
assert!(!native_window.is_null());
if native_window == nil { let device =
return Err(anyhow!("window returned nil from initializer")); metal::Device::system_default().expect("could not find default metal device");
}
let device = metal::Device::system_default()
.ok_or_else(|| anyhow!("could not find default metal device"))?;
let layer: id = msg_send![class!(CAMetalLayer), layer]; let layer: id = msg_send![class!(CAMetalLayer), layer];
let _: () = msg_send![layer, setDevice: device.as_ptr()]; let _: () = msg_send![layer, setDevice: device.as_ptr()];
@ -175,18 +173,17 @@ impl Window {
let native_view: id = msg_send![VIEW_CLASS, alloc]; let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view); let native_view = NSView::init(native_view);
if native_view == nil { assert!(!native_view.is_null());
return Err(anyhow!("view return nil from initializer"));
}
let window = Self(Rc::new(RefCell::new(WindowState { let window = Self(Rc::new(RefCell::new(WindowState {
id,
native_window, native_window,
event_callback: None, event_callback: None,
resize_callback: None, resize_callback: None,
synthetic_drag_counter: 0, synthetic_drag_counter: 0,
executor, executor,
scene_to_render: Default::default(), scene_to_render: Default::default(),
renderer: Renderer::new(device.clone(), PIXEL_FORMAT, fonts)?, renderer: Renderer::new(device.clone(), PIXEL_FORMAT, fonts),
command_queue: device.new_command_queue(), command_queue: device.new_command_queue(),
layer, layer,
}))); })));
@ -227,7 +224,20 @@ impl Window {
pool.drain(); pool.drain();
Ok(window) window
}
}
pub fn key_window_id() -> Option<usize> {
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)
}
} }
} }
} }

View file

@ -17,13 +17,12 @@ use crate::{
text_layout::Line, text_layout::Line,
Menu, Scene, Menu, Scene,
}; };
use anyhow::Result;
use async_task::Runnable; use async_task::Runnable;
pub use event::Event; 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 { pub trait Platform {
fn on_menu_command(&self, callback: Box<dyn FnMut(&str)>); fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>);
fn on_become_active(&self, callback: Box<dyn FnMut()>); fn on_become_active(&self, callback: Box<dyn FnMut()>);
fn on_resign_active(&self, callback: Box<dyn FnMut()>); fn on_resign_active(&self, callback: Box<dyn FnMut()>);
fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>); fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
@ -36,13 +35,15 @@ pub trait Platform {
fn activate(&self, ignoring_other_apps: bool); fn activate(&self, ignoring_other_apps: bool);
fn open_window( fn open_window(
&self, &self,
id: usize,
options: WindowOptions, options: WindowOptions,
executor: Rc<executor::Foreground>, executor: Rc<executor::Foreground>,
) -> Result<Box<dyn Window>>; ) -> Box<dyn Window>;
fn key_window_id(&self) -> Option<usize>;
fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>; fn prompt_for_paths(&self, options: PathPromptOptions) -> Option<Vec<PathBuf>>;
fn quit(&self); fn quit(&self);
fn copy(&self, text: &str); fn copy(&self, text: &str);
fn set_menus(&self, menus: &[Menu]); fn set_menus(&self, menus: Vec<Menu>);
} }
pub trait Dispatcher: Send + Sync { pub trait Dispatcher: Send + Sync {

View file

@ -1,6 +1,6 @@
use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::vector::Vector2F;
use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::{any::Any, rc::Rc};
struct Platform { struct Platform {
dispatcher: Arc<dyn super::Dispatcher>, dispatcher: Arc<dyn super::Dispatcher>,
@ -27,7 +27,7 @@ impl Platform {
} }
impl super::Platform for Platform { impl super::Platform for Platform {
fn on_menu_command(&self, _: Box<dyn FnMut(&str)>) {} fn on_menu_command(&self, _: Box<dyn FnMut(&str, Option<&dyn Any>)>) {}
fn on_become_active(&self, _: Box<dyn FnMut()>) {} fn on_become_active(&self, _: Box<dyn FnMut()>) {}
@ -53,13 +53,18 @@ impl super::Platform for Platform {
fn open_window( fn open_window(
&self, &self,
_: usize,
options: super::WindowOptions, options: super::WindowOptions,
_executor: Rc<super::executor::Foreground>, _executor: Rc<super::executor::Foreground>,
) -> anyhow::Result<Box<dyn super::Window>> { ) -> Box<dyn super::Window> {
Ok(Box::new(Window::new(options.bounds.size()))) Box::new(Window::new(options.bounds.size()))
} }
fn set_menus(&self, _menus: &[crate::Menu]) {} fn key_window_id(&self) -> Option<usize> {
None
}
fn set_menus(&self, _menus: Vec<crate::Menu>) {}
fn quit(&self) {} fn quit(&self) {}

View file

@ -10,6 +10,6 @@ mod test;
mod time; mod time;
mod timer; mod timer;
mod util; mod util;
mod watch; pub mod watch;
pub mod workspace; pub mod workspace;
mod worktree; mod worktree;

View file

@ -1,5 +1,4 @@
use fs::OpenOptions; use fs::OpenOptions;
use gpui::PathPromptOptions;
use log::LevelFilter; use log::LevelFilter;
use simplelog::SimpleLogger; use simplelog::SimpleLogger;
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
@ -13,29 +12,8 @@ fn main() {
let app = gpui::App::new(assets::Assets).unwrap(); let app = gpui::App::new(assets::Assets).unwrap();
let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap(); let (_, settings_rx) = settings::channel(&app.font_cache()).unwrap();
app.set_menus(menus::MENUS); app.run(move |ctx| {
app.on_menu_command({ ctx.set_menus(menus::menus(settings_rx.clone()));
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(),
},
);
}
}
_ => ctx.dispatch_global_action(command, ()),
}
})
.run(move |ctx| {
workspace::init(ctx); workspace::init(ctx);
editor::init(ctx); editor::init(ctx);
file_finder::init(ctx); file_finder::init(ctx);

View file

@ -1,60 +1,71 @@
use crate::{settings::Settings, watch::Receiver};
use gpui::{Menu, MenuItem}; use gpui::{Menu, MenuItem};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub const MENUS: &'static [Menu] = &[ pub fn menus(settings: Receiver<Settings>) -> Vec<Menu<'static>> {
Menu { vec![
name: "Zed", Menu {
items: &[ name: "Zed",
MenuItem::Action { items: vec![
name: "About Zed…", MenuItem::Action {
keystroke: None, name: "About Zed…",
action: "app:about-zed", keystroke: None,
}, action: "app:about-zed",
MenuItem::Separator, arg: None,
MenuItem::Action { },
name: "Quit", MenuItem::Separator,
keystroke: Some("cmd-q"), MenuItem::Action {
action: "app:quit", name: "Quit",
}, keystroke: Some("cmd-q"),
], action: "app:quit",
}, arg: None,
Menu { },
name: "File", ],
items: &[MenuItem::Action { },
name: "Open…", Menu {
keystroke: Some("cmd-o"), name: "File",
action: "app:open", items: vec![MenuItem::Action {
}], name: "Open…",
}, keystroke: Some("cmd-o"),
Menu { action: "workspace:open",
name: "Edit", arg: Some(Box::new(settings)),
items: &[ }],
MenuItem::Action { },
name: "Undo", Menu {
keystroke: Some("cmd-z"), name: "Edit",
action: "editor:undo", items: vec![
}, MenuItem::Action {
MenuItem::Action { name: "Undo",
name: "Redo", keystroke: Some("cmd-z"),
keystroke: Some("cmd-Z"), action: "buffer:undo",
action: "editor:redo", arg: None,
}, },
MenuItem::Separator, MenuItem::Action {
MenuItem::Action { name: "Redo",
name: "Cut", keystroke: Some("cmd-Z"),
keystroke: Some("cmd-x"), action: "buffer:redo",
action: "editor:cut", arg: None,
}, },
MenuItem::Action { MenuItem::Separator,
name: "Copy", MenuItem::Action {
keystroke: Some("cmd-c"), name: "Cut",
action: "editor:copy", keystroke: Some("cmd-x"),
}, action: "buffer:cut",
MenuItem::Action { arg: None,
name: "Paste", },
keystroke: Some("cmd-v"), MenuItem::Action {
action: "editor:paste", name: "Copy",
}, keystroke: Some("cmd-c"),
], action: "buffer:copy",
}, arg: None,
]; },
MenuItem::Action {
name: "Paste",
keystroke: Some("cmd-v"),
action: "buffer:paste",
arg: None,
},
],
},
]
}

View file

@ -8,11 +8,15 @@ pub use pane_group::*;
pub use workspace::*; pub use workspace::*;
pub use workspace_view::*; pub use workspace_view::*;
use crate::{settings::Settings, watch}; use crate::{
use gpui::MutableAppContext; settings::Settings,
watch::{self, Receiver},
};
use gpui::{MutableAppContext, PathPromptOptions};
use std::path::PathBuf; use std::path::PathBuf;
pub fn init(app: &mut MutableAppContext) { pub fn init(app: &mut MutableAppContext) {
app.add_global_action("workspace:open", open);
app.add_global_action("workspace:open_paths", open_paths); app.add_global_action("workspace:open_paths", open_paths);
app.add_global_action("app:quit", quit); app.add_global_action("app:quit", quit);
pane::init(app); pane::init(app);
@ -24,6 +28,22 @@ pub struct OpenParams {
pub settings: watch::Receiver<Settings>, pub settings: watch::Receiver<Settings>,
} }
fn open(settings: &Receiver<Settings>, ctx: &mut MutableAppContext) {
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.clone(),
},
);
}
}
fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { fn open_paths(params: &OpenParams, app: &mut MutableAppContext) {
log::info!("open paths {:?}", params.paths); log::info!("open paths {:?}", params.paths);