A rather large and not very clean update.
- Adds support for NSSplitViewController. - Reworks NSMenu support to be cleaner with enum variants. - Reworks the Foundation underpinnings to be a bit safer and more clear in how they're used and passed around. - Changes to docs structure for push towards v0.1. - Examples updated to account for changes.
This commit is contained in:
parent
5cd59b5636
commit
10c513edad
|
@ -10,7 +10,9 @@ categories = ["gui", "os::macos-apis", "os::ios-apis"]
|
||||||
keywords = ["gui", "macos", "ios", "appkit", "uikit"]
|
keywords = ["gui", "macos", "ios", "appkit", "uikit"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
default-target = "x86_64-apple-darwin"
|
default-target = "x86_64-apple-darwin"
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
block = "0.1.6"
|
block = "0.1.6"
|
||||||
|
@ -37,4 +39,4 @@ color_fallbacks = []
|
||||||
quicklook = []
|
quicklook = []
|
||||||
user-notifications = ["uuid"]
|
user-notifications = ["uuid"]
|
||||||
webview = []
|
webview = []
|
||||||
webview-downloading = []
|
webview-downloading-macos = []
|
||||||
|
|
|
@ -51,16 +51,16 @@ impl WindowDelegate for AppWindow {
|
||||||
window.set_content_view(&self.content);
|
window.set_content_view(&self.content);
|
||||||
|
|
||||||
LayoutConstraint::activate(&[
|
LayoutConstraint::activate(&[
|
||||||
self.blue.top.constraint_equal_to(&self.content.top).offset(16.),
|
self.blue.top.constraint_equal_to(&self.content.top).offset(46.),
|
||||||
self.blue.leading.constraint_equal_to(&self.content.leading).offset(16.),
|
self.blue.leading.constraint_equal_to(&self.content.leading).offset(16.),
|
||||||
self.blue.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
self.blue.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
||||||
self.blue.width.constraint_equal_to_constant(100.),
|
self.blue.width.constraint_equal_to_constant(100.),
|
||||||
|
|
||||||
self.red.top.constraint_equal_to(&self.content.top).offset(16.),
|
self.red.top.constraint_equal_to(&self.content.top).offset(46.),
|
||||||
self.red.leading.constraint_equal_to(&self.blue.trailing).offset(16.),
|
self.red.leading.constraint_equal_to(&self.blue.trailing).offset(16.),
|
||||||
self.red.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
self.red.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
||||||
|
|
||||||
self.green.top.constraint_equal_to(&self.content.top).offset(16.),
|
self.green.top.constraint_equal_to(&self.content.top).offset(46.),
|
||||||
self.green.leading.constraint_equal_to(&self.red.trailing).offset(16.),
|
self.green.leading.constraint_equal_to(&self.red.trailing).offset(16.),
|
||||||
self.green.trailing.constraint_equal_to(&self.content.trailing).offset(-16.),
|
self.green.trailing.constraint_equal_to(&self.content.trailing).offset(-16.),
|
||||||
self.green.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
self.green.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
||||||
|
|
|
@ -2,7 +2,6 @@ use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use cacao::lazy_static::lazy_static;
|
use cacao::lazy_static::lazy_static;
|
||||||
use cacao::macos::App;
|
use cacao::macos::App;
|
||||||
use cacao::notification_center::Dispatcher;
|
|
||||||
|
|
||||||
use crate::CalculatorApp;
|
use crate::CalculatorApp;
|
||||||
|
|
||||||
|
@ -24,7 +23,6 @@ pub enum Msg {
|
||||||
/// on the main thread.
|
/// on the main thread.
|
||||||
pub fn dispatch(msg: Msg) {
|
pub fn dispatch(msg: Msg) {
|
||||||
println!("Dispatching UI message: {:?}", msg);
|
println!("Dispatching UI message: {:?}", msg);
|
||||||
//App::<CalculatorApp, Msg>::dispatch_main(msg)
|
|
||||||
CALCULATOR.run(msg)
|
CALCULATOR.run(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl ViewDelegate for CalculatorView {
|
||||||
self.results_wrapper.top.constraint_equal_to(&view.top),
|
self.results_wrapper.top.constraint_equal_to(&view.top),
|
||||||
self.results_wrapper.leading.constraint_equal_to(&view.leading),
|
self.results_wrapper.leading.constraint_equal_to(&view.leading),
|
||||||
self.results_wrapper.trailing.constraint_equal_to(&view.trailing),
|
self.results_wrapper.trailing.constraint_equal_to(&view.trailing),
|
||||||
self.results_wrapper.height.constraint_equal_to_constant(52.),
|
self.results_wrapper.height.constraint_equal_to_constant(80.),
|
||||||
|
|
||||||
self.label.leading.constraint_equal_to(&self.results_wrapper.leading).offset(22.),
|
self.label.leading.constraint_equal_to(&self.results_wrapper.leading).offset(22.),
|
||||||
self.label.trailing.constraint_equal_to(&self.results_wrapper.trailing).offset(-16.),
|
self.label.trailing.constraint_equal_to(&self.results_wrapper.trailing).offset(-16.),
|
||||||
|
|
|
@ -16,11 +16,10 @@ use cacao::macos::window::{Window, WindowConfig, TitleVisibility};
|
||||||
use cacao::macos::{Event, EventMask, EventMonitor};
|
use cacao::macos::{Event, EventMask, EventMonitor};
|
||||||
use cacao::color::Color;
|
use cacao::color::Color;
|
||||||
use cacao::notification_center::Dispatcher;
|
use cacao::notification_center::Dispatcher;
|
||||||
use cacao::view::{View, ViewDelegate};
|
use cacao::view::View;
|
||||||
|
|
||||||
mod button_row;
|
mod button_row;
|
||||||
mod calculator;
|
mod calculator;
|
||||||
use calculator::{dispatch, Msg};
|
|
||||||
|
|
||||||
mod content_view;
|
mod content_view;
|
||||||
use content_view::CalculatorView;
|
use content_view::CalculatorView;
|
||||||
|
@ -38,7 +37,7 @@ impl AppDelegate for CalculatorApp {
|
||||||
// Event Monitors need to be started after the App has been activated.
|
// Event Monitors need to be started after the App has been activated.
|
||||||
// We use an RwLock here, but it's possible this entire method can be
|
// We use an RwLock here, but it's possible this entire method can be
|
||||||
// &mut self and you wouldn't need these kinds of shenanigans.
|
// &mut self and you wouldn't need these kinds of shenanigans.
|
||||||
//self.start_monitoring();
|
self.start_monitoring();
|
||||||
|
|
||||||
self.window.set_title("Calculator");
|
self.window.set_title("Calculator");
|
||||||
self.window.set_background_color(Color::rgb(49,49,49));
|
self.window.set_background_color(Color::rgb(49,49,49));
|
||||||
|
@ -70,7 +69,8 @@ impl CalculatorApp {
|
||||||
let characters = evt.characters();
|
let characters = evt.characters();
|
||||||
println!("{}", characters);
|
println!("{}", characters);
|
||||||
|
|
||||||
match characters.as_ref() {
|
//use calculator::{dispatch, Msg};
|
||||||
|
/*match characters.as_ref() {
|
||||||
"0" => dispatch(Msg::Push(0)),
|
"0" => dispatch(Msg::Push(0)),
|
||||||
"1" => dispatch(Msg::Push(1)),
|
"1" => dispatch(Msg::Push(1)),
|
||||||
"2" => dispatch(Msg::Push(2)),
|
"2" => dispatch(Msg::Push(2)),
|
||||||
|
@ -90,7 +90,7 @@ impl CalculatorApp {
|
||||||
"c" => dispatch(Msg::Clear),
|
"c" => dispatch(Msg::Clear),
|
||||||
"." => dispatch(Msg::Decimal),
|
"." => dispatch(Msg::Decimal),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
None
|
None
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -12,58 +12,58 @@ use crate::storage::{dispatch_ui, Message};
|
||||||
pub fn menu() -> Vec<Menu> {
|
pub fn menu() -> Vec<Menu> {
|
||||||
vec![
|
vec![
|
||||||
Menu::new("", vec![
|
Menu::new("", vec![
|
||||||
MenuItem::about("Todos"),
|
MenuItem::About("Todos".to_string()),
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
|
|
||||||
MenuItem::entry("Preferences").key(",").action(|| {
|
MenuItem::new("Preferences").key(",").action(|| {
|
||||||
dispatch_ui(Message::OpenPreferencesWindow);
|
dispatch_ui(Message::OpenPreferencesWindow);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::services(),
|
MenuItem::Services,
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::hide(),
|
MenuItem::Hide,
|
||||||
MenuItem::hide_others(),
|
MenuItem::HideOthers,
|
||||||
MenuItem::show_all(),
|
MenuItem::ShowAll,
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::quit()
|
MenuItem::Quit
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Menu::new("File", vec![
|
Menu::new("File", vec![
|
||||||
MenuItem::entry("Open/Show Window").key("n").action(|| {
|
MenuItem::new("Open/Show Window").key("n").action(|| {
|
||||||
dispatch_ui(Message::OpenMainWindow);
|
dispatch_ui(Message::OpenMainWindow);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
|
|
||||||
MenuItem::entry("Add Todo").key("+").action(|| {
|
MenuItem::new("Add Todo").key("+").action(|| {
|
||||||
dispatch_ui(Message::OpenNewTodoSheet);
|
dispatch_ui(Message::OpenNewTodoSheet);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::close_window(),
|
MenuItem::CloseWindow
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Menu::new("Edit", vec![
|
Menu::new("Edit", vec![
|
||||||
MenuItem::undo(),
|
MenuItem::Undo,
|
||||||
MenuItem::redo(),
|
MenuItem::Redo,
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::cut(),
|
MenuItem::Cut,
|
||||||
MenuItem::copy(),
|
MenuItem::Copy,
|
||||||
MenuItem::paste(),
|
MenuItem::Paste,
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::select_all()
|
MenuItem::SelectAll
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Menu::new("View", vec![
|
Menu::new("View", vec![
|
||||||
MenuItem::enter_full_screen()
|
MenuItem::EnterFullScreen
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Menu::new("Window", vec![
|
Menu::new("Window", vec![
|
||||||
MenuItem::minimize(),
|
MenuItem::Minimize,
|
||||||
MenuItem::zoom(),
|
MenuItem::Zoom,
|
||||||
MenuItem::Separator,
|
MenuItem::Separator,
|
||||||
MenuItem::entry("Bring All to Front")
|
MenuItem::new("Bring All to Front")
|
||||||
]),
|
]),
|
||||||
|
|
||||||
Menu::new("Help", vec![])
|
Menu::new("Help", vec![])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Implements an example toolbar for a Preferences app. Could be cleaner, probably worth cleaning
|
//! Implements an example toolbar for a Preferences app. Could be cleaner, probably worth cleaning
|
||||||
//! up at some point.
|
//! up at some point.
|
||||||
|
|
||||||
use cacao::macos::toolbar::{Toolbar, ToolbarDelegate, ToolbarItem};
|
use cacao::macos::toolbar::{Toolbar, ToolbarDelegate, ToolbarItem, ItemIdentifier};
|
||||||
use cacao::image::{Image, MacSystemIcon};
|
use cacao::image::{Image, MacSystemIcon};
|
||||||
|
|
||||||
use crate::storage::{dispatch_ui, Message};
|
use crate::storage::{dispatch_ui, Message};
|
||||||
|
@ -46,16 +46,16 @@ impl ToolbarDelegate for PreferencesToolbar {
|
||||||
toolbar.set_selected("general");
|
toolbar.set_selected("general");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_item_identifiers(&self) -> Vec<&'static str> {
|
fn allowed_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||||
vec!["general", "advanced"]
|
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_item_identifiers(&self) -> Vec<&'static str> {
|
fn default_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||||
vec!["general", "advanced"]
|
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selectable_item_identifiers(&self) -> Vec<&'static str> {
|
fn selectable_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||||
vec!["general", "advanced"]
|
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn item_for(&self, identifier: &str) -> &ToolbarItem {
|
fn item_for(&self, identifier: &str) -> &ToolbarItem {
|
||||||
|
|
|
@ -48,7 +48,7 @@ impl TodosListView {
|
||||||
self.view.as_ref().unwrap().perform_batch_updates(|listview| {
|
self.view.as_ref().unwrap().perform_batch_updates(|listview| {
|
||||||
// We know we always insert at the 0 index, so this is a simple calculation.
|
// We know we always insert at the 0 index, so this is a simple calculation.
|
||||||
// You'd need to diff yourself for anything more complicated.
|
// You'd need to diff yourself for anything more complicated.
|
||||||
listview.insert_rows(0..1, RowAnimation::SlideDown);
|
listview.insert_rows(&[0], RowAnimation::SlideDown);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
//! The main Todos window toolbar. Contains a button to enable adding a new task.
|
//! The main Todos window toolbar. Contains a button to enable adding a new task.
|
||||||
|
|
||||||
use cacao::button::Button;
|
use cacao::button::Button;
|
||||||
use cacao::macos::toolbar::{Toolbar, ToolbarDelegate, ToolbarItem, ToolbarDisplayMode};
|
use cacao::macos::toolbar::{
|
||||||
|
Toolbar, ToolbarDelegate, ToolbarItem,
|
||||||
|
ToolbarDisplayMode, ItemIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
use crate::storage::{dispatch_ui, Message};
|
use crate::storage::{dispatch_ui, Message};
|
||||||
|
|
||||||
|
@ -31,12 +34,12 @@ impl ToolbarDelegate for TodosToolbar {
|
||||||
toolbar.set_display_mode(ToolbarDisplayMode::IconOnly);
|
toolbar.set_display_mode(ToolbarDisplayMode::IconOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_item_identifiers(&self) -> Vec<&'static str> {
|
fn allowed_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||||
vec!["AddTodoButton"]
|
vec![ItemIdentifier::Custom("AddTodoButton")]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_item_identifiers(&self) -> Vec<&'static str> {
|
fn default_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||||
vec!["AddTodoButton"]
|
vec![ItemIdentifier::Custom("AddTodoButton")]
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only have one item, so we don't care about the identifier.
|
// We only have one item, so we don't care about the identifier.
|
||||||
|
|
|
@ -10,6 +10,7 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
use crate::image::Image;
|
||||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
||||||
use crate::invoker::TargetActionHandler;
|
use crate::invoker::TargetActionHandler;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
@ -28,6 +29,7 @@ extern "C" {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
pub objc: ShareId<Object>,
|
pub objc: ShareId<Object>,
|
||||||
|
pub image: Option<Image>,
|
||||||
handler: Option<TargetActionHandler>,
|
handler: Option<TargetActionHandler>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
@ -62,7 +64,7 @@ impl Button {
|
||||||
let title = NSString::new(text);
|
let title = NSString::new(text);
|
||||||
|
|
||||||
let view: id = unsafe {
|
let view: id = unsafe {
|
||||||
let button: id = msg_send![register_class(), buttonWithTitle:title target:nil action:nil];
|
let button: id = msg_send![register_class(), buttonWithTitle:&*title target:nil action:nil];
|
||||||
let _: () = msg_send![button, setWantsLayer:YES];
|
let _: () = msg_send![button, setWantsLayer:YES];
|
||||||
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
button
|
button
|
||||||
|
@ -70,6 +72,7 @@ impl Button {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
handler: None,
|
handler: None,
|
||||||
|
image: None,
|
||||||
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
||||||
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
||||||
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
|
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
|
||||||
|
@ -82,6 +85,14 @@ impl Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_image(&mut self, image: Image) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setImage:&*image.0];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image = Some(image);
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the bezel style for this button.
|
/// Sets the bezel style for this button.
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
||||||
|
@ -115,10 +126,10 @@ impl Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_key_equivalent(&self, key: &str) {
|
pub fn set_key_equivalent(&self, key: &str) {
|
||||||
let key = NSString::new(key).into_inner();
|
let key = NSString::new(key);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setKeyEquivalent:key];
|
let _: () = msg_send![&*self.objc, setKeyEquivalent:&*key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
//! that enables this functionality, we want to be able to provide this with some level of
|
//! that enables this functionality, we want to be able to provide this with some level of
|
||||||
//! backwards compatibility for Mojave, as that's still a supported OS.
|
//! backwards compatibility for Mojave, as that's still a supported OS.
|
||||||
|
|
||||||
|
use std::os::raw::c_void;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
use core_graphics::base::CGFloat;
|
use core_graphics::base::CGFloat;
|
||||||
|
@ -26,7 +27,6 @@ pub(crate) const AQUA_LIGHT_COLOR_HIGH_CONTRAST: &'static str = "AQUA_LIGHT_COLO
|
||||||
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
|
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
|
||||||
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
static NSAppearanceNameAqua: id;
|
static NSAppearanceNameAqua: id;
|
||||||
|
@ -56,7 +56,7 @@ fn get_effective_color(this: &Object) -> id {
|
||||||
NSAppearanceNameAccessibilityHighContrastDarkAqua
|
NSAppearanceNameAccessibilityHighContrastDarkAqua
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let style: id = msg_send![appearance, bestMatchFromAppearancesWithNames:names.into_inner()];
|
let style: id = msg_send![appearance, bestMatchFromAppearancesWithNames:&*names];
|
||||||
|
|
||||||
if style == NSAppearanceNameDarkAqua {
|
if style == NSAppearanceNameDarkAqua {
|
||||||
return *this.get_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST);
|
return *this.get_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST);
|
||||||
|
|
|
@ -242,7 +242,11 @@ pub enum Color {
|
||||||
DarkText,
|
DarkText,
|
||||||
|
|
||||||
/// The un-adaptable color for text on a dark background.
|
/// The un-adaptable color for text on a dark background.
|
||||||
LightText
|
LightText,
|
||||||
|
|
||||||
|
WindowBackgroundColor,
|
||||||
|
|
||||||
|
UnderPageBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
|
@ -484,5 +488,7 @@ unsafe fn to_objc(obj: &Color) -> id {
|
||||||
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
||||||
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
||||||
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
||||||
|
Color::WindowBackgroundColor => system_color_with_fallback!(color, windowBackgroundColor, clearColor),
|
||||||
|
Color::UnderPageBackgroundColor => system_color_with_fallback!(color, underPageBackgroundColor, clearColor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
//! a loop.
|
//! a loop.
|
||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//! ```rust
|
//! ```rust,no_run
|
||||||
//! use std::collections::HashMap;
|
//! use std::collections::HashMap;
|
||||||
//! use cacao::defaults::{UserDefaults, Value};
|
//! use cacao::defaults::{UserDefaults, Value};
|
||||||
//!
|
//!
|
||||||
|
@ -27,7 +27,6 @@
|
||||||
//! map
|
//! map
|
||||||
//! });
|
//! });
|
||||||
//!
|
//!
|
||||||
//! // Ignore the unwrap() calls, it's a demo ;P
|
|
||||||
//! let value = defaults.get("test").unwrap().as_str().unwrap();
|
//! let value = defaults.get("test").unwrap().as_str().unwrap();
|
||||||
//! assert_eq!(value, "value");
|
//! assert_eq!(value, "value");
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -38,7 +37,10 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, to_bool, YES, NO, BOOL, NSData, NSString, NSDictionary, NSNumber};
|
use crate::foundation::{
|
||||||
|
id, nil, to_bool, YES, NO, BOOL,
|
||||||
|
NSData, NSString, NSMutableDictionary, NSNumber
|
||||||
|
};
|
||||||
|
|
||||||
mod value;
|
mod value;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
|
@ -92,7 +94,7 @@ impl UserDefaults {
|
||||||
|
|
||||||
UserDefaults(unsafe {
|
UserDefaults(unsafe {
|
||||||
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
|
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
|
||||||
Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()])
|
Id::from_ptr(msg_send![alloc, initWithSuiteName:&*name])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,10 +116,10 @@ impl UserDefaults {
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, Value>) {
|
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, Value>) {
|
||||||
let dictionary = NSDictionary::from(values);
|
let dictionary = NSMutableDictionary::from(values);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, registerDefaults:dictionary.into_inner()];
|
let _: () = msg_send![&*self.0, registerDefaults:&*dictionary];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +153,7 @@ impl UserDefaults {
|
||||||
let key = NSString::new(key.as_ref());
|
let key = NSString::new(key.as_ref());
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()];
|
let _: () = msg_send![&*self.0, removeObjectForKey:&*key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +178,7 @@ impl UserDefaults {
|
||||||
let key = NSString::new(key.as_ref());
|
let key = NSString::new(key.as_ref());
|
||||||
|
|
||||||
let result: id = unsafe {
|
let result: id = unsafe {
|
||||||
msg_send![&*self.0, objectForKey:key.into_inner()]
|
msg_send![&*self.0, objectForKey:&*key]
|
||||||
};
|
};
|
||||||
|
|
||||||
if result == nil {
|
if result == nil {
|
||||||
|
@ -184,12 +186,12 @@ impl UserDefaults {
|
||||||
}
|
}
|
||||||
|
|
||||||
if NSData::is(result) {
|
if NSData::is(result) {
|
||||||
let data = NSData::wrap(result);
|
let data = NSData::retain(result);
|
||||||
return Some(Value::Data(data.into_vec()));
|
return Some(Value::Data(data.into_vec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if NSString::is(result) {
|
if NSString::is(result) {
|
||||||
let s = NSString::wrap(result).to_str().to_string();
|
let s = NSString::retain(result).to_string();
|
||||||
return Some(Value::String(s));
|
return Some(Value::String(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +215,7 @@ impl UserDefaults {
|
||||||
x => {
|
x => {
|
||||||
// Debugging code that should be removed at some point.
|
// Debugging code that should be removed at some point.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
println!("Code: {}", x);
|
println!("Unexpected code type found: {}", x);
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -241,7 +243,7 @@ impl UserDefaults {
|
||||||
pub fn is_forced_for_key<K: AsRef<str>>(&self, key: K) -> bool {
|
pub fn is_forced_for_key<K: AsRef<str>>(&self, key: K) -> bool {
|
||||||
let result: BOOL = unsafe {
|
let result: BOOL = unsafe {
|
||||||
let key = NSString::new(key.as_ref());
|
let key = NSString::new(key.as_ref());
|
||||||
msg_send![&*self.0, objectIsForcedForKey:key.into_inner()]
|
msg_send![&*self.0, objectIsForcedForKey:&*key]
|
||||||
};
|
};
|
||||||
|
|
||||||
to_bool(result)
|
to_bool(result)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::foundation::{id, NSData, NSDictionary, NSString, NSNumber};
|
use crate::foundation::{id, NSData, NSMutableDictionary, NSString, NSNumber};
|
||||||
|
|
||||||
/// Represents a Value that can be stored or queried with `UserDefaults`.
|
/// Represents a Value that can be stored or queried with `UserDefaults`.
|
||||||
///
|
///
|
||||||
|
@ -136,22 +136,22 @@ impl From<Value> for id {
|
||||||
// period.
|
// period.
|
||||||
fn from(value: Value) -> Self {
|
fn from(value: Value) -> Self {
|
||||||
match value {
|
match value {
|
||||||
Value::Bool(b) => NSNumber::bool(b).into_inner(),
|
Value::Bool(b) => NSNumber::bool(b).into(),
|
||||||
Value::String(s) => NSString::new(&s).into_inner(),
|
Value::String(s) => NSString::new(&s).into(),
|
||||||
Value::Float(f) => NSNumber::float(f).into_inner(),
|
Value::Float(f) => NSNumber::float(f).into(),
|
||||||
Value::Integer(i) => NSNumber::integer(i).into_inner(),
|
Value::Integer(i) => NSNumber::integer(i).into(),
|
||||||
Value::Data(data) => NSData::new(data).into_inner()
|
Value::Data(data) => NSData::new(data).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K> From<HashMap<K, Value>> for NSDictionary
|
impl<K> From<HashMap<K, Value>> for NSMutableDictionary
|
||||||
where
|
where
|
||||||
K: AsRef<str>
|
K: AsRef<str>
|
||||||
{
|
{
|
||||||
/// Translates a `HashMap` of `Value`s into an `NSDictionary`.
|
/// Translates a `HashMap` of `Value`s into an `NSDictionary`.
|
||||||
fn from(map: HashMap<K, Value>) -> Self {
|
fn from(map: HashMap<K, Value>) -> Self {
|
||||||
let mut dictionary = NSDictionary::new();
|
let mut dictionary = NSMutableDictionary::new();
|
||||||
|
|
||||||
for (key, value) in map.into_iter() {
|
for (key, value) in map.into_iter() {
|
||||||
let k = NSString::new(key.as_ref());
|
let k = NSString::new(key.as_ref());
|
||||||
|
|
10
src/error.rs
10
src/error.rs
|
@ -33,16 +33,16 @@ impl Error {
|
||||||
pub fn new(error: id) -> Self {
|
pub fn new(error: id) -> Self {
|
||||||
let (code, domain, description) = unsafe {
|
let (code, domain, description) = unsafe {
|
||||||
let code: usize = msg_send![error, code];
|
let code: usize = msg_send![error, code];
|
||||||
let domain = NSString::wrap(msg_send![error, domain]);
|
let domain = NSString::retain(msg_send![error, domain]);
|
||||||
let description = NSString::wrap(msg_send![error, localizedDescription]);
|
let description = NSString::retain(msg_send![error, localizedDescription]);
|
||||||
|
|
||||||
(code, domain, description)
|
(code, domain, description)
|
||||||
};
|
};
|
||||||
|
|
||||||
Error {
|
Error {
|
||||||
code: code,
|
code,
|
||||||
domain: domain.to_str().to_string(),
|
domain: domain.to_string(),
|
||||||
description: description.to_str().to_string()
|
description: description.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl FileManager {
|
||||||
create:NO
|
create:NO
|
||||||
error:nil];
|
error:nil];
|
||||||
|
|
||||||
NSString::wrap(msg_send![dir, absoluteString])
|
NSString::retain(msg_send![dir, absoluteString])
|
||||||
};
|
};
|
||||||
|
|
||||||
Url::parse(directory.to_str()).map_err(|e| e.into())
|
Url::parse(directory.to_str()).map_err(|e| e.into())
|
||||||
|
@ -70,8 +70,8 @@ impl FileManager {
|
||||||
let to = NSString::new(to.as_str());
|
let to = NSString::new(to.as_str());
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let from_url: id = msg_send![class!(NSURL), URLWithString:from.into_inner()];
|
let from_url: id = msg_send![class!(NSURL), URLWithString:&*from];
|
||||||
let to_url: id = msg_send![class!(NSURL), URLWithString:to.into_inner()];
|
let to_url: id = msg_send![class!(NSURL), URLWithString:&*to];
|
||||||
|
|
||||||
// This should potentially be write(), but the backing class handles this logic
|
// This should potentially be write(), but the backing class handles this logic
|
||||||
// already, so... going to leave it as read.
|
// already, so... going to leave it as read.
|
||||||
|
|
|
@ -102,7 +102,7 @@ pub fn get_url(panel: &Object) -> Option<String> {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let path: id = msg_send![url, path];
|
let path: id = msg_send![url, path];
|
||||||
Some(NSString::wrap(path).to_str().to_string())
|
Some(NSString::retain(path).to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ use objc_id::ShareId;
|
||||||
use crate::foundation::{id, YES, NO, NSInteger, NSString};
|
use crate::foundation::{id, YES, NO, NSInteger, NSString};
|
||||||
use crate::filesystem::enums::ModalResponse;
|
use crate::filesystem::enums::ModalResponse;
|
||||||
|
|
||||||
|
use crate::macos::window::{Window, WindowDelegate};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileSelectPanel {
|
pub struct FileSelectPanel {
|
||||||
/// The internal Objective C `NSOpenPanel` instance.
|
/// The internal Objective C `NSOpenPanel` instance.
|
||||||
|
@ -122,7 +124,13 @@ impl FileSelectPanel {
|
||||||
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
||||||
/// the system runs and manages that in another process, and we're still abiding by the general
|
/// the system runs and manages that in another process, and we're still abiding by the general
|
||||||
/// retain/ownership rules here.
|
/// retain/ownership rules here.
|
||||||
pub fn show<F: Fn(Vec<PathBuf>) + 'static>(&self, handler: F) {
|
///
|
||||||
|
/// This is offered for scenarios where you don't necessarily have a Window (e.g, a shell
|
||||||
|
/// script) or can't easily pass one to use as a sheet.
|
||||||
|
pub fn show<F>(&self, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(Vec<PathBuf>) + 'static
|
||||||
|
{
|
||||||
let panel = self.panel.clone();
|
let panel = self.panel.clone();
|
||||||
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||||
let response: ModalResponse = result.into();
|
let response: ModalResponse = result.into();
|
||||||
|
@ -137,30 +145,46 @@ impl FileSelectPanel {
|
||||||
let _: () = msg_send![&*self.panel, beginWithCompletionHandler:completion.copy()];
|
let _: () = msg_send![&*self.panel, beginWithCompletionHandler:completion.copy()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shows the panel as a modal. Currently, this method accepts `Window`s which use a delegate.
|
||||||
|
/// If you're using a `Window` without a delegate, you may need to opt to use the `show()`
|
||||||
|
/// method.
|
||||||
|
///
|
||||||
|
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
||||||
|
/// the system runs and manages that in another process, and we're still abiding by the general
|
||||||
|
/// retain/ownership rules here.
|
||||||
|
pub fn begin_sheet<T, F>(&self, window: &Window<T>, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(Vec<PathBuf>) + 'static
|
||||||
|
{
|
||||||
|
let panel = self.panel.clone();
|
||||||
|
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||||
|
let response: ModalResponse = result.into();
|
||||||
|
|
||||||
|
handler(match response {
|
||||||
|
ModalResponse::Ok => get_urls(&panel),
|
||||||
|
_ => Vec::new()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.panel, beginSheetModalForWindow:&*window.objc completionHandler:completion.copy()];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the selected URLs from the provided panel.
|
/// Retrieves the selected URLs from the provided panel.
|
||||||
/// This is currently a bit ugly, but it's also not something that needs to be the best thing in
|
/// This is currently a bit ugly, but it's also not something that needs to be the best thing in
|
||||||
/// the world as it (ideally) shouldn't be called repeatedly in hot spots.
|
/// the world as it (ideally) shouldn't be called repeatedly in hot spots.
|
||||||
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
|
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
|
||||||
let mut paths: Vec<PathBuf> = vec![];
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let urls: id = msg_send![&*panel, URLs];
|
let urls: id = msg_send![&*panel, URLs];
|
||||||
let mut count: usize = msg_send![urls, count];
|
let count: usize = msg_send![urls, count];
|
||||||
|
|
||||||
loop {
|
(0..count).map(|index| {
|
||||||
if count == 0 {
|
let url: id = msg_send![urls, objectAtIndex:index];
|
||||||
break;
|
let path = NSString::retain(msg_send![url, path]).to_string();
|
||||||
}
|
path.into()
|
||||||
|
}).collect()
|
||||||
let url: id = msg_send![urls, objectAtIndex:count-1];
|
|
||||||
let path = NSString::wrap(msg_send![url, path]).to_str().to_string();
|
|
||||||
paths.push(path.into());
|
|
||||||
count -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paths.reverse();
|
|
||||||
paths
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +1,4 @@
|
||||||
//! A wrapper type for `NSArray`.
|
use std::ops::{Deref, DerefMut};
|
||||||
//!
|
|
||||||
//! This is abstracted out as we need to use `NSArray` in a ton of
|
|
||||||
//! instances in this framework, and down the road I'd like to investigate using `CFArray` instead
|
|
||||||
//! of `NSArray` (i.e, if the ObjC runtime is ever pulled or something - perhaps those types would
|
|
||||||
//! stick around).
|
|
||||||
//!
|
|
||||||
//! Essentially, consider this some sanity/cleanliness/future-proofing. End users should never need
|
|
||||||
//! to touch this.
|
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
|
@ -15,15 +7,68 @@ use objc_id::Id;
|
||||||
use crate::foundation::id;
|
use crate::foundation::id;
|
||||||
|
|
||||||
/// A wrapper for `NSArray` that makes common operations in our framework a bit easier to handle
|
/// A wrapper for `NSArray` that makes common operations in our framework a bit easier to handle
|
||||||
/// and reason about.
|
/// and reason about. This also provides a central place to look at replacing with `CFArray` if
|
||||||
|
/// ever deemed necessary (unlikely, given how much Apple has optimized the Foundation classes, but
|
||||||
|
/// still...).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NSArray(pub Id<Object>);
|
pub struct NSArray(pub Id<Object>);
|
||||||
|
|
||||||
|
impl NSArray {
|
||||||
|
/// Given a set of `Object`s, creates and retains an `NSArray` that holds them.
|
||||||
|
pub fn new(objects: &[id]) -> Self {
|
||||||
|
NSArray(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSArray),
|
||||||
|
arrayWithObjects:objects.as_ptr()
|
||||||
|
count:objects.len()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In some cases, we're vended an `NSArray` by the system that we need to call retain on.
|
||||||
|
/// This handles that case.
|
||||||
|
pub fn retain(array: id) -> Self {
|
||||||
|
NSArray(unsafe {
|
||||||
|
Id::from_ptr(array)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In some cases, we're vended an `NSArray` by the system, and it's ideal to not retain that.
|
||||||
|
/// This handles that edge case.
|
||||||
|
pub fn from_retained(array: id) -> Self {
|
||||||
|
NSArray(unsafe {
|
||||||
|
Id::from_retained_ptr(array)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `count` (`len()` equivalent) for the backing `NSArray`.
|
||||||
|
pub fn count(&self) -> usize {
|
||||||
|
unsafe { msg_send![&*self.0, count] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper method for mapping over the backing `NSArray` items and producing a Rust `Vec<T>`.
|
||||||
|
/// Often times we need to map in this framework to convert between Rust types, so isolating
|
||||||
|
/// this out makes life much easier.
|
||||||
|
pub fn map<T, F: Fn(id) -> T>(&self, transform: F) -> Vec<T> {
|
||||||
|
let count = self.count();
|
||||||
|
let objc = &*self.0;
|
||||||
|
|
||||||
|
// I don't know if it's worth trying to get in with NSFastEnumeration here. I'm content to
|
||||||
|
// just rely on Rust, but someone is free to profile it if they want.
|
||||||
|
(0..count).map(|index| {
|
||||||
|
let item: id = unsafe { msg_send![objc, objectAtIndex:index] };
|
||||||
|
transform(item)
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Vec<&Object>> for NSArray {
|
impl From<Vec<&Object>> for NSArray {
|
||||||
/// Given a set of `Object`s, creates an `NSArray` that holds them.
|
/// Given a set of `Object`s, creates an `NSArray` that holds them.
|
||||||
fn from(objects: Vec<&Object>) -> Self {
|
fn from(objects: Vec<&Object>) -> Self {
|
||||||
NSArray(unsafe {
|
NSArray(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()])
|
Id::from_ptr(msg_send![class!(NSArray),
|
||||||
|
arrayWithObjects:objects.as_ptr()
|
||||||
|
count:objects.len()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,53 +77,33 @@ impl From<Vec<id>> for NSArray {
|
||||||
/// Given a set of `*mut Object`s, creates an `NSArray` that holds them.
|
/// Given a set of `*mut Object`s, creates an `NSArray` that holds them.
|
||||||
fn from(objects: Vec<id>) -> Self {
|
fn from(objects: Vec<id>) -> Self {
|
||||||
NSArray(unsafe {
|
NSArray(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()])
|
Id::from_ptr(msg_send![class!(NSArray),
|
||||||
|
arrayWithObjects:objects.as_ptr()
|
||||||
|
count:objects.len()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NSArray {
|
impl From<NSArray> for id {
|
||||||
/// Given a set of `Object`s, creates an `NSArray` that holds them.
|
/// Consumes and returns the pointer to the underlying NSArray.
|
||||||
pub fn new(objects: &[id]) -> Self {
|
fn from(mut array: NSArray) -> Self {
|
||||||
NSArray(unsafe {
|
&mut *array
|
||||||
Id::from_ptr(msg_send![class!(NSArray), arrayWithObjects:objects.as_ptr() count:objects.len()])
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In some cases, we're vended an `NSArray` by the system, and it's ideal to not retain that.
|
impl Deref for NSArray {
|
||||||
/// This handles that edge case.
|
type Target = Object;
|
||||||
pub fn wrap(array: id) -> Self {
|
|
||||||
NSArray(unsafe {
|
/// Derefs to the underlying Objective-C Object.
|
||||||
Id::from_ptr(array)
|
fn deref(&self) -> &Object {
|
||||||
})
|
&*self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consumes and returns the underlying Objective-C value.
|
impl DerefMut for NSArray {
|
||||||
pub fn into_inner(mut self) -> id {
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref_mut(&mut self) -> &mut Object {
|
||||||
&mut *self.0
|
&mut *self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `count` (`len()` equivalent) for the backing `NSArray`.
|
|
||||||
pub fn count(&self) -> usize {
|
|
||||||
unsafe { msg_send![self.0, count] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A helper method for mapping over the backing `NSArray` items.
|
|
||||||
/// Often times we need to map in this framework to convert between Rust types, so isolating
|
|
||||||
/// this out makes life much easier.
|
|
||||||
pub fn map<T, F: Fn(id) -> T>(&self, transform: F) -> Vec<T> {
|
|
||||||
let count = self.count();
|
|
||||||
let mut ret: Vec<T> = Vec::with_capacity(count);
|
|
||||||
let mut index = 0;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let item: id = unsafe { msg_send![&*self.0, objectAtIndex:index] };
|
|
||||||
ret.push(transform(item));
|
|
||||||
|
|
||||||
index += 1;
|
|
||||||
if index == count { break }
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,46 @@
|
||||||
//! A lightweight wrapper around `NSAutoreleasePool`.
|
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
|
/// A wrapper around `NSAutoReleasePool`. The core `App` structures create and manage one of these,
|
||||||
|
/// but it's conceivable that users might need to create their own.
|
||||||
|
///
|
||||||
|
/// When this is dropped, we automatically send a `drain` message to the underlying pool. You can
|
||||||
|
/// also call `drain()` yourself if you need to drain for whatever reason.
|
||||||
pub struct AutoReleasePool(pub Id<Object>);
|
pub struct AutoReleasePool(pub Id<Object>);
|
||||||
|
|
||||||
impl AutoReleasePool {
|
impl AutoReleasePool {
|
||||||
|
/// Creates and returns a new `AutoReleasePool`. You need to take care to keep this alive for
|
||||||
|
/// as long as you need it.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
AutoReleasePool(unsafe {
|
AutoReleasePool(unsafe {
|
||||||
Id::from_retained_ptr(msg_send![class!(NSAutoreleasePool), new])
|
Id::from_retained_ptr(msg_send![class!(NSAutoreleasePool), new])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drains the underlying AutoreleasePool.
|
||||||
pub fn drain(&self) {
|
pub fn drain(&self) {
|
||||||
let _: () = unsafe { msg_send![&*self.0, drain] };
|
let _: () = unsafe { msg_send![&*self.0, drain] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a function with a one-off AutoReleasePool.
|
||||||
|
///
|
||||||
|
/// This will create a custom NSAutoreleasePool, run your handler, and automatically drain
|
||||||
|
/// when done. This is (roughly, ish) equivalent to `@autoreleasepool {}` under ARC. If you
|
||||||
|
/// need to perform Cocoa calls on a different thread, it's important to ensure they're backed
|
||||||
|
/// with an autorelease pool - otherwise your memory footprint will continue to grow.
|
||||||
|
pub fn run<F>(handler: F)
|
||||||
|
where
|
||||||
|
F: Fn() + 'static
|
||||||
|
{
|
||||||
|
let _pool = AutoReleasePool::new();
|
||||||
|
handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AutoReleasePool {
|
||||||
|
/// Drains the underlying NSAutoreleasePool.
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _: () = unsafe { msg_send![&*self.0, drain] };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ lazy_static! {
|
||||||
/// constantly calling into the runtime, we store pointers to Class types here after first lookup
|
/// constantly calling into the runtime, we store pointers to Class types here after first lookup
|
||||||
/// and/or creation. The general store format is (roughly speaking) as follows:
|
/// and/or creation. The general store format is (roughly speaking) as follows:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
/// {
|
/// {
|
||||||
/// "subclass_type": {
|
/// "subclass_type": {
|
||||||
/// "superclass_type": *const Class as usize
|
/// "superclass_type": *const Class as usize
|
||||||
|
@ -39,8 +39,8 @@ impl ClassMap {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
// Top-level classes, like `NSView`, we cache here. The reasoning is that if a subclass
|
// Top-level classes, like `NSView`, we cache here. The reasoning is that if a subclass
|
||||||
// is being created, we can avoid querying the runtime for the superclass - i.e, many subclasses
|
// is being created, we can avoid querying the runtime for the superclass - i.e, many
|
||||||
// will have `NSView` as their superclass.
|
// subclasses will have `NSView` as their superclass.
|
||||||
map.insert("_supers", HashMap::new());
|
map.insert("_supers", HashMap::new());
|
||||||
|
|
||||||
map
|
map
|
||||||
|
@ -48,7 +48,11 @@ impl ClassMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to load a previously registered subclass.
|
/// Attempts to load a previously registered subclass.
|
||||||
pub fn load_subclass(&self, subclass_name: &'static str, superclass_name: &'static str) -> Option<*const Class> {
|
pub fn load_subclass(
|
||||||
|
&self,
|
||||||
|
subclass_name: &'static str,
|
||||||
|
superclass_name: &'static str
|
||||||
|
) -> Option<*const Class> {
|
||||||
let reader = self.0.read().unwrap();
|
let reader = self.0.read().unwrap();
|
||||||
|
|
||||||
if let Some(inner) = (*reader).get(subclass_name) {
|
if let Some(inner) = (*reader).get(subclass_name) {
|
||||||
|
@ -152,7 +156,11 @@ where
|
||||||
},
|
},
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
panic!("Subclass of type {}_{} could not be allocated.", subclass_name, superclass_name);
|
panic!(
|
||||||
|
"Subclass of type {}_{} could not be allocated.",
|
||||||
|
subclass_name,
|
||||||
|
superclass_name
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
//! A wrapper for `NSData`.
|
|
||||||
//!
|
|
||||||
//! This more or less exists to try and wrap the specific APIs we use to interface with Cocoa. Note
|
|
||||||
//! that in general this is only concerned with bridging for arguments - i.e, we need an `NSData`
|
|
||||||
//! from a `Vec<u8>` to satisfy the Cocoa side of things. It's expected that all processing of data
|
|
||||||
//! happens solely on the Rust side of things before coming through here.
|
|
||||||
//!
|
|
||||||
//! tl;dr this is an intentionally limited API.
|
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
|
@ -23,6 +15,8 @@ use crate::foundation::{id, to_bool, BOOL, YES, NO, NSUInteger};
|
||||||
///
|
///
|
||||||
/// Supports constructing a new `NSData` from a `Vec<u8>`, wrapping and retaining an existing
|
/// Supports constructing a new `NSData` from a `Vec<u8>`, wrapping and retaining an existing
|
||||||
/// pointer from the Objective-C side, and turning an `NSData` into a `Vec<u8>`.
|
/// pointer from the Objective-C side, and turning an `NSData` into a `Vec<u8>`.
|
||||||
|
///
|
||||||
|
/// This is an intentionally limited API.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NSData(pub Id<Object>);
|
pub struct NSData(pub Id<Object>);
|
||||||
|
|
||||||
|
@ -56,14 +50,21 @@ impl NSData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If we're vended an NSData from a method (e.g, a push notification token) we might want to
|
/// Given a (presumably) `NSData`, wraps and retains it.
|
||||||
/// wrap it while we figure out what to do with it. This does that.
|
pub fn retain(data: id) -> Self {
|
||||||
pub fn wrap(data: id) -> Self {
|
|
||||||
NSData(unsafe {
|
NSData(unsafe {
|
||||||
Id::from_ptr(data)
|
Id::from_ptr(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If we're vended an NSData from a method (e.g, a push notification token) we might want to
|
||||||
|
/// wrap it while we figure out what to do with it. This does that.
|
||||||
|
pub fn from_retained(data: id) -> Self {
|
||||||
|
NSData(unsafe {
|
||||||
|
Id::from_retained_ptr(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// A helper method for determining if a given `NSObject` is an `NSData`.
|
/// A helper method for determining if a given `NSObject` is an `NSData`.
|
||||||
pub fn is(obj: id) -> bool {
|
pub fn is(obj: id) -> bool {
|
||||||
let result: BOOL = unsafe {
|
let result: BOOL = unsafe {
|
||||||
|
@ -117,9 +118,27 @@ impl NSData {
|
||||||
|
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSData> for id {
|
||||||
/// Consumes and returns the underlying `NSData`.
|
/// Consumes and returns the underlying `NSData`.
|
||||||
pub fn into_inner(mut self) -> id {
|
fn from(mut data: NSData) -> Self {
|
||||||
|
&mut *data.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for NSData {
|
||||||
|
type Target = Object;
|
||||||
|
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref(&self) -> &Object {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for NSData {
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref_mut(&mut self) -> &mut Object {
|
||||||
&mut *self.0
|
&mut *self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, NSString};
|
use crate::foundation::{id, NSString};
|
||||||
|
|
||||||
/// A wrapper for `NSDictionary`. Behind the scenes we actually wrap `NSMutableDictionary`, and
|
/// A wrapper for `NSMutableDictionary`.
|
||||||
/// rely on Rust doing the usual borrow-checking guards that it does so well.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NSDictionary(pub Id<Object>);
|
pub struct NSMutableDictionary(pub Id<Object>);
|
||||||
|
|
||||||
impl Default for NSDictionary {
|
impl Default for NSMutableDictionary {
|
||||||
|
/// Returns a blank NSMutableDictionary.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
NSDictionary::new()
|
NSMutableDictionary::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NSDictionary {
|
impl NSMutableDictionary {
|
||||||
/// Constructs an `NSMutableDictionary` and retains it.
|
/// Constructs an `NSMutableDictionary` and retains it.
|
||||||
///
|
///
|
||||||
/// Why mutable? It's just easier for working with it, as they're (mostly) interchangeable when
|
/// Why mutable? It's just easier for working with it, as they're (mostly) interchangeable when
|
||||||
|
@ -23,7 +26,7 @@ impl NSDictionary {
|
||||||
/// object model. You can, of course, bypass it and `msg_send![]` yourself, but it'd require an
|
/// object model. You can, of course, bypass it and `msg_send![]` yourself, but it'd require an
|
||||||
/// `unsafe {}` block... so you'll know you're in special territory then.
|
/// `unsafe {}` block... so you'll know you're in special territory then.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
NSDictionary(unsafe {
|
NSMutableDictionary(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSMutableDictionary), new])
|
Id::from_ptr(msg_send![class!(NSMutableDictionary), new])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -33,7 +36,7 @@ impl NSDictionary {
|
||||||
/// This intentionally requires `NSString` be allocated ahead of time.
|
/// This intentionally requires `NSString` be allocated ahead of time.
|
||||||
pub fn insert(&mut self, key: NSString, object: id) {
|
pub fn insert(&mut self, key: NSString, object: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, setObject:object forKey:key.into_inner()];
|
let _: () = msg_send![&*self.0, setObject:object forKey:&*key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,3 +45,19 @@ impl NSDictionary {
|
||||||
&mut *self.0
|
&mut *self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for NSMutableDictionary {
|
||||||
|
type Target = Object;
|
||||||
|
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref(&self) -> &Object {
|
||||||
|
&*self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for NSMutableDictionary {
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref_mut(&mut self) -> &mut Object {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ mod data;
|
||||||
pub use data::NSData;
|
pub use data::NSData;
|
||||||
|
|
||||||
mod dictionary;
|
mod dictionary;
|
||||||
pub use dictionary::NSDictionary;
|
pub use dictionary::NSMutableDictionary;
|
||||||
|
|
||||||
mod number;
|
mod number;
|
||||||
pub use number::NSNumber;
|
pub use number::NSNumber;
|
||||||
|
|
|
@ -7,7 +7,7 @@ use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, to_bool, BOOL, YES, NO, NSInteger};
|
use crate::foundation::{id, to_bool, BOOL, YES, NO, NSInteger};
|
||||||
|
|
||||||
/// Wrapper for a retained `NSNumber` object.
|
/// Wrapper for a `NSNumber` object.
|
||||||
///
|
///
|
||||||
/// In general we strive to avoid using this in the codebase, but it's a requirement for moving
|
/// In general we strive to avoid using this in the codebase, but it's a requirement for moving
|
||||||
/// objects in and out of certain situations (e.g, `UserDefaults`).
|
/// objects in and out of certain situations (e.g, `UserDefaults`).
|
||||||
|
@ -15,18 +15,26 @@ use crate::foundation::{id, to_bool, BOOL, YES, NO, NSInteger};
|
||||||
pub struct NSNumber(pub Id<Object>);
|
pub struct NSNumber(pub Id<Object>);
|
||||||
|
|
||||||
impl NSNumber {
|
impl NSNumber {
|
||||||
|
/// If we're vended an NSNumber from a method (e.g, `NSUserDefaults` querying) we might want to
|
||||||
|
/// wrap (and retain) it while we figure out what to do with it. This does that.
|
||||||
|
pub fn retain(data: id) -> Self {
|
||||||
|
NSNumber(unsafe {
|
||||||
|
Id::from_ptr(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// If we're vended an NSNumber from a method (e.g, `NSUserDefaults` querying) we might want to
|
/// If we're vended an NSNumber from a method (e.g, `NSUserDefaults` querying) we might want to
|
||||||
/// wrap it while we figure out what to do with it. This does that.
|
/// wrap it while we figure out what to do with it. This does that.
|
||||||
pub fn wrap(data: id) -> Self {
|
pub fn wrap(data: id) -> Self {
|
||||||
NSNumber(unsafe {
|
NSNumber(unsafe {
|
||||||
Id::from_ptr(data)
|
Id::from_retained_ptr(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a `numberWithBool` instance of `NSNumber` and retains it.
|
/// Constructs a `numberWithBool` instance of `NSNumber` and retains it.
|
||||||
pub fn bool(value: bool) -> Self {
|
pub fn bool(value: bool) -> Self {
|
||||||
NSNumber(unsafe {
|
NSNumber(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSNumber), numberWithBool:match value {
|
Id::from_retained_ptr(msg_send![class!(NSNumber), numberWithBool:match value {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}])
|
}])
|
||||||
|
@ -36,14 +44,14 @@ impl NSNumber {
|
||||||
/// Constructs a `numberWithInteger` instance of `NSNumber` and retains it.
|
/// Constructs a `numberWithInteger` instance of `NSNumber` and retains it.
|
||||||
pub fn integer(value: i64) -> Self {
|
pub fn integer(value: i64) -> Self {
|
||||||
NSNumber(unsafe {
|
NSNumber(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger])
|
Id::from_retained_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a `numberWithDouble` instance of `NSNumber` and retains it.
|
/// Constructs a `numberWithDouble` instance of `NSNumber` and retains it.
|
||||||
pub fn float(value: f64) -> Self {
|
pub fn float(value: f64) -> Self {
|
||||||
NSNumber(unsafe {
|
NSNumber(unsafe {
|
||||||
Id::from_ptr(msg_send![class!(NSNumber), numberWithDouble:value])
|
Id::from_retained_ptr(msg_send![class!(NSNumber), numberWithDouble:value])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +59,7 @@ impl NSNumber {
|
||||||
/// to inform you how you should pull the underlying data out of the `NSNumber`.
|
/// to inform you how you should pull the underlying data out of the `NSNumber`.
|
||||||
///
|
///
|
||||||
/// For more information:
|
/// For more information:
|
||||||
/// [https://nshipster.com/type-encodings/](https://nshipster.com/type-encodings/)
|
/// <https://nshipster.com/type-encodings/>
|
||||||
pub fn objc_type(&self) -> &str {
|
pub fn objc_type(&self) -> &str {
|
||||||
unsafe {
|
unsafe {
|
||||||
let t: *const c_char = msg_send![&*self.0, objCType];
|
let t: *const c_char = msg_send![&*self.0, objCType];
|
||||||
|
@ -101,9 +109,11 @@ impl NSNumber {
|
||||||
|
|
||||||
to_bool(result)
|
to_bool(result)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSNumber> for id {
|
||||||
/// Consumes and returns the underlying `NSNumber`.
|
/// Consumes and returns the underlying `NSNumber`.
|
||||||
pub fn into_inner(mut self) -> id {
|
fn from(mut number: NSNumber) -> Self {
|
||||||
&mut *self.0
|
&mut *number.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use std::{slice, str};
|
use std::{fmt, slice, str};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
@ -14,25 +16,58 @@ const UTF8_ENCODING: usize = 4;
|
||||||
/// We can make a few safety guarantees in this module as the UTF8 code on the Foundation
|
/// We can make a few safety guarantees in this module as the UTF8 code on the Foundation
|
||||||
/// side is fairly battle tested.
|
/// side is fairly battle tested.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NSString(pub Id<Object>);
|
pub struct NSString<'a> {
|
||||||
|
pub objc: Id<Object>,
|
||||||
|
phantom: PhantomData<&'a ()>
|
||||||
|
}
|
||||||
|
|
||||||
impl NSString {
|
impl<'a> NSString<'a> {
|
||||||
/// Creates a new `NSString`. Note that `NSString` lives on the heap, so this allocates
|
/// Creates a new `NSString`.
|
||||||
/// accordingly.
|
|
||||||
pub fn new(s: &str) -> Self {
|
pub fn new(s: &str) -> Self {
|
||||||
NSString(unsafe {
|
NSString {
|
||||||
|
objc: unsafe {
|
||||||
let nsstring: *mut Object = msg_send![class!(NSString), alloc];
|
let nsstring: *mut Object = msg_send![class!(NSString), alloc];
|
||||||
//msg_send![nsstring, initWithBytesNoCopy:s.as_ptr() length:s.len() encoding:4 freeWhenDone:NO]
|
Id::from_ptr(msg_send![nsstring, initWithBytes:s.as_ptr()
|
||||||
Id::from_ptr(msg_send![nsstring, initWithBytes:s.as_ptr() length:s.len() encoding:UTF8_ENCODING])
|
length:s.len()
|
||||||
})
|
encoding:UTF8_ENCODING
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `NSString` without copying the bytes for the passed-in string.
|
||||||
|
pub fn no_copy(s: &'a str) -> Self {
|
||||||
|
NSString {
|
||||||
|
objc: unsafe {
|
||||||
|
let nsstring: id = msg_send![class!(NSString), alloc];
|
||||||
|
Id::from_ptr(msg_send![nsstring, initWithBytesNoCopy:s.as_ptr()
|
||||||
|
length:s.len()
|
||||||
|
encoding:UTF8_ENCODING
|
||||||
|
freeWhenDone:NO
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In cases where we're vended an `NSString` by the system, this can be used to wrap and
|
/// In cases where we're vended an `NSString` by the system, this can be used to wrap and
|
||||||
/// retain it.
|
/// retain it.
|
||||||
pub fn wrap(object: id) -> Self {
|
pub fn retain(object: id) -> Self {
|
||||||
NSString(unsafe {
|
NSString {
|
||||||
Id::from_ptr(object)
|
objc: unsafe { Id::from_ptr(object) },
|
||||||
})
|
phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In some cases, we want to wrap a system-provided NSString without retaining it.
|
||||||
|
pub fn from_retained(object: id) -> Self {
|
||||||
|
NSString {
|
||||||
|
objc: unsafe { Id::from_retained_ptr(object) },
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility method for checking whether an `NSObject` is an `NSString`.
|
/// Utility method for checking whether an `NSObject` is an `NSString`.
|
||||||
|
@ -44,7 +79,7 @@ impl NSString {
|
||||||
/// Helper method for returning the UTF8 bytes for this `NSString`.
|
/// Helper method for returning the UTF8 bytes for this `NSString`.
|
||||||
fn bytes(&self) -> *const u8 {
|
fn bytes(&self) -> *const u8 {
|
||||||
unsafe {
|
unsafe {
|
||||||
let bytes: *const c_char = msg_send![&*self.0, UTF8String];
|
let bytes: *const c_char = msg_send![&*self.objc, UTF8String];
|
||||||
bytes as *const u8
|
bytes as *const u8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +87,7 @@ impl NSString {
|
||||||
/// Helper method for grabbing the proper byte length for this `NSString` (the UTF8 variant).
|
/// Helper method for grabbing the proper byte length for this `NSString` (the UTF8 variant).
|
||||||
fn bytes_len(&self) -> usize {
|
fn bytes_len(&self) -> usize {
|
||||||
unsafe {
|
unsafe {
|
||||||
msg_send![&*self.0, lengthOfBytesUsingEncoding:UTF8_ENCODING]
|
msg_send![&*self.objc, lengthOfBytesUsingEncoding:UTF8_ENCODING]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,9 +106,33 @@ impl NSString {
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
self.to_str().to_string()
|
self.to_str().to_string()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes and returns the underlying `NSString` instance.
|
impl fmt::Display for NSString<'_> {
|
||||||
pub fn into_inner(mut self) -> id {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
&mut *self.0
|
write!(f, "{}", self.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSString<'_>> for id {
|
||||||
|
/// Consumes and returns the pointer to the underlying NSString instance.
|
||||||
|
fn from(mut string: NSString) -> Self {
|
||||||
|
&mut *string.objc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for NSString<'_> {
|
||||||
|
type Target = Object;
|
||||||
|
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref(&self) -> &Object {
|
||||||
|
&*self.objc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for NSString<'_> {
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref_mut(&mut self) -> &mut Object {
|
||||||
|
&mut *self.objc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,14 @@ pub enum MacSystemIcon {
|
||||||
PreferencesAdvanced,
|
PreferencesAdvanced,
|
||||||
|
|
||||||
/// A standard "Accounts" preferences icon. This is intended for usage in Preferences toolbars.
|
/// A standard "Accounts" preferences icon. This is intended for usage in Preferences toolbars.
|
||||||
PreferencesUserAccounts
|
PreferencesUserAccounts,
|
||||||
|
|
||||||
|
/// Returns a stock "+" icon that's common to the system. Use this for buttons that need the
|
||||||
|
/// symbol.
|
||||||
|
Add
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
impl MacSystemIcon {
|
impl MacSystemIcon {
|
||||||
/// Maps system icons to their pre-11.0 framework identifiers.
|
/// Maps system icons to their pre-11.0 framework identifiers.
|
||||||
pub fn to_str(&self) -> &'static str {
|
pub fn to_str(&self) -> &'static str {
|
||||||
|
@ -29,6 +34,7 @@ impl MacSystemIcon {
|
||||||
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
|
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
|
||||||
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
|
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
|
||||||
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
|
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
|
||||||
|
MacSystemIcon::Add => "NSImageNameAddTemplate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +43,31 @@ impl MacSystemIcon {
|
||||||
match self {
|
match self {
|
||||||
MacSystemIcon::PreferencesGeneral => "gearshape",
|
MacSystemIcon::PreferencesGeneral => "gearshape",
|
||||||
MacSystemIcon::PreferencesAdvanced => "slider.vertical.3",
|
MacSystemIcon::PreferencesAdvanced => "slider.vertical.3",
|
||||||
MacSystemIcon::PreferencesUserAccounts => "person.crop.circle"
|
MacSystemIcon::PreferencesUserAccounts => "at",
|
||||||
|
MacSystemIcon::Add => "plus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SFSymbol {
|
||||||
|
PaperPlane,
|
||||||
|
PaperPlaneFilled,
|
||||||
|
SquareAndArrowUpOnSquare,
|
||||||
|
SquareAndArrowUpOnSquareFill,
|
||||||
|
SquareAndArrowDownOnSquare,
|
||||||
|
SquareAndArrowDownOnSquareFill
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SFSymbol {
|
||||||
|
pub fn to_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::PaperPlane => "paperplane",
|
||||||
|
Self::PaperPlaneFilled => "paperplane.fill",
|
||||||
|
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
|
||||||
|
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
|
||||||
|
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
|
||||||
|
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,14 +119,36 @@ impl Image {
|
||||||
Image(unsafe {
|
Image(unsafe {
|
||||||
ShareId::from_ptr(match os::is_minimum_version(11) {
|
ShareId::from_ptr(match os::is_minimum_version(11) {
|
||||||
true => {
|
true => {
|
||||||
let icon = NSString::new(icon.to_sfsymbol_str()).into_inner();
|
let icon = NSString::new(icon.to_sfsymbol_str());
|
||||||
let desc = NSString::new(accessibility_description).into_inner();
|
let desc = NSString::new(accessibility_description);
|
||||||
msg_send![class!(NSImage), imageWithSystemSymbolName:icon accessibilityDescription:desc]
|
msg_send![class!(NSImage), imageWithSystemSymbolName:&*icon
|
||||||
|
accessibilityDescription:&*desc]
|
||||||
},
|
},
|
||||||
|
|
||||||
false => {
|
false => {
|
||||||
let icon = NSString::new(icon.to_str()).into_inner();
|
let icon = NSString::new(icon.to_str());
|
||||||
msg_send![class!(NSImage), imageNamed:icon]
|
msg_send![class!(NSImage), imageNamed:&*icon]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and returns an Image with the specified `SFSymbol`. Note that `SFSymbol` is
|
||||||
|
/// supported on 11.0+; as such, this will panic if called on a lower system. Take care to
|
||||||
|
/// provide a fallback image or user experience if you need to support an older OS.
|
||||||
|
pub fn symbol(symbol: SFSymbol, accessibility_description: &str) -> Self {
|
||||||
|
Image(unsafe {
|
||||||
|
ShareId::from_ptr(match os::is_minimum_version(11) {
|
||||||
|
true => {
|
||||||
|
let icon = NSString::new(symbol.to_str());
|
||||||
|
let desc = NSString::new(accessibility_description);
|
||||||
|
msg_send![class!(NSImage), imageWithSystemSymbolName:&*icon
|
||||||
|
accessibilityDescription:&*desc]
|
||||||
|
},
|
||||||
|
|
||||||
|
false => {
|
||||||
|
#[cfg(feature = "macos")]
|
||||||
|
panic!("SFSymbols are only supported on macOS 11.0 and up.");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -110,6 +110,15 @@ impl ImageView {
|
||||||
let _: () = msg_send![&*self.objc, setImage:&*image.0];
|
let _: () = msg_send![&*self.objc, setImage:&*image.0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_hidden(&self, hidden: bool) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setHidden:match hidden {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageView {
|
impl Layout for ImageView {
|
||||||
|
|
|
@ -61,9 +61,6 @@ mod ios;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
use ios::{register_view_class, register_view_class_with_delegate};
|
use ios::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
//mod controller;
|
|
||||||
//pub use controller::TextFieldController;
|
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::TextFieldDelegate;
|
pub use traits::TextFieldDelegate;
|
||||||
|
|
||||||
|
@ -199,16 +196,16 @@ impl<T> TextField<T> {
|
||||||
|
|
||||||
/// Grabs the value from the textfield and returns it as an owned String.
|
/// Grabs the value from the textfield and returns it as an owned String.
|
||||||
pub fn get_value(&self) -> String {
|
pub fn get_value(&self) -> String {
|
||||||
let value = NSString::wrap(unsafe {
|
let value = NSString::retain(unsafe {
|
||||||
msg_send![&*self.objc, stringValue]
|
msg_send![&*self.objc, stringValue]
|
||||||
});
|
});
|
||||||
|
|
||||||
value.to_str().to_string()
|
value.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call this to set the background color for the backing layer.
|
/// Call this to set the background color for the backing layer.
|
||||||
pub fn set_background_color(&self, color: Color) {
|
pub fn set_background_color(&self, color: Color) {
|
||||||
let bg = color.into_platform_specific_color();
|
let bg = color.to_objc();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let cg: id = msg_send![bg, CGColor];
|
let cg: id = msg_send![bg, CGColor];
|
||||||
|
@ -222,7 +219,7 @@ impl<T> TextField<T> {
|
||||||
let s = NSString::new(text);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setStringValue:s.into_inner()];
|
let _: () = msg_send![&*self.objc, setStringValue:&*s];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,9 +121,9 @@ where
|
||||||
let dl = register_app_delegate_class::<T>();
|
let dl = register_app_delegate_class::<T>();
|
||||||
let w = register_window_scene_delegate_class::<W, F>();
|
let w = register_window_scene_delegate_class::<W, F>();
|
||||||
|
|
||||||
// This probably needs to be Arc<Mutex<>>'d at some point, but this is still exploratory.
|
|
||||||
let app_delegate = Box::new(delegate);
|
let app_delegate = Box::new(delegate);
|
||||||
let vendor = Box::new(scene_delegate_vendor);
|
let vendor = Box::new(scene_delegate_vendor);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let delegate_ptr: *const T = &*app_delegate;
|
let delegate_ptr: *const T = &*app_delegate;
|
||||||
APP_DELEGATE = delegate_ptr as usize;
|
APP_DELEGATE = delegate_ptr as usize;
|
||||||
|
@ -148,11 +148,11 @@ impl<T, W, F> App<T, W, F> {
|
||||||
let args = std::env::args().map(|arg| CString::new(arg).unwrap() ).collect::<Vec<CString>>();
|
let args = std::env::args().map(|arg| CString::new(arg).unwrap() ).collect::<Vec<CString>>();
|
||||||
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
||||||
|
|
||||||
let s = NSString::new("RSTApplication");
|
let s = NSString::no_copy("RSTApplication");
|
||||||
let s2 = NSString::new("RSTAppDelegate");
|
let s2 = NSString::no_copy("RSTAppDelegate");
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into_inner(), s2.into_inner());
|
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), &*s, &*s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pool.drain();
|
self.pool.drain();
|
||||||
|
|
|
@ -13,18 +13,18 @@ pub enum SessionRole {
|
||||||
//CarPlayApplication
|
//CarPlayApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SessionRole> for NSString {
|
impl From<SessionRole> for NSString<'_> {
|
||||||
fn from(role: SessionRole) -> Self {
|
fn from(role: SessionRole) -> Self {
|
||||||
NSString::new(match role {
|
NSString::new(match role {
|
||||||
SessionRole::Application => "UIWindowSceneSessionRoleApplication",
|
SessionRole::Application => NSString::no_copy("UIWindowSceneSessionRoleApplication"),
|
||||||
SessionRole::ExternalDisplay => "UIWindowSceneSessionRoleExternalDisplay",
|
SessionRole::ExternalDisplay => NSString::no_copy("UIWindowSceneSessionRoleExternalDisplay"),
|
||||||
//SessionRole::CarPlayApplication => ""
|
//SessionRole::CarPlayApplication => ""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NSString> for SessionRole {
|
impl From<NSString<'_>> for SessionRole {
|
||||||
fn from(value: NSString) -> Self {
|
fn from(value: NSString<'_>) -> Self {
|
||||||
match value.to_str() {
|
match value.to_str() {
|
||||||
"UIWindowSceneSessionRoleApplication" => SessionRole::Application,
|
"UIWindowSceneSessionRoleApplication" => SessionRole::Application,
|
||||||
"UIWindowSceneSessionRoleExternalDisplay" => SessionRole::ExternalDisplay,
|
"UIWindowSceneSessionRoleExternalDisplay" => SessionRole::ExternalDisplay,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::id;
|
use crate::foundation::{id, YES, NO};
|
||||||
|
|
||||||
/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
|
/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
|
||||||
/// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or
|
/// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or
|
||||||
|
@ -43,12 +43,12 @@ impl LayoutConstraint {
|
||||||
/// Sets the offset for this constraint.
|
/// Sets the offset for this constraint.
|
||||||
pub fn offset<F: Into<f64>>(self, offset: F) -> Self {
|
pub fn offset<F: Into<f64>>(self, offset: F) -> Self {
|
||||||
let offset: f64 = offset.into();
|
let offset: f64 = offset.into();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let o = offset as CGFloat;
|
let o = offset as CGFloat;
|
||||||
let _: () = msg_send![&*self.constraint, setConstant:o];
|
let _: () = msg_send![&*self.constraint, setConstant:o];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
LayoutConstraint {
|
LayoutConstraint {
|
||||||
constraint: self.constraint,
|
constraint: self.constraint,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
@ -57,6 +57,27 @@ impl LayoutConstraint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_offset<F: Into<f64>>(&self, offset: F) {
|
||||||
|
let offset: f64 = offset.into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let o = offset as CGFloat;
|
||||||
|
let _: () = msg_send![&*self.constraint, setConstant:o];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether this constraint is active or not. If you're doing this across a batch of
|
||||||
|
/// constraints, it's often more performant to batch-deactivate with
|
||||||
|
/// `LayoutConstraint::deactivate()`.
|
||||||
|
pub fn set_active(&self, active: bool) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.constraint, setActive:match active {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Call this with your batch of constraints to activate them.
|
/// Call this with your batch of constraints to activate them.
|
||||||
// If you're astute, you'll note that, yes... this is kind of hacking around some
|
// If you're astute, you'll note that, yes... this is kind of hacking around some
|
||||||
// borrowing rules with how objc_id::Id/objc_id::ShareId works. In this case, to
|
// borrowing rules with how objc_id::Id/objc_id::ShareId works. In this case, to
|
||||||
|
@ -75,4 +96,15 @@ impl LayoutConstraint {
|
||||||
let _: () = msg_send![class!(NSLayoutConstraint), activateConstraints:constraints];
|
let _: () = msg_send![class!(NSLayoutConstraint), activateConstraints:constraints];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deactivate(constraints: &[LayoutConstraint]) {
|
||||||
|
unsafe {
|
||||||
|
let ids: Vec<&Object> = constraints.into_iter().map(|constraint| {
|
||||||
|
&*constraint.constraint
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let constraints: id = msg_send![class!(NSArray), arrayWithObjects:ids.as_ptr() count:ids.len()];
|
||||||
|
let _: () = msg_send![class!(NSLayoutConstraint), deactivateConstraints:constraints];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,15 @@
|
||||||
|
|
||||||
use core_graphics::base::CGFloat;
|
use core_graphics::base::CGFloat;
|
||||||
|
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::id;
|
use crate::foundation::{id, nil, NSInteger};
|
||||||
use crate::layout::constraint::LayoutConstraint;
|
use crate::layout::constraint::LayoutConstraint;
|
||||||
|
|
||||||
|
use super::attributes::{LayoutAttribute, LayoutRelation};
|
||||||
|
|
||||||
/// A wrapper for `NSLayoutAnchor`. You should never be creating this yourself - it's more of a
|
/// A wrapper for `NSLayoutAnchor`. You should never be creating this yourself - it's more of a
|
||||||
/// factory/helper for creating `LayoutConstraint` objects based on your views.
|
/// factory/helper for creating `LayoutConstraint` objects based on your views.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -57,6 +59,18 @@ impl LayoutAnchorDimension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a constraint greater than or equal to a constant value.
|
||||||
|
pub fn constraint_greater_than_or_equal_to_constant(&self, constant: f64) -> LayoutConstraint {
|
||||||
|
match &self.0 {
|
||||||
|
Some(from) => LayoutConstraint::new(unsafe {
|
||||||
|
let value = constant as CGFloat;
|
||||||
|
msg_send![*from, constraintGreaterThanOrEqualToConstant:value]
|
||||||
|
}),
|
||||||
|
|
||||||
|
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return a constraint less than or equal to another dimension anchor.
|
/// Return a constraint less than or equal to another dimension anchor.
|
||||||
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
||||||
match (&self.0, &anchor_to.0) {
|
match (&self.0, &anchor_to.0) {
|
||||||
|
@ -67,4 +81,16 @@ impl LayoutAnchorDimension {
|
||||||
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a constraint greater than or equal to a constant value.
|
||||||
|
pub fn constraint_less_than_or_equal_to_constant(&self, constant: f64) -> LayoutConstraint {
|
||||||
|
match &self.0 {
|
||||||
|
Some(from) => LayoutConstraint::new(unsafe {
|
||||||
|
let value = constant as CGFloat;
|
||||||
|
msg_send![*from, constraintLessThanOrEqualToConstant:value]
|
||||||
|
}),
|
||||||
|
|
||||||
|
_ => { panic!("Attempted to create constraints with an uninitialized anchor!"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/lib.rs
55
src/lib.rs
|
@ -1,6 +1,8 @@
|
||||||
//#![deny(missing_docs)]
|
//#![deny(missing_docs)]
|
||||||
//#![deny(missing_debug_implementations)]
|
//#![deny(missing_debug_implementations)]
|
||||||
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
|
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
|
||||||
|
#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
// Copyright 2019+, the Cacao developers.
|
// Copyright 2019+, the Cacao developers.
|
||||||
// See the COPYRIGHT file at the top-level directory of this distribution.
|
// See the COPYRIGHT file at the top-level directory of this distribution.
|
||||||
|
@ -8,8 +10,8 @@
|
||||||
|
|
||||||
//! # Cacao
|
//! # Cacao
|
||||||
//!
|
//!
|
||||||
//! This library provides safe Rust bindings for `AppKit` on macOS and (eventually) `UIKit` on iOS. It
|
//! This library provides safe Rust bindings for `AppKit` on macOS and (eventually) `UIKit` on iOS and tvOS.
|
||||||
//! tries to do so in a way that, if you've done programming for the framework before (in Swift or
|
//! It tries to do so in a way that, if you've done programming for the framework before (in Swift or
|
||||||
//! Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
//! Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
||||||
//! creative coding and assumptions can get us pretty far.
|
//! creative coding and assumptions can get us pretty far.
|
||||||
//!
|
//!
|
||||||
|
@ -18,14 +20,14 @@
|
||||||
//! already fine for some apps.
|
//! already fine for some apps.
|
||||||
//!
|
//!
|
||||||
//! _Note that this crate relies on the Objective-C runtime. Interfacing with the runtime *requires*
|
//! _Note that this crate relies on the Objective-C runtime. Interfacing with the runtime *requires*
|
||||||
//! unsafe blocks; this crate handles those unsafe interactions for you and provides a safe wrapper,
|
//! unsafe blocks; this crate handles those unsafe interactions for you and provides a mostly safe wrapper,
|
||||||
//! but by using this crate you understand that usage of `unsafe` is a given and will be somewhat
|
//! but by using this crate you understand that usage of `unsafe` is a given and will be somewhat
|
||||||
//! rampant for wrapped controls. This does _not_ mean you can't assess, review, or question unsafe
|
//! rampant for wrapped controls. This does _not_ mean you can't assess, review, or question unsafe
|
||||||
//! usage - just know it's happening, and in large part it's not going away._
|
//! usage - just know it's happening, and in large part it's not going away._
|
||||||
//!
|
//!
|
||||||
//! # Hello World
|
//! # Hello World
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust,no_run
|
||||||
//! use cacao::macos::app::{App, AppDelegate};
|
//! use cacao::macos::app::{App, AppDelegate};
|
||||||
//! use cacao::macos::window::Window;
|
//! use cacao::macos::window::Window;
|
||||||
//!
|
//!
|
||||||
|
@ -48,23 +50,41 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Initialization
|
//! ## Initialization
|
||||||
|
//!
|
||||||
//! Due to the way that AppKit and UIKit programs typically work, you're encouraged to do the bulk
|
//! Due to the way that AppKit and UIKit programs typically work, you're encouraged to do the bulk
|
||||||
//! of your work starting from the `did_finish_launching()` method of your `AppDelegate`. This
|
//! of your work starting from the `did_finish_launching()` method of your `AppDelegate`. This
|
||||||
//! ensures the application has had time to initialize and do any housekeeping necessary behind the
|
//! ensures the application has had time to initialize and do any housekeeping necessary behind the
|
||||||
//! scenes.
|
//! scenes.
|
||||||
//!
|
//!
|
||||||
|
//! Note that, in order for this framework to be useful, you must always elect one of the following
|
||||||
|
//! features:
|
||||||
|
//!
|
||||||
|
//! - `macos`: Implements macOS-specific APIs (menus, toolbars, windowing, and so on).
|
||||||
|
//! - `ios`: Implements iOS-specific APIs (scenes, navigation controllers, and so on).
|
||||||
|
//! - `tvos`: Implements tvOS-specific APIs. Currently barely implemented.
|
||||||
|
//!
|
||||||
|
//! The rest of the features in this framework attempt to expose a common API across the three
|
||||||
|
//! aforementioned feature platforms; if you need something else, you can often implement it
|
||||||
|
//! yourself by accessing the underlying `objc` property of a control and sending messages to it
|
||||||
|
//! directly.
|
||||||
|
//!
|
||||||
//! ## Optional Features
|
//! ## Optional Features
|
||||||
//!
|
//!
|
||||||
//! The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
//! The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
||||||
//!
|
//!
|
||||||
//! - **cloudkit**: Links `CloudKit.framework` and provides some wrappers around CloudKit
|
//! - `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit
|
||||||
//! functionality. Currently not feature complete.
|
//! functionality. Currently not feature complete.
|
||||||
//! - **user-notifications**: Links `UserNotifications.framework` and provides functionality for
|
//! - `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for
|
||||||
|
//! files.
|
||||||
|
//! - `user-notifications`: Links `UserNotifications.framework` and provides functionality for
|
||||||
//! emitting notifications on macOS and iOS. Note that this _requires_ your application be
|
//! emitting notifications on macOS and iOS. Note that this _requires_ your application be
|
||||||
//! code-signed, and will not work without it.
|
//! code-signed, and will not work without it.
|
||||||
//! - **webview**: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
//! - `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
||||||
//! - **webview-downloading**: Enables downloading files from the `WebView` via a private
|
//! This feature is not supported on tvOS, as the platform has no webview control.
|
||||||
//! interface. This is not an App-Store-safe feature, so be aware of that before enabling.
|
//! - `webview-downloading-macos`: Enables downloading files from the `WebView` via a private
|
||||||
|
//! interface. This is not an App-Store-safe feature, so be aware of that before enabling. This
|
||||||
|
//! feature is not supported on iOS (a user would handle downloads very differently) or tvOS
|
||||||
|
//! (there's no web browser there at all).
|
||||||
//!
|
//!
|
||||||
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
||||||
|
|
||||||
|
@ -74,20 +94,18 @@ pub use objc;
|
||||||
pub use url;
|
pub use url;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
|
|
||||||
/// Until we figure out a better way to handle reusable views (i.e, the
|
|
||||||
/// "correct" way for a list view to work), just let the delegates pass
|
|
||||||
/// back the pointer and handle keeping the pools for themselves.
|
|
||||||
pub type Node = objc_id::ShareId<objc::runtime::Object>;
|
|
||||||
|
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "macos")))]
|
||||||
pub mod macos;
|
pub mod macos;
|
||||||
|
|
||||||
#[cfg(feature = "ios")]
|
#[cfg(feature = "ios")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "ios")))]
|
||||||
pub mod ios;
|
pub mod ios;
|
||||||
|
|
||||||
pub mod button;
|
pub mod button;
|
||||||
|
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(any(feature = "cloudkit", doc))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "cloudkit")))]
|
||||||
pub mod cloudkit;
|
pub mod cloudkit;
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
@ -112,15 +130,18 @@ pub mod switch;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "quicklook")]
|
#[cfg(feature = "quicklook")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "quicklook")))]
|
||||||
pub mod quicklook;
|
pub mod quicklook;
|
||||||
|
|
||||||
#[cfg(feature = "user-notifications")]
|
#[cfg(feature = "user-notifications")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "user-notifications")))]
|
||||||
pub mod user_notifications;
|
pub mod user_notifications;
|
||||||
|
|
||||||
pub mod user_activity;
|
pub mod user_activity;
|
||||||
pub(crate) mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
|
||||||
#[cfg(feature = "webview")]
|
#[cfg(any(feature = "webview", doc))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "webview")))]
|
||||||
pub mod webview;
|
pub mod webview;
|
||||||
|
|
|
@ -47,9 +47,12 @@ impl RowAction {
|
||||||
/// on your ListViewRow.
|
/// on your ListViewRow.
|
||||||
///
|
///
|
||||||
/// Additional configuration can be done after initialization, if need be.
|
/// Additional configuration can be done after initialization, if need be.
|
||||||
|
///
|
||||||
|
/// These run on the main thread, as they're UI handlers - so we can avoid Send + Sync on
|
||||||
|
/// our definitions.
|
||||||
pub fn new<F>(title: &str, style: RowActionStyle, handler: F) -> Self
|
pub fn new<F>(title: &str, style: RowActionStyle, handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(RowAction, usize) + Send + Sync + 'static
|
F: Fn(RowAction, usize) + 'static
|
||||||
{
|
{
|
||||||
let title = NSString::new(title);
|
let title = NSString::new(title);
|
||||||
let block = ConcreteBlock::new(move |action: id, row: NSUInteger| {
|
let block = ConcreteBlock::new(move |action: id, row: NSUInteger| {
|
||||||
|
@ -65,7 +68,7 @@ impl RowAction {
|
||||||
RowAction(unsafe {
|
RowAction(unsafe {
|
||||||
let cls = class!(NSTableViewRowAction);
|
let cls = class!(NSTableViewRowAction);
|
||||||
Id::from_ptr(msg_send![cls, rowActionWithStyle:style
|
Id::from_ptr(msg_send![cls, rowActionWithStyle:style
|
||||||
title:title.into_inner()
|
title:&*title
|
||||||
handler:block
|
handler:block
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -76,13 +79,13 @@ impl RowAction {
|
||||||
let title = NSString::new(title);
|
let title = NSString::new(title);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, setTitle:title.into_inner()];
|
let _: () = msg_send![&*self.0, setTitle:&*title];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the background color of this action.
|
/// Sets the background color of this action.
|
||||||
pub fn set_background_color(&mut self, color: Color) {
|
pub fn set_background_color(&mut self, color: Color) {
|
||||||
let color = color.into_platform_specific_color();
|
let color = color.to_objc();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, setBackgroundColor:color];
|
let _: () = msg_send![&*self.0, setBackgroundColor:color];
|
||||||
|
|
|
@ -14,7 +14,8 @@ use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
use objc::{class, sel, sel_impl, msg_send};
|
use objc::{class, sel, sel_impl, msg_send};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{load_or_register_class, id, YES, NO, NSArray, NSInteger, NSUInteger};
|
use crate::macos::menu::{Menu, MenuItem};
|
||||||
|
use crate::foundation::{load_or_register_class, id, nil, YES, NO, NSArray, NSInteger, NSUInteger};
|
||||||
use crate::dragdrop::DragInfo;
|
use crate::dragdrop::DragInfo;
|
||||||
use crate::listview::{
|
use crate::listview::{
|
||||||
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
|
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
|
||||||
|
@ -57,6 +58,45 @@ extern fn view_for_column<T: ListViewDelegate>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern fn will_display_cell<T: ListViewDelegate>(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
_table_view: id,
|
||||||
|
_cell: id,
|
||||||
|
_column: id,
|
||||||
|
item: NSInteger
|
||||||
|
) {
|
||||||
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
|
view.will_display_item(item as usize);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn menu_needs_update<T: ListViewDelegate>(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
menu: id
|
||||||
|
) {
|
||||||
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
|
let items = view.context_menu();
|
||||||
|
let _ = Menu::append(menu, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NSTableView requires listening to an observer to detect row selection changes, but that is...
|
||||||
|
/// even clunkier than what we do in this framework.
|
||||||
|
///
|
||||||
|
/// The other less obvious way is to subclass and override the `shouldSelectRow:` method; here, we
|
||||||
|
/// simply assume things are selectable and call our delegate as if things were selected. This may
|
||||||
|
/// need to change in the future, but it works well enough for now.
|
||||||
|
extern fn select_row<T: ListViewDelegate>(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
_table_view: id,
|
||||||
|
item: NSInteger
|
||||||
|
) -> BOOL {
|
||||||
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
|
view.item_selected(item as usize);
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
extern fn row_actions_for_row<T: ListViewDelegate>(
|
extern fn row_actions_for_row<T: ListViewDelegate>(
|
||||||
this: &Object,
|
this: &Object,
|
||||||
_: Sel,
|
_: Sel,
|
||||||
|
@ -67,14 +107,13 @@ extern fn row_actions_for_row<T: ListViewDelegate>(
|
||||||
let edge: RowEdge = edge.into();
|
let edge: RowEdge = edge.into();
|
||||||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
let actions = view.actions_for(row as usize, edge);
|
let mut ids: NSArray = view.actions_for(row as usize, edge)
|
||||||
|
.iter_mut()
|
||||||
|
.map(|action| &*action.0)
|
||||||
|
.collect::<Vec<&Object>>()
|
||||||
|
.into();
|
||||||
|
|
||||||
//if actions.len() > 0 {
|
&mut *ids
|
||||||
let ids: Vec<&Object> = actions.iter().map(|action| &*action.0).collect();
|
|
||||||
NSArray::from(ids).into_inner()
|
|
||||||
//} else {
|
|
||||||
// NSArray::new(&[]).into_inner()
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||||
|
@ -164,9 +203,16 @@ pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instanc
|
||||||
|
|
||||||
// Tableview-specific
|
// Tableview-specific
|
||||||
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, id) -> NSInteger);
|
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, id) -> NSInteger);
|
||||||
|
decl.add_method(sel!(tableView:willDisplayCell:forTableColumn:row:), will_display_cell::<T> as extern fn(&Object, _, id, id, id, NSInteger));
|
||||||
decl.add_method(sel!(tableView:viewForTableColumn:row:), view_for_column::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
|
decl.add_method(sel!(tableView:viewForTableColumn:row:), view_for_column::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
|
||||||
|
decl.add_method(sel!(tableView:shouldSelectRow:), select_row::<T> as extern fn(&Object, _, id, NSInteger) -> BOOL);
|
||||||
decl.add_method(sel!(tableView:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
|
decl.add_method(sel!(tableView:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
|
||||||
|
|
||||||
|
// A slot for some menu handling; we just let it be done here for now rather than do the
|
||||||
|
// whole delegate run, since things are fast enough nowadays to just replace the entire
|
||||||
|
// menu.
|
||||||
|
decl.add_method(sel!(menuNeedsUpdate:), menu_needs_update::<T> as extern fn(&Object, _, id));
|
||||||
|
|
||||||
// Drag and drop operations (e.g, accepting files)
|
// Drag and drop operations (e.g, accepting files)
|
||||||
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
||||||
decl.add_method(sel!(prepareForDragOperation:), prepare_for_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
|
decl.add_method(sel!(prepareForDragOperation:), prepare_for_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
|
||||||
|
|
|
@ -48,13 +48,16 @@ use objc_id::ShareId;
|
||||||
use objc::runtime::{Class, Object};
|
use objc::runtime::{Class, Object};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSUInteger};
|
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
use crate::scrollview::ScrollView;
|
use crate::scrollview::ScrollView;
|
||||||
use crate::utils::CGSize;
|
use crate::utils::CGSize;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use crate::macos::menu::MenuItem;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
mod macos;
|
mod macos;
|
||||||
|
|
||||||
|
@ -148,21 +151,24 @@ fn common_init(class: *const Class) -> id {
|
||||||
// Let's... make NSTableView into UITableView-ish.
|
// Let's... make NSTableView into UITableView-ish.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
|
// @TODO: Clean this up in a dealloc method.
|
||||||
|
let menu: id = msg_send![class!(NSMenu), new];
|
||||||
|
let _: () = msg_send![menu, setDelegate:tableview];
|
||||||
|
let _: () = msg_send![tableview, setMenu:menu];
|
||||||
|
|
||||||
let _: () = msg_send![tableview, setWantsLayer:YES];
|
let _: () = msg_send![tableview, setWantsLayer:YES];
|
||||||
let _: () = msg_send![tableview, setUsesAutomaticRowHeights:YES];
|
let _: () = msg_send![tableview, setUsesAutomaticRowHeights:YES];
|
||||||
let _: () = msg_send![tableview, setFloatsGroupRows:YES];
|
let _: () = msg_send![tableview, setFloatsGroupRows:YES];
|
||||||
let _: () = msg_send![tableview, setIntercellSpacing:CGSize::new(0., 0.)];
|
//let _: () = msg_send![tableview, setIntercellSpacing:CGSize::new(0., 0.)];
|
||||||
let _: () = msg_send![tableview, setColumnAutoresizingStyle:1];
|
let _: () = msg_send![tableview, setColumnAutoresizingStyle:1];
|
||||||
//msg_send![tableview, setSelectionHighlightStyle:-1];
|
//msg_send![tableview, setSelectionHighlightStyle:-1];
|
||||||
let _: () = msg_send![tableview, setAllowsEmptySelection:YES];
|
//let _: () = msg_send![tableview, setAllowsMultipleSelection:NO];
|
||||||
let _: () = msg_send![tableview, setAllowsMultipleSelection:NO];
|
|
||||||
let _: () = msg_send![tableview, setHeaderView:nil];
|
let _: () = msg_send![tableview, setHeaderView:nil];
|
||||||
|
|
||||||
// NSTableView requires at least one column to be manually added if doing so by code.
|
// NSTableView requires at least one column to be manually added if doing so by code.
|
||||||
// A relic of a bygone era, indeed.
|
let identifier = NSString::no_copy("CacaoListViewColumn");
|
||||||
let identifier = NSString::new("CacaoListViewColumn");
|
|
||||||
let default_column_alloc: id = msg_send![class!(NSTableColumn), new];
|
let default_column_alloc: id = msg_send![class!(NSTableColumn), new];
|
||||||
let default_column: id = msg_send![default_column_alloc, initWithIdentifier:identifier.into_inner()];
|
let default_column: id = msg_send![default_column_alloc, initWithIdentifier:&*identifier];
|
||||||
let _: () = msg_send![default_column, setResizingMask:(1<<0)];
|
let _: () = msg_send![default_column, setResizingMask:(1<<0)];
|
||||||
let _: () = msg_send![tableview, addTableColumn:default_column];
|
let _: () = msg_send![tableview, addTableColumn:default_column];
|
||||||
}
|
}
|
||||||
|
@ -171,12 +177,63 @@ fn common_init(class: *const Class) -> id {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ObjcProperty(Rc<RefCell<Id<Object>>>);
|
||||||
|
|
||||||
|
impl ObjcProperty {
|
||||||
|
pub fn new(obj: id) -> Self {
|
||||||
|
Self(Rc::new(RefCell::new(unsafe {
|
||||||
|
Id::from_ptr(obj)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with<F>(&self, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(&Object)
|
||||||
|
{
|
||||||
|
let borrow = self.0.borrow();
|
||||||
|
handler(&borrow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct PropertyNullable<T>(Rc<RefCell<Option<T>>>);
|
||||||
|
|
||||||
|
impl<T> PropertyNullable<T> {
|
||||||
|
pub fn new(obj: T) -> Self {
|
||||||
|
Self(Rc::new(RefCell::new(Some(obj))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone(&self) -> Self {
|
||||||
|
Self(Rc::clone(&self.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with<F>(&self, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(&T)
|
||||||
|
{
|
||||||
|
let borrow = self.0.borrow();
|
||||||
|
if let Some(s) = &*borrow {
|
||||||
|
handler(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, obj: T) {
|
||||||
|
let mut borrow = self.0.borrow_mut();
|
||||||
|
*borrow = Some(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ListView<T = ()> {
|
pub struct ListView<T = ()> {
|
||||||
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell
|
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell
|
||||||
/// allocation and reuse, which is necessary for an "infinite" listview.
|
/// allocation and reuse, which is necessary for an "infinite" listview.
|
||||||
cell_factory: CellFactory,
|
cell_factory: CellFactory,
|
||||||
|
|
||||||
|
pub menu: PropertyNullable<Vec<MenuItem>>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ShareId<Object>,
|
pub objc: ShareId<Object>,
|
||||||
|
|
||||||
|
@ -245,6 +302,7 @@ impl ListView {
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
cell_factory: CellFactory::new(),
|
cell_factory: CellFactory::new(),
|
||||||
|
menu: PropertyNullable::default(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
||||||
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
|
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
|
||||||
|
@ -302,6 +360,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
|
||||||
|
|
||||||
let mut view = ListView {
|
let mut view = ListView {
|
||||||
cell_factory: cell,
|
cell_factory: cell,
|
||||||
|
menu: PropertyNullable::default(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
||||||
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
|
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
|
||||||
|
@ -331,6 +390,7 @@ impl<T> ListView<T> {
|
||||||
pub(crate) fn clone_as_handle(&self) -> ListView {
|
pub(crate) fn clone_as_handle(&self) -> ListView {
|
||||||
ListView {
|
ListView {
|
||||||
cell_factory: CellFactory::new(),
|
cell_factory: CellFactory::new(),
|
||||||
|
menu: self.menu.clone(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: self.top.clone(),
|
top: self.top.clone(),
|
||||||
leading: self.leading.clone(),
|
leading: self.leading.clone(),
|
||||||
|
@ -361,8 +421,8 @@ impl<T> ListView<T> {
|
||||||
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
|
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let key = NSString::new(identifier).into_inner();
|
let key = NSString::new(identifier);
|
||||||
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:key owner:nil];
|
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:&*key owner:nil];
|
||||||
|
|
||||||
if cell != nil {
|
if cell != nil {
|
||||||
ListViewRow::from_cached(cell)
|
ListViewRow::from_cached(cell)
|
||||||
|
@ -386,6 +446,43 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Style
|
||||||
|
pub fn set_style(&self, style: crate::foundation::NSInteger) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setStyle:style];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_allows_empty_selection(&self, allows: bool) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setAllowsEmptySelection:match allows {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setSelectionHighlightStyle:style];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_row_indexes(&self, indexes: &[usize], extends_existing: bool) {
|
||||||
|
unsafe {
|
||||||
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
|
for index in indexes {
|
||||||
|
let _: () = msg_send![index_set, addIndex:index];
|
||||||
|
}
|
||||||
|
|
||||||
|
let _: () = msg_send![&*self.objc, selectRowIndexes:index_set byExtendingSelection:match extends_existing {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
|
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -398,13 +495,13 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animation: RowAnimation) {
|
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
for index in indexes {
|
for index in indexes {
|
||||||
let x: NSUInteger = index as NSUInteger;
|
let x: NSUInteger = *index as NSUInteger;
|
||||||
let _: () = msg_send![index_set, addIndex:x];
|
let _: () = msg_send![index_set, addIndex:x];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,13 +532,13 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animations: RowAnimation) {
|
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
for index in indexes {
|
for index in indexes {
|
||||||
let x: NSUInteger = index as NSUInteger;
|
let x: NSUInteger = *index as NSUInteger;
|
||||||
let _: () = msg_send![index_set, addIndex:x];
|
let _: () = msg_send![index_set, addIndex:x];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,11 +604,11 @@ impl<T> ListView<T> {
|
||||||
let types: NSArray = types.into_iter().map(|t| {
|
let types: NSArray = types.into_iter().map(|t| {
|
||||||
// This clone probably doesn't need to be here, but it should also be cheap as
|
// This clone probably doesn't need to be here, but it should also be cheap as
|
||||||
// this is just an enum... and this is not an oft called method.
|
// this is just an enum... and this is not an oft called method.
|
||||||
let x: NSString = t.clone().into();
|
let x: NSString = (*t).into();
|
||||||
x.into_inner()
|
x.into()
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()];
|
let _: () = msg_send![&*self.objc, registerForDraggedTypes:&*types];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,6 +617,14 @@ impl<T> ListView<T> {
|
||||||
let _: () = msg_send![&*self.objc, reloadData];
|
let _: () = msg_send![&*self.objc, reloadData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_row_index(&self) -> NSInteger {
|
||||||
|
unsafe { msg_send![&*self.objc, selectedRow] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_clicked_row_index(&self) -> NSInteger {
|
||||||
|
unsafe { msg_send![&*self.objc, clickedRow] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Layout for ListView<T> {
|
impl<T> Layout for ListView<T> {
|
||||||
|
|
|
@ -14,9 +14,9 @@ use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, YES, NO, NSUInteger};
|
use crate::foundation::{id, nil, YES, NO, NSUInteger};
|
||||||
use crate::dragdrop::DragInfo;
|
use crate::dragdrop::DragInfo;
|
||||||
use crate::listview::row::{LISTVIEW_ROW_DELEGATE_PTR, ViewDelegate};
|
use crate::listview::row::{LISTVIEW_ROW_DELEGATE_PTR, BACKGROUND_COLOR, ViewDelegate};
|
||||||
use crate::utils::load;
|
use crate::utils::load;
|
||||||
|
|
||||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||||
|
@ -74,6 +74,19 @@ extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called for layer updates.
|
||||||
|
extern fn update_layer(this: &Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
|
||||||
|
|
||||||
|
if background_color != nil {
|
||||||
|
let layer: id = msg_send![this, layer];
|
||||||
|
let cg: id = msg_send![background_color, CGColor];
|
||||||
|
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Normally, you might not want to do a custom dealloc override. However, reusable cells are
|
/// Normally, you might not want to do a custom dealloc override. However, reusable cells are
|
||||||
/// tricky - since we "forget" them when we give them to the system, we need to make sure to do
|
/// tricky - since we "forget" them when we give them to the system, we need to make sure to do
|
||||||
/// proper cleanup then the backing (cached) version is deallocated on the Objective-C side. Since
|
/// proper cleanup then the backing (cached) version is deallocated on the Objective-C side. Since
|
||||||
|
@ -121,8 +134,10 @@ pub(crate) fn register_listview_row_class_with_delegate<T: ViewDelegate>() -> *c
|
||||||
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
||||||
// move.
|
// move.
|
||||||
decl.add_ivar::<usize>(LISTVIEW_ROW_DELEGATE_PTR);
|
decl.add_ivar::<usize>(LISTVIEW_ROW_DELEGATE_PTR);
|
||||||
|
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
||||||
|
|
||||||
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||||
|
decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
|
||||||
|
|
||||||
// Drag and drop operations (e.g, accepting files)
|
// Drag and drop operations (e.g, accepting files)
|
||||||
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
||||||
|
|
|
@ -66,6 +66,7 @@ mod ios;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
use ios::{register_listview_row_view_class, register_listview_row_class_with_delegate};
|
use ios::{register_listview_row_view_class, register_listview_row_class_with_delegate};
|
||||||
|
|
||||||
|
pub(crate) static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
||||||
pub(crate) static LISTVIEW_ROW_DELEGATE_PTR: &str = "rstListViewRowDelegatePtr";
|
pub(crate) static LISTVIEW_ROW_DELEGATE_PTR: &str = "rstListViewRowDelegatePtr";
|
||||||
|
|
||||||
/// A helper method for instantiating view classes and applying default settings to them.
|
/// A helper method for instantiating view classes and applying default settings to them.
|
||||||
|
@ -264,23 +265,20 @@ impl<T> ListViewRow<T> {
|
||||||
|
|
||||||
/// Sets the identifier, which enables cells to be reused and dequeued properly.
|
/// Sets the identifier, which enables cells to be reused and dequeued properly.
|
||||||
pub fn set_identifier(&self, identifier: &'static str) {
|
pub fn set_identifier(&self, identifier: &'static str) {
|
||||||
let identifier = NSString::new(identifier).into_inner();
|
let identifier = NSString::new(identifier);
|
||||||
|
|
||||||
let objc = self.objc.borrow();
|
let objc = self.objc.borrow();
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&**objc, setIdentifier:identifier];
|
let _: () = msg_send![&**objc, setIdentifier:&*identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call this to set the background color for the backing layer.
|
/// Call this to set the background color for the backing layer.
|
||||||
pub fn set_background_color(&self, color: Color) {
|
pub fn set_background_color(&self, color: Color) {
|
||||||
let bg = color.into_platform_specific_color();
|
let mut objc = self.objc.borrow_mut();
|
||||||
|
|
||||||
let objc = self.objc.borrow();
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let cg: id = msg_send![bg, CGColor];
|
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc());
|
||||||
let layer: id = msg_send![&**objc, layer];
|
|
||||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,12 +288,12 @@ impl<T> ListViewRow<T> {
|
||||||
let types: NSArray = types.into_iter().map(|t| {
|
let types: NSArray = types.into_iter().map(|t| {
|
||||||
// This clone probably doesn't need to be here, but it should also be cheap as
|
// This clone probably doesn't need to be here, but it should also be cheap as
|
||||||
// this is just an enum... and this is not an oft called method.
|
// this is just an enum... and this is not an oft called method.
|
||||||
let x: NSString = t.clone().into();
|
let x: NSString = (*t).into();
|
||||||
x.into_inner()
|
x.into()
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
let objc = self.objc.borrow();
|
let objc = self.objc.borrow();
|
||||||
let _: () = msg_send![&**objc, registerForDraggedTypes:types.into_inner()];
|
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Various traits used for Views.
|
//! Various traits used for Views.
|
||||||
|
|
||||||
use crate::Node;
|
use crate::macos::menu::MenuItem;
|
||||||
use crate::dragdrop::{DragInfo, DragOperation};
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
|
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
|
@ -27,12 +27,23 @@ pub trait ListViewDelegate {
|
||||||
/// Returns the number of items in the list view.
|
/// Returns the number of items in the list view.
|
||||||
fn number_of_items(&self) -> usize;
|
fn number_of_items(&self) -> usize;
|
||||||
|
|
||||||
|
/// Called when an item will be displayed.
|
||||||
|
fn will_display_item(&self, row: usize) {}
|
||||||
|
|
||||||
/// This is temporary and you should not rely on this signature if you
|
/// This is temporary and you should not rely on this signature if you
|
||||||
/// choose to try and work with this. NSTableView & such associated delegate patterns
|
/// choose to try and work with this. NSTableView & such associated delegate patterns
|
||||||
/// are tricky to support in Rust, and while I have a few ideas about them, I haven't
|
/// are tricky to support in Rust, and while I have a few ideas about them, I haven't
|
||||||
/// had time to sit down and figure them out properly yet.
|
/// had time to sit down and figure them out properly yet.
|
||||||
fn item_for(&self, row: usize) -> ListViewRow;
|
fn item_for(&self, row: usize) -> ListViewRow;
|
||||||
|
|
||||||
|
/// Called when an item has been selected (clicked/tapped on).
|
||||||
|
fn item_selected(&self, row: usize) {}
|
||||||
|
|
||||||
|
/// Called when the menu for the tableview is about to be shown. You can update the menu here
|
||||||
|
/// depending on, say, what the user has context-clicked on. You should avoid any expensive
|
||||||
|
/// work in here and return the menu as fast as possible.
|
||||||
|
fn context_menu(&self) -> Vec<MenuItem> { vec![] }
|
||||||
|
|
||||||
/// An optional delegate method; implement this if you'd like swipe-to-reveal to be
|
/// An optional delegate method; implement this if you'd like swipe-to-reveal to be
|
||||||
/// supported for a given row by returning a vector of actions to show.
|
/// supported for a given row by returning a vector of actions to show.
|
||||||
fn actions_for(&self, row: usize, edge: RowEdge) -> Vec<RowAction> { Vec::new() }
|
fn actions_for(&self, row: usize, edge: RowEdge) -> Vec<RowAction> { Vec::new() }
|
||||||
|
|
|
@ -32,6 +32,7 @@ use crate::foundation::{id, NSString};
|
||||||
|
|
||||||
/// Represents an `NSAlert`. Has no information other than the retained pointer to the Objective C
|
/// Represents an `NSAlert`. Has no information other than the retained pointer to the Objective C
|
||||||
/// side, so... don't bother inspecting this.
|
/// side, so... don't bother inspecting this.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Alert(Id<Object>);
|
pub struct Alert(Id<Object>);
|
||||||
|
|
||||||
impl Alert {
|
impl Alert {
|
||||||
|
@ -40,13 +41,13 @@ impl Alert {
|
||||||
pub fn new(title: &str, message: &str) -> Self {
|
pub fn new(title: &str, message: &str) -> Self {
|
||||||
let title = NSString::new(title);
|
let title = NSString::new(title);
|
||||||
let message = NSString::new(message);
|
let message = NSString::new(message);
|
||||||
let x = NSString::new("OK");
|
let ok = NSString::new("OK");
|
||||||
|
|
||||||
Alert(unsafe {
|
Alert(unsafe {
|
||||||
let alert: id = msg_send![class!(NSAlert), new];
|
let alert: id = msg_send![class!(NSAlert), new];
|
||||||
let _: () = msg_send![alert, setMessageText:title];
|
let _: () = msg_send![alert, setMessageText:title];
|
||||||
let _: () = msg_send![alert, setInformativeText:message];
|
let _: () = msg_send![alert, setInformativeText:message];
|
||||||
let _: () = msg_send![alert, addButtonWithTitle:x];
|
let _: () = msg_send![alert, addButtonWithTitle:ok];
|
||||||
Id::from_ptr(alert)
|
Id::from_ptr(alert)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,9 +112,10 @@ extern fn should_handle_reopen<T: AppDelegate>(this: &Object, _: Sel, _: id, has
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application delegate receives a `applicationDockMenu:` request.
|
/// Fires when the application delegate receives a `applicationDockMenu:` request.
|
||||||
|
// @TODO: Make this return Vec<MenuItem>.
|
||||||
extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
|
extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||||
match app::<T>(this).dock_menu() {
|
match app::<T>(this).dock_menu() {
|
||||||
Some(mut menu) => &mut *menu.inner,
|
Some(mut menu) => &mut *menu.0,
|
||||||
None => nil
|
None => nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ extern fn did_change_screen_parameters<T: AppDelegate>(this: &Object, _: Sel, _:
|
||||||
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
||||||
/// notification.
|
/// notification.
|
||||||
extern fn will_continue_user_activity_with_type<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
|
extern fn will_continue_user_activity_with_type<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
|
||||||
let activity = NSString::wrap(activity_type);
|
let activity = NSString::retain(activity_type);
|
||||||
|
|
||||||
match app::<T>(this).will_continue_user_activity(activity.to_str()) {
|
match app::<T>(this).will_continue_user_activity(activity.to_str()) {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -144,7 +145,7 @@ extern fn will_continue_user_activity_with_type<T: AppDelegate>(this: &Object, _
|
||||||
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
|
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
|
||||||
extern fn continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
|
extern fn continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
|
||||||
// @TODO: This needs to support restorable objects, but it involves a larger question about how
|
// @TODO: This needs to support restorable objects, but it involves a larger question about how
|
||||||
// much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's
|
// much `NSObject` retainping we want to do here. For now, pass the handler for whenever it's
|
||||||
// useful.
|
// useful.
|
||||||
let activity = UserActivity::with_inner(activity);
|
let activity = UserActivity::with_inner(activity);
|
||||||
|
|
||||||
|
@ -161,7 +162,7 @@ extern fn continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, a
|
||||||
/// `application:didFailToContinueUserActivityWithType:error:` message.
|
/// `application:didFailToContinueUserActivityWithType:error:` message.
|
||||||
extern fn failed_to_continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
extern fn failed_to_continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
||||||
app::<T>(this).failed_to_continue_user_activity(
|
app::<T>(this).failed_to_continue_user_activity(
|
||||||
NSString::wrap(activity_type).to_str(),
|
NSString::retain(activity_type).to_str(),
|
||||||
Error::new(error)
|
Error::new(error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -197,8 +198,8 @@ extern fn accepted_cloudkit_share<T: AppDelegate>(this: &Object, _: Sel, _: id,
|
||||||
|
|
||||||
/// Fires when the application receives an `application:openURLs` message.
|
/// Fires when the application receives an `application:openURLs` message.
|
||||||
extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
|
extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
|
||||||
let urls = NSArray::wrap(file_urls).map(|url| {
|
let urls = NSArray::retain(file_urls).map(|url| {
|
||||||
let uri = NSString::wrap(unsafe {
|
let uri = NSString::retain(unsafe {
|
||||||
msg_send![url, absoluteString]
|
msg_send![url, absoluteString]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -210,7 +211,7 @@ extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id)
|
||||||
|
|
||||||
/// Fires when the application receives an `application:openFileWithoutUI:` message.
|
/// Fires when the application receives an `application:openFileWithoutUI:` message.
|
||||||
extern fn open_file_without_ui<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
extern fn open_file_without_ui<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
||||||
let filename = NSString::wrap(file);
|
let filename = NSString::retain(file);
|
||||||
|
|
||||||
match app::<T>(this).open_file_without_ui(filename.to_str()) {
|
match app::<T>(this).open_file_without_ui(filename.to_str()) {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -236,7 +237,7 @@ extern fn open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BO
|
||||||
|
|
||||||
/// Fired when the application receives an `application:openTempFile:` message.
|
/// Fired when the application receives an `application:openTempFile:` message.
|
||||||
extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
|
extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
|
||||||
let filename = NSString::wrap(filename);
|
let filename = NSString::retain(filename);
|
||||||
|
|
||||||
match app::<T>(this).open_temp_file(filename.to_str()) {
|
match app::<T>(this).open_temp_file(filename.to_str()) {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -246,7 +247,7 @@ extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename:
|
||||||
|
|
||||||
/// Fired when the application receives an `application:printFile:` message.
|
/// Fired when the application receives an `application:printFile:` message.
|
||||||
extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
|
||||||
let filename = NSString::wrap(file);
|
let filename = NSString::retain(file);
|
||||||
|
|
||||||
match app::<T>(this).print_file(filename.to_str()) {
|
match app::<T>(this).print_file(filename.to_str()) {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -257,8 +258,8 @@ extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) ->
|
||||||
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
|
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
|
||||||
/// message.
|
/// message.
|
||||||
extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
|
extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
|
||||||
let files = NSArray::wrap(files).map(|file| {
|
let files = NSArray::retain(files).map(|file| {
|
||||||
NSString::wrap(file).to_str().to_string()
|
NSString::retain(file).to_str().to_string()
|
||||||
});
|
});
|
||||||
|
|
||||||
let settings = PrintSettings::with_inner(settings);
|
let settings = PrintSettings::with_inner(settings);
|
||||||
|
@ -275,7 +276,7 @@ extern fn did_change_occlusion_state<T: AppDelegate>(this: &Object, _: Sel, _: i
|
||||||
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
||||||
/// matter.
|
/// matter.
|
||||||
extern fn delegate_handles_key<T: AppDelegate>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
|
extern fn delegate_handles_key<T: AppDelegate>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
|
||||||
let key = NSString::wrap(key);
|
let key = NSString::retain(key);
|
||||||
|
|
||||||
match app::<T>(this).delegate_handles_key(key.to_str()) {
|
match app::<T>(this).delegate_handles_key(key.to_str()) {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
|
|
@ -82,9 +82,9 @@ pub(crate) static APP_PTR: &str = "rstAppPtr";
|
||||||
// Calls to App::set_menu() will reconfigure the contents of this Vec, so that's also
|
// Calls to App::set_menu() will reconfigure the contents of this Vec, so that's also
|
||||||
// the easiest way to remove things as necessary - just rebuild your menu. Trying to debug
|
// the easiest way to remove things as necessary - just rebuild your menu. Trying to debug
|
||||||
// and/or predict NSMenu is a whole task that I'm not even sure is worth trying to do.
|
// and/or predict NSMenu is a whole task that I'm not even sure is worth trying to do.
|
||||||
lazy_static! {
|
/*lazy_static! {
|
||||||
static ref MENU_ITEMS_HANDLER_CACHE: Arc<Mutex<Vec<TargetActionHandler>>> = Arc::new(Mutex::new(Vec::new()));
|
static ref MENU_ITEMS_HANDLER_CACHE: Arc<Mutex<Vec<TargetActionHandler>>> = Arc::new(Mutex::new(Vec::new()));
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/// A handler to make some boilerplate less annoying.
|
/// A handler to make some boilerplate less annoying.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -257,36 +257,21 @@ impl App {
|
||||||
/// Sets a set of `Menu`'s as the top level Menu for the current application. Note that behind
|
/// Sets a set of `Menu`'s as the top level Menu for the current application. Note that behind
|
||||||
/// the scenes, Cocoa/AppKit make a copy of the menu you pass in - so we don't retain it, and
|
/// the scenes, Cocoa/AppKit make a copy of the menu you pass in - so we don't retain it, and
|
||||||
/// you shouldn't bother to either.
|
/// you shouldn't bother to either.
|
||||||
///
|
|
||||||
/// The one note here: we internally cache actions to avoid them dropping without the
|
|
||||||
/// Objective-C side knowing. The the comments on the
|
|
||||||
pub fn set_menu(mut menus: Vec<Menu>) {
|
pub fn set_menu(mut menus: Vec<Menu>) {
|
||||||
let mut handlers = vec![];
|
|
||||||
|
|
||||||
let main_menu = unsafe {
|
let main_menu = unsafe {
|
||||||
let menu_cls = class!(NSMenu);
|
let menu_cls = class!(NSMenu);
|
||||||
let item_cls = class!(NSMenuItem);
|
let item_cls = class!(NSMenuItem);
|
||||||
let main_menu: id = msg_send![menu_cls, new];
|
let main_menu: id = msg_send![menu_cls, new];
|
||||||
|
|
||||||
for menu in menus.iter_mut() {
|
for menu in menus.iter_mut() {
|
||||||
handlers.append(&mut menu.actions);
|
|
||||||
|
|
||||||
let item: id = msg_send![item_cls, new];
|
let item: id = msg_send![item_cls, new];
|
||||||
let _: () = msg_send![item, setSubmenu:&*menu.inner];
|
let _: () = msg_send![item, setSubmenu:&*menu.0];
|
||||||
let _: () = msg_send![main_menu, addItem:item];
|
let _: () = msg_send![main_menu, addItem:item];
|
||||||
}
|
}
|
||||||
|
|
||||||
main_menu
|
main_menu
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cache our menu handlers, whatever they may be - since we're replacing the
|
|
||||||
// existing menu, and macOS only has one menu on screen at a time, we can go
|
|
||||||
// ahead and blow away the old ones.
|
|
||||||
{
|
|
||||||
let mut cache = MENU_ITEMS_HANDLER_CACHE.lock().unwrap();
|
|
||||||
*cache = handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_application(move |app| unsafe {
|
shared_application(move |app| unsafe {
|
||||||
let _: () = msg_send![app, setMainMenu:main_menu];
|
let _: () = msg_send![app, setMainMenu:main_menu];
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,11 +32,11 @@ impl Event {
|
||||||
// @TODO: Check here if key event, invalid otherwise.
|
// @TODO: Check here if key event, invalid otherwise.
|
||||||
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
||||||
// should... make it work, I think.
|
// should... make it work, I think.
|
||||||
let characters = NSString::wrap(unsafe {
|
let characters = NSString::from_retained(unsafe {
|
||||||
msg_send![&*self.0, characters]
|
msg_send![&*self.0, characters]
|
||||||
});
|
});
|
||||||
|
|
||||||
characters.to_str().to_string()
|
characters.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
|
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
|
||||||
|
|
|
@ -2,24 +2,38 @@
|
||||||
//! one level deep; this could change in the future but is fine for
|
//! one level deep; this could change in the future but is fine for
|
||||||
//! now.
|
//! now.
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
|
||||||
use objc::runtime::{Object, Sel};
|
|
||||||
use objc_id::ShareId;
|
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use block::ConcreteBlock;
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel};
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NSString, NSUInteger};
|
use crate::foundation::{id, nil, NSString, NSUInteger};
|
||||||
use crate::events::EventModifierFlag;
|
use crate::events::EventModifierFlag;
|
||||||
use crate::invoker::TargetActionHandler;
|
|
||||||
|
static BLOCK_PTR: &'static str = "cacaoMenuItemBlockPtr";
|
||||||
|
|
||||||
|
/// An Action is just an indirection layer to get around Rust and optimizing
|
||||||
|
/// zero-sum types; without this, pointers to callbacks will end up being
|
||||||
|
/// 0x1, and all point to whatever is there first (unsure if this is due to
|
||||||
|
/// Rust or Cocoa or what).
|
||||||
|
///
|
||||||
|
/// Point is, Button aren't created that much in the grand scheme of things,
|
||||||
|
/// and the heap isn't our enemy in a GUI framework anyway. If someone knows
|
||||||
|
/// a better way to do this that doesn't require double-boxing, I'm all ears.
|
||||||
|
pub struct Action(Box<dyn Fn() + 'static>);
|
||||||
|
|
||||||
/// Internal method (shorthand) for generating `NSMenuItem` holders.
|
/// Internal method (shorthand) for generating `NSMenuItem` holders.
|
||||||
fn make_menu_item(
|
fn make_menu_item<S: AsRef<str>>(
|
||||||
title: &str,
|
title: S,
|
||||||
key: Option<&str>,
|
key: Option<&str>,
|
||||||
action: Option<Sel>,
|
action: Option<Sel>,
|
||||||
modifiers: Option<&[EventModifierFlag]>
|
modifiers: Option<&[EventModifierFlag]>
|
||||||
) -> MenuItem {
|
) -> Id<Object> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let title = NSString::new(title);
|
let title = NSString::new(title.as_ref());
|
||||||
|
|
||||||
// Note that AppKit requires a blank string if nil, not nil.
|
// Note that AppKit requires a blank string if nil, not nil.
|
||||||
let key = NSString::new(match key {
|
let key = NSString::new(match key {
|
||||||
|
@ -27,10 +41,16 @@ fn make_menu_item(
|
||||||
None => ""
|
None => ""
|
||||||
});
|
});
|
||||||
|
|
||||||
let alloc: id = msg_send![class!(NSMenuItem), alloc];
|
// Stock menu items that use selectors targeted at system pieces are just standard
|
||||||
let item = ShareId::from_ptr(match action {
|
// `NSMenuItem`s. If there's no custom ones, we use our subclass that has a slot to store a
|
||||||
Some(a) => msg_send![alloc, initWithTitle:title action:a keyEquivalent:key],
|
// handler pointer.
|
||||||
None => msg_send![alloc, initWithTitle:title action:nil keyEquivalent:key]
|
let alloc: id = msg_send![register_menu_item_class(), alloc];
|
||||||
|
let item = Id::from_retained_ptr(match action {
|
||||||
|
Some(a) => msg_send![alloc, initWithTitle:&*title action:a keyEquivalent:&*key],
|
||||||
|
|
||||||
|
None => msg_send![alloc, initWithTitle:&*title
|
||||||
|
action:sel!(fireBlockAction:)
|
||||||
|
keyEquivalent:&*key]
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(modifiers) = modifiers {
|
if let Some(modifiers) = modifiers {
|
||||||
|
@ -44,169 +64,262 @@ fn make_menu_item(
|
||||||
let _: () = msg_send![&*item, setKeyEquivalentModifierMask:key_mask];
|
let _: () = msg_send![&*item, setKeyEquivalentModifierMask:key_mask];
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuItem::Entry((item, None))
|
item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents varying `NSMenuItem` types - e.g, a separator vs an action.
|
/// Represents varying `NSMenuItem` types - e.g, a separator vs an action. If you need something
|
||||||
|
/// outside of the stock item types, you can create a `Custom` variant that supports dispatching a
|
||||||
|
/// callback on the Rust side of things.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MenuItem {
|
pub enum MenuItem {
|
||||||
/// Represents a Menu item that's not a separator - for all intents and purposes, you can consider
|
/// A custom MenuItem. This type functions as a builder, so you can customize it easier.
|
||||||
/// this the real `NSMenuItem`.
|
/// You can (and should) create this variant via the `new(title)` method, but if you need to do
|
||||||
Entry((ShareId<Object>, Option<TargetActionHandler>)),
|
/// something crazier, then wrap it in this and you can hook into the Cacao menu system
|
||||||
|
/// accordingly.
|
||||||
|
Custom(Id<Object>),
|
||||||
|
|
||||||
/// Represents a Separator. You can't do anything with this, but it's useful nonetheless for
|
/// Shows a standard "About" item, which will bring up the necessary window when clicked
|
||||||
|
/// (include a `credits.html` in your App to make use of here). The argument baked in here
|
||||||
|
/// should be your app name.
|
||||||
|
About(String),
|
||||||
|
|
||||||
|
/// A standard "hide the app" menu item.
|
||||||
|
Hide,
|
||||||
|
|
||||||
|
/// A standard "Services" menu item.
|
||||||
|
Services,
|
||||||
|
|
||||||
|
/// A "hide all other windows" menu item.
|
||||||
|
HideOthers,
|
||||||
|
|
||||||
|
/// A menu item to show all the windows for this app.
|
||||||
|
ShowAll,
|
||||||
|
|
||||||
|
/// Close the current window.
|
||||||
|
CloseWindow,
|
||||||
|
|
||||||
|
/// A "quit this app" menu icon.
|
||||||
|
Quit,
|
||||||
|
|
||||||
|
/// A menu item for enabling copying (often text) from responders.
|
||||||
|
Copy,
|
||||||
|
|
||||||
|
/// A menu item for enabling cutting (often text) from responders.
|
||||||
|
Cut,
|
||||||
|
|
||||||
|
/// An "undo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||||
|
/// of events.
|
||||||
|
Undo,
|
||||||
|
|
||||||
|
/// An "redo" menu item; particularly useful for supporting the cut/copy/paste/undo lifecycle
|
||||||
|
/// of events.
|
||||||
|
Redo,
|
||||||
|
|
||||||
|
/// A menu item for selecting all (often text) from responders.
|
||||||
|
SelectAll,
|
||||||
|
|
||||||
|
/// A menu item for pasting (often text) into responders.
|
||||||
|
Paste,
|
||||||
|
|
||||||
|
/// A standard "enter full screen" item.
|
||||||
|
EnterFullScreen,
|
||||||
|
|
||||||
|
/// An item for minimizing the window with the standard system controls.
|
||||||
|
Minimize,
|
||||||
|
|
||||||
|
/// An item for instructing the app to zoom. Your app must react to this with necessary window
|
||||||
|
/// lifecycle events.
|
||||||
|
Zoom,
|
||||||
|
|
||||||
|
/// An item for automatically telling a SplitViewController to hide or show the sidebar. This
|
||||||
|
/// only works on macOS 11.0+.
|
||||||
|
ToggleSidebar,
|
||||||
|
|
||||||
|
/// Represents a Separator. It's useful nonetheless for
|
||||||
/// separating out pieces of the `NSMenu` structure.
|
/// separating out pieces of the `NSMenu` structure.
|
||||||
Separator
|
Separator
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MenuItem {
|
impl MenuItem {
|
||||||
/// Creates and returns a `MenuItem::Entry` with the specified title.
|
/// Consumes and returns a handle for the underlying MenuItem. This is internal as we make a few assumptions
|
||||||
pub fn entry(title: &str) -> Self {
|
/// for how it interacts with our `Menu` setup, but this could be made public in the future.
|
||||||
make_menu_item(title, None, None, None)
|
pub(crate) unsafe fn to_objc(self) -> Id<Object> {
|
||||||
}
|
|
||||||
|
|
||||||
/// Configures the menu item, if it's not a separator, to support a key equivalent.
|
|
||||||
pub fn key(self, key: &str) -> Self {
|
|
||||||
match self {
|
match self {
|
||||||
MenuItem::Separator => MenuItem::Separator,
|
Self::Custom(objc) => objc,
|
||||||
|
|
||||||
MenuItem::Entry((item, action)) => {
|
Self::About(app_name) => {
|
||||||
unsafe {
|
let title = format!("About {}", app_name);
|
||||||
let key = NSString::new(key);
|
|
||||||
let _: () = msg_send![&*item, setKeyEquivalent:key];
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem::Entry((item, action))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attaches a target/action handler to dispatch events.
|
|
||||||
pub fn action<F: Fn() + Send + Sync + 'static>(self, action: F) -> Self {
|
|
||||||
match self {
|
|
||||||
MenuItem::Separator => MenuItem::Separator,
|
|
||||||
|
|
||||||
MenuItem::Entry((item, _)) => {
|
|
||||||
let action = TargetActionHandler::new(&*item, action);
|
|
||||||
MenuItem::Entry((item, Some(action)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "About" item.
|
|
||||||
pub fn about(name: &str) -> Self {
|
|
||||||
let title = format!("About {}", name);
|
|
||||||
make_menu_item(&title, None, Some(sel!(orderFrontStandardAboutPanel:)), None)
|
make_menu_item(&title, None, Some(sel!(orderFrontStandardAboutPanel:)), None)
|
||||||
}
|
},
|
||||||
|
|
||||||
/// Returns a standard "Hide" item.
|
Self::Hide => make_menu_item("Hide", Some("h"), Some(sel!(hide:)), None),
|
||||||
pub fn hide() -> Self {
|
|
||||||
make_menu_item("Hide", Some("h"), Some(sel!(hide:)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the standard "Services" item. This one does some extra work to link in the default
|
// This one is a bit tricky to do right, as we need to expose a submenu, which isn't
|
||||||
/// Services submenu.
|
// supported by MenuItem yet.
|
||||||
pub fn services() -> Self {
|
Self::Services => {
|
||||||
match make_menu_item("Services", None, None, None) {
|
let item = make_menu_item("Services", None, None, None);
|
||||||
// Link in the services menu, which is part of NSApp
|
|
||||||
MenuItem::Entry((item, action)) => {
|
|
||||||
unsafe {
|
|
||||||
let app: id = msg_send![class!(RSTApplication), sharedApplication];
|
let app: id = msg_send![class!(RSTApplication), sharedApplication];
|
||||||
let services: id = msg_send![app, servicesMenu];
|
let services: id = msg_send![app, servicesMenu];
|
||||||
let _: () = msg_send![&*item, setSubmenu:services];
|
let _: () = msg_send![&*item, setSubmenu:services];
|
||||||
}
|
item
|
||||||
|
|
||||||
MenuItem::Entry((item, action))
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Should never be hit
|
Self::HideOthers => make_menu_item(
|
||||||
MenuItem::Separator => MenuItem::Separator
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Hide" item.
|
|
||||||
pub fn hide_others() -> Self {
|
|
||||||
make_menu_item(
|
|
||||||
"Hide Others",
|
"Hide Others",
|
||||||
Some("h"),
|
Some("h"),
|
||||||
Some(sel!(hide:)),
|
Some(sel!(hide:)),
|
||||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
||||||
)
|
),
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Hide" item.
|
Self::ShowAll => make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None),
|
||||||
pub fn show_all() -> Self {
|
Self::CloseWindow => make_menu_item("Close Window", Some("w"), Some(sel!(performClose:)), None),
|
||||||
make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None)
|
Self::Quit => make_menu_item("Quit", Some("q"), Some(sel!(terminate:)), None),
|
||||||
}
|
Self::Copy => make_menu_item("Copy", Some("c"), Some(sel!(copy:)), None),
|
||||||
|
Self::Cut => make_menu_item("Cut", Some("x"), Some(sel!(cut:)), None),
|
||||||
|
Self::Undo => make_menu_item("Undo", Some("z"), Some(sel!(undo:)), None),
|
||||||
|
Self::Redo => make_menu_item("Redo", Some("Z"), Some(sel!(redo:)), None),
|
||||||
|
Self::SelectAll => make_menu_item("Select All", Some("a"), Some(sel!(selectAll:)), None),
|
||||||
|
Self::Paste => make_menu_item("Paste", Some("v"), Some(sel!(paste:)), None),
|
||||||
|
|
||||||
/// Returns a standard "Close Window" item.
|
Self::EnterFullScreen => make_menu_item(
|
||||||
pub fn close_window() -> Self {
|
|
||||||
make_menu_item("Close Window", Some("w"), Some(sel!(performClose:)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Quit" item.
|
|
||||||
pub fn quit() -> Self {
|
|
||||||
make_menu_item("Quit", Some("q"), Some(sel!(terminate:)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Copy" item.
|
|
||||||
pub fn copy() -> Self {
|
|
||||||
make_menu_item("Copy", Some("c"), Some(sel!(copy:)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Undo" item.
|
|
||||||
pub fn undo() -> Self {
|
|
||||||
make_menu_item("Undo", Some("z"), Some(sel!(undo:)), None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a standard "Enter Full Screen" item
|
|
||||||
pub fn enter_full_screen() -> Self {
|
|
||||||
make_menu_item(
|
|
||||||
"Enter Full Screen",
|
"Enter Full Screen",
|
||||||
Some("f"),
|
Some("f"),
|
||||||
Some(sel!(toggleFullScreen:)),
|
Some(sel!(toggleFullScreen:)),
|
||||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Control])
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Control])
|
||||||
)
|
),
|
||||||
|
|
||||||
|
Self::Minimize => make_menu_item("Minimize", Some("m"), Some(sel!(performMiniaturize:)), None),
|
||||||
|
Self::Zoom => make_menu_item("Zoom", None, Some(sel!(performZoom:)), None),
|
||||||
|
|
||||||
|
Self::ToggleSidebar => make_menu_item(
|
||||||
|
"Toggle Sidebar",
|
||||||
|
Some("s"),
|
||||||
|
Some(sel!(toggleSidebar:)),
|
||||||
|
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
||||||
|
),
|
||||||
|
|
||||||
|
Self::Separator => {
|
||||||
|
let cls = class!(NSMenuItem);
|
||||||
|
let separator: id = msg_send![cls, separatorItem];
|
||||||
|
Id::from_ptr(separator)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Miniaturize" item
|
/// Returns a `Custom` menu item, with the given title. You can configure this further with the
|
||||||
pub fn minimize() -> Self {
|
/// builder methods on this object.
|
||||||
make_menu_item(
|
pub fn new<S: AsRef<str>>(title: S) -> Self {
|
||||||
"Minimize",
|
MenuItem::Custom(make_menu_item(title, None, None, None))
|
||||||
Some("m"),
|
|
||||||
Some(sel!(performMiniaturize:)),
|
|
||||||
None
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Zoom" item
|
/// Configures the a custom item to have specified key equivalent. This does nothing if called
|
||||||
pub fn zoom() -> Self {
|
/// on a `MenuItem` type that is not `Custom`,
|
||||||
make_menu_item(
|
pub fn key(self, key: &str) -> Self {
|
||||||
"Zoom",
|
if let MenuItem::Custom(objc) = self {
|
||||||
None,
|
unsafe {
|
||||||
Some(sel!(performZoom:)),
|
let key = NSString::new(key);
|
||||||
None
|
let _: () = msg_send![&*objc, setKeyEquivalent:key];
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Redo" item.
|
return MenuItem::Custom(objc);
|
||||||
pub fn redo() -> Self {
|
|
||||||
make_menu_item("Redo", Some("Z"), Some(sel!(redo:)), None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Cut" item.
|
self
|
||||||
pub fn cut() -> Self {
|
|
||||||
make_menu_item("Cut", Some("x"), Some(sel!(cut:)), None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Select All" item.
|
/// Sets the modifier key flags for this menu item. This does nothing if called on a `MenuItem`
|
||||||
pub fn select_all() -> Self {
|
/// that is not `Custom`.
|
||||||
make_menu_item("Select All", Some("a"), Some(sel!(selectAll:)), None)
|
pub fn modifiers(self, modifiers: &[EventModifierFlag]) -> Self {
|
||||||
|
if let MenuItem::Custom(objc) = self {
|
||||||
|
let mut key_mask: NSUInteger = 0;
|
||||||
|
|
||||||
|
for modifier in modifiers {
|
||||||
|
let y: NSUInteger = modifier.into();
|
||||||
|
key_mask = key_mask | y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a standard "Paste" item.
|
unsafe {
|
||||||
pub fn paste() -> Self {
|
let _: () = msg_send![&*objc, setKeyEquivalentModifierMask:key_mask];
|
||||||
make_menu_item("Paste", Some("v"), Some(sel!(paste:)), None)
|
}
|
||||||
|
|
||||||
|
return MenuItem::Custom(objc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches a target/action handler to dispatch events. This does nothing if called on a
|
||||||
|
/// `MenuItem` that is not `Custom`.
|
||||||
|
///
|
||||||
|
/// Note that we use an extra bit of unsafety here to pass over a heap'd block. We need to do
|
||||||
|
/// this as some menu items live in odd places (the system menu bar), and we need the handlers
|
||||||
|
/// to persist. We inject a custom dealloc method to pull the pointer back and drop the handler
|
||||||
|
/// whenever the menu item goes kaput.
|
||||||
|
pub fn action<F: Fn() + 'static>(self, action: F) -> Self {
|
||||||
|
if let MenuItem::Custom(mut objc) = self {
|
||||||
|
let handler = Box::new(Action(Box::new(action)));
|
||||||
|
let ptr = Box::into_raw(handler);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
(&mut *objc).set_ivar(BLOCK_PTR, ptr as usize);
|
||||||
|
let _: () = msg_send![&*objc, setTarget:&*objc];
|
||||||
|
}
|
||||||
|
|
||||||
|
return MenuItem::Custom(objc);
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// On the Objective-C side, we need to ensure our handler is dropped when this subclass
|
||||||
|
/// is deallocated. Note that NSMenuItem is seemingly odd outside of ARC contexts, and we
|
||||||
|
/// need to do some extra logic to ensure release calls are properly sent.
|
||||||
|
extern fn dealloc_cacao_menuitem(this: &Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let ptr: usize = *this.get_ivar(BLOCK_PTR);
|
||||||
|
let obj = ptr as *mut Action;
|
||||||
|
|
||||||
|
if !obj.is_null() {
|
||||||
|
let _handler = Box::from_raw(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be fine to _not_ do, but considering we go out of our way to loop it back on
|
||||||
|
// itself, it's worth clearing out the slot.
|
||||||
|
//let _: () = msg_send![this, setTarget:nil];
|
||||||
|
|
||||||
|
let _: () = msg_send![super(this, class!(NSMenuItem)), dealloc];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when our custom item needs to fire.
|
||||||
|
extern fn fire_block_action(this: &Object, _: Sel, _item: id) {
|
||||||
|
let action = crate::utils::load::<Action>(this, BLOCK_PTR);
|
||||||
|
(action.0)();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects a custom NSMenuItem subclass that contains a slot to hold a block, as well as a method
|
||||||
|
/// for calling the block.
|
||||||
|
///
|
||||||
|
/// In general, we do not want to do more than we need to here - menus are one of the last areas
|
||||||
|
/// where Carbon still lurks, and subclassing things can get weird.
|
||||||
|
pub(crate) fn register_menu_item_class() -> *const Class {
|
||||||
|
static mut APP_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(NSMenuItem);
|
||||||
|
let mut decl = ClassDecl::new("CacaoMenuItem", superclass).unwrap();
|
||||||
|
decl.add_ivar::<usize>(BLOCK_PTR);
|
||||||
|
|
||||||
|
decl.add_method(sel!(dealloc), dealloc_cacao_menuitem as extern fn(&Object, _));
|
||||||
|
decl.add_method(sel!(fireBlockAction:), fire_block_action as extern fn(&Object, _, id));
|
||||||
|
|
||||||
|
APP_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
APP_CLASS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,13 @@ use objc_id::{Id, ShareId};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, NSString};
|
use crate::foundation::{id, NSInteger, NSString};
|
||||||
use crate::macos::menu::item::MenuItem;
|
use crate::macos::menu::item::MenuItem;
|
||||||
use crate::invoker::TargetActionHandler;
|
|
||||||
|
|
||||||
/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
|
/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
|
||||||
/// them throughout the application lifecycle.
|
/// them throughout the application lifecycle.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Menu {
|
pub struct Menu(pub Id<Object>);
|
||||||
pub inner: Id<Object>,
|
|
||||||
pub actions: Vec<TargetActionHandler>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Menu {
|
impl Menu {
|
||||||
/// Creates a new `Menu` with the given title, and uses the passed items as submenu items.
|
/// Creates a new `Menu` with the given title, and uses the passed items as submenu items.
|
||||||
|
@ -29,41 +25,49 @@ impl Menu {
|
||||||
/// to get the menu functioning.
|
/// to get the menu functioning.
|
||||||
///
|
///
|
||||||
pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
|
pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
|
||||||
let inner = unsafe {
|
Menu(unsafe {
|
||||||
let cls = class!(NSMenu);
|
let cls = class!(NSMenu);
|
||||||
let alloc: id = msg_send![cls, alloc];
|
let alloc: id = msg_send![cls, alloc];
|
||||||
let title = NSString::new(title);
|
let title = NSString::new(title);
|
||||||
let inner: id = msg_send![alloc, initWithTitle:title];
|
let menu: id = msg_send![alloc, initWithTitle:&*title];
|
||||||
Id::from_ptr(inner)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut actions = vec![];
|
for item in items.into_iter() {
|
||||||
|
let objc = item.to_objc();
|
||||||
|
let _: () = msg_send![menu, addItem:&*objc];
|
||||||
|
}
|
||||||
|
|
||||||
for item in items {
|
Id::from_retained_ptr(menu)
|
||||||
match item {
|
})
|
||||||
MenuItem::Entry((item, action)) => {
|
}
|
||||||
|
|
||||||
|
/// Given a set of `MenuItem`s, merges them into an existing Menu (e.g, for a context menu on a
|
||||||
|
/// view).
|
||||||
|
pub fn append(menu: id, items: Vec<MenuItem>) -> id {
|
||||||
|
// You might look at the code below and wonder why we can't just call `removeAllItems`.
|
||||||
|
//
|
||||||
|
// Basically: that doesn't seem to properly decrement the retain count on the underlying
|
||||||
|
// menu item, and we wind up leaking any callbacks for the returned `MenuItem` instances.
|
||||||
|
//
|
||||||
|
// Walking them and calling release after removing them from the underlying store gives us
|
||||||
|
// the correct behavior.
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*inner, addItem:item];
|
let mut count: NSInteger = msg_send![menu, numberOfItems];
|
||||||
|
|
||||||
|
while count != 0 {
|
||||||
|
count -= 1;
|
||||||
|
let item: id = msg_send![menu, itemAtIndex:count];
|
||||||
|
let _: () = msg_send![menu, removeItemAtIndex:count];
|
||||||
|
let _: () = msg_send![item, release];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.is_some() {
|
for item in items.into_iter() {
|
||||||
actions.push(action.unwrap());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
MenuItem::Separator => {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let cls = class!(NSMenuItem);
|
let objc = item.to_objc();
|
||||||
let separator: id = msg_send![cls, separatorItem];
|
let _: () = msg_send![menu, addItem:&*objc];
|
||||||
let _: () = msg_send![&*inner, addItem:separator];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu {
|
menu
|
||||||
inner: inner,
|
|
||||||
actions: actions
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,9 @@
|
||||||
//! Mac-specific implementations.
|
//! This module implements the core components necessary for making a well-formed macOS
|
||||||
|
//! application. These components are ones that are uniquely macOS-specific, and don't have a true
|
||||||
|
//! equivalent on iOS and tvOS as the interaction patterns are significantly different.
|
||||||
//!
|
//!
|
||||||
//! macOS is a much older system than iOS, and as a result has some... quirks, in addition to just
|
//! The coverage here is not exhaustive, but should be sufficient enough for relatively complex
|
||||||
//! plain different APIs. It's tempting to want to find a common one and just implement that, but
|
//! applications. For examples, check the `examples` folder in the repository.
|
||||||
//! unfortunately doing so erases a lot of control and finer points of the macOS platform.
|
|
||||||
//!
|
|
||||||
//! With that said, this framework makes attempts to make things mostly work as you'd expect them
|
|
||||||
//! to from the iOS-side of things, which means we wrap things like `NSView` and `NSTableView` and
|
|
||||||
//! so on to act like their iOS counterparts (we also layer-back everything by default, as it's
|
|
||||||
//! typically what you want).
|
|
||||||
//!
|
|
||||||
//! _However_, there are some specific things that just can't be wrapped well - for example,
|
|
||||||
//! `NSToolbar`. Yes, `UIToolbar` exists, but it's really not close to `NSToolbar` in functionality
|
|
||||||
//! at all. For controls like these, we surface them here - the goal is to enable you to write 90%
|
|
||||||
//! of your app as a cross platform codebase, with the initial 10% being scaffolding code for the
|
|
||||||
//! platform (e.g, NSApplication vs UIApplication lifecycle).
|
|
||||||
|
|
||||||
mod alert;
|
mod alert;
|
||||||
pub use alert::Alert;
|
pub use alert::Alert;
|
||||||
|
@ -25,7 +15,7 @@ mod cursor;
|
||||||
pub use cursor::{Cursor, CursorType};
|
pub use cursor::{Cursor, CursorType};
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::{FocusRingType};
|
pub use enums::FocusRingType;
|
||||||
|
|
||||||
mod event;
|
mod event;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use objc::declare::ClassDecl;
|
||||||
use objc::runtime::{Class, Object, Sel};
|
use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{class, sel, sel_impl, msg_send};
|
use objc::{class, sel, sel_impl, msg_send};
|
||||||
|
|
||||||
use crate::foundation::{load_or_register_class, id, NSArray, NSString};
|
use crate::foundation::{load_or_register_class, id, BOOL, NSArray, NSString};
|
||||||
use crate::macos::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
|
use crate::macos::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
|
||||||
use crate::utils::load;
|
use crate::utils::load;
|
||||||
|
|
||||||
|
@ -15,39 +15,49 @@ extern fn allowed_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _:
|
||||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||||
|
|
||||||
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
|
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
|
||||||
NSString::new(identifier).into_inner()
|
identifier.to_nsstring()
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
identifiers.into_inner()
|
identifiers.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and passes the default item identifiers for this toolbar.
|
/// Retrieves and passes the default item identifiers for this toolbar.
|
||||||
extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||||
|
|
||||||
let identifiers: NSArray = toolbar.default_item_identifiers().iter().map(|identifier| {
|
let identifiers: NSArray = toolbar.default_item_identifiers()
|
||||||
NSString::new(identifier).into_inner()
|
.iter()
|
||||||
}).collect::<Vec<id>>().into();
|
.map(|identifier| identifier.to_nsstring())
|
||||||
|
.collect::<Vec<id>>()
|
||||||
|
.into();
|
||||||
|
|
||||||
identifiers.into_inner()
|
identifiers.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves and passes the default item identifiers for this toolbar.
|
/// Retrieves and passes the default item identifiers for this toolbar.
|
||||||
extern fn selectable_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
extern fn selectable_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||||
|
|
||||||
let identifiers: NSArray = toolbar.selectable_item_identifiers().iter().map(|identifier| {
|
let identifiers: NSArray = toolbar.selectable_item_identifiers()
|
||||||
NSString::new(identifier).into_inner()
|
.iter()
|
||||||
}).collect::<Vec<id>>().into();
|
.map(|identifier| identifier.to_nsstring())
|
||||||
|
.collect::<Vec<id>>()
|
||||||
|
.into();
|
||||||
|
|
||||||
identifiers.into_inner()
|
identifiers.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the controller, grabs whatever item is for this identifier, and returns what the
|
/// Loads the controller, grabs whatever item is for this identifier, and returns what the
|
||||||
/// Objective-C runtime needs.
|
/// Objective-C runtime needs.
|
||||||
extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
|
extern fn item_for_identifier<T: ToolbarDelegate>(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
_: id,
|
||||||
|
identifier: id,
|
||||||
|
_: BOOL
|
||||||
|
) -> id {
|
||||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||||
let identifier = NSString::wrap(identifier);
|
let identifier = NSString::from_retained(identifier);
|
||||||
|
|
||||||
let item = toolbar.item_for(identifier.to_str());
|
let item = toolbar.item_for(identifier.to_str());
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -64,9 +74,21 @@ pub(crate) fn register_toolbar_class<T: ToolbarDelegate>(instance: &T) -> *const
|
||||||
decl.add_ivar::<usize>(TOOLBAR_PTR);
|
decl.add_ivar::<usize>(TOOLBAR_PTR);
|
||||||
|
|
||||||
// Add callback methods
|
// Add callback methods
|
||||||
decl.add_method(sel!(toolbarAllowedItemIdentifiers:), allowed_item_identifiers::<T> as extern fn(&Object, _, _) -> id);
|
decl.add_method(
|
||||||
decl.add_method(sel!(toolbarDefaultItemIdentifiers:), default_item_identifiers::<T> as extern fn(&Object, _, _) -> id);
|
sel!(toolbarAllowedItemIdentifiers:),
|
||||||
decl.add_method(sel!(toolbarSelectableItemIdentifiers:), selectable_item_identifiers::<T> as extern fn(&Object, _, _) -> id);
|
allowed_item_identifiers::<T> as extern fn(&Object, _, _) -> id
|
||||||
decl.add_method(sel!(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:), item_for_identifier::<T> as extern fn(&Object, _, _, _, _) -> id);
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(toolbarDefaultItemIdentifiers:),
|
||||||
|
default_item_identifiers::<T> as extern fn(&Object, _, _) -> id
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(toolbarSelectableItemIdentifiers:),
|
||||||
|
selectable_item_identifiers::<T> as extern fn(&Object, _, _) -> id
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:),
|
||||||
|
item_for_identifier::<T> as extern fn(&Object, _, _, _, _) -> id
|
||||||
|
);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Various types used for Toolbar configuration.
|
//! Various types used for Toolbar configuration.
|
||||||
|
|
||||||
use crate::foundation::NSUInteger;
|
use crate::foundation::{id, NSString, NSUInteger};
|
||||||
|
|
||||||
/// Represents the display mode(s) a Toolbar can render in.
|
/// Represents the display mode(s) a Toolbar can render in.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
@ -29,6 +29,86 @@ impl From<ToolbarDisplayMode> for NSUInteger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an item identifier for items in a Toolbar.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ItemIdentifier {
|
||||||
|
/// Represents a custom item. Use this when you need to handle your own item types.
|
||||||
|
Custom(&'static str),
|
||||||
|
|
||||||
|
/// Represents a standard cloud-sharing icon. Available from 10.12 onwards.
|
||||||
|
CloudSharing,
|
||||||
|
|
||||||
|
/// A flexible space identifier. Fills space, flexibly.
|
||||||
|
FlexibleSpace,
|
||||||
|
|
||||||
|
/// A standard print toolbar item. Will send the necessary print calls to the first responder.
|
||||||
|
Print,
|
||||||
|
|
||||||
|
/// A standard identifier for showing the colors panel.
|
||||||
|
Colors,
|
||||||
|
|
||||||
|
/// A standard identifier for showing the fonts panel.
|
||||||
|
Fonts,
|
||||||
|
|
||||||
|
/// A standard identifier for showing blank space.
|
||||||
|
Space,
|
||||||
|
|
||||||
|
/// Standard toolbar item identifier for a sidebar. Will handle automatically hiding and
|
||||||
|
/// showing a SplitViewController sidebar if it's the window content view controller and the
|
||||||
|
/// first responder.
|
||||||
|
///
|
||||||
|
/// Note that this API was introduced in Big Sur (11.0), and you may need to check against this
|
||||||
|
/// at runtime to ensure behavior is appropriate on older OS versions (if you support them).
|
||||||
|
ToggleSidebar,
|
||||||
|
|
||||||
|
/// Standard toolbar item for a spot that tracks the sidebar border. In your delegate, use this
|
||||||
|
/// to indicate what items should be on the side of the sidebar and content.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
///
|
||||||
|
/// ``` rust
|
||||||
|
/// vec![ItemIdentifier::ToggleSidebar, ItemIdentifier::SidebarTracker, ItemIdentifier::Print]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Would result in the toggle sidebar item showing up in the sidebar on the left, and the
|
||||||
|
/// print item showing up in the content area on the right.
|
||||||
|
///
|
||||||
|
/// Note that this API was introduced in Big Sur (11.0), and you may need to check against this
|
||||||
|
/// at runtime to ensure behavior is appropriate on older OS versions (if you support them).
|
||||||
|
///
|
||||||
|
SidebarTracker
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
static NSToolbarToggleSidebarItemIdentifier: id;
|
||||||
|
static NSToolbarCloudSharingItemIdentifier: id;
|
||||||
|
static NSToolbarFlexibleSpaceItemIdentifier: id;
|
||||||
|
static NSToolbarPrintItemIdentifier: id;
|
||||||
|
static NSToolbarShowColorsItemIdentifier: id;
|
||||||
|
static NSToolbarShowFontsItemIdentifier: id;
|
||||||
|
static NSToolbarSpaceItemIdentifier: id;
|
||||||
|
static NSToolbarSidebarTrackingSeparatorItemIdentifier: id;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemIdentifier {
|
||||||
|
/// Returns the NSString necessary for the toolbar to operate.
|
||||||
|
pub(crate) fn to_nsstring(&self) -> id {
|
||||||
|
unsafe {
|
||||||
|
match self {
|
||||||
|
Self::Custom(s) => NSString::new(s).into(),
|
||||||
|
Self::CloudSharing => NSToolbarCloudSharingItemIdentifier,
|
||||||
|
Self::FlexibleSpace => NSToolbarFlexibleSpaceItemIdentifier,
|
||||||
|
Self::Print => NSToolbarPrintItemIdentifier,
|
||||||
|
Self::Colors => NSToolbarShowColorsItemIdentifier,
|
||||||
|
Self::Fonts => NSToolbarShowFontsItemIdentifier,
|
||||||
|
Self::Space => NSToolbarSpaceItemIdentifier,
|
||||||
|
Self::ToggleSidebar => NSToolbarToggleSidebarItemIdentifier,
|
||||||
|
Self::SidebarTracker => NSToolbarSidebarTrackingSeparatorItemIdentifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents the size mode a Toolbar can use.
|
/// Represents the size mode a Toolbar can use.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum ToolbarSizeMode {
|
pub enum ToolbarSizeMode {
|
||||||
|
|
|
@ -7,10 +7,10 @@ use std::fmt;
|
||||||
use core_graphics::geometry::CGSize;
|
use core_graphics::geometry::CGSize;
|
||||||
|
|
||||||
use objc_id::{Id, ShareId};
|
use objc_id::{Id, ShareId};
|
||||||
use objc::runtime::{Object};
|
use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, NSString};
|
use crate::foundation::{id, YES, NO, NSString};
|
||||||
use crate::invoker::TargetActionHandler;
|
use crate::invoker::TargetActionHandler;
|
||||||
use crate::button::{Button, BezelStyle};
|
use crate::button::{Button, BezelStyle};
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
|
@ -39,8 +39,18 @@ impl ToolbarItem {
|
||||||
};
|
};
|
||||||
|
|
||||||
ToolbarItem {
|
ToolbarItem {
|
||||||
identifier: identifier,
|
identifier,
|
||||||
objc: objc,
|
objc,
|
||||||
|
button: None,
|
||||||
|
image: None,
|
||||||
|
handler: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wrap(item: id) -> Self {
|
||||||
|
ToolbarItem {
|
||||||
|
identifier: "".to_string(),
|
||||||
|
objc: unsafe { Id::from_retained_ptr(item) },
|
||||||
button: None,
|
button: None,
|
||||||
image: None,
|
image: None,
|
||||||
handler: None
|
handler: None
|
||||||
|
@ -50,7 +60,7 @@ impl ToolbarItem {
|
||||||
/// Sets the title for this item.
|
/// Sets the title for this item.
|
||||||
pub fn set_title(&mut self, title: &str) {
|
pub fn set_title(&mut self, title: &str) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let title = NSString::new(title).into_inner();
|
let title = NSString::new(title);
|
||||||
let _: () = msg_send![&*self.objc, setLabel:&*title];
|
let _: () = msg_send![&*self.objc, setLabel:&*title];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,8 +101,18 @@ impl ToolbarItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an action on this item.
|
||||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||||
let handler = TargetActionHandler::new(&*self.objc, action);
|
let handler = TargetActionHandler::new(&*self.objc, action);
|
||||||
self.handler = Some(handler);
|
self.handler = Some(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_bordered(&self, bordered: bool) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setBordered:match bordered {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ mod traits;
|
||||||
pub use traits::ToolbarDelegate;
|
pub use traits::ToolbarDelegate;
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode};
|
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode, ItemIdentifier};
|
||||||
|
|
||||||
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
||||||
|
|
||||||
|
@ -67,9 +67,9 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
|
||||||
});
|
});
|
||||||
|
|
||||||
Toolbar {
|
Toolbar {
|
||||||
identifier: identifier,
|
identifier,
|
||||||
objc: objc,
|
objc,
|
||||||
objc_delegate: objc_delegate,
|
objc_delegate,
|
||||||
delegate: Some(delegate),
|
delegate: Some(delegate),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,10 +117,10 @@ impl<T> Toolbar<T> {
|
||||||
|
|
||||||
/// Sets the item represented by the item identifier to be selected.
|
/// Sets the item represented by the item identifier to be selected.
|
||||||
pub fn set_selected(&self, item_identifier: &str) {
|
pub fn set_selected(&self, item_identifier: &str) {
|
||||||
let identifier = NSString::new(item_identifier).into_inner();
|
let identifier = NSString::new(item_identifier);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setSelectedItemIdentifier:identifier];
|
let _: () = msg_send![&*self.objc, setSelectedItemIdentifier:&*identifier];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
|
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
|
||||||
//! to pull request it.
|
//! to pull request it.
|
||||||
|
|
||||||
use crate::macos::toolbar::{Toolbar, ToolbarItem};
|
use crate::macos::toolbar::{Toolbar, ToolbarItem, ItemIdentifier};
|
||||||
|
|
||||||
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
|
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
|
||||||
pub trait ToolbarDelegate {
|
pub trait ToolbarDelegate {
|
||||||
|
@ -23,14 +23,14 @@ pub trait ToolbarDelegate {
|
||||||
fn did_load(&mut self, _toolbar: Toolbar) {}
|
fn did_load(&mut self, _toolbar: Toolbar) {}
|
||||||
|
|
||||||
/// What items are allowed in this toolbar.
|
/// What items are allowed in this toolbar.
|
||||||
fn allowed_item_identifiers(&self) -> Vec<&'static str>;
|
fn allowed_item_identifiers(&self) -> Vec<ItemIdentifier>;
|
||||||
|
|
||||||
/// The default items in this toolbar.
|
/// The default items in this toolbar.
|
||||||
fn default_item_identifiers(&self) -> Vec<&'static str>;
|
fn default_item_identifiers(&self) -> Vec<ItemIdentifier>;
|
||||||
|
|
||||||
/// The default items in this toolbar. This defaults to a blank `Vec`, and is an optional
|
/// The default items in this toolbar. This defaults to a blank `Vec`, and is an optional
|
||||||
/// method - mostly useful for Preferences windows.
|
/// method - mostly useful for Preferences windows.
|
||||||
fn selectable_item_identifiers(&self) -> Vec<&'static str> { vec![] }
|
fn selectable_item_identifiers(&self) -> Vec<ItemIdentifier> { vec![] }
|
||||||
|
|
||||||
/// For a given `identifier`, return the `ToolbarItem` that should be displayed.
|
/// For a given `identifier`, return the `ToolbarItem` that should be displayed.
|
||||||
fn item_for(&self, _identifier: &str) -> &ToolbarItem;
|
fn item_for(&self, _identifier: &str) -> &ToolbarItem;
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl Default for WindowConfig {
|
||||||
|
|
||||||
config.set_styles(&[
|
config.set_styles(&[
|
||||||
WindowStyle::Resizable, WindowStyle::Miniaturizable, WindowStyle::UnifiedTitleAndToolbar,
|
WindowStyle::Resizable, WindowStyle::Miniaturizable, WindowStyle::UnifiedTitleAndToolbar,
|
||||||
WindowStyle::Closable, WindowStyle::Titled
|
WindowStyle::Closable, WindowStyle::Titled, WindowStyle::FullSizeContentView
|
||||||
]);
|
]);
|
||||||
|
|
||||||
config
|
config
|
||||||
|
|
|
@ -229,6 +229,14 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the content size for this window.
|
||||||
|
pub fn set_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||||
|
unsafe {
|
||||||
|
let size = CGSize::new(width.into(), height.into());
|
||||||
|
let _: () = msg_send![&*self.objc, setContentSize:size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the minimum size this window can shrink to.
|
/// Sets the minimum size this window can shrink to.
|
||||||
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -245,6 +253,14 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the minimum size this window can shrink to.
|
||||||
|
pub fn set_minimum_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||||
|
unsafe {
|
||||||
|
let size = CGSize::new(width.into(), height.into());
|
||||||
|
let _: () = msg_send![&*self.objc, setMinSize:size];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Used for setting a toolbar on this window.
|
/// Used for setting a toolbar on this window.
|
||||||
pub fn set_toolbar<TC: ToolbarDelegate>(&self, toolbar: &Toolbar<TC>) {
|
pub fn set_toolbar<TC: ToolbarDelegate>(&self, toolbar: &Toolbar<TC>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -430,6 +446,12 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the backing scale (e.g, `1.0` for non retina, `2.0` for retina) used on this
|
/// Returns the backing scale (e.g, `1.0` for non retina, `2.0` for retina) used on this
|
||||||
/// window.
|
/// window.
|
||||||
///
|
///
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl URLRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn url(&self) -> String {
|
pub fn url(&self) -> String {
|
||||||
NSString::wrap(unsafe {
|
NSString::from_retained(unsafe {
|
||||||
let url: id = msg_send![&*self.inner, URL];
|
let url: id = msg_send![&*self.inner, URL];
|
||||||
msg_send![url, absoluteString]
|
msg_send![url, absoluteString]
|
||||||
}).to_string()
|
}).to_string()
|
||||||
|
|
|
@ -1314,10 +1314,10 @@ pub enum NotificationName {
|
||||||
WKAccessibilityReduceMotionStatusDidChange
|
WKAccessibilityReduceMotionStatusDidChange
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<NotificationName> for NSString {
|
impl From<NotificationName> for NSString<'_> {
|
||||||
fn from(name: NotificationName) -> Self {
|
fn from(name: NotificationName) -> Self {
|
||||||
match name {
|
match name {
|
||||||
_ => NSString::new("")
|
_ => NSString::no_copy("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl Pasteboard {
|
||||||
pub fn named(name: PasteboardName) -> Self {
|
pub fn named(name: PasteboardName) -> Self {
|
||||||
Pasteboard(unsafe {
|
Pasteboard(unsafe {
|
||||||
let name: NSString = name.into();
|
let name: NSString = name.into();
|
||||||
ShareId::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithName:&*name.0])
|
ShareId::from_ptr(msg_send![class!(NSPasteboard), pasteboardWithName:&*name])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,16 @@ impl Pasteboard {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A shorthand helper method for copying some text to the clipboard.
|
||||||
|
pub fn copy_text(&self, text: &str) {
|
||||||
|
let contents = NSString::new(text);
|
||||||
|
let ptype: NSString = PasteboardType::String.into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, setString:&*contents forType:ptype];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Releases the receiver’s resources in the pasteboard server. It's rare-ish to need to use
|
/// Releases the receiver’s resources in the pasteboard server. It's rare-ish to need to use
|
||||||
/// this, but considering this stuff happens on the Objective-C side you may need it.
|
/// this, but considering this stuff happens on the Objective-C side you may need it.
|
||||||
pub fn release_globally(&self) {
|
pub fn release_globally(&self) {
|
||||||
|
@ -85,8 +95,8 @@ impl Pasteboard {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let urls = NSArray::wrap(contents).map(|url| {
|
let urls = NSArray::retain(contents).map(|url| {
|
||||||
let path = NSString::wrap(msg_send![url, path]);
|
let path = NSString::retain(msg_send![url, path]);
|
||||||
Url::parse(&format!("file://{}", path.to_str()))
|
Url::parse(&format!("file://{}", path.to_str()))
|
||||||
}).into_iter().filter_map(|r| r.ok()).collect();
|
}).into_iter().filter_map(|r| r.ok()).collect();
|
||||||
|
|
||||||
|
@ -114,8 +124,8 @@ impl Pasteboard {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let urls = NSArray::wrap(contents).map(|url| {
|
let urls = NSArray::retain(contents).map(|url| {
|
||||||
let path = NSString::wrap(msg_send![url, path]).to_str().to_string();
|
let path = NSString::retain(msg_send![url, path]).to_str().to_string();
|
||||||
PathBuf::from(path)
|
PathBuf::from(path)
|
||||||
}).into_iter().collect();
|
}).into_iter().collect();
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub enum PasteboardName {
|
||||||
Ruler
|
Ruler
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PasteboardName> for NSString {
|
impl From<PasteboardName> for NSString<'_> {
|
||||||
fn from(name: PasteboardName) -> Self {
|
fn from(name: PasteboardName) -> Self {
|
||||||
NSString::new(match name {
|
NSString::new(match name {
|
||||||
PasteboardName::Drag => "Apple CFPasteboard drag",
|
PasteboardName::Drag => "Apple CFPasteboard drag",
|
||||||
|
@ -83,7 +83,7 @@ pub enum PasteboardType {
|
||||||
TIFF
|
TIFF
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PasteboardType> for NSString {
|
impl From<PasteboardType> for NSString<'_> {
|
||||||
fn from(pboard_type: PasteboardType) -> Self {
|
fn from(pboard_type: PasteboardType) -> Self {
|
||||||
NSString::new(match pboard_type {
|
NSString::new(match pboard_type {
|
||||||
PasteboardType::URL => "public.url",
|
PasteboardType::URL => "public.url",
|
||||||
|
|
|
@ -109,7 +109,7 @@ impl ThumbnailConfig {
|
||||||
unsafe {
|
unsafe {
|
||||||
let size = CGSize::new(self.size.0, self.size.1);
|
let size = CGSize::new(self.size.0, self.size.1);
|
||||||
// @TODO: Check nil here, or other bad conversion
|
// @TODO: Check nil here, or other bad conversion
|
||||||
let from_url: id = msg_send![class!(NSURL), fileURLWithPath:file.into_inner()];
|
let from_url: id = msg_send![class!(NSURL), fileURLWithPath:&*file];
|
||||||
|
|
||||||
let request: id = msg_send![class!(QLThumbnailGenerationRequest), alloc];
|
let request: id = msg_send![class!(QLThumbnailGenerationRequest), alloc];
|
||||||
let request: id = msg_send![request, initWithFileAtURL:from_url
|
let request: id = msg_send![request, initWithFileAtURL:from_url
|
||||||
|
|
|
@ -19,7 +19,7 @@ impl Default for Font {
|
||||||
objc: unsafe {
|
objc: unsafe {
|
||||||
let cls = class!(NSFont);
|
let cls = class!(NSFont);
|
||||||
let default_size: id = msg_send![cls, labelFontSize];
|
let default_size: id = msg_send![cls, labelFontSize];
|
||||||
msg_send![cls, labelFontOfSize:default_size]
|
ShareId::from_ptr(msg_send![cls, labelFontOfSize:default_size])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,15 @@ impl Font {
|
||||||
pub fn system(size: CGFloat) -> Self {
|
pub fn system(size: CGFloat) -> Self {
|
||||||
Font {
|
Font {
|
||||||
objc: unsafe {
|
objc: unsafe {
|
||||||
msg_send![class!(NSFont), systemFontOfSize:size]
|
ShareId::from_ptr(msg_send![class!(NSFont), systemFontOfSize:size])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bold_system(size: CGFloat) -> Self {
|
||||||
|
Font {
|
||||||
|
objc: unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSFont), boldSystemFontOfSize:size])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,6 @@ mod ios;
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
use ios::{register_view_class, register_view_class_with_delegate};
|
use ios::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
//mod controller;
|
|
||||||
//pub use controller::LabelController;
|
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::LabelDelegate;
|
pub use traits::LabelDelegate;
|
||||||
|
|
||||||
|
@ -75,8 +72,8 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let view: id = {
|
let view: id = {
|
||||||
// This sucks, but for now, sure.
|
// This sucks, but for now, sure.
|
||||||
let blank = NSString::new("");
|
let blank = NSString::no_copy("");
|
||||||
msg_send![registration_fn(), labelWithString:blank.into_inner()]
|
msg_send![registration_fn(), labelWithString:&*blank]
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
|
@ -230,17 +227,17 @@ impl<T> Label<T> {
|
||||||
let s = NSString::new(text);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setStringValue:s.into_inner()];
|
let _: () = msg_send![&*self.objc, setStringValue:&*s];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the text currently held in the label.
|
/// Retrieve the text currently held in the label.
|
||||||
pub fn text(&self) -> String {
|
pub fn get_text(&self) -> String {
|
||||||
let s = NSString::wrap(unsafe {
|
let s = NSString::retain(unsafe {
|
||||||
msg_send![&*self.objc, stringValue]
|
msg_send![&*self.objc, stringValue]
|
||||||
});
|
});
|
||||||
|
|
||||||
s.to_str().to_string()
|
s.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||||
|
@ -256,6 +253,15 @@ impl<T> Label<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_hidden(&self, hidden: bool) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setHidden:match hidden {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
|
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -68,6 +68,9 @@ use ios::{register_view_class, register_view_class_with_delegate};
|
||||||
mod controller;
|
mod controller;
|
||||||
pub use controller::ViewController;
|
pub use controller::ViewController;
|
||||||
|
|
||||||
|
mod splitviewcontroller;
|
||||||
|
pub use splitviewcontroller::SplitViewController;
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::ViewDelegate;
|
pub use traits::ViewDelegate;
|
||||||
|
|
||||||
|
@ -199,7 +202,7 @@ impl<T> View<T> {
|
||||||
height: self.height.clone(),
|
height: self.height.clone(),
|
||||||
center_x: self.center_x.clone(),
|
center_x: self.center_x.clone(),
|
||||||
center_y: self.center_y.clone(),
|
center_y: self.center_y.clone(),
|
||||||
objc: Rc::clone(&self.objc) //.clone()
|
objc: Rc::clone(&self.objc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,27 +213,18 @@ impl<T> View<T> {
|
||||||
unsafe {
|
unsafe {
|
||||||
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc());
|
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc());
|
||||||
}
|
}
|
||||||
/*let bg = color.as_ref().into_platform_specific_color();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let cg: id = msg_send![bg, CGColor];
|
|
||||||
let layer: id = msg_send![&*self.objc, layer];
|
|
||||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register this view for drag and drop operations.
|
/// Register this view for drag and drop operations.
|
||||||
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let types: NSArray = types.into_iter().map(|t| {
|
let types: NSArray = types.into_iter().map(|t| {
|
||||||
// This clone probably doesn't need to be here, but it should also be cheap as
|
let x: NSString = (*t).into();
|
||||||
// this is just an enum... and this is not an oft called method.
|
x.into()
|
||||||
let x: NSString = t.clone().into();
|
|
||||||
x.into_inner()
|
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
let objc = self.objc.borrow();
|
let objc = self.objc.borrow();
|
||||||
let _: () = msg_send![&**objc, registerForDraggedTypes:types.into_inner()];
|
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
72
src/view/splitviewcontroller/ios.rs
Normal file
72
src/view/splitviewcontroller/ios.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::sync::Once;
|
||||||
|
use std::unreachable;
|
||||||
|
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::{BOOL};
|
||||||
|
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
||||||
|
use crate::utils::{load, as_bool};
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewWillAppear:` message.
|
||||||
|
extern fn will_appear<T: ViewDelegate>(this: &mut Object, _: Sel, animated: BOOL) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(UIViewController)), viewWillAppear:animated];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.will_appear(as_bool(animated));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewDidAppear:` message.
|
||||||
|
extern fn did_appear<T: ViewDelegate>(this: &mut Object, _: Sel, animated: BOOL) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(UIViewController)), viewDidAppear:animated];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.did_appear(as_bool(animated));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewWillDisappear:` message.
|
||||||
|
extern fn will_disappear<T: ViewDelegate>(this: &mut Object, _: Sel, animated: BOOL) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(UIViewController)), viewWillDisappear:animated];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.will_disappear(as_bool(animated));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewDidDisappear:` message.
|
||||||
|
extern fn did_disappear<T: ViewDelegate>(this: &mut Object, _: Sel, animated: BOOL) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(UIViewController)), viewDidDisappear:animated];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.did_disappear(as_bool(animated));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers an `NSViewDelegate`.
|
||||||
|
pub(crate) fn register_view_controller_class<T: ViewDelegate + 'static>() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UIViewController);
|
||||||
|
let mut decl = ClassDecl::new("RSTViewController", superclass).unwrap();
|
||||||
|
|
||||||
|
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
decl.add_method(sel!(viewWillAppear:), will_appear::<T> as extern fn(&mut Object, _, BOOL));
|
||||||
|
decl.add_method(sel!(viewDidAppear:), did_appear::<T> as extern fn(&mut Object, _, BOOL));
|
||||||
|
decl.add_method(sel!(viewWillDisappear:), will_disappear::<T> as extern fn(&mut Object, _, BOOL));
|
||||||
|
decl.add_method(sel!(viewDidDisappear:), did_disappear::<T> as extern fn(&mut Object, _, BOOL));
|
||||||
|
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
63
src/view/splitviewcontroller/macos.rs
Normal file
63
src/view/splitviewcontroller/macos.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//! Hoists a basic `NSViewController`.
|
||||||
|
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::load_or_register_class;
|
||||||
|
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
||||||
|
use crate::utils::load;
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewWillAppear` message.
|
||||||
|
extern fn will_appear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(NSViewController)), viewWillAppear];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.will_appear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewDidAppear` message.
|
||||||
|
extern fn did_appear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(NSViewController)), viewDidAppear];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.did_appear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewWillDisappear` message.
|
||||||
|
extern fn will_disappear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(NSViewController)), viewWillDisappear];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.will_disappear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when the view controller receives a `viewDidDisappear` message.
|
||||||
|
extern fn did_disappear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![super(this, class!(NSViewController)), viewDidDisappear];
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
controller.did_disappear(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers an `NSViewDelegate`.
|
||||||
|
pub(crate) fn register_view_controller_class<T: ViewDelegate + 'static>(instance: &T) -> *const Class {
|
||||||
|
load_or_register_class("NSViewController", instance.subclass_name(), |decl| unsafe {
|
||||||
|
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
decl.add_method(sel!(viewWillAppear), will_appear::<T> as extern fn(&mut Object, _));
|
||||||
|
decl.add_method(sel!(viewDidAppear), did_appear::<T> as extern fn(&mut Object, _));
|
||||||
|
decl.add_method(sel!(viewWillDisappear), will_disappear::<T> as extern fn(&mut Object, _));
|
||||||
|
decl.add_method(sel!(viewDidDisappear), did_disappear::<T> as extern fn(&mut Object, _));
|
||||||
|
})
|
||||||
|
}
|
123
src/view/splitviewcontroller/mod.rs
Normal file
123
src/view/splitviewcontroller/mod.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
use objc_id::ShareId;
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::{id, nil, NSString};
|
||||||
|
use crate::layout::{Layout};
|
||||||
|
use crate::macos::toolbar::ToolbarItem;
|
||||||
|
use crate::view::{View, ViewController, ViewDelegate};
|
||||||
|
use crate::utils::Controller;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SplitViewItem<T> {
|
||||||
|
pub objc: ShareId<Object>,
|
||||||
|
pub view_controller: ViewController<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> SplitViewItem<T>
|
||||||
|
where
|
||||||
|
T: ViewDelegate + 'static
|
||||||
|
{
|
||||||
|
pub fn item(view: T) -> Self {
|
||||||
|
let view_controller = ViewController::new(view);
|
||||||
|
|
||||||
|
let objc = unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), splitViewItemWithViewController:&*view_controller.objc])
|
||||||
|
};
|
||||||
|
|
||||||
|
SplitViewItem {
|
||||||
|
objc,
|
||||||
|
view_controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sidebar(view: T) -> Self {
|
||||||
|
let view_controller = ViewController::new(view);
|
||||||
|
|
||||||
|
let objc = unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), sidebarWithViewController:&*view_controller.objc])
|
||||||
|
};
|
||||||
|
|
||||||
|
SplitViewItem {
|
||||||
|
objc,
|
||||||
|
view_controller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SplitViewController<Sidebar, Content> {
|
||||||
|
pub objc: ShareId<Object>,
|
||||||
|
pub sidebar: SplitViewItem<Sidebar>,
|
||||||
|
pub content: SplitViewItem<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Sidebar, Content> SplitViewController<Sidebar, Content>
|
||||||
|
where
|
||||||
|
Sidebar: ViewDelegate + 'static,
|
||||||
|
Content: ViewDelegate + 'static
|
||||||
|
{
|
||||||
|
pub fn new(sidebar: Sidebar, content: Content) -> Self {
|
||||||
|
let sidebar = SplitViewItem::sidebar(sidebar);
|
||||||
|
let content = SplitViewItem::item(content);
|
||||||
|
|
||||||
|
let objc = unsafe {
|
||||||
|
let vc: id = msg_send![class!(NSSplitViewController), new];
|
||||||
|
let _: () = msg_send![vc, addSplitViewItem:&*sidebar.objc];
|
||||||
|
let _: () = msg_send![vc, addSplitViewItem:&*content.objc];
|
||||||
|
ShareId::from_ptr(vc)
|
||||||
|
};
|
||||||
|
|
||||||
|
SplitViewController { objc, sidebar, content }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Toggles the sidebar, if it exists, with an animation. If there's no sidebar in this split view
|
||||||
|
/// (which is highly unlikely, unless you went out of your way to duck this) then it will do
|
||||||
|
/// nothing.
|
||||||
|
pub fn toggle_sidebar(&self) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, toggleSidebar:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_autosave_name(&self, name: &str) {
|
||||||
|
let name = NSString::new(name);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let split_view: id = msg_send![&*self.objc, splitView];
|
||||||
|
let _: () = msg_send![split_view, setAutosaveName:&*name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*/// This method can be used to acquire an item for Toolbar instances that tracks a specified
|
||||||
|
/// divider (`divider_index`) of this split view. This method is only supported on macOS 11.0+;
|
||||||
|
/// it will return `None` on 10.15 and below.
|
||||||
|
///
|
||||||
|
/// You should call this and pass + store the item in your Toolbar, and vend it to the system
|
||||||
|
/// with your `ToolbarDelegate`.
|
||||||
|
pub fn tracking_separator_toolbar_item(&self, divider_index: usize) -> Option<ToolbarItem> {
|
||||||
|
if crate::utils::os::is_minimum_version(11) {
|
||||||
|
unsafe {
|
||||||
|
let split_view: id = msg_send![&*self.objc, splitView];
|
||||||
|
let item: id = msg_send![class!(NSTrackingSeparatorToolbarItem), trackingSeparatorToolbarItemWithIdentifier:
|
||||||
|
splitView:split_view
|
||||||
|
dividerIndex:divider_index as NSInteger
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Sidebar, Content> Controller for SplitViewController<Sidebar, Content> {
|
||||||
|
fn get_backing_node(&self) -> ShareId<Object> {
|
||||||
|
self.objc.clone()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue