Merge pull request #74 from terhechte/popover_support
This commit is contained in:
commit
1d76dce75f
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;
|
||||
pub use traits::AppDelegate;
|
||||
|
||||
use super::window::Window;
|
||||
|
||||
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
||||
|
||||
/// A handler to make some boilerplate less annoying.
|
||||
#[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] };
|
||||
handler(app);
|
||||
handler(app)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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::objc_access::ObjcAccess;
|
||||
use crate::utils::{os, Controller};
|
||||
use crate::view::View;
|
||||
|
||||
mod class;
|
||||
use class::register_window_class_with_delegate;
|
||||
|
@ -109,7 +110,14 @@ impl Window {
|
|||
|
||||
Window {
|
||||
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.
|
||||
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
||||
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 {
|
||||
fn from(rect: Rect) -> CGRect {
|
||||
CGRect::new(&CGPoint::new(rect.left, rect.top), &CGSize::new(rect.width, rect.height))
|
||||
|
|
|
@ -77,6 +77,8 @@ mod splitviewcontroller;
|
|||
#[cfg(feature = "appkit")]
|
||||
pub use splitviewcontroller::SplitViewController;
|
||||
|
||||
mod popover;
|
||||
pub use popover::*;
|
||||
mod traits;
|
||||
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