add support for popovers
This commit is contained in:
parent
68da052a8f
commit
7e724934a5
195
examples/popover.rs
Normal file
195
examples/popover.rs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
//! This example showcases how to use a `Popover`.
|
||||||
|
//! This requires multiple types:
|
||||||
|
//! - A Window with a Controller / View
|
||||||
|
//! - A Popover
|
||||||
|
//! - Another Controller / View
|
||||||
|
|
||||||
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowDelegate};
|
||||||
|
use cacao::appkit::{App, AppDelegate};
|
||||||
|
use cacao::button::Button;
|
||||||
|
use cacao::geometry::{Edge, Rect};
|
||||||
|
use cacao::layout::{Layout, LayoutConstraint};
|
||||||
|
use cacao::notification_center::Dispatcher;
|
||||||
|
use cacao::text::{Font, Label};
|
||||||
|
use cacao::view::{Popover, PopoverConfig, View, ViewController, ViewDelegate};
|
||||||
|
|
||||||
|
struct BasicApp {
|
||||||
|
window: WindowController<MyWindow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppDelegate for BasicApp {
|
||||||
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new(
|
||||||
|
"",
|
||||||
|
vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Menu::new("File", vec![MenuItem::CloseWindow]),
|
||||||
|
Menu::new("View", vec![MenuItem::EnterFullScreen]),
|
||||||
|
Menu::new(
|
||||||
|
"Window",
|
||||||
|
vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
App::activate();
|
||||||
|
|
||||||
|
self.window.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyWindow {
|
||||||
|
controller: Option<ViewController<PopoverExampleContentView>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowDelegate for MyWindow {
|
||||||
|
const NAME: &'static str = "MyWindow";
|
||||||
|
|
||||||
|
fn did_load(&mut self, window: Window) {
|
||||||
|
window.set_minimum_content_size(400., 400.);
|
||||||
|
window.set_title("A Basic Window!?");
|
||||||
|
let view = PopoverExampleContentView::new();
|
||||||
|
let controller = ViewController::new(view);
|
||||||
|
window.set_content_view_controller(&controller);
|
||||||
|
self.controller = Some(controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn will_close(&self) {
|
||||||
|
println!("Closing now!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MyWindow {
|
||||||
|
pub fn on_message(&self, message: Msg) {
|
||||||
|
if let Some(delegate) = self.controller.as_ref().map(|e| &e.view).and_then(|v| v.delegate.as_ref()) {
|
||||||
|
delegate.on_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new(
|
||||||
|
"com.test.window-delegate",
|
||||||
|
BasicApp {
|
||||||
|
window: WindowController::with(WindowConfig::default(), MyWindow::default()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Msg {
|
||||||
|
Click,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct PopoverExampleContentView {
|
||||||
|
view: Option<View>,
|
||||||
|
button: Option<Button>,
|
||||||
|
popover: Option<Popover<PopoverExampleContentViewController>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopoverExampleContentView {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
view: None,
|
||||||
|
button: None,
|
||||||
|
popover: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_message(&self, message: Msg) {
|
||||||
|
match message {
|
||||||
|
Msg::Click => {
|
||||||
|
let Some(ref popover) = self.popover else { return };
|
||||||
|
let Some(ref button) = self.button else { return };
|
||||||
|
popover.show_popover(Rect::zero(), button, Edge::MaxY);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewDelegate for PopoverExampleContentView {
|
||||||
|
const NAME: &'static str = "PopoverExampleContentView";
|
||||||
|
|
||||||
|
fn did_load(&mut self, view: cacao::view::View) {
|
||||||
|
let mut button = Button::new("Show");
|
||||||
|
button.set_action(|| dispatch_ui(Msg::Click));
|
||||||
|
|
||||||
|
let controller = PopoverExampleContentViewController::new();
|
||||||
|
let config = PopoverConfig {
|
||||||
|
animates: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let popover = Popover::new(controller, config);
|
||||||
|
self.popover = Some(popover);
|
||||||
|
|
||||||
|
view.add_subview(&button);
|
||||||
|
|
||||||
|
LayoutConstraint::activate(&[
|
||||||
|
button.center_x.constraint_equal_to(&view.center_x),
|
||||||
|
button.center_y.constraint_equal_to(&view.center_y),
|
||||||
|
]);
|
||||||
|
|
||||||
|
self.view = Some(view);
|
||||||
|
self.button = Some(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch_ui(message: Msg) {
|
||||||
|
println!("Dispatching UI message: {:?}", message);
|
||||||
|
App::<BasicApp, Msg>::dispatch_main(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dispatcher for BasicApp {
|
||||||
|
type Message = Msg;
|
||||||
|
|
||||||
|
// Handles a message that came over on the main (UI) thread.
|
||||||
|
fn on_ui_message(&self, message: Self::Message) {
|
||||||
|
if let Some(d) = &self.window.window.delegate {
|
||||||
|
d.on_message(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PopoverExampleContentViewController {
|
||||||
|
pub label: Label,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PopoverExampleContentViewController {
|
||||||
|
fn new() -> Self {
|
||||||
|
let label = Label::new();
|
||||||
|
let font = Font::system(20.);
|
||||||
|
label.set_font(&font);
|
||||||
|
label.set_text("Hello");
|
||||||
|
Self { label }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewDelegate for PopoverExampleContentViewController {
|
||||||
|
const NAME: &'static str = "PopoverExampleContentViewController";
|
||||||
|
|
||||||
|
fn did_load(&mut self, view: View) {
|
||||||
|
view.add_subview(&self.label);
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,13 +63,15 @@ pub use enums::*;
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::AppDelegate;
|
pub use traits::AppDelegate;
|
||||||
|
|
||||||
|
use super::window::Window;
|
||||||
|
|
||||||
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
||||||
|
|
||||||
/// A handler to make some boilerplate less annoying.
|
/// A handler to make some boilerplate less annoying.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn shared_application<F: Fn(id)>(handler: F) {
|
fn shared_application<T, F: Fn(id) -> T>(handler: F) -> T {
|
||||||
let app: id = unsafe { msg_send![register_app_class(), sharedApplication] };
|
let app: id = unsafe { msg_send![register_app_class(), sharedApplication] };
|
||||||
handler(app);
|
handler(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper for `NSApplication` in AppKit/Cocoa, and `UIApplication` in UIKit/Cocoa Touch.
|
/// A wrapper for `NSApplication` in AppKit/Cocoa, and `UIApplication` in UIKit/Cocoa Touch.
|
||||||
|
@ -298,6 +300,13 @@ impl App {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main_window() -> Window {
|
||||||
|
shared_application(|app| unsafe {
|
||||||
|
let window: id = msg_send![app, mainWindow];
|
||||||
|
Window::existing(window)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Terminates the application, firing the requisite cleanup delegate methods in the process.
|
/// Terminates the application, firing the requisite cleanup delegate methods in the process.
|
||||||
///
|
///
|
||||||
/// This is typically called when the user chooses to quit via the App menu.
|
/// This is typically called when the user chooses to quit via the App menu.
|
||||||
|
|
|
@ -23,6 +23,7 @@ use crate::foundation::{id, nil, to_bool, NSInteger, NSString, NSUInteger, NO, Y
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::objc_access::ObjcAccess;
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::{os, Controller};
|
use crate::utils::{os, Controller};
|
||||||
|
use crate::view::View;
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
use class::register_window_class_with_delegate;
|
use class::register_window_class_with_delegate;
|
||||||
|
@ -109,7 +110,14 @@ impl Window {
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
objc: objc,
|
objc: objc,
|
||||||
delegate: None
|
delegate: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) unsafe fn existing(window: *mut Object) -> Window {
|
||||||
|
Window {
|
||||||
|
objc: ShareId::from_ptr(window),
|
||||||
|
delegate: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,6 +299,11 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the objc ContentView from the window
|
||||||
|
pub(crate) unsafe fn content_view(&self) -> id {
|
||||||
|
let id: *mut Object = msg_send![&*self.objc, contentView];
|
||||||
|
id
|
||||||
|
}
|
||||||
/// Given a view, sets it as the content view for this window.
|
/// Given a view, sets it as the content view for this window.
|
||||||
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
||||||
view.with_backing_obj_mut(|backing_node| unsafe {
|
view.with_backing_obj_mut(|backing_node| unsafe {
|
||||||
|
|
|
@ -41,6 +41,14 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum Edge {
|
||||||
|
MinX = 0,
|
||||||
|
MinY = 1,
|
||||||
|
MaxX = 2,
|
||||||
|
MaxY = 3,
|
||||||
|
}
|
||||||
impl From<Rect> for CGRect {
|
impl From<Rect> for CGRect {
|
||||||
fn from(rect: Rect) -> CGRect {
|
fn from(rect: Rect) -> CGRect {
|
||||||
CGRect::new(&CGPoint::new(rect.left, rect.top), &CGSize::new(rect.width, rect.height))
|
CGRect::new(&CGPoint::new(rect.left, rect.top), &CGSize::new(rect.width, rect.height))
|
||||||
|
|
|
@ -77,6 +77,8 @@ mod splitviewcontroller;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
pub use splitviewcontroller::SplitViewController;
|
pub use splitviewcontroller::SplitViewController;
|
||||||
|
|
||||||
|
mod popover;
|
||||||
|
pub use popover::*;
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::ViewDelegate;
|
pub use traits::ViewDelegate;
|
||||||
|
|
||||||
|
|
95
src/view/popover/mod.rs
Normal file
95
src/view/popover/mod.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use core_graphics::geometry::CGRect;
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc_id::ShareId;
|
||||||
|
|
||||||
|
use crate::appkit::toolbar::ToolbarItem;
|
||||||
|
use crate::appkit::window::Window;
|
||||||
|
use crate::appkit::App;
|
||||||
|
use crate::foundation::{id, nil, NSString};
|
||||||
|
use crate::geometry::{Edge, Rect};
|
||||||
|
use crate::layout::Layout;
|
||||||
|
use crate::utils::{os, CGSize, Controller};
|
||||||
|
use crate::view::{View, ViewController, ViewDelegate};
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
|
#[repr(i64)]
|
||||||
|
pub enum PopoverBehaviour {
|
||||||
|
/// Your application assumes responsibility for closing the popover.
|
||||||
|
ApplicationDefined = 0,
|
||||||
|
/// The system will close the popover when the user interacts with a user interface element outside the popover.
|
||||||
|
Transient = 1,
|
||||||
|
/// The system will close the popover when the user interacts with user interface elements in the window containing the popover's positioning view.
|
||||||
|
Semitransient = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PopoverConfig {
|
||||||
|
pub content_size: CGSize,
|
||||||
|
pub animates: bool,
|
||||||
|
pub behaviour: PopoverBehaviour,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PopoverConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
content_size: CGSize {
|
||||||
|
width: 320.0,
|
||||||
|
height: 320.0,
|
||||||
|
},
|
||||||
|
animates: true,
|
||||||
|
behaviour: PopoverBehaviour::Transient,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Popover<Content> {
|
||||||
|
/// A reference to the underlying Objective-C NSPopover
|
||||||
|
pub objc: ShareId<Object>,
|
||||||
|
|
||||||
|
/// The wrapped ViewController.
|
||||||
|
pub view_controller: ViewController<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Content> Popover<Content>
|
||||||
|
where
|
||||||
|
Content: ViewDelegate + 'static,
|
||||||
|
{
|
||||||
|
pub fn new(content: Content, config: PopoverConfig) -> Self {
|
||||||
|
let view_controller = ViewController::new(content);
|
||||||
|
let objc = unsafe {
|
||||||
|
let pop: id = msg_send![class!(NSPopover), new];
|
||||||
|
let _: () = msg_send![pop, setContentSize: config.content_size];
|
||||||
|
let _: () = msg_send![pop, setBehavior: config.behaviour as i64];
|
||||||
|
let _: () = msg_send![pop, setAnimates: config.animates];
|
||||||
|
let _: () = msg_send![pop, setContentViewController: &*view_controller.objc];
|
||||||
|
|
||||||
|
ShareId::from_ptr(pop)
|
||||||
|
};
|
||||||
|
|
||||||
|
Popover { objc, view_controller }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Content> Popover<Content> {
|
||||||
|
/// Show a popover relative to a view
|
||||||
|
pub fn show_popover<V: Layout>(&self, relative_to: Rect, view: &V, edge: Edge) {
|
||||||
|
let rect: CGRect = relative_to.into();
|
||||||
|
unsafe {
|
||||||
|
view.with_backing_obj_mut(|obj| {
|
||||||
|
let _: () = msg_send![&*self.objc, showRelativeToRect:rect ofView: &*obj preferredEdge: edge as u32];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the popover relative to the content view of the main window
|
||||||
|
pub fn show_popover_main(&self, rect: Rect, edge: Edge) {
|
||||||
|
let window = App::main_window();
|
||||||
|
unsafe {
|
||||||
|
let content_view = window.content_view();
|
||||||
|
let rect: CGRect = rect.into();
|
||||||
|
let _: () = msg_send![&*self.objc, showRelativeToRect:rect ofView: content_view preferredEdge: edge as u32];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue