Fix NSData wrapper so storing and retrieving bytes in UserDefaults works as expected, with hopefully little to no cloning issues. Wrap the NSNumber logic into a proper type. More documentation/cleanup, still ongoing.
This commit is contained in:
parent
3f9c9f992c
commit
f4ca9770e1
|
@ -28,7 +28,7 @@ struct BasicApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppDelegate for BasicApp {
|
impl AppDelegate for BasicApp {
|
||||||
fn did_finish_launching(&mut self) {
|
fn did_finish_launching(&self) {
|
||||||
self.window.set_minimum_content_size(400., 400.);
|
self.window.set_minimum_content_size(400., 400.);
|
||||||
self.window.set_title("Hello World!");
|
self.window.set_title("Hello World!");
|
||||||
self.window.show();
|
self.window.show();
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cacao::macos::app::{App, AppDelegate};
|
use cacao::macos::app::{App, AppDelegate};
|
||||||
use cacao::defaults::{UserDefaults, DefaultValue};
|
use cacao::defaults::{UserDefaults, Value};
|
||||||
|
use cacao::foundation::NSData;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct DefaultsTest;
|
struct DefaultsTest;
|
||||||
|
@ -14,16 +15,39 @@ impl AppDelegate for DefaultsTest {
|
||||||
|
|
||||||
defaults.register({
|
defaults.register({
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("LOL", DefaultValue::string("laugh"));
|
//map.insert("LOL", Value::string("laugh"));
|
||||||
map.insert("X", DefaultValue::Integer(1));
|
//map.insert("X", Value::Integer(1));
|
||||||
map.insert("X2", DefaultValue::Float(1.0));
|
//map.insert("X2", Value::Float(1.0));
|
||||||
map.insert("BOOL", DefaultValue::bool(true));
|
map.insert("BOOL", Value::bool(true));
|
||||||
|
|
||||||
|
println!("Test equivalency:");
|
||||||
|
let s = "BYTES TEST".to_string().into_bytes();
|
||||||
|
println!(" {:?}", s);
|
||||||
|
let x = NSData::new(s);
|
||||||
|
println!(" {:?}", x.bytes());
|
||||||
|
|
||||||
|
let s2 = "BYTES TEST".to_string().into_bytes();
|
||||||
|
map.insert("BYTES", Value::Data(s2));
|
||||||
|
|
||||||
map
|
map
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("Retrieved LOL: {:?}", defaults.get("LOL"));
|
//println!("Retrieved LOL: {:?}", defaults.get("LOL"));
|
||||||
println!("Retrieved LOL: {:?}", defaults.get("X"));
|
//println!("Retrieved LOL: {:?}", defaults.get("X"));
|
||||||
println!("Retrieved LOL: {:?}", defaults.get("X2"));
|
//println!("Retrieved LOL: {:?}", defaults.get("X2"));
|
||||||
|
|
||||||
|
let bytes = defaults.get("BYTES").unwrap();
|
||||||
|
println!("Bytes: {:?}", bytes);
|
||||||
|
let data = match std::str::from_utf8(bytes.as_data().unwrap()) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error converting bytes {}", e);
|
||||||
|
"Error converting bytes"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Retrieved Bytes: {}", data);
|
||||||
|
|
||||||
|
|
||||||
App::terminate();
|
App::terminate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Wraps `NSUserDefaults`, providing an interface to fetch and store small amounts of data.
|
//! Wraps `NSUserDefaults`, providing an interface to fetch and store small amounts of data.
|
||||||
//!
|
//!
|
||||||
//! In general, this tries to take an approach popularized by `serde_json`'s `Value` struct. In
|
//! In general, this tries to take an approach popularized by `serde_json`'s `Value` struct. In
|
||||||
//! this case, `DefaultValue` handles wrapping types for insertion/retrieval, shepherding between
|
//! this case, `Value` handles wrapping types for insertion/retrieval, shepherding between
|
||||||
//! the Objective-C runtime and your Rust code.
|
//! the Objective-C runtime and your Rust code.
|
||||||
//!
|
//!
|
||||||
//! It currently supports a number of primitive types, as well as a generic `Data` type for custom
|
//! It currently supports a number of primitive types, as well as a generic `Data` type for custom
|
||||||
|
@ -17,13 +17,13 @@
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use std::collections::HashMap;
|
//! use std::collections::HashMap;
|
||||||
//! use cacao::defaults::{UserDefaults, DefaultValue};
|
//! use cacao::defaults::{UserDefaults, Value};
|
||||||
//!
|
//!
|
||||||
//! let mut defaults = UserDefaults::standard();
|
//! let mut defaults = UserDefaults::standard();
|
||||||
//!
|
//!
|
||||||
//! defaults.register({
|
//! defaults.register({
|
||||||
//! let map = HashMap::new();
|
//! let map = HashMap::new();
|
||||||
//! map.insert("test", DefaultValue::string("value"));
|
//! map.insert("test", Value::string("value"));
|
||||||
//! map
|
//! map
|
||||||
//! });
|
//! });
|
||||||
//!
|
//!
|
||||||
|
@ -33,18 +33,15 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::unreachable;
|
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, BOOL, NSInteger, NSString, NSDictionary};
|
use crate::foundation::{id, nil, YES, BOOL, NSData, NSInteger, NSString, NSDictionary, NSNumber};
|
||||||
|
|
||||||
mod value;
|
mod value;
|
||||||
pub use value::DefaultValue;
|
pub use value::Value;
|
||||||
|
|
||||||
/// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing
|
/// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing
|
||||||
/// pieces of information (preferences, or _defaults_) to persist across application launches.
|
/// pieces of information (preferences, or _defaults_) to persist across application launches.
|
||||||
|
@ -106,17 +103,17 @@ impl UserDefaults {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use std::collections::HashMap;
|
/// use std::collections::HashMap;
|
||||||
///
|
///
|
||||||
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
/// use cacao::defaults::{UserDefaults, Value};
|
||||||
///
|
///
|
||||||
/// let mut defaults = UserDefaults::standard();
|
/// let mut defaults = UserDefaults::standard();
|
||||||
///
|
///
|
||||||
/// defaults.register({
|
/// defaults.register({
|
||||||
/// let mut map = HashMap::new();
|
/// let mut map = HashMap::new();
|
||||||
/// map.insert("test", DefaultValue::Bool(true));
|
/// map.insert("test", Value::Bool(true));
|
||||||
/// map
|
/// map
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, DefaultValue>) {
|
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, Value>) {
|
||||||
let dictionary = NSDictionary::from(values);
|
let dictionary = NSDictionary::from(values);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -128,14 +125,14 @@ impl UserDefaults {
|
||||||
/// `NSUserDefaults` store, and asynchronously persists to the disk.
|
/// `NSUserDefaults` store, and asynchronously persists to the disk.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
/// use cacao::defaults::{UserDefaults, Value};
|
||||||
///
|
///
|
||||||
/// let mut defaults = UserDefaults::standard();
|
/// let mut defaults = UserDefaults::standard();
|
||||||
/// defaults.insert("test", DefaultValue::Bool(true));
|
/// defaults.insert("test", Value::Bool(true));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn insert<K: AsRef<str>>(&mut self, key: K, value: DefaultValue) {
|
pub fn insert<K: AsRef<str>>(&mut self, key: K, value: Value) {
|
||||||
let key = NSString::new(key.as_ref());
|
let key = NSString::new(key.as_ref());
|
||||||
let value: id = (&value).into();
|
let value: id = value.into();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.0, setObject:value forKey:key];
|
let _: () = msg_send![&*self.0, setObject:value forKey:key];
|
||||||
|
@ -145,7 +142,7 @@ impl UserDefaults {
|
||||||
/// Remove the default associated with the key. If the key doesn't exist, this is a noop.
|
/// Remove the default associated with the key. If the key doesn't exist, this is a noop.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
/// use cacao::defaults::{UserDefaults, Value};
|
||||||
///
|
///
|
||||||
/// let mut defaults = UserDefaults::standard();
|
/// let mut defaults = UserDefaults::standard();
|
||||||
/// defaults.remove("test");
|
/// defaults.remove("test");
|
||||||
|
@ -158,7 +155,7 @@ impl UserDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a `DefaultValue` for the given key, from which you can further extract the data you
|
/// Returns a `Value` for the given key, from which you can further extract the data you
|
||||||
/// need. Note that this does a `nil` check and will return `None` in such cases, with the
|
/// need. Note that this does a `nil` check and will return `None` in such cases, with the
|
||||||
/// exception of `bool` values, where it will always return either `true` or `false`. This is
|
/// exception of `bool` values, where it will always return either `true` or `false`. This is
|
||||||
/// due to the underlying storage engine used for `NSUserDefaults`.
|
/// due to the underlying storage engine used for `NSUserDefaults`.
|
||||||
|
@ -167,15 +164,15 @@ impl UserDefaults {
|
||||||
/// returns new immutable copies each time, so we're free to vend them as such.
|
/// returns new immutable copies each time, so we're free to vend them as such.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
/// use cacao::defaults::{UserDefaults, Value};
|
||||||
///
|
///
|
||||||
/// let mut defaults = UserDefaults::standard();
|
/// let mut defaults = UserDefaults::standard();
|
||||||
/// defaults.insert("test", DefaultValue::string("value"));
|
/// defaults.insert("test", Value::string("value"));
|
||||||
///
|
///
|
||||||
/// let value = defaults.get("test").unwrap().as_str().unwrap();
|
/// let value = defaults.get("test").unwrap().as_str().unwrap();
|
||||||
/// assert_eq!(value, "value");
|
/// assert_eq!(value, "value");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get<K: AsRef<str>>(&self, key: K) -> Option<DefaultValue> {
|
pub fn get<K: AsRef<str>>(&self, key: K) -> Option<Value> {
|
||||||
let key = NSString::new(key.as_ref());
|
let key = NSString::new(key.as_ref());
|
||||||
|
|
||||||
let result: id = unsafe {
|
let result: id = unsafe {
|
||||||
|
@ -186,36 +183,32 @@ impl UserDefaults {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_string: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSString)] };
|
if NSData::is(result) {
|
||||||
if is_string == YES {
|
let data = NSData::wrap(result);
|
||||||
|
return Some(Value::Data(data.into_vec()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if NSString::is(result) {
|
||||||
let s = NSString::wrap(result).to_str().to_string();
|
let s = NSString::wrap(result).to_str().to_string();
|
||||||
return Some(DefaultValue::String(s));
|
return Some(Value::String(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
// This works, but might not be the best approach. We basically need to inspect the
|
// This works, but might not be the best approach. We basically need to inspect the
|
||||||
// `NSNumber` returned and see what the wrapped encoding type is. `q` and `d` represent
|
// `NSNumber` returned and see what the wrapped encoding type is. `q` and `d` represent
|
||||||
// `NSInteger` (platform specific) and `double` (f64) respectively, but conceivably we
|
// `NSInteger` (platform specific) and `double` (f64) respectively, but conceivably we
|
||||||
// might need others.
|
// might need others.
|
||||||
let is_number: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSNumber)] };
|
if NSNumber::is(result) {
|
||||||
if is_number == YES {
|
let number = NSNumber::wrap(result);
|
||||||
unsafe {
|
|
||||||
let t: *const c_char = msg_send![result, objCType];
|
|
||||||
let slice = CStr::from_ptr(t);
|
|
||||||
|
|
||||||
if let Ok(code) = slice.to_str() {
|
return match number.objc_type() {
|
||||||
println!("Code: {}", code);
|
"q" => Some(Value::Integer(number.as_i64())),
|
||||||
|
"d" => Some(Value::Float(number.as_f64())),
|
||||||
|
|
||||||
if code == "q" {
|
_ => {
|
||||||
let v: NSInteger = msg_send![result, integerValue];
|
// @TODO: Verify this area.
|
||||||
return Some(DefaultValue::Integer(v as i64));
|
None
|
||||||
}
|
|
||||||
|
|
||||||
if code == "d" {
|
|
||||||
let v: f64 = msg_send![result, doubleValue];
|
|
||||||
return Some(DefaultValue::Float(v));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
|
|
|
@ -1,35 +1,44 @@
|
||||||
//!
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, YES, NO, nil, NSInteger, NSDictionary, NSString};
|
use crate::foundation::{id, YES, NO, NSData, NSInteger, NSDictionary, NSString};
|
||||||
|
|
||||||
|
/// Represents a Value that can be stored or queried with `UserDefaults`.
|
||||||
|
///
|
||||||
|
/// In general, this wraps a few types that should hopefully work for most cases. Note that the
|
||||||
|
/// `Value` always owns whatever it holds - this is both for ergonomic considerations, as
|
||||||
|
/// well as contractual obligations with the underlying `NSUserDefaults` system.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum DefaultValue {
|
pub enum Value {
|
||||||
|
/// Represents a Boolean value.
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
|
||||||
|
/// Represents a String value.
|
||||||
String(String),
|
String(String),
|
||||||
|
|
||||||
|
/// Represents a Float (`f64`) value.
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Integer(i64)
|
|
||||||
|
/// Represents an Integer (`i64`) value.
|
||||||
|
Integer(i64),
|
||||||
|
|
||||||
|
/// Represents Data (bytes). You can use this to store arbitrary things that aren't supported
|
||||||
|
/// above. You're responsible for moving things back and forth to the necessary types.
|
||||||
|
Data(Vec<u8>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultValue {
|
impl Value {
|
||||||
/// A handy initializer for `DefaultValue::Bool`.
|
/// A handy initializer for `Value::String`.
|
||||||
pub fn bool(value: bool) -> Self {
|
|
||||||
DefaultValue::Bool(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handy initializer for `DefaultValue::String`;
|
|
||||||
pub fn string<S: Into<String>>(value: S) -> Self {
|
pub fn string<S: Into<String>>(value: S) -> Self {
|
||||||
DefaultValue::String(value.into())
|
Value::String(value.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the value is a boolean value. Returns `false` otherwise.
|
/// Returns `true` if the value is a boolean value. Returns `false` otherwise.
|
||||||
pub fn is_boolean(&self) -> bool {
|
pub fn is_boolean(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Bool(_) => true,
|
Value::Bool(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +46,7 @@ impl DefaultValue {
|
||||||
/// If this is a Bool, it returns the associated bool. Returns `None` otherwise.
|
/// If this is a Bool, it returns the associated bool. Returns `None` otherwise.
|
||||||
pub fn as_bool(&self) -> Option<bool> {
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Bool(v) => Some(*v),
|
Value::Bool(v) => Some(*v),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +54,7 @@ impl DefaultValue {
|
||||||
/// Returns `true` if the value is a string. Returns `false` otherwise.
|
/// Returns `true` if the value is a string. Returns `false` otherwise.
|
||||||
pub fn is_string(&self) -> bool {
|
pub fn is_string(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::String(_) => true,
|
Value::String(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +62,7 @@ impl DefaultValue {
|
||||||
/// If this is a String, it returns a &str. Returns `None` otherwise.
|
/// If this is a String, it returns a &str. Returns `None` otherwise.
|
||||||
pub fn as_str(&self) -> Option<&str> {
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::String(s) => Some(s),
|
Value::String(s) => Some(s),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +70,7 @@ impl DefaultValue {
|
||||||
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
||||||
pub fn is_integer(&self) -> bool {
|
pub fn is_integer(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Integer(_) => true,
|
Value::Integer(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,7 +78,7 @@ impl DefaultValue {
|
||||||
/// If this is a int, returns it (`i32`). Returns `None` otherwise.
|
/// If this is a int, returns it (`i32`). Returns `None` otherwise.
|
||||||
pub fn as_i32(&self) -> Option<i32> {
|
pub fn as_i32(&self) -> Option<i32> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Integer(i) => Some(*i as i32),
|
Value::Integer(i) => Some(*i as i32),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +86,7 @@ impl DefaultValue {
|
||||||
/// If this is a int, returns it (`i64`). Returns `None` otherwise.
|
/// If this is a int, returns it (`i64`). Returns `None` otherwise.
|
||||||
pub fn as_i64(&self) -> Option<i64> {
|
pub fn as_i64(&self) -> Option<i64> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Integer(i) => Some(*i as i64),
|
Value::Integer(i) => Some(*i as i64),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +94,7 @@ impl DefaultValue {
|
||||||
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
||||||
pub fn is_float(&self) -> bool {
|
pub fn is_float(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Float(_) => true,
|
Value::Float(_) => true,
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +102,7 @@ impl DefaultValue {
|
||||||
/// If this is a float, returns it (`f32`). Returns `None` otherwise.
|
/// If this is a float, returns it (`f32`). Returns `None` otherwise.
|
||||||
pub fn as_f32(&self) -> Option<f32> {
|
pub fn as_f32(&self) -> Option<f32> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Float(f) => Some(*f as f32),
|
Value::Float(f) => Some(*f as f32),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,42 +110,60 @@ impl DefaultValue {
|
||||||
/// If this is a float, returns it (`f64`). Returns `None` otherwise.
|
/// If this is a float, returns it (`f64`). Returns `None` otherwise.
|
||||||
pub fn as_f64(&self) -> Option<f64> {
|
pub fn as_f64(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
DefaultValue::Float(f) => Some(*f as f64),
|
Value::Float(f) => Some(*f as f64),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the value is data. Returns `false` otherwise.
|
||||||
|
pub fn is_data(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Value::Data(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is data, returns it (`&[u8]`). If you need to own the underlying buffer, you can
|
||||||
|
/// extract it yourself. Returns `None` if this is not Data.
|
||||||
|
pub fn as_data(&self) -> Option<&[u8]> {
|
||||||
|
match self {
|
||||||
|
Value::Data(data) => Some(data),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&DefaultValue> for id {
|
impl From<Value> for id {
|
||||||
/// Shepherds `DefaultValue` types into `NSObject`s that can be stored in `NSUserDefaults`.
|
/// Shepherds `Value` types into `NSObject`s that can be stored in `NSUserDefaults`.
|
||||||
// These currently work, but may not be exhaustive and should be looked over past the preview
|
// These currently work, but may not be exhaustive and should be looked over past the preview
|
||||||
// period.
|
// period.
|
||||||
fn from(value: &DefaultValue) -> Self {
|
fn from(value: Value) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
match value {
|
match value {
|
||||||
DefaultValue::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b {
|
Value::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}],
|
}],
|
||||||
|
|
||||||
DefaultValue::String(s) => NSString::new(&s).into_inner(),
|
Value::String(s) => NSString::new(&s).into_inner(),
|
||||||
DefaultValue::Float(f) => msg_send![class!(NSNumber), numberWithDouble:*f],
|
Value::Float(f) => msg_send![class!(NSNumber), numberWithDouble:f],
|
||||||
DefaultValue::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:*i as NSInteger]
|
Value::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:i as NSInteger],
|
||||||
|
Value::Data(data) => NSData::new(data).into_inner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K> From<HashMap<K, DefaultValue>> for NSDictionary
|
impl<K> From<HashMap<K, Value>> for NSDictionary
|
||||||
where
|
where
|
||||||
K: AsRef<str>
|
K: AsRef<str>
|
||||||
{
|
{
|
||||||
/// Translates a `HashMap` of `DefaultValue`s into an `NSDictionary`.
|
/// Translates a `HashMap` of `Value`s into an `NSDictionary`.
|
||||||
fn from(map: HashMap<K, DefaultValue>) -> Self {
|
fn from(map: HashMap<K, Value>) -> Self {
|
||||||
NSDictionary(unsafe {
|
NSDictionary(unsafe {
|
||||||
let dictionary: id = msg_send![class!(NSMutableDictionary), new];
|
let dictionary: id = msg_send![class!(NSMutableDictionary), new];
|
||||||
|
|
||||||
for (key, value) in map.iter() {
|
for (key, value) in map.into_iter() {
|
||||||
let k = NSString::new(key.as_ref());
|
let k = NSString::new(key.as_ref());
|
||||||
let v: id = value.into();
|
let v: id = value.into();
|
||||||
let _: () = msg_send![dictionary, setObject:v forKey:k];
|
let _: () = msg_send![dictionary, setObject:v forKey:k];
|
||||||
|
|
129
src/foundation/data.rs
Normal file
129
src/foundation/data.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
//! 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::os::raw::c_void;
|
||||||
|
use std::slice;
|
||||||
|
|
||||||
|
use block::{Block, ConcreteBlock};
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, BOOL, YES, NO, NSUInteger};
|
||||||
|
|
||||||
|
/// Wrapper for a retained `NSData` object.
|
||||||
|
///
|
||||||
|
/// 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>`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NSData(pub Id<Object>);
|
||||||
|
|
||||||
|
impl NSData {
|
||||||
|
/// Given a vector of bytes, creates, retains, and returns a wrapped `NSData`.
|
||||||
|
///
|
||||||
|
/// This method is borrowed straight out of [objc-foundation](objc-foundation) by the amazing
|
||||||
|
/// Steven Sheldon, and just tweaked slightly to fit the desired API semantics here.
|
||||||
|
///
|
||||||
|
/// [objc-foundation]: https://crates.io/crates/objc-foundation
|
||||||
|
pub fn new(bytes: Vec<u8>) -> Self {
|
||||||
|
let capacity = bytes.capacity();
|
||||||
|
|
||||||
|
let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe {
|
||||||
|
// Recreate the Vec and let it drop
|
||||||
|
let _ = Vec::from_raw_parts(bytes as *mut u8, len, capacity);
|
||||||
|
});
|
||||||
|
let dealloc = dealloc.copy();
|
||||||
|
let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc;
|
||||||
|
|
||||||
|
let mut bytes = bytes;
|
||||||
|
let bytes_ptr = bytes.as_mut_ptr() as *mut c_void;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let obj: id = msg_send![class!(NSData), alloc];
|
||||||
|
let obj: id = msg_send![obj, initWithBytesNoCopy:bytes_ptr
|
||||||
|
length:bytes.len()
|
||||||
|
deallocator:dealloc];
|
||||||
|
mem::forget(bytes);
|
||||||
|
NSData(Id::from_ptr(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
NSData(unsafe {
|
||||||
|
Id::from_ptr(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper method for determining if a given `NSObject` is an `NSData`.
|
||||||
|
pub fn is(obj: id) -> bool {
|
||||||
|
let result: BOOL = unsafe {
|
||||||
|
msg_send![obj, isKindOfClass:class!(NSData)]
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
YES => true,
|
||||||
|
NO => false,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the underlying `NSData` bytes.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
unsafe {
|
||||||
|
let x: NSUInteger = msg_send![&*self.0, length];
|
||||||
|
x as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the underlying bytes for the wrapped `NSData`.
|
||||||
|
///
|
||||||
|
/// This, like `NSData::new()`, is cribbed from [objc-foundation](objc-foundation).
|
||||||
|
///
|
||||||
|
/// [objc-foundation](https://crates.io/crates/objc-foundation)
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
let ptr: *const c_void = unsafe { msg_send![&*self.0, bytes] };
|
||||||
|
|
||||||
|
// The bytes pointer may be null for length zero
|
||||||
|
let (ptr, len) = if ptr.is_null() {
|
||||||
|
(0x1 as *const u8, 0)
|
||||||
|
} else {
|
||||||
|
(ptr as *const u8, self.len())
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
slice::from_raw_parts(ptr, len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Vec, copies the NSData (safely, but quickly) bytes into that Vec, and
|
||||||
|
/// consumes the NSData enabling it to release (provided nothing in Cocoa is using it).
|
||||||
|
///
|
||||||
|
// A point of discussion: I think this is the safest way to handle it, however I could be
|
||||||
|
// wrong - it's a bit defensive but I can't think of a way to reliably return an owned set of
|
||||||
|
// this data without messing up the Objective-C side of things. Thankfully this isn't used too
|
||||||
|
// often, but still... open to ideas.
|
||||||
|
pub fn into_vec(self) -> Vec<u8> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
|
||||||
|
let bytes = self.bytes();
|
||||||
|
data.resize(bytes.len(), 0);
|
||||||
|
data.copy_from_slice(bytes);
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and returns the underlying `NSData`.
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSString};
|
use crate::foundation::id;
|
||||||
|
|
||||||
/// A wrapper for `NSDictionary`. Behind the scenes we actually wrap `NSMutableDictionary`, and
|
/// 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.
|
/// rely on Rust doing the usual borrow-checking guards that it does so well.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
//! This module contains some lightweight wrappers over certain data types that we use throughout
|
//! This module contains some lightweight wrappers over Foundation data types.
|
||||||
//! the framework. Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While
|
//!
|
||||||
|
//! Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While
|
||||||
//! this isn't a clone of their module (we don't need everything from there, but remaining
|
//! this isn't a clone of their module (we don't need everything from there, but remaining
|
||||||
//! compatible in case an end-user wants to drop that low is deal), it's worth linking their
|
//! compatible in case an end-user wants to drop that low is deal), it's worth linking their
|
||||||
//! license and repository - they've done really incredible work and it's 100% worth acknowledging.
|
//! license and repository - they've done really incredible work and it's 100% worth acknowledging.
|
||||||
|
@ -7,6 +8,12 @@
|
||||||
//! - [core-foundation-rs Repository](https://github.com/servo/core-foundation-rs)
|
//! - [core-foundation-rs Repository](https://github.com/servo/core-foundation-rs)
|
||||||
//! - [core-foundation-rs MIT License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-MIT)
|
//! - [core-foundation-rs MIT License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-MIT)
|
||||||
//! - [core-foundation-rs Apache License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-APACHE)
|
//! - [core-foundation-rs Apache License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-APACHE)
|
||||||
|
//!
|
||||||
|
//! ## Why?
|
||||||
|
//! A good question. The existing wrappers tend to use traits over `id`, which works well for some
|
||||||
|
//! cases, but I found it frustrating and messy trying to work with them. These provide the API I
|
||||||
|
//! was looking for, and help provide all the proper `retain`/`release` logic needed for the
|
||||||
|
//! Objective-C side.
|
||||||
|
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
@ -14,30 +21,44 @@
|
||||||
use objc::runtime;
|
use objc::runtime;
|
||||||
pub use objc::runtime::{BOOL, NO, YES};
|
pub use objc::runtime::{BOOL, NO, YES};
|
||||||
|
|
||||||
pub mod autoreleasepool;
|
mod autoreleasepool;
|
||||||
pub use autoreleasepool::AutoReleasePool;
|
pub use autoreleasepool::AutoReleasePool;
|
||||||
|
|
||||||
pub mod array;
|
mod array;
|
||||||
pub use array::NSArray;
|
pub use array::NSArray;
|
||||||
|
|
||||||
pub mod string;
|
mod data;
|
||||||
pub use string::NSString;
|
pub use data::NSData;
|
||||||
|
|
||||||
pub mod dictionary;
|
mod dictionary;
|
||||||
pub use dictionary::NSDictionary;
|
pub use dictionary::NSDictionary;
|
||||||
|
|
||||||
|
mod number;
|
||||||
|
pub use number::NSNumber;
|
||||||
|
|
||||||
|
mod string;
|
||||||
|
pub use string::NSString;
|
||||||
|
|
||||||
|
/// More or less maps over to Objective-C's `id` type, which... can really be anything.
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub type id = *mut runtime::Object;
|
pub type id = *mut runtime::Object;
|
||||||
|
|
||||||
|
/// Exactly what it sounds like.
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
pub const nil: id = 0 as id;
|
pub const nil: id = 0 as id;
|
||||||
|
|
||||||
|
/// Platform-specific.
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
pub type NSInteger = libc::c_int;
|
pub type NSInteger = libc::c_int;
|
||||||
|
|
||||||
|
/// Platform-specific.
|
||||||
#[cfg(target_pointer_width = "32")]
|
#[cfg(target_pointer_width = "32")]
|
||||||
pub type NSUInteger = libc::c_uint;
|
pub type NSUInteger = libc::c_uint;
|
||||||
|
|
||||||
|
/// Platform-specific.
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
pub type NSInteger = libc::c_long;
|
pub type NSInteger = libc::c_long;
|
||||||
|
|
||||||
|
/// Platform-specific.
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
pub type NSUInteger = libc::c_ulong;
|
pub type NSUInteger = libc::c_ulong;
|
||||||
|
|
88
src/foundation/number.rs
Normal file
88
src/foundation/number.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
//! A wrapper for `NSNumber`.
|
||||||
|
//!
|
||||||
|
//! There are a few places where we have to interact with this type (e.g, `NSUserDefaults`) and so
|
||||||
|
//! this type exists to wrap those unsafe operations.
|
||||||
|
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, BOOL, YES, NO, NSInteger};
|
||||||
|
|
||||||
|
/// Wrapper for a retained `NSNumber` object.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NSNumber(pub Id<Object>);
|
||||||
|
|
||||||
|
impl NSNumber {
|
||||||
|
pub fn bool(value: bool) -> Self {
|
||||||
|
NSNumber(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSNumber), numberWithBool:match value {
|
||||||
|
true => YES,
|
||||||
|
false => NO,
|
||||||
|
_ => unreachable!()
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn integer(value: i64) -> Self {
|
||||||
|
NSNumber(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn float(value: f64) -> Self {
|
||||||
|
NSNumber(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSNumber), numberWithDouble:value])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn objc_type(&self) -> &str {
|
||||||
|
unsafe {
|
||||||
|
let t: *const c_char = msg_send![&*self.0, objCType];
|
||||||
|
let slice = CStr::from_ptr(t);
|
||||||
|
slice.to_str().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_i64(&self) -> i64 {
|
||||||
|
unsafe {
|
||||||
|
let i: NSInteger = msg_send![&*self.0, integerValue];
|
||||||
|
i as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_f64(&self) -> f64 {
|
||||||
|
unsafe {
|
||||||
|
msg_send![&*self.0, doubleValue]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper method for determining if a given `NSObject` is an `NSNumber`.
|
||||||
|
pub fn is(obj: id) -> bool {
|
||||||
|
let result: BOOL = unsafe {
|
||||||
|
msg_send![obj, isKindOfClass:class!(NSNumber)]
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
YES => true,
|
||||||
|
NO => false,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and returns the underlying `NSNumber`.
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::id;
|
use crate::foundation::{id, BOOL, YES, NO};
|
||||||
|
|
||||||
const UTF8_ENCODING: usize = 4;
|
const UTF8_ENCODING: usize = 4;
|
||||||
|
|
||||||
|
@ -36,6 +36,16 @@ impl NSString {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is(obj: id) -> bool {
|
||||||
|
let result: BOOL = unsafe { msg_send![obj, isKindOfClass:class!(NSString)] };
|
||||||
|
|
||||||
|
match result {
|
||||||
|
YES => true,
|
||||||
|
NO => false,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_inner(mut self) -> id {
|
pub fn into_inner(mut self) -> id {
|
||||||
&mut *self.0
|
&mut *self.0
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
//!
|
//!
|
||||||
//! # Hello World
|
//! # Hello World
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust
|
||||||
//! use cacao::app::{App, AppDelegate};
|
//! use cacao::macos::app::{App, AppDelegate};
|
||||||
//! use cacao::window::Window;
|
//! use cacao::macos::window::Window;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
//! struct BasicApp {
|
//! struct BasicApp {
|
||||||
|
|
|
@ -11,9 +11,6 @@
|
||||||
//! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks.
|
//! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks.
|
||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Example
|
||||||
//! ```rust,no_run
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
|
|
Loading…
Reference in a new issue