mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 13:21:43 +03:00
Merge pull request #14 from zed-industries/menu-commands
Make the application menu dispatch commands on the focused view
This commit is contained in:
commit
0d69b632b1
214
gpui/src/app.rs
214
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<MenuItem<'a>>,
|
||||
}
|
||||
|
||||
pub enum MenuItem<'a> {
|
||||
@ -77,6 +77,7 @@ pub enum MenuItem<'a> {
|
||||
name: &'a str,
|
||||
keystroke: Option<&'a str>,
|
||||
action: &'a str,
|
||||
arg: Option<Box<dyn Any + 'static>>,
|
||||
},
|
||||
Separator,
|
||||
}
|
||||
@ -127,9 +128,27 @@ impl App {
|
||||
let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
|
||||
let app = Self(Rc::new(RefCell::new(MutableAppContext::new(
|
||||
foreground,
|
||||
platform,
|
||||
platform.clone(),
|
||||
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));
|
||||
Ok(app)
|
||||
}
|
||||
@ -169,20 +188,6 @@ impl App {
|
||||
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
|
||||
where
|
||||
F: 'static + FnMut(Vec<PathBuf>, &mut MutableAppContext),
|
||||
@ -197,10 +202,6 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_menus(&self, menus: &[Menu]) {
|
||||
self.0.borrow().platform.set_menus(menus);
|
||||
}
|
||||
|
||||
pub fn run<F>(self, on_finish_launching: F)
|
||||
where
|
||||
F: 'static + FnOnce(&mut MutableAppContext),
|
||||
@ -383,8 +384,8 @@ pub struct MutableAppContext {
|
||||
subscriptions: HashMap<usize, Vec<Subscription>>,
|
||||
observations: HashMap<usize, Vec<Observation>>,
|
||||
window_invalidations: HashMap<usize, WindowInvalidation>,
|
||||
invalidation_callbacks:
|
||||
HashMap<usize, Box<dyn FnMut(WindowInvalidation, &mut MutableAppContext)>>,
|
||||
presenters_and_platform_windows:
|
||||
HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
|
||||
debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
|
||||
foreground: Rc<executor::Foreground>,
|
||||
future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
|
||||
@ -422,7 +423,7 @@ impl MutableAppContext {
|
||||
subscriptions: HashMap::new(),
|
||||
observations: HashMap::new(),
|
||||
window_invalidations: HashMap::new(),
|
||||
invalidation_callbacks: HashMap::new(),
|
||||
presenters_and_platform_windows: HashMap::new(),
|
||||
debug_elements_callbacks: HashMap::new(),
|
||||
foreground,
|
||||
future_handlers: Default::default(),
|
||||
@ -454,15 +455,6 @@ impl MutableAppContext {
|
||||
&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)
|
||||
where
|
||||
F: 'static + Fn(&AppContext) -> crate::json::Value,
|
||||
@ -573,6 +565,10 @@ impl MutableAppContext {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn set_menus(&self, menus: Vec<Menu>) {
|
||||
self.platform.set_menus(menus);
|
||||
}
|
||||
|
||||
pub fn dispatch_action<T: 'static + Any>(
|
||||
&mut self,
|
||||
window_id: usize,
|
||||
@ -634,7 +630,7 @@ impl MutableAppContext {
|
||||
}
|
||||
|
||||
if !halted_dispatch {
|
||||
self.dispatch_global_action_with_dyn_arg(name, arg);
|
||||
self.dispatch_global_action_any(name, arg);
|
||||
}
|
||||
|
||||
self.flush_effects();
|
||||
@ -642,10 +638,10 @@ impl MutableAppContext {
|
||||
}
|
||||
|
||||
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) {
|
||||
self.pending_flushes += 1;
|
||||
for handler in handlers.iter_mut().rev() {
|
||||
@ -741,87 +737,75 @@ impl MutableAppContext {
|
||||
}
|
||||
|
||||
fn open_platform_window(&mut self, window_id: usize) {
|
||||
match self.platform.open_window(
|
||||
let mut window = self.platform.open_window(
|
||||
window_id,
|
||||
WindowOptions {
|
||||
bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)),
|
||||
title: "Zed".into(),
|
||||
},
|
||||
self.foreground.clone(),
|
||||
) {
|
||||
Err(e) => log::error!("error opening window: {}", e),
|
||||
Ok(mut window) => {
|
||||
let text_layout_cache = TextLayoutCache::new(self.platform.fonts());
|
||||
let presenter = Rc::new(RefCell::new(Presenter::new(
|
||||
window_id,
|
||||
self.font_cache.clone(),
|
||||
text_layout_cache,
|
||||
self.assets.clone(),
|
||||
self,
|
||||
)));
|
||||
);
|
||||
let text_layout_cache = TextLayoutCache::new(self.platform.fonts());
|
||||
let presenter = Rc::new(RefCell::new(Presenter::new(
|
||||
window_id,
|
||||
self.font_cache.clone(),
|
||||
text_layout_cache,
|
||||
self.assets.clone(),
|
||||
self,
|
||||
)));
|
||||
|
||||
{
|
||||
let mut app = self.upgrade();
|
||||
let presenter = presenter.clone();
|
||||
window.on_event(Box::new(move |event| {
|
||||
app.update(|ctx| {
|
||||
if let Event::KeyDown { keystroke, .. } = &event {
|
||||
if ctx
|
||||
.dispatch_keystroke(
|
||||
window_id,
|
||||
presenter.borrow().dispatch_path(ctx.as_ref()),
|
||||
keystroke,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut app = self.upgrade();
|
||||
let presenter = presenter.clone();
|
||||
window.on_event(Box::new(move |event| {
|
||||
app.update(|ctx| {
|
||||
if let Event::KeyDown { keystroke, .. } = &event {
|
||||
if ctx
|
||||
.dispatch_keystroke(
|
||||
window_id,
|
||||
presenter.borrow().dispatch_path(ctx.as_ref()),
|
||||
keystroke,
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let actions =
|
||||
presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
|
||||
for action in actions {
|
||||
ctx.dispatch_action_any(
|
||||
window_id,
|
||||
&action.path,
|
||||
action.name,
|
||||
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 actions = presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
|
||||
for action in actions {
|
||||
ctx.dispatch_action_any(
|
||||
window_id,
|
||||
&action.path,
|
||||
action.name,
|
||||
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);
|
||||
})
|
||||
}));
|
||||
}
|
||||
|
||||
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>
|
||||
@ -922,9 +906,17 @@ impl MutableAppContext {
|
||||
std::mem::swap(&mut invalidations, &mut self.window_invalidations);
|
||||
|
||||
for (window_id, invalidation) in invalidations {
|
||||
if let Some(mut callback) = self.invalidation_callbacks.remove(&window_id) {
|
||||
callback(invalidation, self);
|
||||
self.invalidation_callbacks.insert(window_id, callback);
|
||||
if let Some((presenter, mut window)) =
|
||||
self.presenters_and_platform_windows.remove(&window_id)
|
||||
{
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::{BoolExt as _, Dispatcher, FontSystem, Window};
|
||||
use crate::{executor, keymap::Keystroke, platform, Event, Menu, MenuItem};
|
||||
use anyhow::Result;
|
||||
use cocoa::{
|
||||
appkit::{
|
||||
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
|
||||
@ -20,6 +19,7 @@ use objc::{
|
||||
};
|
||||
use ptr::null_mut;
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
ffi::{c_void, CStr},
|
||||
os::raw::c_char,
|
||||
@ -76,7 +76,7 @@ pub struct MacPlatform {
|
||||
dispatcher: Arc<Dispatcher>,
|
||||
fonts: Arc<FontSystem>,
|
||||
callbacks: RefCell<Callbacks>,
|
||||
menu_item_actions: RefCell<Vec<String>>,
|
||||
menu_item_actions: RefCell<Vec<(String, Option<Box<dyn Any>>)>>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@ -84,7 +84,7 @@ struct Callbacks {
|
||||
become_active: Option<Box<dyn FnMut()>>,
|
||||
resign_active: Option<Box<dyn FnMut()>>,
|
||||
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>)>>,
|
||||
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 mut menu_item_actions = self.menu_item_actions.borrow_mut();
|
||||
menu_item_actions.clear();
|
||||
@ -107,8 +107,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 +122,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 +163,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 +191,7 @@ impl platform::Platform for MacPlatform {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -231,10 +233,15 @@ impl platform::Platform for MacPlatform {
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
id: usize,
|
||||
options: platform::WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
) -> Result<Box<dyn platform::Window>> {
|
||||
Ok(Box::new(Window::open(options, executor, self.fonts())?))
|
||||
) -> Box<dyn platform::Window> {
|
||||
Box::new(Window::open(id, options, executor, self.fonts()))
|
||||
}
|
||||
|
||||
fn key_window_id(&self) -> Option<usize> {
|
||||
Window::key_window_id()
|
||||
}
|
||||
|
||||
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 {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
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() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ use crate::{
|
||||
scene::Layer,
|
||||
Scene,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
|
||||
use shaders::{ToFloat2 as _, ToUchar4 as _};
|
||||
@ -41,10 +40,10 @@ impl Renderer {
|
||||
device: metal::Device,
|
||||
pixel_format: metal::MTLPixelFormat,
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
) -> Result<Self> {
|
||||
) -> Self {
|
||||
let library = device
|
||||
.new_library_with_data(SHADERS_METALLIB)
|
||||
.map_err(|message| anyhow!("error building metal library: {}", message))?;
|
||||
.expect("error building metal library");
|
||||
|
||||
let unit_vertices = [
|
||||
(0., 0.).to_float2(),
|
||||
@ -73,7 +72,7 @@ impl Renderer {
|
||||
"quad_vertex",
|
||||
"quad_fragment",
|
||||
pixel_format,
|
||||
)?;
|
||||
);
|
||||
let shadow_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
@ -81,7 +80,7 @@ impl Renderer {
|
||||
"shadow_vertex",
|
||||
"shadow_fragment",
|
||||
pixel_format,
|
||||
)?;
|
||||
);
|
||||
let sprite_pipeline_state = build_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
@ -89,7 +88,7 @@ impl Renderer {
|
||||
"sprite_vertex",
|
||||
"sprite_fragment",
|
||||
pixel_format,
|
||||
)?;
|
||||
);
|
||||
let path_atlas_pipeline_state = build_path_atlas_pipeline_state(
|
||||
&device,
|
||||
&library,
|
||||
@ -97,8 +96,8 @@ impl Renderer {
|
||||
"path_atlas_vertex",
|
||||
"path_atlas_fragment",
|
||||
MTLPixelFormat::R8Unorm,
|
||||
)?;
|
||||
Ok(Self {
|
||||
);
|
||||
Self {
|
||||
sprite_cache,
|
||||
path_atlases,
|
||||
quad_pipeline_state,
|
||||
@ -107,7 +106,7 @@ impl Renderer {
|
||||
path_atlas_pipeline_state,
|
||||
unit_vertices,
|
||||
instances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
@ -713,13 +712,13 @@ fn build_pipeline_state(
|
||||
vertex_fn_name: &str,
|
||||
fragment_fn_name: &str,
|
||||
pixel_format: metal::MTLPixelFormat,
|
||||
) -> Result<metal::RenderPipelineState> {
|
||||
) -> metal::RenderPipelineState {
|
||||
let vertex_fn = library
|
||||
.get_function(vertex_fn_name, None)
|
||||
.map_err(|message| anyhow!("error locating vertex function: {}", message))?;
|
||||
.expect("error locating vertex function");
|
||||
let fragment_fn = library
|
||||
.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();
|
||||
descriptor.set_label(label);
|
||||
@ -737,7 +736,7 @@ fn build_pipeline_state(
|
||||
|
||||
device
|
||||
.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(
|
||||
@ -747,13 +746,13 @@ fn build_path_atlas_pipeline_state(
|
||||
vertex_fn_name: &str,
|
||||
fragment_fn_name: &str,
|
||||
pixel_format: metal::MTLPixelFormat,
|
||||
) -> Result<metal::RenderPipelineState> {
|
||||
) -> metal::RenderPipelineState {
|
||||
let vertex_fn = library
|
||||
.get_function(vertex_fn_name, None)
|
||||
.map_err(|message| anyhow!("error locating vertex function: {}", message))?;
|
||||
.expect("error locating vertex function");
|
||||
let fragment_fn = library
|
||||
.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();
|
||||
descriptor.set_label(label);
|
||||
@ -771,7 +770,7 @@ fn build_path_atlas_pipeline_state(
|
||||
|
||||
device
|
||||
.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 {
|
||||
|
@ -4,11 +4,10 @@ use crate::{
|
||||
platform::{self, Event, WindowContext},
|
||||
Scene,
|
||||
};
|
||||
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 +117,7 @@ unsafe fn build_classes() {
|
||||
pub struct Window(Rc<RefCell<WindowState>>);
|
||||
|
||||
struct WindowState {
|
||||
id: usize,
|
||||
native_window: id,
|
||||
event_callback: Option<Box<dyn FnMut(Event)>>,
|
||||
resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>,
|
||||
@ -131,10 +131,11 @@ struct WindowState {
|
||||
|
||||
impl Window {
|
||||
pub fn open(
|
||||
id: usize,
|
||||
options: platform::WindowOptions,
|
||||
executor: Rc<executor::Foreground>,
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
) -> Result<Self> {
|
||||
) -> Self {
|
||||
const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
|
||||
unsafe {
|
||||
@ -153,13 +154,10 @@ impl Window {
|
||||
NSBackingStoreBuffered,
|
||||
NO,
|
||||
);
|
||||
assert!(!native_window.is_null());
|
||||
|
||||
if native_window == nil {
|
||||
return Err(anyhow!("window returned nil from initializer"));
|
||||
}
|
||||
|
||||
let device = metal::Device::system_default()
|
||||
.ok_or_else(|| anyhow!("could not find default metal device"))?;
|
||||
let device =
|
||||
metal::Device::system_default().expect("could not find default metal device");
|
||||
|
||||
let layer: id = msg_send![class!(CAMetalLayer), layer];
|
||||
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 = NSView::init(native_view);
|
||||
if native_view == nil {
|
||||
return Err(anyhow!("view return nil from initializer"));
|
||||
}
|
||||
assert!(!native_view.is_null());
|
||||
|
||||
let window = Self(Rc::new(RefCell::new(WindowState {
|
||||
id,
|
||||
native_window,
|
||||
event_callback: None,
|
||||
resize_callback: None,
|
||||
synthetic_drag_counter: 0,
|
||||
executor,
|
||||
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(),
|
||||
layer,
|
||||
})));
|
||||
@ -227,7 +224,20 @@ impl Window {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,12 @@ use crate::{
|
||||
text_layout::Line,
|
||||
Menu, Scene,
|
||||
};
|
||||
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<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_resign_active(&self, callback: Box<dyn FnMut()>);
|
||||
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 open_window(
|
||||
&self,
|
||||
id: usize,
|
||||
options: WindowOptions,
|
||||
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 quit(&self);
|
||||
fn copy(&self, text: &str);
|
||||
fn set_menus(&self, menus: &[Menu]);
|
||||
fn set_menus(&self, menus: Vec<Menu>);
|
||||
}
|
||||
|
||||
pub trait Dispatcher: Send + Sync {
|
||||
|
@ -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<dyn super::Dispatcher>,
|
||||
@ -27,7 +27,7 @@ impl 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()>) {}
|
||||
|
||||
@ -53,13 +53,18 @@ impl super::Platform for Platform {
|
||||
|
||||
fn open_window(
|
||||
&self,
|
||||
_: usize,
|
||||
options: super::WindowOptions,
|
||||
_executor: Rc<super::executor::Foreground>,
|
||||
) -> anyhow::Result<Box<dyn super::Window>> {
|
||||
Ok(Box::new(Window::new(options.bounds.size())))
|
||||
) -> Box<dyn super::Window> {
|
||||
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) {}
|
||||
|
||||
|
@ -10,6 +10,6 @@ mod test;
|
||||
mod time;
|
||||
mod timer;
|
||||
mod util;
|
||||
mod watch;
|
||||
pub mod watch;
|
||||
pub mod workspace;
|
||||
mod worktree;
|
||||
|
@ -1,5 +1,4 @@
|
||||
use fs::OpenOptions;
|
||||
use gpui::PathPromptOptions;
|
||||
use log::LevelFilter;
|
||||
use simplelog::SimpleLogger;
|
||||
use std::{fs, path::PathBuf};
|
||||
@ -13,29 +12,8 @@ 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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => ctx.dispatch_global_action(command, ()),
|
||||
}
|
||||
})
|
||||
.run(move |ctx| {
|
||||
app.run(move |ctx| {
|
||||
ctx.set_menus(menus::menus(settings_rx.clone()));
|
||||
workspace::init(ctx);
|
||||
editor::init(ctx);
|
||||
file_finder::init(ctx);
|
||||
|
125
zed/src/menus.rs
125
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<Settings>) -> Vec<Menu<'static>> {
|
||||
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: "workspace:open",
|
||||
arg: Some(Box::new(settings)),
|
||||
}],
|
||||
},
|
||||
Menu {
|
||||
name: "Edit",
|
||||
items: vec![
|
||||
MenuItem::Action {
|
||||
name: "Undo",
|
||||
keystroke: Some("cmd-z"),
|
||||
action: "buffer:undo",
|
||||
arg: None,
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Redo",
|
||||
keystroke: Some("cmd-Z"),
|
||||
action: "buffer:redo",
|
||||
arg: None,
|
||||
},
|
||||
MenuItem::Separator,
|
||||
MenuItem::Action {
|
||||
name: "Cut",
|
||||
keystroke: Some("cmd-x"),
|
||||
action: "buffer:cut",
|
||||
arg: None,
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Copy",
|
||||
keystroke: Some("cmd-c"),
|
||||
action: "buffer:copy",
|
||||
arg: None,
|
||||
},
|
||||
MenuItem::Action {
|
||||
name: "Paste",
|
||||
keystroke: Some("cmd-v"),
|
||||
action: "buffer:paste",
|
||||
arg: None,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -8,11 +8,15 @@ pub use pane_group::*;
|
||||
pub use workspace::*;
|
||||
pub use workspace_view::*;
|
||||
|
||||
use crate::{settings::Settings, watch};
|
||||
use gpui::MutableAppContext;
|
||||
use crate::{
|
||||
settings::Settings,
|
||||
watch::{self, Receiver},
|
||||
};
|
||||
use gpui::{MutableAppContext, PathPromptOptions};
|
||||
use std::path::PathBuf;
|
||||
|
||||
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("app:quit", quit);
|
||||
pane::init(app);
|
||||
@ -24,6 +28,22 @@ pub struct OpenParams {
|
||||
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) {
|
||||
log::info!("open paths {:?}", params.paths);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user