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"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
default-target = "x86_64-apple-darwin"
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
block = "0.1.6"
|
||||
|
@ -37,4 +39,4 @@ color_fallbacks = []
|
|||
quicklook = []
|
||||
user-notifications = ["uuid"]
|
||||
webview = []
|
||||
webview-downloading = []
|
||||
webview-downloading-macos = []
|
||||
|
|
|
@ -51,16 +51,16 @@ impl WindowDelegate for AppWindow {
|
|||
window.set_content_view(&self.content);
|
||||
|
||||
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.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
|
||||
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.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.trailing.constraint_equal_to(&self.content.trailing).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::macos::App;
|
||||
use cacao::notification_center::Dispatcher;
|
||||
|
||||
use crate::CalculatorApp;
|
||||
|
||||
|
@ -24,7 +23,6 @@ pub enum Msg {
|
|||
/// on the main thread.
|
||||
pub fn dispatch(msg: Msg) {
|
||||
println!("Dispatching UI message: {:?}", msg);
|
||||
//App::<CalculatorApp, Msg>::dispatch_main(msg)
|
||||
CALCULATOR.run(msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ impl ViewDelegate for CalculatorView {
|
|||
self.results_wrapper.top.constraint_equal_to(&view.top),
|
||||
self.results_wrapper.leading.constraint_equal_to(&view.leading),
|
||||
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.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::color::Color;
|
||||
use cacao::notification_center::Dispatcher;
|
||||
use cacao::view::{View, ViewDelegate};
|
||||
use cacao::view::View;
|
||||
|
||||
mod button_row;
|
||||
mod calculator;
|
||||
use calculator::{dispatch, Msg};
|
||||
|
||||
mod content_view;
|
||||
use content_view::CalculatorView;
|
||||
|
@ -38,7 +37,7 @@ impl AppDelegate for CalculatorApp {
|
|||
// 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
|
||||
// &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_background_color(Color::rgb(49,49,49));
|
||||
|
@ -70,7 +69,8 @@ impl CalculatorApp {
|
|||
let characters = evt.characters();
|
||||
println!("{}", characters);
|
||||
|
||||
match characters.as_ref() {
|
||||
//use calculator::{dispatch, Msg};
|
||||
/*match characters.as_ref() {
|
||||
"0" => dispatch(Msg::Push(0)),
|
||||
"1" => dispatch(Msg::Push(1)),
|
||||
"2" => dispatch(Msg::Push(2)),
|
||||
|
@ -90,7 +90,7 @@ impl CalculatorApp {
|
|||
"c" => dispatch(Msg::Clear),
|
||||
"." => dispatch(Msg::Decimal),
|
||||
_ => {}
|
||||
}
|
||||
}*/
|
||||
|
||||
None
|
||||
}));
|
||||
|
|
|
@ -12,58 +12,58 @@ use crate::storage::{dispatch_ui, Message};
|
|||
pub fn menu() -> Vec<Menu> {
|
||||
vec![
|
||||
Menu::new("", vec![
|
||||
MenuItem::about("Todos"),
|
||||
MenuItem::About("Todos".to_string()),
|
||||
MenuItem::Separator,
|
||||
|
||||
MenuItem::entry("Preferences").key(",").action(|| {
|
||||
MenuItem::new("Preferences").key(",").action(|| {
|
||||
dispatch_ui(Message::OpenPreferencesWindow);
|
||||
}),
|
||||
|
||||
MenuItem::Separator,
|
||||
MenuItem::services(),
|
||||
MenuItem::Services,
|
||||
MenuItem::Separator,
|
||||
MenuItem::hide(),
|
||||
MenuItem::hide_others(),
|
||||
MenuItem::show_all(),
|
||||
MenuItem::Hide,
|
||||
MenuItem::HideOthers,
|
||||
MenuItem::ShowAll,
|
||||
MenuItem::Separator,
|
||||
MenuItem::quit()
|
||||
MenuItem::Quit
|
||||
]),
|
||||
|
||||
Menu::new("File", vec![
|
||||
MenuItem::entry("Open/Show Window").key("n").action(|| {
|
||||
MenuItem::new("Open/Show Window").key("n").action(|| {
|
||||
dispatch_ui(Message::OpenMainWindow);
|
||||
}),
|
||||
|
||||
MenuItem::Separator,
|
||||
|
||||
MenuItem::entry("Add Todo").key("+").action(|| {
|
||||
MenuItem::new("Add Todo").key("+").action(|| {
|
||||
dispatch_ui(Message::OpenNewTodoSheet);
|
||||
}),
|
||||
|
||||
MenuItem::Separator,
|
||||
MenuItem::close_window(),
|
||||
MenuItem::CloseWindow
|
||||
]),
|
||||
|
||||
Menu::new("Edit", vec![
|
||||
MenuItem::undo(),
|
||||
MenuItem::redo(),
|
||||
MenuItem::Undo,
|
||||
MenuItem::Redo,
|
||||
MenuItem::Separator,
|
||||
MenuItem::cut(),
|
||||
MenuItem::copy(),
|
||||
MenuItem::paste(),
|
||||
MenuItem::Cut,
|
||||
MenuItem::Copy,
|
||||
MenuItem::Paste,
|
||||
MenuItem::Separator,
|
||||
MenuItem::select_all()
|
||||
MenuItem::SelectAll
|
||||
]),
|
||||
|
||||
Menu::new("View", vec![
|
||||
MenuItem::enter_full_screen()
|
||||
MenuItem::EnterFullScreen
|
||||
]),
|
||||
|
||||
Menu::new("Window", vec![
|
||||
MenuItem::minimize(),
|
||||
MenuItem::zoom(),
|
||||
MenuItem::Minimize,
|
||||
MenuItem::Zoom,
|
||||
MenuItem::Separator,
|
||||
MenuItem::entry("Bring All to Front")
|
||||
MenuItem::new("Bring All to Front")
|
||||
]),
|
||||
|
||||
Menu::new("Help", vec![])
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Implements an example toolbar for a Preferences app. Could be cleaner, probably worth cleaning
|
||||
//! 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 crate::storage::{dispatch_ui, Message};
|
||||
|
@ -46,16 +46,16 @@ impl ToolbarDelegate for PreferencesToolbar {
|
|||
toolbar.set_selected("general");
|
||||
}
|
||||
|
||||
fn allowed_item_identifiers(&self) -> Vec<&'static str> {
|
||||
vec!["general", "advanced"]
|
||||
fn allowed_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||
}
|
||||
|
||||
fn default_item_identifiers(&self) -> Vec<&'static str> {
|
||||
vec!["general", "advanced"]
|
||||
fn default_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||
}
|
||||
|
||||
fn selectable_item_identifiers(&self) -> Vec<&'static str> {
|
||||
vec!["general", "advanced"]
|
||||
fn selectable_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||
vec![ItemIdentifier::Custom("general"), ItemIdentifier::Custom("advanced")]
|
||||
}
|
||||
|
||||
fn item_for(&self, identifier: &str) -> &ToolbarItem {
|
||||
|
|
|
@ -48,7 +48,7 @@ impl TodosListView {
|
|||
self.view.as_ref().unwrap().perform_batch_updates(|listview| {
|
||||
// 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.
|
||||
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.
|
||||
|
||||
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};
|
||||
|
||||
|
@ -31,12 +34,12 @@ impl ToolbarDelegate for TodosToolbar {
|
|||
toolbar.set_display_mode(ToolbarDisplayMode::IconOnly);
|
||||
}
|
||||
|
||||
fn allowed_item_identifiers(&self) -> Vec<&'static str> {
|
||||
vec!["AddTodoButton"]
|
||||
fn allowed_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||
vec![ItemIdentifier::Custom("AddTodoButton")]
|
||||
}
|
||||
|
||||
fn default_item_identifiers(&self) -> Vec<&'static str> {
|
||||
vec!["AddTodoButton"]
|
||||
fn default_item_identifiers(&self) -> Vec<ItemIdentifier> {
|
||||
vec![ItemIdentifier::Custom("AddTodoButton")]
|
||||
}
|
||||
|
||||
// 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 crate::color::Color;
|
||||
use crate::image::Image;
|
||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
|
@ -28,6 +29,7 @@ extern "C" {
|
|||
#[derive(Debug)]
|
||||
pub struct Button {
|
||||
pub objc: ShareId<Object>,
|
||||
pub image: Option<Image>,
|
||||
handler: Option<TargetActionHandler>,
|
||||
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
|
@ -62,7 +64,7 @@ impl Button {
|
|||
let title = NSString::new(text);
|
||||
|
||||
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, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
button
|
||||
|
@ -70,6 +72,7 @@ impl Button {
|
|||
|
||||
Button {
|
||||
handler: None,
|
||||
image: None,
|
||||
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
||||
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
||||
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.
|
||||
#[cfg(feature = "macos")]
|
||||
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
||||
|
@ -115,10 +126,10 @@ impl Button {
|
|||
}
|
||||
|
||||
pub fn set_key_equivalent(&self, key: &str) {
|
||||
let key = NSString::new(key).into_inner();
|
||||
let key = NSString::new(key);
|
||||
|
||||
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
|
||||
//! backwards compatibility for Mojave, as that's still a supported OS.
|
||||
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::Once;
|
||||
|
||||
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_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
||||
|
||||
use std::os::raw::c_void;
|
||||
|
||||
extern "C" {
|
||||
static NSAppearanceNameAqua: id;
|
||||
|
@ -56,7 +56,7 @@ fn get_effective_color(this: &Object) -> id {
|
|||
NSAppearanceNameAccessibilityHighContrastDarkAqua
|
||||
]);
|
||||
|
||||
let style: id = msg_send![appearance, bestMatchFromAppearancesWithNames:names.into_inner()];
|
||||
let style: id = msg_send![appearance, bestMatchFromAppearancesWithNames:&*names];
|
||||
|
||||
if style == NSAppearanceNameDarkAqua {
|
||||
return *this.get_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST);
|
||||
|
|
|
@ -242,7 +242,11 @@ pub enum Color {
|
|||
DarkText,
|
||||
|
||||
/// The un-adaptable color for text on a dark background.
|
||||
LightText
|
||||
LightText,
|
||||
|
||||
WindowBackgroundColor,
|
||||
|
||||
UnderPageBackgroundColor
|
||||
}
|
||||
|
||||
impl Color {
|
||||
|
@ -484,5 +488,7 @@ unsafe fn to_objc(obj: &Color) -> id {
|
|||
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
||||
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
||||
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.
|
||||
//!
|
||||
//! ## Example
|
||||
//! ```rust
|
||||
//! ```rust,no_run
|
||||
//! use std::collections::HashMap;
|
||||
//! use cacao::defaults::{UserDefaults, Value};
|
||||
//!
|
||||
|
@ -27,7 +27,6 @@
|
|||
//! map
|
||||
//! });
|
||||
//!
|
||||
//! // Ignore the unwrap() calls, it's a demo ;P
|
||||
//! let value = defaults.get("test").unwrap().as_str().unwrap();
|
||||
//! assert_eq!(value, "value");
|
||||
//! ```
|
||||
|
@ -38,7 +37,10 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use objc::runtime::Object;
|
||||
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;
|
||||
pub use value::Value;
|
||||
|
@ -92,7 +94,7 @@ impl UserDefaults {
|
|||
|
||||
UserDefaults(unsafe {
|
||||
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>) {
|
||||
let dictionary = NSDictionary::from(values);
|
||||
let dictionary = NSMutableDictionary::from(values);
|
||||
|
||||
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());
|
||||
|
||||
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 result: id = unsafe {
|
||||
msg_send![&*self.0, objectForKey:key.into_inner()]
|
||||
msg_send![&*self.0, objectForKey:&*key]
|
||||
};
|
||||
|
||||
if result == nil {
|
||||
|
@ -184,12 +186,12 @@ impl UserDefaults {
|
|||
}
|
||||
|
||||
if NSData::is(result) {
|
||||
let data = NSData::wrap(result);
|
||||
let data = NSData::retain(result);
|
||||
return Some(Value::Data(data.into_vec()));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -213,7 +215,7 @@ impl UserDefaults {
|
|||
x => {
|
||||
// Debugging code that should be removed at some point.
|
||||
#[cfg(debug_assertions)]
|
||||
println!("Code: {}", x);
|
||||
println!("Unexpected code type found: {}", x);
|
||||
|
||||
None
|
||||
}
|
||||
|
@ -241,7 +243,7 @@ impl UserDefaults {
|
|||
pub fn is_forced_for_key<K: AsRef<str>>(&self, key: K) -> bool {
|
||||
let result: BOOL = unsafe {
|
||||
let key = NSString::new(key.as_ref());
|
||||
msg_send![&*self.0, objectIsForcedForKey:key.into_inner()]
|
||||
msg_send![&*self.0, objectIsForcedForKey:&*key]
|
||||
};
|
||||
|
||||
to_bool(result)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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`.
|
||||
///
|
||||
|
@ -136,22 +136,22 @@ impl From<Value> for id {
|
|||
// period.
|
||||
fn from(value: Value) -> Self {
|
||||
match value {
|
||||
Value::Bool(b) => NSNumber::bool(b).into_inner(),
|
||||
Value::String(s) => NSString::new(&s).into_inner(),
|
||||
Value::Float(f) => NSNumber::float(f).into_inner(),
|
||||
Value::Integer(i) => NSNumber::integer(i).into_inner(),
|
||||
Value::Data(data) => NSData::new(data).into_inner()
|
||||
Value::Bool(b) => NSNumber::bool(b).into(),
|
||||
Value::String(s) => NSString::new(&s).into(),
|
||||
Value::Float(f) => NSNumber::float(f).into(),
|
||||
Value::Integer(i) => NSNumber::integer(i).into(),
|
||||
Value::Data(data) => NSData::new(data).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> From<HashMap<K, Value>> for NSDictionary
|
||||
impl<K> From<HashMap<K, Value>> for NSMutableDictionary
|
||||
where
|
||||
K: AsRef<str>
|
||||
{
|
||||
/// Translates a `HashMap` of `Value`s into an `NSDictionary`.
|
||||
fn from(map: HashMap<K, Value>) -> Self {
|
||||
let mut dictionary = NSDictionary::new();
|
||||
let mut dictionary = NSMutableDictionary::new();
|
||||
|
||||
for (key, value) in map.into_iter() {
|
||||
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 {
|
||||
let (code, domain, description) = unsafe {
|
||||
let code: usize = msg_send![error, code];
|
||||
let domain = NSString::wrap(msg_send![error, domain]);
|
||||
let description = NSString::wrap(msg_send![error, localizedDescription]);
|
||||
let domain = NSString::retain(msg_send![error, domain]);
|
||||
let description = NSString::retain(msg_send![error, localizedDescription]);
|
||||
|
||||
(code, domain, description)
|
||||
};
|
||||
|
||||
Error {
|
||||
code: code,
|
||||
domain: domain.to_str().to_string(),
|
||||
description: description.to_str().to_string()
|
||||
code,
|
||||
domain: domain.to_string(),
|
||||
description: description.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,7 +56,7 @@ impl FileManager {
|
|||
create:NO
|
||||
error:nil];
|
||||
|
||||
NSString::wrap(msg_send![dir, absoluteString])
|
||||
NSString::retain(msg_send![dir, absoluteString])
|
||||
};
|
||||
|
||||
Url::parse(directory.to_str()).map_err(|e| e.into())
|
||||
|
@ -70,8 +70,8 @@ impl FileManager {
|
|||
let to = NSString::new(to.as_str());
|
||||
|
||||
unsafe {
|
||||
let from_url: id = msg_send![class!(NSURL), URLWithString:from.into_inner()];
|
||||
let to_url: id = msg_send![class!(NSURL), URLWithString:to.into_inner()];
|
||||
let from_url: id = msg_send![class!(NSURL), URLWithString:&*from];
|
||||
let to_url: id = msg_send![class!(NSURL), URLWithString:&*to];
|
||||
|
||||
// This should potentially be write(), but the backing class handles this logic
|
||||
// already, so... going to leave it as read.
|
||||
|
|
|
@ -102,7 +102,7 @@ pub fn get_url(panel: &Object) -> Option<String> {
|
|||
None
|
||||
} else {
|
||||
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::filesystem::enums::ModalResponse;
|
||||
|
||||
use crate::macos::window::{Window, WindowDelegate};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileSelectPanel {
|
||||
/// 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
|
||||
/// the system runs and manages that in another process, and we're still abiding by the general
|
||||
/// 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 completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||
let response: ModalResponse = result.into();
|
||||
|
@ -137,30 +145,46 @@ impl FileSelectPanel {
|
|||
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.
|
||||
/// 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.
|
||||
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
|
||||
let mut paths: Vec<PathBuf> = vec![];
|
||||
|
||||
unsafe {
|
||||
let urls: id = msg_send![&*panel, URLs];
|
||||
let mut count: usize = msg_send![urls, count];
|
||||
let count: usize = msg_send![urls, count];
|
||||
|
||||
loop {
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
(0..count).map(|index| {
|
||||
let url: id = msg_send![urls, objectAtIndex:index];
|
||||
let path = NSString::retain(msg_send![url, path]).to_string();
|
||||
path.into()
|
||||
}).collect()
|
||||
}
|
||||
|
||||
paths.reverse();
|
||||
paths
|
||||
}
|
||||
|
|
|
@ -1,12 +1,4 @@
|
|||
//! A wrapper type for `NSArray`.
|
||||
//!
|
||||
//! 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 std::ops::{Deref, DerefMut};
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
|
@ -15,15 +7,68 @@ use objc_id::Id;
|
|||
use crate::foundation::id;
|
||||
|
||||
/// 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)]
|
||||
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 {
|
||||
/// Given a set of `Object`s, creates an `NSArray` that holds them.
|
||||
fn from(objects: Vec<&Object>) -> Self {
|
||||
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.
|
||||
fn from(objects: Vec<id>) -> Self {
|
||||
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 {
|
||||
/// Given a set of `Object`s, creates 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()])
|
||||
})
|
||||
impl From<NSArray> for id {
|
||||
/// Consumes and returns the pointer to the underlying NSArray.
|
||||
fn from(mut array: NSArray) -> Self {
|
||||
&mut *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 wrap(array: id) -> Self {
|
||||
NSArray(unsafe {
|
||||
Id::from_ptr(array)
|
||||
})
|
||||
impl Deref for NSArray {
|
||||
type Target = Object;
|
||||
|
||||
/// Derefs to the underlying Objective-C Object.
|
||||
fn deref(&self) -> &Object {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes and returns the underlying Objective-C value.
|
||||
pub fn into_inner(mut self) -> id {
|
||||
impl DerefMut for NSArray {
|
||||
/// Derefs to the underlying Objective-C Object.
|
||||
fn deref_mut(&mut self) -> &mut Object {
|
||||
&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::runtime::Object;
|
||||
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>);
|
||||
|
||||
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 {
|
||||
AutoReleasePool(unsafe {
|
||||
Id::from_retained_ptr(msg_send![class!(NSAutoreleasePool), new])
|
||||
})
|
||||
}
|
||||
|
||||
/// Drains the underlying AutoreleasePool.
|
||||
pub fn drain(&self) {
|
||||
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
|
||||
/// and/or creation. The general store format is (roughly speaking) as follows:
|
||||
///
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// {
|
||||
/// "subclass_type": {
|
||||
/// "superclass_type": *const Class as usize
|
||||
|
@ -39,8 +39,8 @@ impl ClassMap {
|
|||
let mut map = HashMap::new();
|
||||
|
||||
// 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
|
||||
// will have `NSView` as their superclass.
|
||||
// is being created, we can avoid querying the runtime for the superclass - i.e, many
|
||||
// subclasses will have `NSView` as their superclass.
|
||||
map.insert("_supers", HashMap::new());
|
||||
|
||||
map
|
||||
|
@ -48,7 +48,11 @@ impl ClassMap {
|
|||
}
|
||||
|
||||
/// 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();
|
||||
|
||||
if let Some(inner) = (*reader).get(subclass_name) {
|
||||
|
@ -152,7 +156,11 @@ where
|
|||
},
|
||||
|
||||
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::ops::{Deref, DerefMut};
|
||||
use std::os::raw::c_void;
|
||||
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
|
||||
/// pointer from the Objective-C side, and turning an `NSData` into a `Vec<u8>`.
|
||||
///
|
||||
/// This is an intentionally limited API.
|
||||
#[derive(Debug)]
|
||||
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
|
||||
/// wrap it while we figure out what to do with it. This does that.
|
||||
pub fn wrap(data: id) -> Self {
|
||||
/// Given a (presumably) `NSData`, wraps and retains it.
|
||||
pub fn retain(data: id) -> Self {
|
||||
NSData(unsafe {
|
||||
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`.
|
||||
pub fn is(obj: id) -> bool {
|
||||
let result: BOOL = unsafe {
|
||||
|
@ -117,9 +118,27 @@ impl NSData {
|
|||
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSData> for id {
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
use objc_id::Id;
|
||||
|
||||
use crate::foundation::{id, NSString};
|
||||
|
||||
/// A wrapper for `NSDictionary`. Behind the scenes we actually wrap `NSMutableDictionary`, and
|
||||
/// rely on Rust doing the usual borrow-checking guards that it does so well.
|
||||
/// A wrapper for `NSMutableDictionary`.
|
||||
#[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 {
|
||||
NSDictionary::new()
|
||||
NSMutableDictionary::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NSDictionary {
|
||||
impl NSMutableDictionary {
|
||||
/// Constructs an `NSMutableDictionary` and retains it.
|
||||
///
|
||||
/// 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
|
||||
/// `unsafe {}` block... so you'll know you're in special territory then.
|
||||
pub fn new() -> Self {
|
||||
NSDictionary(unsafe {
|
||||
NSMutableDictionary(unsafe {
|
||||
Id::from_ptr(msg_send![class!(NSMutableDictionary), new])
|
||||
})
|
||||
}
|
||||
|
@ -33,7 +36,7 @@ impl NSDictionary {
|
|||
/// This intentionally requires `NSString` be allocated ahead of time.
|
||||
pub fn insert(&mut self, key: NSString, object: id) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
mod dictionary;
|
||||
pub use dictionary::NSDictionary;
|
||||
pub use dictionary::NSMutableDictionary;
|
||||
|
||||
mod number;
|
||||
pub use number::NSNumber;
|
||||
|
|
|
@ -7,7 +7,7 @@ use objc_id::Id;
|
|||
|
||||
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
|
||||
/// 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>);
|
||||
|
||||
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
|
||||
/// wrap it while we figure out what to do with it. This does that.
|
||||
pub fn wrap(data: id) -> Self {
|
||||
NSNumber(unsafe {
|
||||
Id::from_ptr(data)
|
||||
Id::from_retained_ptr(data)
|
||||
})
|
||||
}
|
||||
|
||||
/// Constructs a `numberWithBool` instance of `NSNumber` and retains it.
|
||||
pub fn bool(value: bool) -> Self {
|
||||
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,
|
||||
false => NO
|
||||
}])
|
||||
|
@ -36,14 +44,14 @@ impl NSNumber {
|
|||
/// Constructs a `numberWithInteger` instance of `NSNumber` and retains it.
|
||||
pub fn integer(value: i64) -> Self {
|
||||
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.
|
||||
pub fn float(value: f64) -> Self {
|
||||
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`.
|
||||
///
|
||||
/// For more information:
|
||||
/// [https://nshipster.com/type-encodings/](https://nshipster.com/type-encodings/)
|
||||
/// <https://nshipster.com/type-encodings/>
|
||||
pub fn objc_type(&self) -> &str {
|
||||
unsafe {
|
||||
let t: *const c_char = msg_send![&*self.0, objCType];
|
||||
|
@ -101,9 +109,11 @@ impl NSNumber {
|
|||
|
||||
to_bool(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSNumber> for id {
|
||||
/// Consumes and returns the underlying `NSNumber`.
|
||||
pub fn into_inner(mut self) -> id {
|
||||
&mut *self.0
|
||||
fn from(mut number: NSNumber) -> Self {
|
||||
&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 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
|
||||
/// side is fairly battle tested.
|
||||
#[derive(Debug)]
|
||||
pub struct NSString(pub Id<Object>);
|
||||
pub struct NSString<'a> {
|
||||
pub objc: Id<Object>,
|
||||
phantom: PhantomData<&'a ()>
|
||||
}
|
||||
|
||||
impl NSString {
|
||||
/// Creates a new `NSString`. Note that `NSString` lives on the heap, so this allocates
|
||||
/// accordingly.
|
||||
impl<'a> NSString<'a> {
|
||||
/// Creates a new `NSString`.
|
||||
pub fn new(s: &str) -> Self {
|
||||
NSString(unsafe {
|
||||
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() length:s.len() encoding:UTF8_ENCODING])
|
||||
})
|
||||
NSString {
|
||||
objc: unsafe {
|
||||
let nsstring: *mut Object = msg_send![class!(NSString), alloc];
|
||||
Id::from_ptr(msg_send![nsstring, initWithBytes:s.as_ptr()
|
||||
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
|
||||
/// retain it.
|
||||
pub fn wrap(object: id) -> Self {
|
||||
NSString(unsafe {
|
||||
Id::from_ptr(object)
|
||||
})
|
||||
pub fn retain(object: id) -> Self {
|
||||
NSString {
|
||||
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`.
|
||||
|
@ -44,7 +79,7 @@ impl NSString {
|
|||
/// Helper method for returning the UTF8 bytes for this `NSString`.
|
||||
fn bytes(&self) -> *const u8 {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +87,7 @@ impl NSString {
|
|||
/// Helper method for grabbing the proper byte length for this `NSString` (the UTF8 variant).
|
||||
fn bytes_len(&self) -> usize {
|
||||
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 {
|
||||
self.to_str().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes and returns the underlying `NSString` instance.
|
||||
pub fn into_inner(mut self) -> id {
|
||||
&mut *self.0
|
||||
impl fmt::Display for NSString<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
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,
|
||||
|
||||
/// 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 {
|
||||
/// Maps system icons to their pre-11.0 framework identifiers.
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
|
@ -29,6 +34,7 @@ impl MacSystemIcon {
|
|||
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
|
||||
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
|
||||
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
|
||||
MacSystemIcon::Add => "NSImageNameAddTemplate"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,7 +43,31 @@ impl MacSystemIcon {
|
|||
match self {
|
||||
MacSystemIcon::PreferencesGeneral => "gearshape",
|
||||
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 {
|
||||
ShareId::from_ptr(match os::is_minimum_version(11) {
|
||||
true => {
|
||||
let icon = NSString::new(icon.to_sfsymbol_str()).into_inner();
|
||||
let desc = NSString::new(accessibility_description).into_inner();
|
||||
msg_send![class!(NSImage), imageWithSystemSymbolName:icon accessibilityDescription:desc]
|
||||
let icon = NSString::new(icon.to_sfsymbol_str());
|
||||
let desc = NSString::new(accessibility_description);
|
||||
msg_send![class!(NSImage), imageWithSystemSymbolName:&*icon
|
||||
accessibilityDescription:&*desc]
|
||||
},
|
||||
|
||||
false => {
|
||||
let icon = NSString::new(icon.to_str()).into_inner();
|
||||
msg_send![class!(NSImage), imageNamed:icon]
|
||||
let icon = NSString::new(icon.to_str());
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setHidden:match hidden {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ImageView {
|
||||
|
|
|
@ -61,9 +61,6 @@ mod ios;
|
|||
#[cfg(target_os = "ios")]
|
||||
use ios::{register_view_class, register_view_class_with_delegate};
|
||||
|
||||
//mod controller;
|
||||
//pub use controller::TextFieldController;
|
||||
|
||||
mod traits;
|
||||
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.
|
||||
pub fn get_value(&self) -> String {
|
||||
let value = NSString::wrap(unsafe {
|
||||
let value = NSString::retain(unsafe {
|
||||
msg_send![&*self.objc, stringValue]
|
||||
});
|
||||
|
||||
value.to_str().to_string()
|
||||
value.to_string()
|
||||
}
|
||||
|
||||
/// Call this to set the background color for the backing layer.
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
let bg = color.into_platform_specific_color();
|
||||
let bg = color.to_objc();
|
||||
|
||||
unsafe {
|
||||
let cg: id = msg_send![bg, CGColor];
|
||||
|
@ -222,7 +219,7 @@ impl<T> TextField<T> {
|
|||
let s = NSString::new(text);
|
||||
|
||||
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 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 vendor = Box::new(scene_delegate_vendor);
|
||||
|
||||
unsafe {
|
||||
let delegate_ptr: *const T = &*app_delegate;
|
||||
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 c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
||||
|
||||
let s = NSString::new("RSTApplication");
|
||||
let s2 = NSString::new("RSTAppDelegate");
|
||||
let s = NSString::no_copy("RSTApplication");
|
||||
let s2 = NSString::no_copy("RSTAppDelegate");
|
||||
|
||||
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();
|
||||
|
|
|
@ -13,18 +13,18 @@ pub enum SessionRole {
|
|||
//CarPlayApplication
|
||||
}
|
||||
|
||||
impl From<SessionRole> for NSString {
|
||||
impl From<SessionRole> for NSString<'_> {
|
||||
fn from(role: SessionRole) -> Self {
|
||||
NSString::new(match role {
|
||||
SessionRole::Application => "UIWindowSceneSessionRoleApplication",
|
||||
SessionRole::ExternalDisplay => "UIWindowSceneSessionRoleExternalDisplay",
|
||||
SessionRole::Application => NSString::no_copy("UIWindowSceneSessionRoleApplication"),
|
||||
SessionRole::ExternalDisplay => NSString::no_copy("UIWindowSceneSessionRoleExternalDisplay"),
|
||||
//SessionRole::CarPlayApplication => ""
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NSString> for SessionRole {
|
||||
fn from(value: NSString) -> Self {
|
||||
impl From<NSString<'_>> for SessionRole {
|
||||
fn from(value: NSString<'_>) -> Self {
|
||||
match value.to_str() {
|
||||
"UIWindowSceneSessionRoleApplication" => SessionRole::Application,
|
||||
"UIWindowSceneSessionRoleExternalDisplay" => SessionRole::ExternalDisplay,
|
||||
|
|
|
@ -8,7 +8,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use objc::runtime::Object;
|
||||
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
|
||||
/// 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.
|
||||
pub fn offset<F: Into<f64>>(self, offset: F) -> Self {
|
||||
let offset: f64 = offset.into();
|
||||
|
||||
unsafe {
|
||||
let o = offset as CGFloat;
|
||||
let _: () = msg_send![&*self.constraint, setConstant:o];
|
||||
}
|
||||
|
||||
|
||||
LayoutConstraint {
|
||||
constraint: self.constraint,
|
||||
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.
|
||||
// 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
|
||||
|
@ -75,4 +96,15 @@ impl LayoutConstraint {
|
|||
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 objc::{msg_send, sel, sel_impl};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::id;
|
||||
use crate::foundation::{id, nil, NSInteger};
|
||||
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
|
||||
/// factory/helper for creating `LayoutConstraint` objects based on your views.
|
||||
#[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.
|
||||
pub fn constraint_less_than_or_equal_to(&self, anchor_to: &LayoutAnchorDimension) -> LayoutConstraint {
|
||||
match (&self.0, &anchor_to.0) {
|
||||
|
@ -67,4 +81,16 @@ impl LayoutAnchorDimension {
|
|||
_ => { 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_debug_implementations)]
|
||||
#![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.
|
||||
// See the COPYRIGHT file at the top-level directory of this distribution.
|
||||
|
@ -8,8 +10,8 @@
|
|||
|
||||
//! # Cacao
|
||||
//!
|
||||
//! This library provides safe Rust bindings for `AppKit` on macOS and (eventually) `UIKit` on iOS. It
|
||||
//! tries to do so in a way that, if you've done programming for the framework before (in Swift or
|
||||
//! This library provides safe Rust bindings for `AppKit` on macOS and (eventually) `UIKit` on iOS and tvOS.
|
||||
//! 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
|
||||
//! creative coding and assumptions can get us pretty far.
|
||||
//!
|
||||
|
@ -18,14 +20,14 @@
|
|||
//! already fine for some apps.
|
||||
//!
|
||||
//! _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
|
||||
//! 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._
|
||||
//!
|
||||
//! # Hello World
|
||||
//!
|
||||
//! ```rust
|
||||
//! ```rust,no_run
|
||||
//! use cacao::macos::app::{App, AppDelegate};
|
||||
//! use cacao::macos::window::Window;
|
||||
//!
|
||||
|
@ -48,23 +50,41 @@
|
|||
//! ```
|
||||
//!
|
||||
//! ## Initialization
|
||||
//!
|
||||
//! 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
|
||||
//! ensures the application has had time to initialize and do any housekeeping necessary behind the
|
||||
//! 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
|
||||
//!
|
||||
//! 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.
|
||||
//! - **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
|
||||
//! code-signed, and will not work without it.
|
||||
//! - **webview**: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
||||
//! - **webview-downloading**: 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.
|
||||
//! - `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
||||
//! This feature is not supported on tvOS, as the platform has no webview control.
|
||||
//! - `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
|
||||
|
||||
|
@ -74,20 +94,18 @@ pub use objc;
|
|||
pub use url;
|
||||
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_attr(docsrs, doc(cfg(feature = "macos")))]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "ios")))]
|
||||
pub mod ios;
|
||||
|
||||
pub mod button;
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
#[cfg(any(feature = "cloudkit", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "cloudkit")))]
|
||||
pub mod cloudkit;
|
||||
|
||||
pub mod color;
|
||||
|
@ -112,15 +130,18 @@ pub mod switch;
|
|||
pub mod text;
|
||||
|
||||
#[cfg(feature = "quicklook")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "quicklook")))]
|
||||
pub mod quicklook;
|
||||
|
||||
#[cfg(feature = "user-notifications")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "user-notifications")))]
|
||||
pub mod user_notifications;
|
||||
|
||||
pub mod user_activity;
|
||||
pub(crate) mod utils;
|
||||
pub mod utils;
|
||||
|
||||
pub mod view;
|
||||
|
||||
#[cfg(feature = "webview")]
|
||||
#[cfg(any(feature = "webview", doc))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "webview")))]
|
||||
pub mod webview;
|
||||
|
|
|
@ -47,9 +47,12 @@ impl RowAction {
|
|||
/// on your ListViewRow.
|
||||
///
|
||||
/// 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
|
||||
where
|
||||
F: Fn(RowAction, usize) + Send + Sync + 'static
|
||||
F: Fn(RowAction, usize) + 'static
|
||||
{
|
||||
let title = NSString::new(title);
|
||||
let block = ConcreteBlock::new(move |action: id, row: NSUInteger| {
|
||||
|
@ -65,7 +68,7 @@ impl RowAction {
|
|||
RowAction(unsafe {
|
||||
let cls = class!(NSTableViewRowAction);
|
||||
Id::from_ptr(msg_send![cls, rowActionWithStyle:style
|
||||
title:title.into_inner()
|
||||
title:&*title
|
||||
handler:block
|
||||
])
|
||||
})
|
||||
|
@ -76,13 +79,13 @@ impl RowAction {
|
|||
let title = NSString::new(title);
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.0, setTitle:title.into_inner()];
|
||||
let _: () = msg_send![&*self.0, setTitle:&*title];
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the background color of this action.
|
||||
pub fn set_background_color(&mut self, color: Color) {
|
||||
let color = color.into_platform_specific_color();
|
||||
let color = color.to_objc();
|
||||
|
||||
unsafe {
|
||||
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_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::listview::{
|
||||
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>(
|
||||
this: &Object,
|
||||
_: Sel,
|
||||
|
@ -67,14 +107,13 @@ extern fn row_actions_for_row<T: ListViewDelegate>(
|
|||
let edge: RowEdge = edge.into();
|
||||
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 {
|
||||
let ids: Vec<&Object> = actions.iter().map(|action| &*action.0).collect();
|
||||
NSArray::from(ids).into_inner()
|
||||
//} else {
|
||||
// NSArray::new(&[]).into_inner()
|
||||
//}
|
||||
&mut *ids
|
||||
}
|
||||
|
||||
/// 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
|
||||
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: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);
|
||||
|
||||
// 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)
|
||||
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);
|
||||
|
|
|
@ -48,13 +48,16 @@ use objc_id::ShareId;
|
|||
use objc::runtime::{Class, Object};
|
||||
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::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::pasteboard::PasteboardType;
|
||||
use crate::scrollview::ScrollView;
|
||||
use crate::utils::CGSize;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
use crate::macos::menu::MenuItem;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
|
@ -148,21 +151,24 @@ fn common_init(class: *const Class) -> id {
|
|||
// Let's... make NSTableView into UITableView-ish.
|
||||
#[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, setUsesAutomaticRowHeights: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];
|
||||
//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];
|
||||
|
||||
// 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::new("CacaoListViewColumn");
|
||||
let identifier = NSString::no_copy("CacaoListViewColumn");
|
||||
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![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)]
|
||||
pub struct ListView<T = ()> {
|
||||
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell
|
||||
/// allocation and reuse, which is necessary for an "infinite" listview.
|
||||
cell_factory: CellFactory,
|
||||
|
||||
pub menu: PropertyNullable<Vec<MenuItem>>,
|
||||
|
||||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ShareId<Object>,
|
||||
|
||||
|
@ -245,6 +302,7 @@ impl ListView {
|
|||
|
||||
ListView {
|
||||
cell_factory: CellFactory::new(),
|
||||
menu: PropertyNullable::default(),
|
||||
delegate: None,
|
||||
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
||||
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 {
|
||||
cell_factory: cell,
|
||||
menu: PropertyNullable::default(),
|
||||
delegate: None,
|
||||
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
|
||||
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 {
|
||||
ListView {
|
||||
cell_factory: CellFactory::new(),
|
||||
menu: self.menu.clone(),
|
||||
delegate: None,
|
||||
top: self.top.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> {
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
let key = NSString::new(identifier).into_inner();
|
||||
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:key owner:nil];
|
||||
let key = NSString::new(identifier);
|
||||
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:&*key owner:nil];
|
||||
|
||||
if cell != nil {
|
||||
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) {
|
||||
#[cfg(target_os = "macos")]
|
||||
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")]
|
||||
unsafe {
|
||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||
|
||||
for index in indexes {
|
||||
let x: NSUInteger = index as NSUInteger;
|
||||
let x: NSUInteger = *index as NSUInteger;
|
||||
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")]
|
||||
unsafe {
|
||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||
|
||||
for index in indexes {
|
||||
let x: NSUInteger = index as NSUInteger;
|
||||
let x: NSUInteger = *index as NSUInteger;
|
||||
let _: () = msg_send![index_set, addIndex:x];
|
||||
}
|
||||
|
||||
|
@ -507,11 +604,11 @@ impl<T> ListView<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 is just an enum... and this is not an oft called method.
|
||||
let x: NSString = t.clone().into();
|
||||
x.into_inner()
|
||||
let x: NSString = (*t).into();
|
||||
x.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];
|
||||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
|
@ -14,9 +14,9 @@ use objc::runtime::{Class, Object, Sel, BOOL};
|
|||
use objc::{class, msg_send, sel, sel_impl};
|
||||
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::listview::row::{LISTVIEW_ROW_DELEGATE_PTR, ViewDelegate};
|
||||
use crate::listview::row::{LISTVIEW_ROW_DELEGATE_PTR, BACKGROUND_COLOR, ViewDelegate};
|
||||
use crate::utils::load;
|
||||
|
||||
/// 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
|
||||
/// 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
|
||||
|
@ -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
|
||||
// move.
|
||||
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!(updateLayer), update_layer as extern fn(&Object, _));
|
||||
|
||||
// Drag and drop operations (e.g, accepting files)
|
||||
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
||||
|
|
|
@ -66,6 +66,7 @@ mod ios;
|
|||
#[cfg(target_os = "ios")]
|
||||
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";
|
||||
|
||||
/// 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.
|
||||
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();
|
||||
unsafe {
|
||||
let _: () = msg_send![&**objc, setIdentifier:identifier];
|
||||
let _: () = msg_send![&**objc, setIdentifier:&*identifier];
|
||||
}
|
||||
}
|
||||
|
||||
/// Call this to set the background color for the backing layer.
|
||||
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 {
|
||||
let cg: id = msg_send![bg, CGColor];
|
||||
let layer: id = msg_send![&**objc, layer];
|
||||
let _: () = msg_send![layer, setBackgroundColor:cg];
|
||||
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,12 +288,12 @@ impl<T> ListViewRow<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 is just an enum... and this is not an oft called method.
|
||||
let x: NSString = t.clone().into();
|
||||
x.into_inner()
|
||||
let x: NSString = (*t).into();
|
||||
x.into()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
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.
|
||||
|
||||
use crate::Node;
|
||||
use crate::macos::menu::MenuItem;
|
||||
use crate::dragdrop::{DragInfo, DragOperation};
|
||||
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
|
||||
use crate::layout::Layout;
|
||||
|
@ -27,12 +27,23 @@ pub trait ListViewDelegate {
|
|||
/// Returns the number of items in the list view.
|
||||
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
|
||||
/// 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
|
||||
/// had time to sit down and figure them out properly yet.
|
||||
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
|
||||
/// 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() }
|
||||
|
|
|
@ -32,6 +32,7 @@ use crate::foundation::{id, NSString};
|
|||
|
||||
/// Represents an `NSAlert`. Has no information other than the retained pointer to the Objective C
|
||||
/// side, so... don't bother inspecting this.
|
||||
#[derive(Debug)]
|
||||
pub struct Alert(Id<Object>);
|
||||
|
||||
impl Alert {
|
||||
|
@ -40,13 +41,13 @@ impl Alert {
|
|||
pub fn new(title: &str, message: &str) -> Self {
|
||||
let title = NSString::new(title);
|
||||
let message = NSString::new(message);
|
||||
let x = NSString::new("OK");
|
||||
let ok = NSString::new("OK");
|
||||
|
||||
Alert(unsafe {
|
||||
let alert: id = msg_send![class!(NSAlert), new];
|
||||
let _: () = msg_send![alert, setMessageText:title];
|
||||
let _: () = msg_send![alert, setInformativeText:message];
|
||||
let _: () = msg_send![alert, addButtonWithTitle:x];
|
||||
let _: () = msg_send![alert, addButtonWithTitle:ok];
|
||||
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.
|
||||
// @TODO: Make this return Vec<MenuItem>.
|
||||
extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||
match app::<T>(this).dock_menu() {
|
||||
Some(mut menu) => &mut *menu.inner,
|
||||
Some(mut menu) => &mut *menu.0,
|
||||
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:`
|
||||
/// notification.
|
||||
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()) {
|
||||
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.
|
||||
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
|
||||
// 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.
|
||||
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.
|
||||
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(
|
||||
NSString::wrap(activity_type).to_str(),
|
||||
NSString::retain(activity_type).to_str(),
|
||||
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.
|
||||
extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
|
||||
let urls = NSArray::wrap(file_urls).map(|url| {
|
||||
let uri = NSString::wrap(unsafe {
|
||||
let urls = NSArray::retain(file_urls).map(|url| {
|
||||
let uri = NSString::retain(unsafe {
|
||||
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.
|
||||
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()) {
|
||||
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.
|
||||
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()) {
|
||||
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.
|
||||
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()) {
|
||||
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:`
|
||||
/// message.
|
||||
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| {
|
||||
NSString::wrap(file).to_str().to_string()
|
||||
let files = NSArray::retain(files).map(|file| {
|
||||
NSString::retain(file).to_str().to_string()
|
||||
});
|
||||
|
||||
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
|
||||
/// matter.
|
||||
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()) {
|
||||
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
|
||||
// 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.
|
||||
lazy_static! {
|
||||
/*lazy_static! {
|
||||
static ref MENU_ITEMS_HANDLER_CACHE: Arc<Mutex<Vec<TargetActionHandler>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
}
|
||||
}*/
|
||||
|
||||
/// A handler to make some boilerplate less annoying.
|
||||
#[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
|
||||
/// 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.
|
||||
///
|
||||
/// 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>) {
|
||||
let mut handlers = vec![];
|
||||
|
||||
let main_menu = unsafe {
|
||||
let menu_cls = class!(NSMenu);
|
||||
let item_cls = class!(NSMenuItem);
|
||||
let main_menu: id = msg_send![menu_cls, new];
|
||||
|
||||
for menu in menus.iter_mut() {
|
||||
handlers.append(&mut menu.actions);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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 {
|
||||
let _: () = msg_send![app, setMainMenu:main_menu];
|
||||
});
|
||||
|
|
|
@ -32,11 +32,11 @@ impl Event {
|
|||
// @TODO: Check here if key event, invalid otherwise.
|
||||
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
||||
// should... make it work, I think.
|
||||
let characters = NSString::wrap(unsafe {
|
||||
let characters = NSString::from_retained(unsafe {
|
||||
msg_send![&*self.0, characters]
|
||||
});
|
||||
|
||||
characters.to_str().to_string()
|
||||
characters.to_string()
|
||||
}
|
||||
|
||||
/*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
|
||||
//! now.
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::{Object, Sel};
|
||||
use objc_id::ShareId;
|
||||
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::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.
|
||||
fn make_menu_item(
|
||||
title: &str,
|
||||
fn make_menu_item<S: AsRef<str>>(
|
||||
title: S,
|
||||
key: Option<&str>,
|
||||
action: Option<Sel>,
|
||||
modifiers: Option<&[EventModifierFlag]>
|
||||
) -> MenuItem {
|
||||
) -> Id<Object> {
|
||||
unsafe {
|
||||
let title = NSString::new(title);
|
||||
let title = NSString::new(title.as_ref());
|
||||
|
||||
// Note that AppKit requires a blank string if nil, not nil.
|
||||
let key = NSString::new(match key {
|
||||
|
@ -27,10 +41,16 @@ fn make_menu_item(
|
|||
None => ""
|
||||
});
|
||||
|
||||
let alloc: id = msg_send![class!(NSMenuItem), alloc];
|
||||
let item = ShareId::from_ptr(match action {
|
||||
Some(a) => msg_send![alloc, initWithTitle:title action:a keyEquivalent:key],
|
||||
None => msg_send![alloc, initWithTitle:title action:nil keyEquivalent:key]
|
||||
// Stock menu items that use selectors targeted at system pieces are just standard
|
||||
// `NSMenuItem`s. If there's no custom ones, we use our subclass that has a slot to store a
|
||||
// handler pointer.
|
||||
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 {
|
||||
|
@ -44,169 +64,262 @@ fn make_menu_item(
|
|||
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)]
|
||||
pub enum MenuItem {
|
||||
/// Represents a Menu item that's not a separator - for all intents and purposes, you can consider
|
||||
/// this the real `NSMenuItem`.
|
||||
Entry((ShareId<Object>, Option<TargetActionHandler>)),
|
||||
/// A custom MenuItem. This type functions as a builder, so you can customize it easier.
|
||||
/// You can (and should) create this variant via the `new(title)` method, but if you need to do
|
||||
/// 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.
|
||||
Separator
|
||||
}
|
||||
|
||||
impl MenuItem {
|
||||
/// Creates and returns a `MenuItem::Entry` with the specified title.
|
||||
pub fn entry(title: &str) -> Self {
|
||||
make_menu_item(title, None, None, None)
|
||||
}
|
||||
|
||||
/// Configures the menu item, if it's not a separator, to support a key equivalent.
|
||||
pub fn key(self, key: &str) -> Self {
|
||||
/// Consumes and returns a handle for the underlying MenuItem. This is internal as we make a few assumptions
|
||||
/// for how it interacts with our `Menu` setup, but this could be made public in the future.
|
||||
pub(crate) unsafe fn to_objc(self) -> Id<Object> {
|
||||
match self {
|
||||
MenuItem::Separator => MenuItem::Separator,
|
||||
Self::Custom(objc) => objc,
|
||||
|
||||
MenuItem::Entry((item, action)) => {
|
||||
unsafe {
|
||||
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)
|
||||
}
|
||||
|
||||
/// Returns a standard "Hide" item.
|
||||
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
|
||||
/// Services submenu.
|
||||
pub fn services() -> Self {
|
||||
match 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 services: id = msg_send![app, servicesMenu];
|
||||
let _: () = msg_send![&*item, setSubmenu:services];
|
||||
}
|
||||
|
||||
MenuItem::Entry((item, action))
|
||||
Self::About(app_name) => {
|
||||
let title = format!("About {}", app_name);
|
||||
make_menu_item(&title, None, Some(sel!(orderFrontStandardAboutPanel:)), None)
|
||||
},
|
||||
|
||||
// Should never be hit
|
||||
MenuItem::Separator => MenuItem::Separator
|
||||
Self::Hide => make_menu_item("Hide", Some("h"), Some(sel!(hide:)), None),
|
||||
|
||||
// This one is a bit tricky to do right, as we need to expose a submenu, which isn't
|
||||
// supported by MenuItem yet.
|
||||
Self::Services => {
|
||||
let item = make_menu_item("Services", None, None, None);
|
||||
let app: id = msg_send![class!(RSTApplication), sharedApplication];
|
||||
let services: id = msg_send![app, servicesMenu];
|
||||
let _: () = msg_send![&*item, setSubmenu:services];
|
||||
item
|
||||
},
|
||||
|
||||
Self::HideOthers => make_menu_item(
|
||||
"Hide Others",
|
||||
Some("h"),
|
||||
Some(sel!(hide:)),
|
||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
||||
),
|
||||
|
||||
Self::ShowAll => make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None),
|
||||
Self::CloseWindow => make_menu_item("Close Window", Some("w"), Some(sel!(performClose:)), 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),
|
||||
|
||||
Self::EnterFullScreen => make_menu_item(
|
||||
"Enter Full Screen",
|
||||
Some("f"),
|
||||
Some(sel!(toggleFullScreen:)),
|
||||
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 "Hide" item.
|
||||
pub fn hide_others() -> Self {
|
||||
make_menu_item(
|
||||
"Hide Others",
|
||||
Some("h"),
|
||||
Some(sel!(hide:)),
|
||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Option])
|
||||
)
|
||||
/// Returns a `Custom` menu item, with the given title. You can configure this further with the
|
||||
/// builder methods on this object.
|
||||
pub fn new<S: AsRef<str>>(title: S) -> Self {
|
||||
MenuItem::Custom(make_menu_item(title, None, None, None))
|
||||
}
|
||||
|
||||
/// Returns a standard "Hide" item.
|
||||
pub fn show_all() -> Self {
|
||||
make_menu_item("Show All", None, Some(sel!(unhideAllApplications:)), None)
|
||||
/// Configures the a custom item to have specified key equivalent. This does nothing if called
|
||||
/// on a `MenuItem` type that is not `Custom`,
|
||||
pub fn key(self, key: &str) -> Self {
|
||||
if let MenuItem::Custom(objc) = self {
|
||||
unsafe {
|
||||
let key = NSString::new(key);
|
||||
let _: () = msg_send![&*objc, setKeyEquivalent:key];
|
||||
}
|
||||
|
||||
return MenuItem::Custom(objc);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a standard "Close Window" item.
|
||||
pub fn close_window() -> Self {
|
||||
make_menu_item("Close Window", Some("w"), Some(sel!(performClose:)), None)
|
||||
/// Sets the modifier key flags for this menu item. This does nothing if called on a `MenuItem`
|
||||
/// that is not `Custom`.
|
||||
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;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![&*objc, setKeyEquivalentModifierMask:key_mask];
|
||||
}
|
||||
|
||||
return MenuItem::Custom(objc);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns a standard "Quit" item.
|
||||
pub fn quit() -> Self {
|
||||
make_menu_item("Quit", Some("q"), Some(sel!(terminate:)), None)
|
||||
}
|
||||
/// 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);
|
||||
|
||||
/// Returns a standard "Copy" item.
|
||||
pub fn copy() -> Self {
|
||||
make_menu_item("Copy", Some("c"), Some(sel!(copy:)), None)
|
||||
}
|
||||
unsafe {
|
||||
(&mut *objc).set_ivar(BLOCK_PTR, ptr as usize);
|
||||
let _: () = msg_send![&*objc, setTarget:&*objc];
|
||||
}
|
||||
|
||||
/// Returns a standard "Undo" item.
|
||||
pub fn undo() -> Self {
|
||||
make_menu_item("Undo", Some("z"), Some(sel!(undo:)), None)
|
||||
}
|
||||
return MenuItem::Custom(objc);
|
||||
}
|
||||
|
||||
/// Returns a standard "Enter Full Screen" item
|
||||
pub fn enter_full_screen() -> Self {
|
||||
make_menu_item(
|
||||
"Enter Full Screen",
|
||||
Some("f"),
|
||||
Some(sel!(toggleFullScreen:)),
|
||||
Some(&[EventModifierFlag::Command, EventModifierFlag::Control])
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a standard "Miniaturize" item
|
||||
pub fn minimize() -> Self {
|
||||
make_menu_item(
|
||||
"Minimize",
|
||||
Some("m"),
|
||||
Some(sel!(performMiniaturize:)),
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a standard "Zoom" item
|
||||
pub fn zoom() -> Self {
|
||||
make_menu_item(
|
||||
"Zoom",
|
||||
None,
|
||||
Some(sel!(performZoom:)),
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns a standard "Redo" item.
|
||||
pub fn redo() -> Self {
|
||||
make_menu_item("Redo", Some("Z"), Some(sel!(redo:)), None)
|
||||
}
|
||||
|
||||
/// Returns a standard "Cut" item.
|
||||
pub fn cut() -> Self {
|
||||
make_menu_item("Cut", Some("x"), Some(sel!(cut:)), None)
|
||||
}
|
||||
|
||||
/// Returns a standard "Select All" item.
|
||||
pub fn select_all() -> Self {
|
||||
make_menu_item("Select All", Some("a"), Some(sel!(selectAll:)), None)
|
||||
}
|
||||
|
||||
/// Returns a standard "Paste" item.
|
||||
pub fn paste() -> Self {
|
||||
make_menu_item("Paste", Some("v"), Some(sel!(paste:)), None)
|
||||
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::{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::invoker::TargetActionHandler;
|
||||
|
||||
/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
|
||||
/// them throughout the application lifecycle.
|
||||
#[derive(Debug)]
|
||||
pub struct Menu {
|
||||
pub inner: Id<Object>,
|
||||
pub actions: Vec<TargetActionHandler>
|
||||
}
|
||||
pub struct Menu(pub Id<Object>);
|
||||
|
||||
impl Menu {
|
||||
/// 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.
|
||||
///
|
||||
pub fn new(title: &str, items: Vec<MenuItem>) -> Self {
|
||||
let inner = unsafe {
|
||||
Menu(unsafe {
|
||||
let cls = class!(NSMenu);
|
||||
let alloc: id = msg_send![cls, alloc];
|
||||
let title = NSString::new(title);
|
||||
let inner: id = msg_send![alloc, initWithTitle:title];
|
||||
Id::from_ptr(inner)
|
||||
};
|
||||
let menu: id = msg_send![alloc, initWithTitle:&*title];
|
||||
|
||||
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 {
|
||||
match item {
|
||||
MenuItem::Entry((item, action)) => {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*inner, addItem:item];
|
||||
}
|
||||
Id::from_retained_ptr(menu)
|
||||
})
|
||||
}
|
||||
|
||||
if action.is_some() {
|
||||
actions.push(action.unwrap());
|
||||
}
|
||||
},
|
||||
/// 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 {
|
||||
let mut count: NSInteger = msg_send![menu, numberOfItems];
|
||||
|
||||
MenuItem::Separator => {
|
||||
unsafe {
|
||||
let cls = class!(NSMenuItem);
|
||||
let separator: id = msg_send![cls, separatorItem];
|
||||
let _: () = msg_send![&*inner, addItem:separator];
|
||||
}
|
||||
}
|
||||
while count != 0 {
|
||||
count -= 1;
|
||||
let item: id = msg_send![menu, itemAtIndex:count];
|
||||
let _: () = msg_send![menu, removeItemAtIndex:count];
|
||||
let _: () = msg_send![item, release];
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
inner: inner,
|
||||
actions: actions
|
||||
for item in items.into_iter() {
|
||||
unsafe {
|
||||
let objc = item.to_objc();
|
||||
let _: () = msg_send![menu, addItem:&*objc];
|
||||
}
|
||||
}
|
||||
|
||||
menu
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
//! plain different APIs. It's tempting to want to find a common one and just implement that, but
|
||||
//! 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).
|
||||
//! The coverage here is not exhaustive, but should be sufficient enough for relatively complex
|
||||
//! applications. For examples, check the `examples` folder in the repository.
|
||||
|
||||
mod alert;
|
||||
pub use alert::Alert;
|
||||
|
@ -25,7 +15,7 @@ mod cursor;
|
|||
pub use cursor::{Cursor, CursorType};
|
||||
|
||||
mod enums;
|
||||
pub use enums::{FocusRingType};
|
||||
pub use enums::FocusRingType;
|
||||
|
||||
mod event;
|
||||
pub use event::*;
|
||||
|
|
|
@ -6,7 +6,7 @@ use objc::declare::ClassDecl;
|
|||
use objc::runtime::{Class, Object, Sel};
|
||||
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::utils::load;
|
||||
|
||||
|
@ -15,39 +15,49 @@ extern fn allowed_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _:
|
|||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||
|
||||
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
identifier.to_nsstring()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
identifiers.into_inner()
|
||||
identifiers.into()
|
||||
}
|
||||
|
||||
/// Retrieves and passes the default item identifiers for this toolbar.
|
||||
extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||
|
||||
let identifiers: NSArray = toolbar.default_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).collect::<Vec<id>>().into();
|
||||
let identifiers: NSArray = toolbar.default_item_identifiers()
|
||||
.iter()
|
||||
.map(|identifier| identifier.to_nsstring())
|
||||
.collect::<Vec<id>>()
|
||||
.into();
|
||||
|
||||
identifiers.into_inner()
|
||||
identifiers.into()
|
||||
}
|
||||
|
||||
/// Retrieves and passes the default item identifiers for this toolbar.
|
||||
extern fn selectable_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||
let toolbar = load::<T>(this, TOOLBAR_PTR);
|
||||
|
||||
let identifiers: NSArray = toolbar.selectable_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).collect::<Vec<id>>().into();
|
||||
let identifiers: NSArray = toolbar.selectable_item_identifiers()
|
||||
.iter()
|
||||
.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
|
||||
/// 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 identifier = NSString::wrap(identifier);
|
||||
let identifier = NSString::from_retained(identifier);
|
||||
|
||||
let item = toolbar.item_for(identifier.to_str());
|
||||
unsafe {
|
||||
|
@ -64,9 +74,21 @@ pub(crate) fn register_toolbar_class<T: ToolbarDelegate>(instance: &T) -> *const
|
|||
decl.add_ivar::<usize>(TOOLBAR_PTR);
|
||||
|
||||
// Add callback methods
|
||||
decl.add_method(sel!(toolbarAllowedItemIdentifiers:), allowed_item_identifiers::<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);
|
||||
decl.add_method(
|
||||
sel!(toolbarAllowedItemIdentifiers:),
|
||||
allowed_item_identifiers::<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.
|
||||
|
||||
use crate::foundation::NSUInteger;
|
||||
use crate::foundation::{id, NSString, NSUInteger};
|
||||
|
||||
/// Represents the display mode(s) a Toolbar can render in.
|
||||
#[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.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum ToolbarSizeMode {
|
||||
|
|
|
@ -7,10 +7,10 @@ use std::fmt;
|
|||
use core_graphics::geometry::CGSize;
|
||||
|
||||
use objc_id::{Id, ShareId};
|
||||
use objc::runtime::{Object};
|
||||
use objc::runtime::Object;
|
||||
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::button::{Button, BezelStyle};
|
||||
use crate::image::Image;
|
||||
|
@ -39,8 +39,18 @@ impl ToolbarItem {
|
|||
};
|
||||
|
||||
ToolbarItem {
|
||||
identifier: identifier,
|
||||
objc: objc,
|
||||
identifier,
|
||||
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,
|
||||
image: None,
|
||||
handler: None
|
||||
|
@ -50,7 +60,7 @@ impl ToolbarItem {
|
|||
/// Sets the title for this item.
|
||||
pub fn set_title(&mut self, title: &str) {
|
||||
unsafe {
|
||||
let title = NSString::new(title).into_inner();
|
||||
let title = NSString::new(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) {
|
||||
let handler = TargetActionHandler::new(&*self.objc, action);
|
||||
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;
|
||||
|
||||
mod enums;
|
||||
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode};
|
||||
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode, ItemIdentifier};
|
||||
|
||||
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
||||
|
||||
|
@ -67,9 +67,9 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
|
|||
});
|
||||
|
||||
Toolbar {
|
||||
identifier: identifier,
|
||||
objc: objc,
|
||||
objc_delegate: objc_delegate,
|
||||
identifier,
|
||||
objc,
|
||||
objc_delegate,
|
||||
delegate: Some(delegate),
|
||||
}
|
||||
}
|
||||
|
@ -117,10 +117,10 @@ impl<T> Toolbar<T> {
|
|||
|
||||
/// Sets the item represented by the item identifier to be selected.
|
||||
pub fn set_selected(&self, item_identifier: &str) {
|
||||
let identifier = NSString::new(item_identifier).into_inner();
|
||||
let identifier = NSString::new(item_identifier);
|
||||
|
||||
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
|
||||
//! 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`.
|
||||
pub trait ToolbarDelegate {
|
||||
|
@ -23,14 +23,14 @@ pub trait ToolbarDelegate {
|
|||
fn did_load(&mut self, _toolbar: 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.
|
||||
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
|
||||
/// 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.
|
||||
fn item_for(&self, _identifier: &str) -> &ToolbarItem;
|
||||
|
|
|
@ -46,7 +46,7 @@ impl Default for WindowConfig {
|
|||
|
||||
config.set_styles(&[
|
||||
WindowStyle::Resizable, WindowStyle::Miniaturizable, WindowStyle::UnifiedTitleAndToolbar,
|
||||
WindowStyle::Closable, WindowStyle::Titled
|
||||
WindowStyle::Closable, WindowStyle::Titled, WindowStyle::FullSizeContentView
|
||||
]);
|
||||
|
||||
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.
|
||||
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||
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.
|
||||
pub fn set_toolbar<TC: ToolbarDelegate>(&self, toolbar: &Toolbar<TC>) {
|
||||
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
|
||||
/// window.
|
||||
///
|
||||
|
|
|
@ -20,7 +20,7 @@ impl URLRequest {
|
|||
}
|
||||
|
||||
pub fn url(&self) -> String {
|
||||
NSString::wrap(unsafe {
|
||||
NSString::from_retained(unsafe {
|
||||
let url: id = msg_send![&*self.inner, URL];
|
||||
msg_send![url, absoluteString]
|
||||
}).to_string()
|
||||
|
|
|
@ -1314,10 +1314,10 @@ pub enum NotificationName {
|
|||
WKAccessibilityReduceMotionStatusDidChange
|
||||
}
|
||||
|
||||
impl From<NotificationName> for NSString {
|
||||
impl From<NotificationName> for NSString<'_> {
|
||||
fn from(name: NotificationName) -> Self {
|
||||
match name {
|
||||
_ => NSString::new("")
|
||||
_ => NSString::no_copy("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ impl Pasteboard {
|
|||
pub fn named(name: PasteboardName) -> Self {
|
||||
Pasteboard(unsafe {
|
||||
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
|
||||
/// this, but considering this stuff happens on the Objective-C side you may need it.
|
||||
pub fn release_globally(&self) {
|
||||
|
@ -85,8 +95,8 @@ impl Pasteboard {
|
|||
}));
|
||||
}
|
||||
|
||||
let urls = NSArray::wrap(contents).map(|url| {
|
||||
let path = NSString::wrap(msg_send![url, path]);
|
||||
let urls = NSArray::retain(contents).map(|url| {
|
||||
let path = NSString::retain(msg_send![url, path]);
|
||||
Url::parse(&format!("file://{}", path.to_str()))
|
||||
}).into_iter().filter_map(|r| r.ok()).collect();
|
||||
|
||||
|
@ -114,8 +124,8 @@ impl Pasteboard {
|
|||
}));
|
||||
}
|
||||
|
||||
let urls = NSArray::wrap(contents).map(|url| {
|
||||
let path = NSString::wrap(msg_send![url, path]).to_str().to_string();
|
||||
let urls = NSArray::retain(contents).map(|url| {
|
||||
let path = NSString::retain(msg_send![url, path]).to_str().to_string();
|
||||
PathBuf::from(path)
|
||||
}).into_iter().collect();
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ pub enum PasteboardName {
|
|||
Ruler
|
||||
}
|
||||
|
||||
impl From<PasteboardName> for NSString {
|
||||
impl From<PasteboardName> for NSString<'_> {
|
||||
fn from(name: PasteboardName) -> Self {
|
||||
NSString::new(match name {
|
||||
PasteboardName::Drag => "Apple CFPasteboard drag",
|
||||
|
@ -83,7 +83,7 @@ pub enum PasteboardType {
|
|||
TIFF
|
||||
}
|
||||
|
||||
impl From<PasteboardType> for NSString {
|
||||
impl From<PasteboardType> for NSString<'_> {
|
||||
fn from(pboard_type: PasteboardType) -> Self {
|
||||
NSString::new(match pboard_type {
|
||||
PasteboardType::URL => "public.url",
|
||||
|
|
|
@ -109,7 +109,7 @@ impl ThumbnailConfig {
|
|||
unsafe {
|
||||
let size = CGSize::new(self.size.0, self.size.1);
|
||||
// @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![request, initWithFileAtURL:from_url
|
||||
|
|
|
@ -19,7 +19,7 @@ impl Default for Font {
|
|||
objc: unsafe {
|
||||
let cls = class!(NSFont);
|
||||
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 {
|
||||
Font {
|
||||
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")]
|
||||
use ios::{register_view_class, register_view_class_with_delegate};
|
||||
|
||||
//mod controller;
|
||||
//pub use controller::LabelController;
|
||||
|
||||
mod traits;
|
||||
pub use traits::LabelDelegate;
|
||||
|
||||
|
@ -75,8 +72,8 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
|||
#[cfg(target_os = "macos")]
|
||||
let view: id = {
|
||||
// This sucks, but for now, sure.
|
||||
let blank = NSString::new("");
|
||||
msg_send![registration_fn(), labelWithString:blank.into_inner()]
|
||||
let blank = NSString::no_copy("");
|
||||
msg_send![registration_fn(), labelWithString:&*blank]
|
||||
};
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
|
@ -230,17 +227,17 @@ impl<T> Label<T> {
|
|||
let s = NSString::new(text);
|
||||
|
||||
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.
|
||||
pub fn text(&self) -> String {
|
||||
let s = NSString::wrap(unsafe {
|
||||
pub fn get_text(&self) -> String {
|
||||
let s = NSString::retain(unsafe {
|
||||
msg_send![&*self.objc, stringValue]
|
||||
});
|
||||
|
||||
s.to_str().to_string()
|
||||
s.to_string()
|
||||
}
|
||||
|
||||
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) {
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe {
|
||||
|
|
|
@ -68,6 +68,9 @@ use ios::{register_view_class, register_view_class_with_delegate};
|
|||
mod controller;
|
||||
pub use controller::ViewController;
|
||||
|
||||
mod splitviewcontroller;
|
||||
pub use splitviewcontroller::SplitViewController;
|
||||
|
||||
mod traits;
|
||||
pub use traits::ViewDelegate;
|
||||
|
||||
|
@ -199,7 +202,7 @@ impl<T> View<T> {
|
|||
height: self.height.clone(),
|
||||
center_x: self.center_x.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 {
|
||||
(&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.
|
||||
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
||||
unsafe {
|
||||
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 is just an enum... and this is not an oft called method.
|
||||
let x: NSString = t.clone().into();
|
||||
x.into_inner()
|
||||
let x: NSString = (*t).into();
|
||||
x.into()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
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