From d10f6f60adad0b64f511531f3d5bd4ea0b6cf0b8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 14 Sep 2022 15:43:51 +0200 Subject: [PATCH] Toggle contacts popover when clicking on status bar icon --- .../src/contacts_popover.rs | 26 +++++++ .../src/contacts_status_item.rs | 63 ++++++++++++++-- crates/gpui/src/app.rs | 29 ++++++-- crates/gpui/src/platform.rs | 5 +- crates/gpui/src/platform/mac/status_item.rs | 45 +++++++++--- crates/gpui/src/platform/mac/window.rs | 71 ++++++++++++++----- crates/gpui/src/platform/test.rs | 11 ++- crates/zed/src/main.rs | 1 + crates/zed/src/zed.rs | 1 + 9 files changed, 208 insertions(+), 44 deletions(-) create mode 100644 crates/contacts_status_item/src/contacts_popover.rs diff --git a/crates/contacts_status_item/src/contacts_popover.rs b/crates/contacts_status_item/src/contacts_popover.rs new file mode 100644 index 0000000000..0365a2ee2a --- /dev/null +++ b/crates/contacts_status_item/src/contacts_popover.rs @@ -0,0 +1,26 @@ +use gpui::{color::Color, elements::*, Entity, RenderContext, View}; + +pub struct ContactsPopover; + +impl Entity for ContactsPopover { + type Event = (); +} + +impl View for ContactsPopover { + fn ui_name() -> &'static str { + "ContactsPopover" + } + + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + Empty::new() + .contained() + .with_background_color(Color::red()) + .boxed() + } +} + +impl ContactsPopover { + pub fn new() -> Self { + Self + } +} diff --git a/crates/contacts_status_item/src/contacts_status_item.rs b/crates/contacts_status_item/src/contacts_status_item.rs index f3363f18d3..70e8083a76 100644 --- a/crates/contacts_status_item/src/contacts_status_item.rs +++ b/crates/contacts_status_item/src/contacts_status_item.rs @@ -1,6 +1,24 @@ -use gpui::{color::Color, elements::*, Appearance, Entity, RenderContext, View}; +mod contacts_popover; -pub struct ContactsStatusItem; +use contacts_popover::ContactsPopover; +use gpui::{ + actions, + color::Color, + elements::*, + geometry::{rect::RectF, vector::vec2f}, + Appearance, Entity, MouseButton, MutableAppContext, RenderContext, View, ViewContext, + ViewHandle, +}; + +actions!(contacts_status_item, [ToggleContactsPopover]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(ContactsStatusItem::toggle_contacts_popover); +} + +pub struct ContactsStatusItem { + popover: Option>, +} impl Entity for ContactsStatusItem { type Event = (); @@ -16,15 +34,46 @@ impl View for ContactsStatusItem { Appearance::Light | Appearance::VibrantLight => Color::black(), Appearance::Dark | Appearance::VibrantDark => Color::white(), }; - Svg::new("icons/zed_22.svg") - .with_color(color) - .aligned() - .boxed() + MouseEventHandler::new::(0, cx, |_, _| { + Svg::new("icons/zed_22.svg") + .with_color(color) + .aligned() + .boxed() + }) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ToggleContactsPopover); + }) + .boxed() } } impl ContactsStatusItem { pub fn new() -> Self { - Self + Self { popover: None } + } + + fn toggle_contacts_popover(&mut self, _: &ToggleContactsPopover, cx: &mut ViewContext) { + match self.popover.take() { + Some(popover) => { + cx.remove_window(popover.window_id()); + } + None => { + let window_bounds = cx.window_bounds(); + let size = vec2f(360., 460.); + let origin = window_bounds.lower_left() + + vec2f(window_bounds.width() / 2. - size.x() / 2., 0.); + self.popover = Some( + cx.add_window( + gpui::WindowOptions { + bounds: gpui::WindowBounds::Fixed(RectF::new(origin, size)), + titlebar: None, + center: false, + }, + |_| ContactsPopover::new(), + ) + .1, + ); + } + } } } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index c8e8efec79..3ea88995f1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1244,6 +1244,10 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } + pub fn window_bounds(&self, window_id: usize) -> RectF { + self.presenters_and_platform_windows[&window_id].1.bounds() + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -2032,10 +2036,12 @@ impl MutableAppContext { window_id, })); - let scene = - presenter - .borrow_mut() - .build_scene(window.size(), window.scale_factor(), false, self); + let scene = presenter.borrow_mut().build_scene( + window.content_size(), + window.scale_factor(), + false, + self, + ); window.present_scene(scene); self.presenters_and_platform_windows .insert(window_id, (presenter.clone(), window)); @@ -2411,8 +2417,12 @@ impl MutableAppContext { { let mut presenter = presenter.borrow_mut(); presenter.invalidate(&mut invalidation, window.appearance(), self); - let scene = - presenter.build_scene(window.size(), window.scale_factor(), false, self); + let scene = presenter.build_scene( + window.content_size(), + window.scale_factor(), + false, + self, + ); window.present_scene(scene); } self.presenters_and_platform_windows @@ -2477,7 +2487,8 @@ impl MutableAppContext { window.appearance(), self, ); - let scene = presenter.build_scene(window.size(), window.scale_factor(), true, self); + let scene = + presenter.build_scene(window.content_size(), window.scale_factor(), true, self); window.present_scene(scene); } self.presenters_and_platform_windows = presenters; @@ -3749,6 +3760,10 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } + pub fn window_bounds(&self) -> RectF { + self.app.window_bounds(self.window_id) + } + pub fn prompt( &self, level: PromptLevel, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d2ae7d8d17..f2fd09d30b 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -128,7 +128,8 @@ pub trait Window { fn zoom(&self); fn toggle_full_screen(&self); - fn size(&self) -> Vector2F; + fn bounds(&self) -> RectF; + fn content_size(&self) -> Vector2F; fn scale_factor(&self) -> f32; fn titlebar_height(&self) -> f32; fn present_scene(&mut self, scene: Scene); @@ -140,6 +141,7 @@ pub trait Window { pub struct WindowOptions<'a> { pub bounds: WindowBounds, pub titlebar: Option>, + pub center: bool, } #[derive(Debug)] @@ -273,6 +275,7 @@ impl<'a> Default for WindowOptions<'a> { appears_transparent: Default::default(), traffic_light_position: Default::default(), }), + center: false, } } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index b99b707bff..1ed39894a1 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -1,5 +1,8 @@ use crate::{ - geometry::vector::{vec2f, Vector2F}, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, platform::{ self, mac::{ @@ -10,7 +13,7 @@ use crate::{ Event, FontSystem, Scene, }; use cocoa::{ - appkit::{NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, + appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, base::{id, nil, YES}, foundation::{NSPoint, NSRect, NSSize, NSString}, }; @@ -163,7 +166,7 @@ impl StatusItem { let state = state.borrow(); let layer = state.renderer.layer(); let scale_factor = state.scale_factor(); - let size = state.size() * scale_factor; + let size = state.content_size() * scale_factor; layer.set_contents_scale(scale_factor.into()); layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into())); } @@ -235,8 +238,12 @@ impl platform::Window for StatusItem { unimplemented!() } - fn size(&self) -> Vector2F { - self.0.borrow().size() + fn bounds(&self) -> RectF { + self.0.borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.borrow().content_size() } fn scale_factor(&self) -> f32 { @@ -264,10 +271,28 @@ impl platform::Window for StatusItem { } impl StatusItemState { - fn size(&self) -> Vector2F { + fn bounds(&self) -> RectF { + unsafe { + let window: id = msg_send![self.native_item.button(), window]; + let screen_frame = window.screen().visibleFrame(); + let window_frame = NSWindow::frame(window); + let origin = vec2f( + window_frame.origin.x as f32, + (window_frame.origin.y - screen_frame.size.height - window_frame.size.height) + as f32, + ); + let size = vec2f( + window_frame.size.width as f32, + window_frame.size.height as f32, + ); + RectF::new(origin, size) + } + } + + fn content_size(&self) -> Vector2F { unsafe { let NSSize { width, height, .. } = - NSWindow::frame(self.native_item.button().superview().superview()).size; + NSView::frame(self.native_item.button().superview().superview()).size; vec2f(width as f32, height as f32) } } @@ -275,7 +300,7 @@ impl StatusItemState { fn scale_factor(&self) -> f32 { unsafe { let window: id = msg_send![self.native_item.button(), window]; - window.screen().backingScaleFactor() as f32 + NSScreen::backingScaleFactor(window.screen()) as f32 } } } @@ -292,7 +317,9 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { unsafe { if let Some(state) = get_state(this).upgrade() { let mut state_borrow = state.as_ref().borrow_mut(); - if let Some(event) = Event::from_native(native_event, Some(state_borrow.size().y())) { + if let Some(event) = + Event::from_native(native_event, Some(state_borrow.content_size().y())) + { if let Some(mut callback) = state_borrow.event_callback.take() { drop(state_borrow); callback(event); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 5cb45719eb..00688f1ea5 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -335,12 +335,6 @@ impl Window { unsafe { let pool = NSAutoreleasePool::new(nil); - let frame = match options.bounds { - WindowBounds::Maximized => RectF::new(Default::default(), vec2f(1024., 768.)), - WindowBounds::Fixed(rect) => rect, - } - .to_ns_rect(); - let mut style_mask; if let Some(titlebar) = options.titlebar.as_ref() { style_mask = NSWindowStyleMask::NSClosableWindowMask @@ -357,16 +351,31 @@ impl Window { let native_window: id = msg_send![WINDOW_CLASS, alloc]; let native_window = native_window.initWithContentRect_styleMask_backing_defer_( - frame, + RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(), style_mask, NSBackingStoreBuffered, NO, ); assert!(!native_window.is_null()); - if matches!(options.bounds, WindowBounds::Maximized) { - let screen = native_window.screen(); - native_window.setFrame_display_(screen.visibleFrame(), YES); + let screen = native_window.screen(); + match options.bounds { + WindowBounds::Maximized => { + native_window.setFrame_display_(screen.visibleFrame(), YES); + } + WindowBounds::Fixed(top_left_bounds) => { + let frame = screen.visibleFrame(); + let bottom_left_bounds = RectF::new( + vec2f( + top_left_bounds.origin_x(), + frame.size.height as f32 + - top_left_bounds.origin_y() + - top_left_bounds.height(), + ), + top_left_bounds.size(), + ); + native_window.setFrame_display_(bottom_left_bounds.to_ns_rect(), YES); + } } let native_view: id = msg_send![VIEW_CLASS, alloc]; @@ -438,7 +447,10 @@ impl Window { native_window.setContentView_(native_view.autorelease()); native_window.makeFirstResponder_(native_view); - native_window.center(); + if options.center { + native_window.center(); + } + native_window.makeKeyAndOrderFront_(nil); let _: () = msg_send![ native_window, @@ -633,8 +645,12 @@ impl platform::Window for Window { .detach(); } - fn size(&self) -> Vector2F { - self.0.as_ref().borrow().size() + fn bounds(&self) -> RectF { + self.0.as_ref().borrow().bounds() + } + + fn content_size(&self) -> Vector2F { + self.0.as_ref().borrow().content_size() } fn scale_factor(&self) -> f32 { @@ -706,7 +722,24 @@ impl WindowState { } } - fn size(&self) -> Vector2F { + fn bounds(&self) -> RectF { + unsafe { + let screen_frame = self.native_window.screen().visibleFrame(); + let window_frame = NSWindow::frame(self.native_window); + let origin = vec2f( + window_frame.origin.x as f32, + (window_frame.origin.y - screen_frame.size.height - window_frame.size.height) + as f32, + ); + let size = vec2f( + window_frame.size.width as f32, + window_frame.size.height as f32, + ); + RectF::new(origin, size) + } + } + + fn content_size(&self) -> Vector2F { let NSSize { width, height, .. } = unsafe { NSView::frame(self.native_window.contentView()) }.size; vec2f(width as f32, height as f32) @@ -783,7 +816,8 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut window_state_borrow = window_state.as_ref().borrow_mut(); - let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; + let event = + unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; if let Some(event) = event { if key_equivalent { @@ -875,7 +909,8 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let weak_window_state = Rc::downgrade(&window_state); let mut window_state_borrow = window_state.as_ref().borrow_mut(); - let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; + let event = + unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) }; if let Some(event) = event { match &event { Event::MouseMoved( @@ -1060,7 +1095,7 @@ extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) { unsafe { let scale_factor = window_state_borrow.scale_factor() as f64; - let size = window_state_borrow.size(); + let size = window_state_borrow.content_size(); let drawable_size: NSSize = NSSize { width: size.x() as f64 * scale_factor, height: size.y() as f64 * scale_factor, @@ -1087,7 +1122,7 @@ extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) { let window_state = unsafe { get_window_state(this) }; let window_state_borrow = window_state.as_ref().borrow(); - if window_state_borrow.size() == vec2f(size.width as f32, size.height as f32) { + if window_state_borrow.content_size() == vec2f(size.width as f32, size.height as f32) { return; } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 9a4f3638be..9a458a1dd9 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -1,6 +1,9 @@ use super::{AppVersion, CursorStyle, WindowBounds}; use crate::{ - geometry::vector::{vec2f, Vector2F}, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, keymap, Action, ClipboardItem, }; use anyhow::{anyhow, Result}; @@ -283,7 +286,11 @@ impl super::Window for Window { fn toggle_full_screen(&self) {} - fn size(&self) -> Vector2F { + fn bounds(&self) -> RectF { + RectF::new(Default::default(), self.size) + } + + fn content_size(&self) -> Vector2F { self.size } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 287ac721fd..868ad15d67 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -105,6 +105,7 @@ fn main() { watch_settings_file(default_settings, settings_file, themes.clone(), cx); watch_keymap_file(keymap_file, cx); + contacts_status_item::init(cx); context_menu::init(cx); project::Project::init(&client); client::Channel::init(&client); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 59e9f8de9d..a14e85e93b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -335,6 +335,7 @@ pub fn build_window_options() -> WindowOptions<'static> { appears_transparent: true, traffic_light_position: Some(vec2f(8., 8.)), }), + center: false, } }