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:
simlay 2023-07-10 03:42:46 -04:00 committed by GitHub
parent c148bbe6a3
commit e4785bb50f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 545 additions and 105 deletions

View file

@ -102,3 +102,6 @@ required-features = ["appkit"]
[[example]]
name = "safe_area"
required-features = ["appkit"]
[[example]]
name = "popover"
required-features = ["appkit"]

View file

@ -1,5 +1,7 @@
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::color::Color;
@ -15,22 +17,62 @@ impl AppDelegate for TestApp {
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 red: View,
pub green: 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 {
const NAME: &'static str = "RootView";
fn did_load(&mut self, view: View) {
self.red.set_background_color(Color::SystemRed);
self.red.layer.set_corner_radius(16.);
view.add_subview(&self.red);
self.label.set_text("my label");
self.label.set_text_color(Color::SystemWhite);
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);
view.add_subview(&self.green);
@ -43,19 +85,26 @@ impl ViewDelegate for RootView {
self.image.set_image(&Image::with_data(image_bytes));
view.add_subview(&self.image);
self.input.set_text("my input box 1");
view.add_subview(&self.input);
LayoutConstraint::activate(&[
self.red.top.constraint_equal_to(&view.top).offset(16.),
self.red.leading.constraint_equal_to(&view.leading).offset(16.),
self.red.trailing.constraint_equal_to(&view.trailing).offset(-16.),
self.red.height.constraint_equal_to_constant(100.),
self.green.top.constraint_equal_to(&self.red.bottom).offset(16.),
self.label.leading.constraint_equal_to(&view.leading).offset(16.),
self.label.top.constraint_equal_to(&view.top).offset(16.),
self.label.height.constraint_equal_to_constant(100.),
self.label.trailing.constraint_equal_to(&view.trailing).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.trailing.constraint_equal_to(&view.trailing).offset(-16.),
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.leading.constraint_equal_to(&view.leading).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)
]);
}
}

View file

@ -353,5 +353,14 @@ impl Drop for Button {
/// Registers an `NSButton` subclass, and configures it to hold some ivars
/// for various things we need to store.
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");
}

View file

@ -8,6 +8,7 @@ use crate::layout::Layout;
use crate::objc_access::ObjcAccess;
use crate::utils::properties::ObjcProperty;
use crate::layer::Layer;
#[cfg(feature = "autolayout")]
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
@ -52,6 +53,10 @@ pub struct ImageView {
/// A pointer to the Objective-C runtime view controller.
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.
#[cfg(feature = "autolayout")]
pub top: LayoutAnchorY,
@ -135,6 +140,8 @@ impl ImageView {
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(view),
layer: Layer::wrap(unsafe { msg_send![view, layer] }),
objc: ObjcProperty::retain(view)
}
}

View file

@ -44,7 +44,7 @@
//! For more information on Autolayout, view the module or check out the examples folder.
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 crate::color::Color;
@ -64,11 +64,11 @@ mod appkit;
#[cfg(feature = "appkit")]
use appkit::{register_view_class, register_view_class_with_delegate};
//#[cfg(feature = "uikit")]
//mod uikit;
#[cfg(feature = "uikit")]
mod uikit;
//#[cfg(feature = "uikit")]
//use uikit::{register_view_class, register_view_class_with_delegate};
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
use uikit::{register_view_class, register_view_class_with_delegate};
mod traits;
pub use traits::TextFieldDelegate;
@ -200,50 +200,52 @@ where
let class = register_view_class_with_delegate(&delegate);
let mut delegate = Box::new(delegate);
let label = common_init(class);
let input = common_init(class);
unsafe {
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,
objc: ObjcProperty::retain(label),
objc: ObjcProperty::retain(input),
#[cfg(feature = "autolayout")]
top: LayoutAnchorY::top(label),
top: LayoutAnchorY::top(input),
#[cfg(feature = "autolayout")]
left: LayoutAnchorX::left(label),
left: LayoutAnchorX::left(input),
#[cfg(feature = "autolayout")]
leading: LayoutAnchorX::leading(label),
leading: LayoutAnchorX::leading(input),
#[cfg(feature = "autolayout")]
right: LayoutAnchorX::right(label),
right: LayoutAnchorX::right(input),
#[cfg(feature = "autolayout")]
trailing: LayoutAnchorX::trailing(label),
trailing: LayoutAnchorX::trailing(input),
#[cfg(feature = "autolayout")]
bottom: LayoutAnchorY::bottom(label),
bottom: LayoutAnchorY::bottom(input),
#[cfg(feature = "autolayout")]
width: LayoutAnchorDimension::width(label),
width: LayoutAnchorDimension::width(input),
#[cfg(feature = "autolayout")]
height: LayoutAnchorDimension::height(label),
height: LayoutAnchorDimension::height(input),
#[cfg(feature = "autolayout")]
center_x: LayoutAnchorX::center(label),
center_x: LayoutAnchorX::center(input),
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(label)
center_y: LayoutAnchorY::center(input)
};
(&mut delegate).did_load(label.clone_as_handle());
label.delegate = Some(delegate);
label
(&mut delegate).did_load(input.clone_as_handle());
input.delegate = Some(delegate);
input
}
}
@ -290,10 +292,16 @@ impl<T> TextField<T> {
}
/// Grabs the value from the textfield and returns it as an owned String.
#[cfg(feature = "appkit")]
pub fn get_value(&self) -> String {
self.objc
.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.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
@ -309,7 +317,10 @@ impl<T> TextField<T> {
let s = NSString::new(text);
self.objc.with_mut(|obj| unsafe {
#[cfg(feature = "appkit")]
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);
self.objc.with_mut(|obj| unsafe {
#[cfg(feature = "appkit")]
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) {
self.objc.with_mut(|obj| unsafe {
let alignment: NSInteger = alignment.into();
#[cfg(feature = "appkit")]
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
View 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
);
})
}

View file

@ -118,7 +118,7 @@ pub mod cloudkit;
pub mod color;
#[cfg(feature = "appkit")]
#[cfg(any(feature = "appkit", feature = "uikit"))]
pub mod control;
#[cfg(feature = "appkit")]
@ -140,7 +140,7 @@ pub mod geometry;
#[cfg(any(feature = "appkit", feature = "uikit"))]
pub mod image;
#[cfg(feature = "appkit")]
#[cfg(any(feature = "appkit", feature = "uikit"))]
pub mod input;
pub(crate) mod invoker;
@ -161,7 +161,7 @@ pub mod pasteboard;
#[cfg(feature = "appkit")]
pub mod progress;
#[cfg(feature = "appkit")]
#[cfg(any(feature = "appkit", feature = "uikit"))]
pub mod scrollview;
#[cfg(feature = "appkit")]
@ -170,7 +170,6 @@ pub mod switch;
#[cfg(feature = "appkit")]
pub mod select;
#[cfg(feature = "appkit")]
pub mod text;
#[cfg(feature = "quicklook")]

View file

@ -55,6 +55,7 @@ use crate::layer::Layer;
use crate::layout::Layout;
use crate::objc_access::ObjcAccess;
use crate::utils::properties::ObjcProperty;
#[cfg(all(feature = "appkit", target_os = "macos"))]
use crate::view::{ViewAnimatorProxy, ViewDelegate};
#[cfg(feature = "autolayout")]
@ -96,6 +97,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Debug)]
pub struct ListViewRow<T = ()> {
/// An object that supports limited animations. Can be cloned into animation closures.
#[cfg(all(feature = "appkit", target_os = "macos"))]
pub animator: ViewAnimatorProxy,
/// A pointer to the Objective-C runtime view controller.
@ -163,6 +165,7 @@ impl ListViewRow {
ListViewRow {
delegate: None,
objc: ObjcProperty::retain(view),
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: ViewAnimatorProxy::new(view),
#[cfg(feature = "autolayout")]
@ -227,6 +230,7 @@ where
let view = ListViewRow {
delegate: Some(delegate),
objc: ObjcProperty::retain(view),
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: ViewAnimatorProxy::new(view),
#[cfg(feature = "autolayout")]
@ -283,6 +287,7 @@ where
let mut view = ListViewRow {
delegate: None,
objc: ObjcProperty::retain(view),
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: ViewAnimatorProxy::new(view),
#[cfg(feature = "autolayout")]
@ -335,6 +340,7 @@ where
ListViewRow {
delegate: None,
objc: self.objc.clone(),
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: self.animator.clone(),
#[cfg(feature = "autolayout")]
@ -384,6 +390,7 @@ impl<T> ListViewRow<T> {
is_handle: true,
layer: Layer::new(), // @TODO: Fix & return cloned true layer for this row.
objc: self.objc.clone(),
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: self.animator.clone(),
#[cfg(feature = "autolayout")]

View file

@ -50,7 +50,6 @@ use crate::color::Color;
use crate::foundation::{id, nil, NSArray, NSString, NO, YES};
use crate::layout::Layout;
use crate::objc_access::ObjcAccess;
use crate::pasteboard::PasteboardType;
use crate::utils::properties::ObjcProperty;
#[cfg(feature = "autolayout")]
@ -62,11 +61,11 @@ mod appkit;
#[cfg(feature = "appkit")]
use appkit::{register_scrollview_class, register_scrollview_class_with_delegate};
//#[cfg(feature = "uikit")]
//mod ios;
#[cfg(feature = "uikit")]
mod uikit;
//#[cfg(feature = "uikit")]
//use ios::{register_view_class, register_view_class_with_delegate};
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
use uikit::{register_scrollview_class, register_scrollview_class_with_delegate};
mod traits;
pub use traits::ScrollViewDelegate;
@ -334,3 +333,8 @@ impl<T> Drop for ScrollView<T> {
}*/
}
}
#[test]
fn test_scrollview() {
let view = ScrollView::new();
}

View file

@ -1,3 +1,4 @@
#[cfg(feature = "appkit")]
use crate::dragdrop::{DragInfo, DragOperation};
use crate::scrollview::ScrollView;
@ -22,24 +23,29 @@ pub trait ScrollViewDelegate {
/// Called when this has been removed from the view heirarchy.
fn did_disappear(&self, _animated: bool) {}
#[cfg(feature = "appkit")]
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
fn dragging_entered(&self, _info: DragInfo) -> DragOperation {
DragOperation::None
}
#[cfg(feature = "appkit")]
/// 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 {
false
}
#[cfg(feature = "appkit")]
/// 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 {
false
}
#[cfg(feature = "appkit")]
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
fn conclude_drag_operation(&self, _info: DragInfo) {}
#[cfg(feature = "appkit")]
/// Invoked when the dragged image exits the destinations bounds rectangle (in the case of a view) or its frame
/// rectangle (in the case of a window object).
fn dragging_exited(&self, _info: DragInfo) {}

138
src/scrollview/uikit.rs Normal file
View 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 }
}

View file

@ -19,27 +19,39 @@ pub struct Font(pub ShareId<Object>);
impl Default for Font {
/// Returns the default `labelFont` on macOS.
fn default() -> Self {
Font(unsafe {
let cls = class!(NSFont);
let default_size: id = msg_send![cls, labelFontSize];
ShareId::from_ptr(msg_send![cls, labelFontOfSize: default_size])
})
let cls = Self::class();
let default_size: id = unsafe { msg_send![cls, labelFontSize] };
#[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 {
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.
pub fn system(size: f64) -> Self {
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.
pub fn bold_system(size: f64) -> Self {
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
@ -78,3 +90,10 @@ impl AsRef<Font> for Font {
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);
}

View file

@ -49,6 +49,7 @@ use objc_id::ShareId;
use crate::color::Color;
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NSUInteger, NO, YES};
use crate::layer::Layer;
use crate::layout::Layout;
use crate::objc_access::ObjcAccess;
use crate::text::{AttributedString, Font, LineBreakMode, TextAlign};
@ -63,11 +64,11 @@ mod appkit;
#[cfg(feature = "appkit")]
use appkit::{register_view_class, register_view_class_with_delegate};
//#[cfg(feature = "uikit")]
//mod uikit;
#[cfg(feature = "uikit")]
mod uikit;
//#[cfg(feature = "uikit")]
//use uikit::{register_view_class, register_view_class_with_delegate};
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
use uikit::{register_view_class, register_view_class_with_delegate};
mod traits;
pub use traits::LabelDelegate;
@ -156,6 +157,10 @@ pub struct Label<T = ()> {
/// A pointer to the delegate for this view.
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.
#[cfg(feature = "autolayout")]
pub top: LayoutAnchorY,
@ -207,9 +212,12 @@ impl Label {
/// Returns a default `Label`, suitable for
pub fn new() -> Self {
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 {
delegate: None,
delegate,
#[cfg(feature = "autolayout")]
top: LayoutAnchorY::top(view),
@ -241,6 +249,8 @@ impl Label {
#[cfg(feature = "autolayout")]
center_y: LayoutAnchorY::center(view),
layer: Layer::wrap(unsafe { msg_send![view, layer] }),
objc: ObjcProperty::retain(view)
}
}
@ -255,51 +265,12 @@ where
pub fn with(delegate: T) -> Label<T> {
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 {
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);
};
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
Label::init(view, Some(delegate))
}
}
@ -342,6 +313,8 @@ impl<T> Label<T> {
#[cfg(feature = "autolayout")]
center_y: self.center_y.clone(),
layer: self.layer.clone(),
objc: self.objc.clone()
}
}
@ -372,28 +345,51 @@ impl<T> Label<T> {
let s = NSString::new(text);
self.objc.with_mut(|obj| unsafe {
#[cfg(feature = "appkit")]
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.
pub fn set_attributed_text(&self, text: AttributedString) {
self.objc.with_mut(|obj| unsafe {
#[cfg(feature = "appkit")]
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.
#[cfg(feature = "appkit")]
pub fn get_text(&self) -> String {
self.objc
.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.
pub fn set_text_alignment(&self, alignment: TextAlign) {
self.objc.with_mut(|obj| unsafe {
let alignment: NSInteger = alignment.into();
#[cfg(feature = "appkit")]
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
View 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 }
}

View file

@ -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
/// `UIApplication` and `UIApplicationDelegate` classes.
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 mut s = NSString::new("RSTApplication_UIApplication");
let mut s2 = NSString::new("RSTAppDelegate_NSObject");
let cls = register_app_class();
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 {
println!("RUNNING?!");
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into(), s2.into());
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), cls_name, dl_name);
}
//self.pool.drain();

View file

@ -2,7 +2,8 @@ use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
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;
/// A wrapper for UISceneConfiguration.
@ -26,7 +27,10 @@ impl SceneConfig {
let config: id = msg_send![cls, configurationWithName:name sessionRole:role];
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)
})

View file

@ -77,7 +77,9 @@ mod splitviewcontroller;
#[cfg(feature = "appkit")]
pub use splitviewcontroller::SplitViewController;
#[cfg(feature = "appkit")]
mod popover;
#[cfg(feature = "appkit")]
pub use popover::*;
mod traits;
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);
}