iOS support for label, text input, font, more tests (#55)
* Added a bunch of unit tests and added text input to uikit feature * cargo fmt * I dunno what this is but it wasnt checked in * Fix uikit unit tests * maybe fix cargo fmt * Fix iOS run * fix cargo fmt * Maybe fix cargo fmt * maybe fix cargo fmt * cargo fmt * Try to fix cargo fmt one more time * cargo fmt
This commit is contained in:
parent
c148bbe6a3
commit
e4785bb50f
|
@ -102,3 +102,6 @@ required-features = ["appkit"]
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "safe_area"
|
name = "safe_area"
|
||||||
required-features = ["appkit"]
|
required-features = ["appkit"]
|
||||||
|
[[example]]
|
||||||
|
name = "popover"
|
||||||
|
required-features = ["appkit"]
|
|
@ -1,5 +1,7 @@
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use cacao::input::{TextField, TextFieldDelegate};
|
||||||
|
use cacao::text::{Label, TextAlign};
|
||||||
use cacao::uikit::{App, AppDelegate, Scene, SceneConfig, SceneConnectionOptions, SceneSession, Window, WindowSceneDelegate};
|
use cacao::uikit::{App, AppDelegate, Scene, SceneConfig, SceneConnectionOptions, SceneSession, Window, WindowSceneDelegate};
|
||||||
|
|
||||||
use cacao::color::Color;
|
use cacao::color::Color;
|
||||||
|
@ -15,22 +17,62 @@ impl AppDelegate for TestApp {
|
||||||
SceneConfig::new("Default Configuration", session.role())
|
SceneConfig::new("Default Configuration", session.role())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ConsoleLogger(String);
|
||||||
|
|
||||||
|
impl TextFieldDelegate for ConsoleLogger {
|
||||||
|
const NAME: &'static str = "ConsoleLogger";
|
||||||
|
|
||||||
|
fn text_should_begin_editing(&self, value: &str) -> bool {
|
||||||
|
println!("{} should begin editing: {}", self.0, value);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_did_change(&self, value: &str) {
|
||||||
|
println!("{} text did change to {}", self.0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_did_end_editing(&self, value: &str) {
|
||||||
|
println!("{} did end editing: {}", self.0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_should_end_editing(&self, value: &str) -> bool {
|
||||||
|
println!("{} should end editing: {}", self.0, value);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct RootView {
|
pub struct RootView {
|
||||||
pub red: View,
|
|
||||||
pub green: View,
|
pub green: View,
|
||||||
pub blue: View,
|
pub blue: View,
|
||||||
pub image: ImageView
|
pub label: Label,
|
||||||
|
pub image: ImageView,
|
||||||
|
pub input: TextField<ConsoleLogger>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RootView {
|
||||||
|
fn default() -> Self {
|
||||||
|
RootView {
|
||||||
|
green: View::new(),
|
||||||
|
blue: View::new(),
|
||||||
|
label: Label::new(),
|
||||||
|
image: ImageView::new(),
|
||||||
|
input: TextField::with(ConsoleLogger("input_1".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewDelegate for RootView {
|
impl ViewDelegate for RootView {
|
||||||
const NAME: &'static str = "RootView";
|
const NAME: &'static str = "RootView";
|
||||||
|
|
||||||
fn did_load(&mut self, view: View) {
|
fn did_load(&mut self, view: View) {
|
||||||
self.red.set_background_color(Color::SystemRed);
|
self.label.set_text("my label");
|
||||||
self.red.layer.set_corner_radius(16.);
|
self.label.set_text_color(Color::SystemWhite);
|
||||||
view.add_subview(&self.red);
|
self.label.set_background_color(Color::SystemRed);
|
||||||
|
self.label.layer.set_corner_radius(16.);
|
||||||
|
self.label.set_text_alignment(TextAlign::Center);
|
||||||
|
|
||||||
|
view.add_subview(&self.label);
|
||||||
|
|
||||||
self.green.set_background_color(Color::SystemGreen);
|
self.green.set_background_color(Color::SystemGreen);
|
||||||
view.add_subview(&self.green);
|
view.add_subview(&self.green);
|
||||||
|
@ -43,19 +85,26 @@ impl ViewDelegate for RootView {
|
||||||
self.image.set_image(&Image::with_data(image_bytes));
|
self.image.set_image(&Image::with_data(image_bytes));
|
||||||
view.add_subview(&self.image);
|
view.add_subview(&self.image);
|
||||||
|
|
||||||
|
self.input.set_text("my input box 1");
|
||||||
|
view.add_subview(&self.input);
|
||||||
|
|
||||||
LayoutConstraint::activate(&[
|
LayoutConstraint::activate(&[
|
||||||
self.red.top.constraint_equal_to(&view.top).offset(16.),
|
self.label.leading.constraint_equal_to(&view.leading).offset(16.),
|
||||||
self.red.leading.constraint_equal_to(&view.leading).offset(16.),
|
self.label.top.constraint_equal_to(&view.top).offset(16.),
|
||||||
self.red.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
self.label.height.constraint_equal_to_constant(100.),
|
||||||
self.red.height.constraint_equal_to_constant(100.),
|
self.label.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
||||||
self.green.top.constraint_equal_to(&self.red.bottom).offset(16.),
|
self.green.top.constraint_equal_to(&self.label.bottom).offset(16.),
|
||||||
self.green.leading.constraint_equal_to(&view.leading).offset(16.),
|
self.green.leading.constraint_equal_to(&view.leading).offset(16.),
|
||||||
self.green.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
self.green.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
||||||
self.green.height.constraint_equal_to_constant(120.),
|
self.green.height.constraint_equal_to_constant(120.),
|
||||||
|
self.input.center_x.constraint_equal_to(&self.green.center_x),
|
||||||
|
self.input.center_y.constraint_equal_to(&self.green.center_y),
|
||||||
self.blue.top.constraint_equal_to(&self.green.bottom).offset(16.),
|
self.blue.top.constraint_equal_to(&self.green.bottom).offset(16.),
|
||||||
self.blue.leading.constraint_equal_to(&view.leading).offset(16.),
|
self.blue.leading.constraint_equal_to(&view.leading).offset(16.),
|
||||||
self.blue.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
self.blue.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
||||||
self.blue.bottom.constraint_equal_to(&view.bottom).offset(-16.)
|
self.blue.bottom.constraint_equal_to(&view.bottom).offset(-16.),
|
||||||
|
self.image.center_x.constraint_equal_to(&self.blue.center_x),
|
||||||
|
self.image.center_y.constraint_equal_to(&self.blue.center_y)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,5 +353,14 @@ impl Drop for Button {
|
||||||
/// Registers an `NSButton` subclass, and configures it to hold some ivars
|
/// Registers an `NSButton` subclass, and configures it to hold some ivars
|
||||||
/// for various things we need to store.
|
/// for various things we need to store.
|
||||||
fn register_class() -> *const Class {
|
fn register_class() -> *const Class {
|
||||||
load_or_register_class("NSButton", "RSTButton", |decl| unsafe {})
|
#[cfg(feature = "appkit")]
|
||||||
|
let super_class = "NSButton";
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let super_class = "UIButton";
|
||||||
|
load_or_register_class(super_class, "RSTButton", |decl| unsafe {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_button() {
|
||||||
|
let button = Button::new("foobar");
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ use crate::layout::Layout;
|
||||||
use crate::objc_access::ObjcAccess;
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
|
use crate::layer::Layer;
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
||||||
|
|
||||||
|
@ -52,6 +53,10 @@ pub struct ImageView {
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
|
/// References the underlying layer. This is consistent across AppKit & UIKit - in AppKit
|
||||||
|
/// we explicitly opt in to layer backed views.
|
||||||
|
pub layer: Layer,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
@ -135,6 +140,8 @@ impl ImageView {
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
|
layer: Layer::wrap(unsafe { msg_send![view, layer] }),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view)
|
objc: ObjcProperty::retain(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
//! For more information on Autolayout, view the module or check out the examples folder.
|
//! For more information on Autolayout, view the module or check out the examples folder.
|
||||||
|
|
||||||
use objc::runtime::{Class, Object};
|
use objc::runtime::{Class, Object};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
@ -64,11 +64,11 @@ mod appkit;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
use appkit::{register_view_class, register_view_class_with_delegate};
|
use appkit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(feature = "uikit")]
|
||||||
//mod uikit;
|
mod uikit;
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
//use uikit::{register_view_class, register_view_class_with_delegate};
|
use uikit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::TextFieldDelegate;
|
pub use traits::TextFieldDelegate;
|
||||||
|
@ -200,50 +200,52 @@ where
|
||||||
let class = register_view_class_with_delegate(&delegate);
|
let class = register_view_class_with_delegate(&delegate);
|
||||||
let mut delegate = Box::new(delegate);
|
let mut delegate = Box::new(delegate);
|
||||||
|
|
||||||
let label = common_init(class);
|
let input = common_init(class);
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: *const T = &*delegate;
|
let ptr: *const T = &*delegate;
|
||||||
(&mut *label).set_ivar(TEXTFIELD_DELEGATE_PTR, ptr as usize);
|
(&mut *input).set_ivar(TEXTFIELD_DELEGATE_PTR, ptr as usize);
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "uikit")]
|
||||||
|
let _: () = unsafe { msg_send![input, setDelegate: input] };
|
||||||
|
|
||||||
let mut label = TextField {
|
let mut input = TextField {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
objc: ObjcProperty::retain(label),
|
objc: ObjcProperty::retain(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(label),
|
top: LayoutAnchorY::top(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
left: LayoutAnchorX::left(label),
|
left: LayoutAnchorX::left(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
leading: LayoutAnchorX::leading(label),
|
leading: LayoutAnchorX::leading(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
right: LayoutAnchorX::right(label),
|
right: LayoutAnchorX::right(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: LayoutAnchorX::trailing(label),
|
trailing: LayoutAnchorX::trailing(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: LayoutAnchorY::bottom(label),
|
bottom: LayoutAnchorY::bottom(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
width: LayoutAnchorDimension::width(label),
|
width: LayoutAnchorDimension::width(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
height: LayoutAnchorDimension::height(label),
|
height: LayoutAnchorDimension::height(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: LayoutAnchorX::center(label),
|
center_x: LayoutAnchorX::center(input),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(label)
|
center_y: LayoutAnchorY::center(input)
|
||||||
};
|
};
|
||||||
|
|
||||||
(&mut delegate).did_load(label.clone_as_handle());
|
(&mut delegate).did_load(input.clone_as_handle());
|
||||||
label.delegate = Some(delegate);
|
input.delegate = Some(delegate);
|
||||||
label
|
input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,10 +292,16 @@ impl<T> TextField<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Grabs the value from the textfield and returns it as an owned String.
|
/// Grabs the value from the textfield and returns it as an owned String.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub fn get_value(&self) -> String {
|
pub fn get_value(&self) -> String {
|
||||||
self.objc
|
self.objc
|
||||||
.get(|obj| unsafe { NSString::retain(msg_send![obj, stringValue]).to_string() })
|
.get(|obj| unsafe { NSString::retain(msg_send![obj, stringValue]).to_string() })
|
||||||
}
|
}
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
pub fn get_value(&self) -> String {
|
||||||
|
self.objc
|
||||||
|
.get(|obj| unsafe { NSString::retain(msg_send![obj, text]).to_string() })
|
||||||
|
}
|
||||||
|
|
||||||
/// Call this to set the background color for the backing layer.
|
/// Call this to set the background color for the backing layer.
|
||||||
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
||||||
|
@ -309,7 +317,10 @@ impl<T> TextField<T> {
|
||||||
let s = NSString::new(text);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setStringValue:&*s];
|
let _: () = msg_send![obj, setStringValue:&*s];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setText:&*s];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +329,10 @@ impl<T> TextField<T> {
|
||||||
let s = NSString::new(text);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setPlaceholderString:&*s];
|
let _: () = msg_send![obj, setPlaceholderString:&*s];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setPlaceholder:&*s];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,7 +340,10 @@ impl<T> TextField<T> {
|
||||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let alignment: NSInteger = alignment.into();
|
let alignment: NSInteger = alignment.into();
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setAlignment: alignment];
|
let _: () = msg_send![obj, setAlignment: alignment];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setTextAlignment: alignment];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -401,3 +418,16 @@ impl<T> Drop for TextField<T> {
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_text_view() {
|
||||||
|
let text_field = TextField::new();
|
||||||
|
let value = text_field.get_value();
|
||||||
|
assert!(value.is_empty());
|
||||||
|
text_field.set_background_color(Color::SystemBlue);
|
||||||
|
text_field.set_text("foobar");
|
||||||
|
let value = text_field.get_value();
|
||||||
|
assert_eq!(value, "foobar".to_string());
|
||||||
|
text_field.set_text_alignment(TextAlign::Left);
|
||||||
|
text_field.set_font(Font::default());
|
||||||
|
}
|
||||||
|
|
95
src/input/uikit.rs
Normal file
95
src/input/uikit.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, load_or_register_class, NSString, NSUInteger, NO, YES};
|
||||||
|
use crate::input::{TextFieldDelegate, TEXTFIELD_DELEGATE_PTR};
|
||||||
|
use crate::utils::load;
|
||||||
|
|
||||||
|
/// Called when editing this text field has ended (e.g. user pressed enter).
|
||||||
|
extern "C" fn text_did_end_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
|
||||||
|
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
|
||||||
|
let s = NSString::retain(unsafe { msg_send![this, text] });
|
||||||
|
view.text_did_end_editing(s.to_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn text_did_begin_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
|
||||||
|
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
|
||||||
|
let s = NSString::retain(unsafe { msg_send![this, text] });
|
||||||
|
view.text_did_begin_editing(s.to_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn text_did_change<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
|
||||||
|
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
|
||||||
|
let s = NSString::retain(unsafe { msg_send![this, text] });
|
||||||
|
view.text_did_change(s.to_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn text_should_begin_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) -> BOOL {
|
||||||
|
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
|
||||||
|
let s = NSString::retain(unsafe { msg_send![this, text] });
|
||||||
|
|
||||||
|
match view.text_should_begin_editing(s.to_str()) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn text_should_end_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) -> BOOL {
|
||||||
|
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
|
||||||
|
let s = NSString::retain(unsafe { msg_send![this, text] });
|
||||||
|
match view.text_should_end_editing(s.to_str()) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects an `UITextField` subclass. This is used for the default views that don't use delegates - we
|
||||||
|
/// have separate classes here since we don't want to waste cycles on methods that will never be
|
||||||
|
/// used if there's no delegates.
|
||||||
|
pub(crate) fn register_view_class() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UITextField);
|
||||||
|
let decl = ClassDecl::new("RSTTextInputField", superclass).unwrap();
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects an `UITextField` subclass, with some callback and pointer ivars for what we
|
||||||
|
/// need to do.
|
||||||
|
pub(crate) fn register_view_class_with_delegate<T: TextFieldDelegate>(instance: &T) -> *const Class {
|
||||||
|
load_or_register_class("UITextField", instance.subclass_name(), |decl| unsafe {
|
||||||
|
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
||||||
|
// move.
|
||||||
|
decl.add_ivar::<usize>(TEXTFIELD_DELEGATE_PTR);
|
||||||
|
|
||||||
|
decl.add_method(
|
||||||
|
sel!(textFieldDidEndEditing:),
|
||||||
|
text_did_end_editing::<T> as extern "C" fn(&mut Object, _, _)
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(textFieldDidBeginEditing:),
|
||||||
|
text_did_begin_editing::<T> as extern "C" fn(&mut Object, _, _)
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(textFieldDidChangeSelection:),
|
||||||
|
text_did_change::<T> as extern "C" fn(&mut Object, _, _)
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(textFieldShouldBeginEditing:),
|
||||||
|
text_should_begin_editing::<T> as extern "C" fn(&mut Object, Sel, id) -> BOOL
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(textFieldShouldEndEditing:),
|
||||||
|
text_should_end_editing::<T> as extern "C" fn(&mut Object, Sel, id) -> BOOL
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
|
@ -118,7 +118,7 @@ pub mod cloudkit;
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(any(feature = "appkit", feature = "uikit"))]
|
||||||
pub mod control;
|
pub mod control;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
|
@ -140,7 +140,7 @@ pub mod geometry;
|
||||||
#[cfg(any(feature = "appkit", feature = "uikit"))]
|
#[cfg(any(feature = "appkit", feature = "uikit"))]
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(any(feature = "appkit", feature = "uikit"))]
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub(crate) mod invoker;
|
pub(crate) mod invoker;
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ pub mod pasteboard;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(any(feature = "appkit", feature = "uikit"))]
|
||||||
pub mod scrollview;
|
pub mod scrollview;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
|
@ -170,7 +170,6 @@ pub mod switch;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod select;
|
pub mod select;
|
||||||
|
|
||||||
#[cfg(feature = "appkit")]
|
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "quicklook")]
|
#[cfg(feature = "quicklook")]
|
||||||
|
|
|
@ -55,6 +55,7 @@ use crate::layer::Layer;
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::objc_access::ObjcAccess;
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -96,6 +97,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ListViewRow<T = ()> {
|
pub struct ListViewRow<T = ()> {
|
||||||
/// An object that supports limited animations. Can be cloned into animation closures.
|
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
pub animator: ViewAnimatorProxy,
|
pub animator: ViewAnimatorProxy,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
|
@ -163,6 +165,7 @@ impl ListViewRow {
|
||||||
ListViewRow {
|
ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
animator: ViewAnimatorProxy::new(view),
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -227,6 +230,7 @@ where
|
||||||
let view = ListViewRow {
|
let view = ListViewRow {
|
||||||
delegate: Some(delegate),
|
delegate: Some(delegate),
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
animator: ViewAnimatorProxy::new(view),
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -283,6 +287,7 @@ where
|
||||||
let mut view = ListViewRow {
|
let mut view = ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
animator: ViewAnimatorProxy::new(view),
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -335,6 +340,7 @@ where
|
||||||
ListViewRow {
|
ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
objc: self.objc.clone(),
|
objc: self.objc.clone(),
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
animator: self.animator.clone(),
|
animator: self.animator.clone(),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -384,6 +390,7 @@ impl<T> ListViewRow<T> {
|
||||||
is_handle: true,
|
is_handle: true,
|
||||||
layer: Layer::new(), // @TODO: Fix & return cloned true layer for this row.
|
layer: Layer::new(), // @TODO: Fix & return cloned true layer for this row.
|
||||||
objc: self.objc.clone(),
|
objc: self.objc.clone(),
|
||||||
|
#[cfg(all(feature = "appkit", target_os = "macos"))]
|
||||||
animator: self.animator.clone(),
|
animator: self.animator.clone(),
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
|
|
@ -50,7 +50,6 @@ use crate::color::Color;
|
||||||
use crate::foundation::{id, nil, NSArray, NSString, NO, YES};
|
use crate::foundation::{id, nil, NSArray, NSString, NO, YES};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::objc_access::ObjcAccess;
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::pasteboard::PasteboardType;
|
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
|
@ -62,11 +61,11 @@ mod appkit;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
use appkit::{register_scrollview_class, register_scrollview_class_with_delegate};
|
use appkit::{register_scrollview_class, register_scrollview_class_with_delegate};
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(feature = "uikit")]
|
||||||
//mod ios;
|
mod uikit;
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
//use ios::{register_view_class, register_view_class_with_delegate};
|
use uikit::{register_scrollview_class, register_scrollview_class_with_delegate};
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::ScrollViewDelegate;
|
pub use traits::ScrollViewDelegate;
|
||||||
|
@ -334,3 +333,8 @@ impl<T> Drop for ScrollView<T> {
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scrollview() {
|
||||||
|
let view = ScrollView::new();
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
use crate::dragdrop::{DragInfo, DragOperation};
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
use crate::scrollview::ScrollView;
|
use crate::scrollview::ScrollView;
|
||||||
|
|
||||||
|
@ -22,24 +23,29 @@ pub trait ScrollViewDelegate {
|
||||||
/// Called when this has been removed from the view heirarchy.
|
/// Called when this has been removed from the view heirarchy.
|
||||||
fn did_disappear(&self, _animated: bool) {}
|
fn did_disappear(&self, _animated: bool) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
||||||
fn dragging_entered(&self, _info: DragInfo) -> DragOperation {
|
fn dragging_entered(&self, _info: DragInfo) -> DragOperation {
|
||||||
DragOperation::None
|
DragOperation::None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
/// Invoked when the image is released, allowing the receiver to agree to or refuse drag operation.
|
/// Invoked when the image is released, allowing the receiver to agree to or refuse drag operation.
|
||||||
fn prepare_for_drag_operation(&self, _info: DragInfo) -> bool {
|
fn prepare_for_drag_operation(&self, _info: DragInfo) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
/// Invoked after the released image has been removed from the screen, signaling the receiver to import the pasteboard data.
|
/// Invoked after the released image has been removed from the screen, signaling the receiver to import the pasteboard data.
|
||||||
fn perform_drag_operation(&self, _info: DragInfo) -> bool {
|
fn perform_drag_operation(&self, _info: DragInfo) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
|
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
|
||||||
fn conclude_drag_operation(&self, _info: DragInfo) {}
|
fn conclude_drag_operation(&self, _info: DragInfo) {}
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
/// Invoked when the dragged image exits the destination’s bounds rectangle (in the case of a view) or its frame
|
/// Invoked when the dragged image exits the destination’s bounds rectangle (in the case of a view) or its frame
|
||||||
/// rectangle (in the case of a window object).
|
/// rectangle (in the case of a window object).
|
||||||
fn dragging_exited(&self, _info: DragInfo) {}
|
fn dragging_exited(&self, _info: DragInfo) {}
|
||||||
|
|
138
src/scrollview/uikit.rs
Normal file
138
src/scrollview/uikit.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
//! This module does one specific thing: register a custom `NSView` class that's... brought to the
|
||||||
|
//! modern era.
|
||||||
|
//!
|
||||||
|
//! I kid, I kid.
|
||||||
|
//!
|
||||||
|
//! It just enforces that coordinates are judged from the top-left, which is what most people look
|
||||||
|
//! for in the modern era. It also implements a few helpers for things like setting a background
|
||||||
|
//! color, and enforcing layer backing by default.
|
||||||
|
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
|
use objc::{class, sel, sel_impl};
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, NSUInteger, NO, YES};
|
||||||
|
use crate::scrollview::{ScrollViewDelegate, SCROLLVIEW_DELEGATE_PTR};
|
||||||
|
use crate::utils::load;
|
||||||
|
|
||||||
|
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||||
|
extern "C" fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
use crate::dragdrop::DragInfo;
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern "C" fn dragging_entered<T: ScrollViewDelegate>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
||||||
|
let view = load::<T>(this, SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
view.dragging_entered(DragInfo {
|
||||||
|
info: unsafe { Id::from_ptr(info) }
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern "C" fn prepare_for_drag_operation<T: ScrollViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
|
let view = load::<T>(this, SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
match view.prepare_for_drag_operation(DragInfo {
|
||||||
|
info: unsafe { Id::from_ptr(info) }
|
||||||
|
}) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern "C" fn perform_drag_operation<T: ScrollViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
|
let view = load::<T>(this, SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
match view.perform_drag_operation(DragInfo {
|
||||||
|
info: unsafe { Id::from_ptr(info) }
|
||||||
|
}) {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern "C" fn conclude_drag_operation<T: ScrollViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
||||||
|
let view = load::<T>(this, SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
view.conclude_drag_operation(DragInfo {
|
||||||
|
info: unsafe { Id::from_ptr(info) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern "C" fn dragging_exited<T: ScrollViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
||||||
|
let view = load::<T>(this, SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
view.dragging_exited(DragInfo {
|
||||||
|
info: unsafe { Id::from_ptr(info) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Injects an `UIScrollView` subclass.
|
||||||
|
pub(crate) fn register_scrollview_class() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UIScrollView);
|
||||||
|
let decl = ClassDecl::new("RSTScrollView", superclass).unwrap();
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects an `NSView` subclass, with some callback and pointer ivars for what we
|
||||||
|
/// need to do.
|
||||||
|
pub(crate) fn register_scrollview_class_with_delegate<T: ScrollViewDelegate>() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UIScrollView);
|
||||||
|
let mut decl = ClassDecl::new("RSTScrollViewWithDelegate", superclass).unwrap();
|
||||||
|
|
||||||
|
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
||||||
|
// move.
|
||||||
|
decl.add_ivar::<usize>(SCROLLVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
|
decl.add_method(sel!(isFlipped), enforce_normalcy as extern "C" fn(&Object, _) -> BOOL);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Drag and drop operations (e.g, accepting files)
|
||||||
|
decl.add_method(
|
||||||
|
sel!(draggingEntered:),
|
||||||
|
dragging_entered::<T> as extern "C" fn(&mut Object, _, _) -> NSUInteger
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(prepareForDragOperation:),
|
||||||
|
prepare_for_drag_operation::<T> as extern "C" fn(&mut Object, _, _) -> BOOL
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(performDragOperation:),
|
||||||
|
perform_drag_operation::<T> as extern "C" fn(&mut Object, _, _) -> BOOL
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(concludeDragOperation:),
|
||||||
|
conclude_drag_operation::<T> as extern "C" fn(&mut Object, _, _)
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(draggingExited:),
|
||||||
|
dragging_exited::<T> as extern "C" fn(&mut Object, _, _)
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
|
@ -19,27 +19,39 @@ pub struct Font(pub ShareId<Object>);
|
||||||
impl Default for Font {
|
impl Default for Font {
|
||||||
/// Returns the default `labelFont` on macOS.
|
/// Returns the default `labelFont` on macOS.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Font(unsafe {
|
let cls = Self::class();
|
||||||
let cls = class!(NSFont);
|
let default_size: id = unsafe { msg_send![cls, labelFontSize] };
|
||||||
let default_size: id = msg_send![cls, labelFontSize];
|
|
||||||
ShareId::from_ptr(msg_send![cls, labelFontOfSize: default_size])
|
#[cfg(feature = "appkit")]
|
||||||
})
|
let font = Font(unsafe { ShareId::from_ptr(msg_send![cls, labelFontOfSize: default_size]) });
|
||||||
|
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let font = Font(unsafe { ShareId::from_ptr(msg_send![cls, systemFontOfSize: default_size]) });
|
||||||
|
font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
|
fn class() -> &'static Class {
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
|
let class = class!(NSFont);
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let class = class!(UIFont);
|
||||||
|
|
||||||
|
class
|
||||||
|
}
|
||||||
/// Creates and returns a default system font at the specified size.
|
/// Creates and returns a default system font at the specified size.
|
||||||
pub fn system(size: f64) -> Self {
|
pub fn system(size: f64) -> Self {
|
||||||
let size = size as CGFloat;
|
let size = size as CGFloat;
|
||||||
|
|
||||||
Font(unsafe { ShareId::from_ptr(msg_send![class!(NSFont), systemFontOfSize: size]) })
|
Font(unsafe { ShareId::from_ptr(msg_send![Self::class(), systemFontOfSize: size]) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates and returns a default bold system font at the specified size.
|
/// Creates and returns a default bold system font at the specified size.
|
||||||
pub fn bold_system(size: f64) -> Self {
|
pub fn bold_system(size: f64) -> Self {
|
||||||
let size = size as CGFloat;
|
let size = size as CGFloat;
|
||||||
|
|
||||||
Font(unsafe { ShareId::from_ptr(msg_send![class!(NSFont), boldSystemFontOfSize: size]) })
|
Font(unsafe { ShareId::from_ptr(msg_send![Self::class(), boldSystemFontOfSize: size]) })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates and returns a monospace system font at the specified size and weight
|
/// Creates and returns a monospace system font at the specified size and weight
|
||||||
|
@ -78,3 +90,10 @@ impl AsRef<Font> for Font {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn font_test() {
|
||||||
|
let default_font = Font::default();
|
||||||
|
let system_font = Font::system(100.0);
|
||||||
|
let bold_system_font = Font::bold_system(100.0);
|
||||||
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NSUInteger, NO, YES};
|
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NSUInteger, NO, YES};
|
||||||
|
use crate::layer::Layer;
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::objc_access::ObjcAccess;
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::text::{AttributedString, Font, LineBreakMode, TextAlign};
|
use crate::text::{AttributedString, Font, LineBreakMode, TextAlign};
|
||||||
|
@ -63,11 +64,11 @@ mod appkit;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
use appkit::{register_view_class, register_view_class_with_delegate};
|
use appkit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(feature = "uikit")]
|
||||||
//mod uikit;
|
mod uikit;
|
||||||
|
|
||||||
//#[cfg(feature = "uikit")]
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
//use uikit::{register_view_class, register_view_class_with_delegate};
|
use uikit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::LabelDelegate;
|
pub use traits::LabelDelegate;
|
||||||
|
@ -156,6 +157,10 @@ pub struct Label<T = ()> {
|
||||||
/// A pointer to the delegate for this view.
|
/// A pointer to the delegate for this view.
|
||||||
pub delegate: Option<Box<T>>,
|
pub delegate: Option<Box<T>>,
|
||||||
|
|
||||||
|
/// References the underlying layer. This is consistent across AppKit & UIKit - in AppKit
|
||||||
|
/// we explicitly opt in to layer backed views.
|
||||||
|
pub layer: Layer,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
@ -207,9 +212,12 @@ impl Label {
|
||||||
/// Returns a default `Label`, suitable for
|
/// Returns a default `Label`, suitable for
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let view = allocate_view(register_view_class);
|
let view = allocate_view(register_view_class);
|
||||||
|
Self::init(view, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init<T>(view: id, delegate: Option<Box<T>>) -> Label<T> {
|
||||||
Label {
|
Label {
|
||||||
delegate: None,
|
delegate,
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(view),
|
top: LayoutAnchorY::top(view),
|
||||||
|
@ -241,6 +249,8 @@ impl Label {
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
|
layer: Layer::wrap(unsafe { msg_send![view, layer] }),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view)
|
objc: ObjcProperty::retain(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,51 +265,12 @@ where
|
||||||
pub fn with(delegate: T) -> Label<T> {
|
pub fn with(delegate: T) -> Label<T> {
|
||||||
let delegate = Box::new(delegate);
|
let delegate = Box::new(delegate);
|
||||||
|
|
||||||
let label = allocate_view(register_view_class_with_delegate::<T>);
|
let view = allocate_view(register_view_class_with_delegate::<T>);
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: *const T = &*delegate;
|
let ptr: *const T = &*delegate;
|
||||||
(&mut *label).set_ivar(LABEL_DELEGATE_PTR, ptr as usize);
|
(&mut *view).set_ivar(LABEL_DELEGATE_PTR, ptr as usize);
|
||||||
};
|
};
|
||||||
|
Label::init(view, Some(delegate))
|
||||||
let mut label = Label {
|
|
||||||
delegate: None,
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
top: LayoutAnchorY::top(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
left: LayoutAnchorX::left(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
leading: LayoutAnchorX::leading(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
right: LayoutAnchorX::right(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
trailing: LayoutAnchorX::trailing(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
bottom: LayoutAnchorY::bottom(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
width: LayoutAnchorDimension::width(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
height: LayoutAnchorDimension::height(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
center_x: LayoutAnchorX::center(label),
|
|
||||||
|
|
||||||
#[cfg(feature = "autolayout")]
|
|
||||||
center_y: LayoutAnchorY::center(label),
|
|
||||||
|
|
||||||
objc: ObjcProperty::retain(label)
|
|
||||||
};
|
|
||||||
|
|
||||||
//(&mut delegate).did_load(label.clone_as_handle());
|
|
||||||
label.delegate = Some(delegate);
|
|
||||||
label
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,6 +313,8 @@ impl<T> Label<T> {
|
||||||
#[cfg(feature = "autolayout")]
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: self.center_y.clone(),
|
center_y: self.center_y.clone(),
|
||||||
|
|
||||||
|
layer: self.layer.clone(),
|
||||||
|
|
||||||
objc: self.objc.clone()
|
objc: self.objc.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -372,28 +345,51 @@ impl<T> Label<T> {
|
||||||
let s = NSString::new(text);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setStringValue:&*s];
|
let _: () = msg_send![obj, setStringValue:&*s];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setText:&*s];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the attributed string to be the attributed string value on this label.
|
/// Sets the attributed string to be the attributed string value on this label.
|
||||||
pub fn set_attributed_text(&self, text: AttributedString) {
|
pub fn set_attributed_text(&self, text: AttributedString) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setAttributedStringValue:&*text];
|
let _: () = msg_send![obj, setAttributedStringValue:&*text];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setAttributedText:&*text];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the text currently held in the label.
|
/// Retrieve the text currently held in the label.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub fn get_text(&self) -> String {
|
pub fn get_text(&self) -> String {
|
||||||
self.objc
|
self.objc
|
||||||
.get(|obj| unsafe { NSString::retain(msg_send![obj, stringValue]).to_string() })
|
.get(|obj| unsafe { NSString::retain(msg_send![obj, stringValue]).to_string() })
|
||||||
}
|
}
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
pub fn get_text(&self) -> String {
|
||||||
|
self.objc.get(|obj| {
|
||||||
|
let val: id = unsafe { msg_send![obj, text] };
|
||||||
|
// Through trial and error, this seems to return a null pointer when there's no
|
||||||
|
// text.
|
||||||
|
if val.is_null() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
NSString::retain(val).to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the text alignment for this label.
|
/// Sets the text alignment for this label.
|
||||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let alignment: NSInteger = alignment.into();
|
let alignment: NSInteger = alignment.into();
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![obj, setAlignment: alignment];
|
let _: () = msg_send![obj, setAlignment: alignment];
|
||||||
|
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
|
||||||
|
let _: () = msg_send![obj, setTextAlignment: alignment];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,3 +463,18 @@ impl<T> Drop for Label<T> {
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_label() {
|
||||||
|
let label = Label::new();
|
||||||
|
let text = label.get_text();
|
||||||
|
assert!(text.is_empty());
|
||||||
|
label.set_background_color(Color::SystemOrange);
|
||||||
|
label.set_text_color(Color::SystemRed);
|
||||||
|
label.set_text_alignment(TextAlign::Right);
|
||||||
|
label.set_text("foobar");
|
||||||
|
let text = label.get_text();
|
||||||
|
assert_eq!(text, "foobar".to_string());
|
||||||
|
label.set_font(Font::system(10.0));
|
||||||
|
label.set_attributed_text(AttributedString::new("foobar"));
|
||||||
|
}
|
||||||
|
|
45
src/text/label/uikit.rs
Normal file
45
src/text/label/uikit.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
|
use objc::{class, sel, sel_impl};
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, NSUInteger, NO, YES};
|
||||||
|
use crate::text::label::{LabelDelegate, LABEL_DELEGATE_PTR};
|
||||||
|
|
||||||
|
/// Injects an `UILabel` subclass. This is used for the default views that don't use delegates - we
|
||||||
|
/// have separate classes here since we don't want to waste cycles on methods that will never be
|
||||||
|
/// used if there's no delegates.
|
||||||
|
pub(crate) fn register_view_class() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UILabel);
|
||||||
|
let decl = ClassDecl::new("RSTTextField", superclass).unwrap();
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Injects an `UILabel` subclass, with some callback and pointer ivars for what we
|
||||||
|
/// need to do.
|
||||||
|
pub(crate) fn register_view_class_with_delegate<T: LabelDelegate>() -> *const Class {
|
||||||
|
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UIView);
|
||||||
|
let mut decl = ClassDecl::new("RSTTextFieldWithDelegate", superclass).unwrap();
|
||||||
|
|
||||||
|
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
||||||
|
// move.
|
||||||
|
decl.add_ivar::<usize>(LABEL_DELEGATE_PTR);
|
||||||
|
|
||||||
|
VIEW_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe { VIEW_CLASS }
|
||||||
|
}
|
|
@ -139,7 +139,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, W, F> App<T, W, F> {
|
impl<T, W, F> App<T, W, F>
|
||||||
|
where
|
||||||
|
T: AppDelegate + 'static
|
||||||
|
{
|
||||||
/// Handles calling through to `UIApplicationMain()`, ensuring that it's using our custom
|
/// Handles calling through to `UIApplicationMain()`, ensuring that it's using our custom
|
||||||
/// `UIApplication` and `UIApplicationDelegate` classes.
|
/// `UIApplication` and `UIApplicationDelegate` classes.
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
|
@ -149,12 +152,14 @@ impl<T, W, F> App<T, W, F> {
|
||||||
|
|
||||||
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
||||||
|
|
||||||
let mut s = NSString::new("RSTApplication_UIApplication");
|
let cls = register_app_class();
|
||||||
let mut s2 = NSString::new("RSTAppDelegate_NSObject");
|
let dl = register_app_delegate_class::<T>();
|
||||||
|
|
||||||
|
let cls_name: id = unsafe { msg_send![cls, className] };
|
||||||
|
let dl_name: id = unsafe { msg_send![dl, className] };
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
println!("RUNNING?!");
|
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), cls_name, dl_name);
|
||||||
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into(), s2.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//self.pool.drain();
|
//self.pool.drain();
|
||||||
|
|
|
@ -2,7 +2,8 @@ use objc::runtime::Object;
|
||||||
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, ClassMap, NSString};
|
use crate::foundation::{id, load_or_register_class, ClassMap, NSString};
|
||||||
|
|
||||||
use crate::uikit::scene::SessionRole;
|
use crate::uikit::scene::SessionRole;
|
||||||
|
|
||||||
/// A wrapper for UISceneConfiguration.
|
/// A wrapper for UISceneConfiguration.
|
||||||
|
@ -26,7 +27,10 @@ impl SceneConfig {
|
||||||
let config: id = msg_send![cls, configurationWithName:name sessionRole:role];
|
let config: id = msg_send![cls, configurationWithName:name sessionRole:role];
|
||||||
|
|
||||||
let _: () = msg_send![config, setSceneClass: class!(UIWindowScene)];
|
let _: () = msg_send![config, setSceneClass: class!(UIWindowScene)];
|
||||||
let _: () = msg_send![config, setDelegateClass: delegate_class];
|
|
||||||
|
// TODO: use register_window_scene_delegate_class rather than load_or_register_class.
|
||||||
|
let window_delegate = load_or_register_class("UIResponder", "RSTWindowSceneDelegate", |decl| unsafe {});
|
||||||
|
let _: () = msg_send![config, setDelegateClass: window_delegate];
|
||||||
|
|
||||||
Id::from_ptr(config)
|
Id::from_ptr(config)
|
||||||
})
|
})
|
||||||
|
|
|
@ -77,7 +77,9 @@ mod splitviewcontroller;
|
||||||
#[cfg(feature = "appkit")]
|
#[cfg(feature = "appkit")]
|
||||||
pub use splitviewcontroller::SplitViewController;
|
pub use splitviewcontroller::SplitViewController;
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
mod popover;
|
mod popover;
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub use popover::*;
|
pub use popover::*;
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::ViewDelegate;
|
pub use traits::ViewDelegate;
|
||||||
|
@ -347,3 +349,10 @@ impl<T> Drop for View<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_view() {
|
||||||
|
let view = View::new();
|
||||||
|
let clone = view.clone_as_handle();
|
||||||
|
view.set_background_color(Color::SystemGreen);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue