Resolve merge conflict
This commit is contained in:
commit
33624e9418
|
@ -13,7 +13,7 @@ Have a look at the [issue tracker](https://github.com/ryanmcgrath/cacao/issues).
|
|||
describing your problem (or a very similar one) there, please open a new issue with
|
||||
the following details:
|
||||
|
||||
- Which versions of Rust and cacao (and macOS/iOS build/device) are you using?
|
||||
- Which versions of Rust and Cacao (and macOS/iOS build/device) are you using?
|
||||
- Which feature flags are you using?
|
||||
- What are you trying to accomplish?
|
||||
- What is the full error you are seeing?
|
||||
|
@ -34,17 +34,17 @@ If you can't find an issue (open or closed) describing your idea on the [issue
|
|||
tracker], open an issue. Adding answers to the following
|
||||
questions in your description is +1:
|
||||
|
||||
- What do you want to do, and how do you expect Alchemy to support you with that?
|
||||
- How might this be added to Alchemy?
|
||||
- What do you want to do, and how do you expect Cacao to support you with that?
|
||||
- How might this be added to Cacao?
|
||||
- What are possible alternatives?
|
||||
- Are there any disadvantages?
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
## Contribute code to Alchemy
|
||||
## Contribute code to Cacao
|
||||
|
||||
### Setting up cacao locally
|
||||
### Setting up Cacao locally
|
||||
|
||||
1. Install Rust. Stable should be fine.
|
||||
2. Clone this repository and open it in your favorite editor.
|
||||
|
@ -57,8 +57,7 @@ In a few cases, though, it's fine to deviate - a good example is branching match
|
|||
|
||||
To run rustfmt tests locally:
|
||||
|
||||
1. Use rustup to set rust toolchain to the version specified in the
|
||||
[rust-toolchain file](./rust-toolchain).
|
||||
1. Use rustup to set Rust toolchain to the latest stable version of Rust.
|
||||
|
||||
2. Install the rustfmt and clippy by running
|
||||
```
|
||||
|
@ -66,18 +65,18 @@ To run rustfmt tests locally:
|
|||
rustup component add clippy-preview
|
||||
```
|
||||
|
||||
3. Run clippy using cargo from the root of your alchemy repo.
|
||||
3. Run clippy nightly using cargo from the root of your Cacao repo.
|
||||
```
|
||||
cargo clippy
|
||||
cargo +nightly clippy
|
||||
```
|
||||
Each PR needs to compile without warning.
|
||||
|
||||
4. Run rustfmt using cargo from the root of your alchemy repo.
|
||||
4. Run rustfmt nightly using cargo from the root of your Cacao repo.
|
||||
|
||||
To see changes that need to be made, run
|
||||
|
||||
```
|
||||
cargo fmt --all -- --check
|
||||
cargo +nightly fmt --all -- --check
|
||||
```
|
||||
|
||||
If all code is properly formatted (e.g. if you have not made any changes),
|
||||
|
@ -89,15 +88,15 @@ To run rustfmt tests locally:
|
|||
Once you are ready to apply the formatting changes, run
|
||||
|
||||
```
|
||||
cargo fmt --all
|
||||
cargo +nightly fmt --all
|
||||
```
|
||||
|
||||
You won't see any output, but all your files will be corrected.
|
||||
|
||||
You can also use rustfmt to make corrections or highlight issues in your editor.
|
||||
Check out [their README](https://github.com/rust-lang-nursery/rustfmt) for details.
|
||||
Check out [their README](https://github.com/rust-lang/rustfmt) for details.
|
||||
|
||||
|
||||
### Notes
|
||||
This project prefers verbose naming, to a certain degree - UI code is read more often than written, so it's
|
||||
worthwhile to ensure that it scans well. It also maps well to existing Cocoa/cacao idioms and is generally preferred.
|
||||
worthwhile to ensure that it scans well. It also maps well to existing Cocoa/Cacao idioms and is generally preferred.
|
||||
|
|
|
@ -19,11 +19,12 @@ default-target = "x86_64-apple-darwin"
|
|||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
bitmask-enum = "2.2.1"
|
||||
block = "0.1.6"
|
||||
core-foundation = "0.9"
|
||||
core-graphics = "0.22"
|
||||
core-graphics = "0.23"
|
||||
dispatch = "0.2.0"
|
||||
infer = { version = "0.13", optional = true }
|
||||
infer = { version = "0.15", optional = true }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
objc = "0.2.7"
|
||||
|
@ -102,3 +103,6 @@ required-features = ["appkit"]
|
|||
[[example]]
|
||||
name = "safe_area"
|
||||
required-features = ["appkit"]
|
||||
[[example]]
|
||||
name = "popover"
|
||||
required-features = ["appkit"]
|
||||
|
|
|
@ -16,7 +16,7 @@ pub fn button(text: &str, msg: Msg) -> Button {
|
|||
button.set_bordered(false);
|
||||
button.set_bezel_style(BezelStyle::SmallSquare);
|
||||
button.set_focus_ring_type(FocusRingType::None);
|
||||
button.set_action(move || dispatch(msg.clone()));
|
||||
button.set_action(move |_| dispatch(msg.clone()));
|
||||
button.set_key_equivalent(&*text.to_lowercase());
|
||||
|
||||
let font = Font::system(22.);
|
||||
|
|
|
@ -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)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
//! - Another Controller / View
|
||||
|
||||
use cacao::appkit::menu::{Menu, MenuItem};
|
||||
use cacao::appkit::segmentedcontrol::SegmentedControl;
|
||||
use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowDelegate};
|
||||
use cacao::appkit::{App, AppDelegate};
|
||||
use cacao::button::Button;
|
||||
use cacao::foundation::NSArray;
|
||||
use cacao::geometry::{Edge, Rect};
|
||||
use cacao::image::Image;
|
||||
use cacao::layout::{Layout, LayoutConstraint};
|
||||
use cacao::notification_center::Dispatcher;
|
||||
use cacao::text::{Font, Label};
|
||||
use cacao::view::{Popover, PopoverConfig, View, ViewController, ViewDelegate};
|
||||
|
||||
struct BasicApp {
|
||||
|
@ -124,7 +126,7 @@ impl ViewDelegate for PopoverExampleContentView {
|
|||
|
||||
fn did_load(&mut self, view: cacao::view::View) {
|
||||
let mut button = Button::new("Show");
|
||||
button.set_action(|| dispatch_ui(Msg::Click));
|
||||
button.set_action(|_| dispatch_ui(Msg::Click));
|
||||
|
||||
let controller = PopoverExampleContentViewController::new();
|
||||
let config = PopoverConfig {
|
||||
|
@ -164,16 +166,21 @@ impl Dispatcher for BasicApp {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct PopoverExampleContentViewController {
|
||||
pub label: Label
|
||||
pub control: SegmentedControl
|
||||
}
|
||||
|
||||
impl PopoverExampleContentViewController {
|
||||
fn new() -> Self {
|
||||
let label = Label::new();
|
||||
let font = Font::system(20.);
|
||||
label.set_font(&font);
|
||||
label.set_text("Hello");
|
||||
Self { label }
|
||||
let images = NSArray::from(vec![
|
||||
&*Image::symbol(cacao::image::SFSymbol::AtSymbol, "Hello").0,
|
||||
&*Image::symbol(cacao::image::SFSymbol::PaperPlane, "Hello").0,
|
||||
&*Image::symbol(cacao::image::SFSymbol::PaperPlaneFilled, "Hello").0,
|
||||
]);
|
||||
let mut control = SegmentedControl::new(images, cacao::appkit::segmentedcontrol::TrackingMode::SelectOne);
|
||||
control.set_action(|index| {
|
||||
println!("Selected Index {index}");
|
||||
});
|
||||
Self { control }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,6 +188,6 @@ impl ViewDelegate for PopoverExampleContentViewController {
|
|||
const NAME: &'static str = "PopoverExampleContentViewController";
|
||||
|
||||
fn did_load(&mut self, view: View) {
|
||||
view.add_subview(&self.label);
|
||||
view.add_subview(&self.control);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ impl ViewDelegate for AddNewTodoContentView {
|
|||
|
||||
let mut button = Button::new("Add");
|
||||
button.set_key_equivalent("\r");
|
||||
button.set_action(|| dispatch_ui(Message::ProcessNewTodo));
|
||||
button.set_action(|_| dispatch_ui(Message::ProcessNewTodo));
|
||||
|
||||
view.add_subview(&instructions);
|
||||
view.add_subview(&input);
|
||||
|
|
|
@ -2,6 +2,7 @@ use cacao::layout::{Layout, LayoutConstraint};
|
|||
use cacao::switch::Switch;
|
||||
use cacao::text::Label;
|
||||
use cacao::view::View;
|
||||
use objc::runtime::Object;
|
||||
|
||||
/// A reusable widget for a toggle; this is effectively a standard checkbox/label combination for
|
||||
/// toggling a boolean value.
|
||||
|
@ -55,7 +56,7 @@ impl ToggleOptionView {
|
|||
/// can toggle your settings and such there.
|
||||
pub fn configure<F>(&mut self, text: &str, subtitle: &str, state: bool, handler: F)
|
||||
where
|
||||
F: Fn() + Send + Sync + 'static
|
||||
F: Fn(*const Object) + Send + Sync + 'static
|
||||
{
|
||||
self.title.set_text(text);
|
||||
self.subtitle.set_text(subtitle);
|
||||
|
|
|
@ -19,7 +19,7 @@ impl Default for PreferencesToolbar {
|
|||
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "General");
|
||||
item.set_image(icon);
|
||||
|
||||
item.set_action(|| {
|
||||
item.set_action(|_| {
|
||||
dispatch_ui(Message::SwitchPreferencesToGeneralPane);
|
||||
});
|
||||
|
||||
|
@ -32,7 +32,7 @@ impl Default for PreferencesToolbar {
|
|||
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesAdvanced, "Advanced");
|
||||
item.set_image(icon);
|
||||
|
||||
item.set_action(|| {
|
||||
item.set_action(|_| {
|
||||
dispatch_ui(Message::SwitchPreferencesToAdvancedPane);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use cacao::defaults::{UserDefaults, Value};
|
||||
use objc::runtime::Object;
|
||||
|
||||
const EXAMPLE: &str = "exampleSetting";
|
||||
|
||||
|
@ -25,7 +26,7 @@ impl Defaults {
|
|||
}
|
||||
|
||||
/// Toggles the example setting.
|
||||
pub fn toggle_should_whatever() {
|
||||
pub fn toggle_should_whatever(_object: *const Object) {
|
||||
toggle_bool(EXAMPLE);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ impl Default for TodosToolbar {
|
|||
item.set_title("Add Todo");
|
||||
item.set_button(Button::new("+ New"));
|
||||
|
||||
item.set_action(|| {
|
||||
item.set_action(|_| {
|
||||
dispatch_ui(Message::OpenNewTodoSheet);
|
||||
});
|
||||
|
||||
|
|
|
@ -187,13 +187,12 @@ extern "C" fn accepted_cloudkit_share<T: AppDelegate>(this: &Object, _: Sel, _:
|
|||
/// Fires when the application receives an `application:openURLs` message.
|
||||
extern "C" fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
|
||||
let urls = NSArray::retain(file_urls)
|
||||
.map(|url| {
|
||||
.iter()
|
||||
.filter_map(|url| {
|
||||
let uri = NSString::retain(unsafe { msg_send![url, absoluteString] });
|
||||
|
||||
Url::parse(uri.to_str())
|
||||
Url::parse(uri.to_str()).ok()
|
||||
})
|
||||
.into_iter()
|
||||
.filter_map(|url| url.ok())
|
||||
.collect();
|
||||
|
||||
app::<T>(this).open_urls(urls);
|
||||
|
@ -263,7 +262,10 @@ extern "C" fn print_files<T: AppDelegate>(
|
|||
settings: id,
|
||||
show_print_panels: BOOL
|
||||
) -> NSUInteger {
|
||||
let files = NSArray::retain(files).map(|file| NSString::retain(file).to_str().to_string());
|
||||
let files = NSArray::retain(files)
|
||||
.iter()
|
||||
.map(|file| NSString::retain(file).to_str().to_string())
|
||||
.collect();
|
||||
|
||||
let settings = PrintSettings::with_inner(settings);
|
||||
|
||||
|
|
|
@ -1,15 +1,54 @@
|
|||
use bitmask_enum::bitmask;
|
||||
use block::ConcreteBlock;
|
||||
|
||||
use objc::runtime::Object;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_id::Id;
|
||||
|
||||
use crate::foundation::{id, nil, NSString};
|
||||
use crate::events::EventType;
|
||||
use crate::foundation::{id, nil, NSInteger, NSPoint, NSString};
|
||||
|
||||
/// An EventMask describes the type of event.
|
||||
#[derive(Debug)]
|
||||
#[bitmask(u64)]
|
||||
pub enum EventMask {
|
||||
KeyDown
|
||||
LeftMouseDown = 1 << 1,
|
||||
LeftMouseUp = 1 << 2,
|
||||
RightMouseDown = 1 << 3,
|
||||
RightMouseUp = 1 << 4,
|
||||
MouseMoved = 1 << 5,
|
||||
LeftMouseDragged = 1 << 6,
|
||||
RightMouseDragged = 1 << 7,
|
||||
MouseEntered = 1 << 8,
|
||||
MouseExited = 1 << 9,
|
||||
KeyDown = 1 << 10,
|
||||
KeyUp = 1 << 11,
|
||||
FlagsChanged = 1 << 12,
|
||||
AppKitDefined = 1 << 13,
|
||||
SystemDefined = 1 << 14,
|
||||
ApplicationDefined = 1 << 15,
|
||||
Periodic = 1 << 16,
|
||||
CursorUpdate = 1 << 17,
|
||||
|
||||
ScrollWheel = 1 << 22,
|
||||
TabletPoint = 1 << 23,
|
||||
TabletProximity = 1 << 24,
|
||||
OtherMouseDown = 1 << 25,
|
||||
OtherMouseUp = 1 << 26,
|
||||
OtherMouseDragged = 1 << 27,
|
||||
|
||||
Gesture = 1 << 29,
|
||||
Magnify = 1 << 30,
|
||||
Swipe = 1 << 31,
|
||||
Rotate = 1 << 18,
|
||||
BeginGesture = 1 << 19,
|
||||
EndGesture = 1 << 20,
|
||||
|
||||
SmartMagnify = 1 << 32,
|
||||
QuickLook = 1 << 33,
|
||||
Pressure = 1 << 34,
|
||||
DirectTouch = 1 << 37,
|
||||
|
||||
ChangeMode = 1 << 38
|
||||
}
|
||||
|
||||
/// A wrapper over an `NSEvent`.
|
||||
|
@ -25,6 +64,16 @@ impl Event {
|
|||
Event(unsafe { Id::from_ptr(objc) })
|
||||
}
|
||||
|
||||
/// The event's type.
|
||||
///
|
||||
/// Corresponds to the `type` getter.
|
||||
pub fn kind(&self) -> EventType {
|
||||
let kind: NSUInteger = unsafe { msg_send![&*self.0, type] };
|
||||
|
||||
unsafe { ::std::mem::transmute(kind) }
|
||||
}
|
||||
|
||||
/// The characters associated with a key-up or key-down event.
|
||||
pub fn characters(&self) -> String {
|
||||
// @TODO: Check here if key event, invalid otherwise.
|
||||
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
||||
|
@ -34,6 +83,26 @@ impl Event {
|
|||
characters.to_string()
|
||||
}
|
||||
|
||||
/// The indices of the currently pressed mouse buttons.
|
||||
pub fn pressed_mouse_buttons() -> NSUInteger {
|
||||
unsafe { msg_send![class!(NSEvent), pressedMouseButtons] }
|
||||
}
|
||||
|
||||
/// Reports the current mouse position in screen coordinates.
|
||||
pub fn mouse_location() -> NSPoint {
|
||||
unsafe { msg_send![class!(NSEvent), mouseLocation] }
|
||||
}
|
||||
|
||||
/// The button number for a mouse event.
|
||||
pub fn button_number(&self) -> NSInteger {
|
||||
unsafe { msg_send![&*self.0, buttonNumber] }
|
||||
}
|
||||
|
||||
/// The number of mouse clicks associated with a mouse-down or mouse-up event.
|
||||
pub fn click_count(&self) -> NSInteger {
|
||||
unsafe { msg_send![&*self.0, clickCount] }
|
||||
}
|
||||
|
||||
/*pub fn contains_modifier_flags(&self, flags: &[EventModifierFlag]) -> bool {
|
||||
let modifier_flags: NSUInteger = unsafe {
|
||||
msg_send![&*self.0, modifierFlags]
|
||||
|
@ -47,13 +116,13 @@ impl Event {
|
|||
false
|
||||
}*/
|
||||
|
||||
/// Register an event handler with the system event stream. This method
|
||||
/// Register an event handler with the local system event stream. This method
|
||||
/// watches for events that occur _within the application_. Events outside
|
||||
/// of the application require installing a `monitor_global_events` handler.
|
||||
/// of the application require installing a `global_monitor` handler.
|
||||
///
|
||||
/// Note that in order to monitor all possible events, both local and global
|
||||
/// monitors are required - the streams don't mix.
|
||||
pub fn local_monitor<F>(_mask: EventMask, handler: F) -> EventMonitor
|
||||
pub fn local_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
||||
where
|
||||
F: Fn(Event) -> Option<Event> + Send + Sync + 'static
|
||||
{
|
||||
|
@ -68,7 +137,33 @@ impl Event {
|
|||
let block = block.copy();
|
||||
|
||||
EventMonitor(unsafe {
|
||||
msg_send![class!(NSEvent), addLocalMonitorForEventsMatchingMask:1024
|
||||
msg_send![class!(NSEvent), addLocalMonitorForEventsMatchingMask:mask.bits
|
||||
handler:block]
|
||||
})
|
||||
}
|
||||
|
||||
/// Register an event handler with the global system event stream. This method
|
||||
/// watches for events that occur _outside the application_. Events within
|
||||
/// the application require installing a `local_monitor` handler.
|
||||
///
|
||||
/// Note that in order to monitor all possible events, both local and global
|
||||
/// monitors are required - the streams don't mix.
|
||||
pub fn global_monitor<F>(mask: EventMask, handler: F) -> EventMonitor
|
||||
where
|
||||
F: Fn(Event) -> Option<Event> + Send + Sync + 'static
|
||||
{
|
||||
let block = ConcreteBlock::new(move |event: id| {
|
||||
let evt = Event::new(event);
|
||||
|
||||
match handler(evt) {
|
||||
Some(mut evt) => &mut *evt.0,
|
||||
None => nil
|
||||
}
|
||||
});
|
||||
let block = block.copy();
|
||||
|
||||
EventMonitor(unsafe {
|
||||
msg_send![class!(NSEvent), addGlobalMonitorForEventsMatchingMask:mask.bits
|
||||
handler:block]
|
||||
})
|
||||
}
|
||||
|
|
79
src/appkit/haptics.rs
Normal file
79
src/appkit/haptics.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::convert::TryFrom;
|
||||
|
||||
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::NSUInteger;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HapticFeedbackPerformer(pub ShareId<Object>);
|
||||
|
||||
impl HapticFeedbackPerformer {
|
||||
pub fn perform(&self, pattern: FeedbackPattern, performance_time: PerformanceTime) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.0, performFeedbackPattern: pattern performanceTime: performance_time];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HapticFeedbackPerformer {
|
||||
/// Returns the default haptic feedback performer.
|
||||
fn default() -> Self {
|
||||
HapticFeedbackPerformer(unsafe {
|
||||
let manager = msg_send![class!(NSHapticFeedbackManager), defaultPerformer];
|
||||
ShareId::from_ptr(manager)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum PerformanceTime {
|
||||
Default = 0,
|
||||
Now = 1,
|
||||
DrawCompleted = 2
|
||||
}
|
||||
|
||||
impl Default for PerformanceTime {
|
||||
fn default() -> Self {
|
||||
Self::Default
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f64> for PerformanceTime {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
||||
match value as u8 {
|
||||
0 => Ok(Self::Default),
|
||||
1 => Ok(Self::Now),
|
||||
2 => Ok(Self::DrawCompleted),
|
||||
_ => Err("Invalid performance time")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FeedbackPattern {
|
||||
Generic = 0,
|
||||
Alignment = 1,
|
||||
LevelChange = 2
|
||||
}
|
||||
|
||||
impl Default for FeedbackPattern {
|
||||
fn default() -> Self {
|
||||
Self::Generic
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<f64> for FeedbackPattern {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
||||
match value as u8 {
|
||||
0 => Ok(Self::Generic),
|
||||
1 => Ok(Self::Alignment),
|
||||
2 => Ok(Self::LevelChange),
|
||||
_ => Err("Invalid feedback pattern")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,3 +27,6 @@ pub mod menu;
|
|||
pub mod printing;
|
||||
pub mod toolbar;
|
||||
pub mod window;
|
||||
|
||||
pub mod haptics;
|
||||
pub mod segmentedcontrol;
|
||||
|
|
346
src/appkit/segmentedcontrol.rs
Normal file
346
src/appkit/segmentedcontrol.rs
Normal file
|
@ -0,0 +1,346 @@
|
|||
//! Wraps `NSSegmentedControl` on appkit
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Once;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::control::Control;
|
||||
use crate::foundation::{id, nil, NSArray, NSString, NSUInteger, BOOL, NO, YES};
|
||||
use crate::image::Image;
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::keys::Key;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::text::{AttributedString, Font};
|
||||
use crate::utils::{load, properties::ObjcProperty};
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
use crate::appkit::FocusRingType;
|
||||
|
||||
/// Wraps `NSButton` on appkit, and `UIButton` on iOS and tvOS.
|
||||
///
|
||||
/// You'd use this type to create a button that a user can interact with. Buttons can be configured
|
||||
/// a number of ways, and support setting a callback to fire when they're clicked or tapped.
|
||||
///
|
||||
/// Some properties are platform-specific; see the documentation for further information.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use cacao::button::Button;
|
||||
/// use cacao::view::View;
|
||||
/// use crate::cacao::layout::Layout;
|
||||
/// let mut button = Button::new("My button title");
|
||||
/// button.set_key_equivalent("c");
|
||||
///
|
||||
/// button.set_action(|_| {
|
||||
/// println!("My button was clicked.");
|
||||
/// });
|
||||
/// let my_view : View<()> = todo!();
|
||||
///
|
||||
/// // Make sure you don't let your Button drop for as long as you need it.
|
||||
/// my_view.add_subview(&button);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct SegmentedControl {
|
||||
/// A handle for the underlying Objective-C object.
|
||||
pub objc: ObjcProperty,
|
||||
|
||||
/// Hold on to the images
|
||||
images: NSArray,
|
||||
|
||||
handler: Option<TargetActionHandler>,
|
||||
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub top: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub leading: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime left layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub left: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub trailing: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime right layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub right: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub bottom: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime width layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub width: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime height layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub height: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub center_x: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub center_y: LayoutAnchorY
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum TrackingMode {
|
||||
SelectOne = 0,
|
||||
SelectMany = 1,
|
||||
SelectMomentary = 2
|
||||
}
|
||||
|
||||
impl SegmentedControl {
|
||||
/// Creates a new `NSSegmentedControl` instance, configures it appropriately,
|
||||
/// and retains the necessary Objective-C runtime pointer.
|
||||
pub fn new(images: NSArray, tracking_mode: TrackingMode) -> Self {
|
||||
let view: id = unsafe {
|
||||
let tracking_mode = tracking_mode as u8 as i32;
|
||||
let control: id = msg_send![register_class(), segmentedControlWithImages:&*images trackingMode:tracking_mode
|
||||
target:nil
|
||||
action:nil
|
||||
];
|
||||
|
||||
let _: () = msg_send![control, setWantsLayer: YES];
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
let _: () = msg_send![control, setTranslatesAutoresizingMaskIntoConstraints: NO];
|
||||
|
||||
control
|
||||
};
|
||||
|
||||
SegmentedControl {
|
||||
handler: None,
|
||||
|
||||
images,
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
left: LayoutAnchorX::left(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
leading: LayoutAnchorX::leading(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
right: LayoutAnchorX::right(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
trailing: LayoutAnchorX::trailing(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
bottom: LayoutAnchorY::bottom(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
width: LayoutAnchorDimension::width(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
height: LayoutAnchorDimension::height(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
center_x: LayoutAnchorX::center(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
center_y: LayoutAnchorY::center(view),
|
||||
|
||||
objc: ObjcProperty::retain(view)
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the segment at index
|
||||
pub fn set_tooltip_segment(&mut self, index: NSUInteger, tooltip: &str) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let converted = NSString::new(tooltip);
|
||||
let _: () = msg_send![obj, setToolTip: converted forSegment: index];
|
||||
})
|
||||
}
|
||||
|
||||
/// Select the segment at index
|
||||
pub fn select_segment(&mut self, index: NSUInteger) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setSelectedSegment: index];
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets an image on the underlying button.
|
||||
pub fn set_image_segment(&mut self, image: Image, segment: NSUInteger) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setImage:&*image.0 forSegment: segment];
|
||||
});
|
||||
}
|
||||
|
||||
/// Attaches a callback for button press events. Don't get too creative now...
|
||||
/// best just to message pass or something.
|
||||
pub fn set_action<F: Fn(i32) + Send + Sync + 'static>(&mut self, action: F) {
|
||||
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
|
||||
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
|
||||
let handler = TargetActionHandler::new(&*this, move |obj: *const Object| unsafe {
|
||||
let selected: i32 = msg_send![obj, selectedSegment];
|
||||
action(selected)
|
||||
});
|
||||
self.handler = Some(handler);
|
||||
}
|
||||
|
||||
/// Call this to set the background color for the backing layer.
|
||||
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
||||
let color: id = color.as_ref().into();
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let cell: id = msg_send![obj, cell];
|
||||
let _: () = msg_send![cell, setBackgroundColor: color];
|
||||
});
|
||||
}
|
||||
|
||||
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
|
||||
/// button will fire.
|
||||
pub fn set_key_equivalent<'a, K>(&self, key: K)
|
||||
where
|
||||
K: Into<Key<'a>>
|
||||
{
|
||||
let key: Key<'a> = key.into();
|
||||
|
||||
self.objc.with_mut(|obj| {
|
||||
let keychar = match key {
|
||||
Key::Char(s) => NSString::new(s),
|
||||
Key::Delete => NSString::new("\u{08}")
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![obj, setKeyEquivalent:&*keychar];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the text color for this button.
|
||||
///
|
||||
/// On appkit, this is done by way of an `AttributedString` under the hood.
|
||||
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
|
||||
#[cfg(feature = "appkit")]
|
||||
self.objc.with_mut(move |obj| unsafe {
|
||||
let text: id = msg_send![obj, attributedTitle];
|
||||
let len: isize = msg_send![text, length];
|
||||
|
||||
let mut attr_str = AttributedString::wrap(text);
|
||||
attr_str.set_text_color(color.as_ref(), 0..len);
|
||||
|
||||
let _: () = msg_send![obj, setAttributedTitle:&*attr_str];
|
||||
});
|
||||
}
|
||||
|
||||
// @TODO: Figure out how to handle oddities like this.
|
||||
/// For buttons on appkit, one might need to disable the border. This does that.
|
||||
#[cfg(feature = "appkit")]
|
||||
pub fn set_bordered(&self, is_bordered: bool) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setBordered:match is_bordered {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the font for this button.
|
||||
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
||||
let font = font.as_ref().clone();
|
||||
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setFont:&*font];
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets how the control should draw a focus ring when a user is focused on it.
|
||||
///
|
||||
/// This is an appkit-only method.
|
||||
#[cfg(feature = "appkit")]
|
||||
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
|
||||
let ring_type: NSUInteger = focus_ring_type.into();
|
||||
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setFocusRingType: ring_type];
|
||||
});
|
||||
}
|
||||
|
||||
/// Toggles the highlighted status of the button.
|
||||
pub fn set_highlighted(&self, highlight: bool) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, highlight:match highlight {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjcAccess for SegmentedControl {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for SegmentedControl {}
|
||||
impl Control for SegmentedControl {}
|
||||
|
||||
impl ObjcAccess for &SegmentedControl {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for &SegmentedControl {}
|
||||
impl Control for &SegmentedControl {}
|
||||
|
||||
impl Drop for SegmentedControl {
|
||||
/// Nils out references on the Objective-C side and removes this from the backing view.
|
||||
// Just to be sure, let's... nil these out. They should be weak references,
|
||||
// but I'd rather be paranoid and remove them later.
|
||||
fn drop(&mut self) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setTarget: nil];
|
||||
let _: () = msg_send![obj, setAction: nil];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers an `NSButton` subclass, and configures it to hold some ivars
|
||||
/// for various things we need to store.
|
||||
fn register_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!(NSSegmentedControl);
|
||||
let decl = ClassDecl::new("RSTSegmentedControl", superclass).unwrap();
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe { VIEW_CLASS }
|
||||
}
|
|
@ -10,6 +10,7 @@ use objc::runtime::Object;
|
|||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_id::{Id, ShareId};
|
||||
|
||||
use crate::appkit::segmentedcontrol::SegmentedControl;
|
||||
use crate::button::{BezelStyle, Button};
|
||||
use crate::foundation::{id, NSString, NO, YES};
|
||||
use crate::image::Image;
|
||||
|
@ -21,6 +22,7 @@ pub struct ToolbarItem {
|
|||
pub identifier: String,
|
||||
pub objc: Id<Object>,
|
||||
pub button: Option<Button>,
|
||||
pub segmented_control: Option<SegmentedControl>,
|
||||
pub image: Option<Image>,
|
||||
handler: Option<TargetActionHandler>
|
||||
}
|
||||
|
@ -42,6 +44,7 @@ impl ToolbarItem {
|
|||
identifier,
|
||||
objc,
|
||||
button: None,
|
||||
segmented_control: None,
|
||||
image: None,
|
||||
handler: None
|
||||
}
|
||||
|
@ -52,6 +55,7 @@ impl ToolbarItem {
|
|||
identifier: "".to_string(),
|
||||
objc: unsafe { Id::from_retained_ptr(item) },
|
||||
button: None,
|
||||
segmented_control: None,
|
||||
image: None,
|
||||
handler: None
|
||||
}
|
||||
|
@ -76,6 +80,15 @@ impl ToolbarItem {
|
|||
self.button = Some(button);
|
||||
}
|
||||
|
||||
/// Sets and takes ownership of the segmented control for this item.
|
||||
pub fn set_segmented_control(&mut self, control: SegmentedControl) {
|
||||
control.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![&*self.objc, setView: obj];
|
||||
});
|
||||
|
||||
self.segmented_control = Some(control);
|
||||
}
|
||||
|
||||
/// Sets and takes ownership of the image for this toolbar item.
|
||||
pub fn set_image(&mut self, image: Image) {
|
||||
unsafe {
|
||||
|
@ -102,7 +115,7 @@ impl ToolbarItem {
|
|||
}
|
||||
|
||||
/// Sets an action on this item.
|
||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||
pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
|
||||
let handler = TargetActionHandler::new(&*self.objc, action);
|
||||
self.handler = Some(handler);
|
||||
}
|
||||
|
|
|
@ -295,6 +295,14 @@ impl<T> Window<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used for setting a toolbar on this window.
|
||||
pub fn toolbar(&self) -> ShareId<Object> {
|
||||
unsafe {
|
||||
let o: *mut Object = msg_send![&*self.objc, toolbar];
|
||||
ShareId::from_ptr(o)
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggles whether the toolbar is shown for this window. Has no effect if no toolbar exists on
|
||||
/// this window.
|
||||
pub fn toggle_toolbar_shown(&self) {
|
||||
|
@ -303,6 +311,14 @@ impl<T> Window<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Set the toolbar style
|
||||
pub fn set_toolbar_style(&self, style: WindowToolbarStyle) {
|
||||
let style: NSUInteger = style.into();
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setToolbarStyle: style];
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the toolbar toggle button is shown. Has no effect if no toolbar exists on this
|
||||
/// window.
|
||||
pub fn set_shows_toolbar_button(&self, shows: bool) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//! let mut button = Button::new("My button title");
|
||||
//! button.set_key_equivalent("c");
|
||||
//!
|
||||
//! button.set_action(|| {
|
||||
//! button.set_action(|_| {
|
||||
//! println!("My button was clicked.");
|
||||
//! });
|
||||
//! let my_view : View<()> = todo!();
|
||||
|
@ -58,7 +58,7 @@ mod enums;
|
|||
/// let mut button = Button::new("My button title");
|
||||
/// button.set_key_equivalent("c");
|
||||
///
|
||||
/// button.set_action(|| {
|
||||
/// button.set_action(|_| {
|
||||
/// println!("My button was clicked.");
|
||||
/// });
|
||||
/// let my_view : View<()> = todo!();
|
||||
|
@ -212,7 +212,7 @@ impl Button {
|
|||
|
||||
/// Attaches a callback for button press events. Don't get too creative now...
|
||||
/// best just to message pass or something.
|
||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||
pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
|
||||
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
|
||||
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
|
||||
let handler = TargetActionHandler::new(&*this, action);
|
||||
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -48,7 +48,45 @@ impl From<&EventModifierFlag> for NSUInteger {
|
|||
|
||||
/// Represents an event type that you can request to be notified about.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(target_pointer_width = "32", repr(u32))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(u64))]
|
||||
pub enum EventType {
|
||||
/// A keydown event.
|
||||
KeyDown
|
||||
LeftMouseDown = 1,
|
||||
LeftMouseUp = 2,
|
||||
RightMouseDown = 3,
|
||||
RightMouseUp = 4,
|
||||
MouseMoved = 5,
|
||||
LeftMouseDragged = 6,
|
||||
RightMouseDragged = 7,
|
||||
MouseEntered = 8,
|
||||
MouseExited = 9,
|
||||
KeyDown = 10,
|
||||
KeyUp = 11,
|
||||
FlagsChanged = 12,
|
||||
AppKitDefined = 13,
|
||||
SystemDefined = 14,
|
||||
ApplicationDefined = 15,
|
||||
Periodic = 16,
|
||||
CursorUpdate = 17,
|
||||
|
||||
ScrollWheel = 22,
|
||||
TabletPoint = 23,
|
||||
TabletProximity = 24,
|
||||
OtherMouseDown = 25,
|
||||
OtherMouseUp = 26,
|
||||
OtherMouseDragged = 27,
|
||||
|
||||
Gesture = 29,
|
||||
Magnify = 30,
|
||||
Swipe = 31,
|
||||
Rotate = 18,
|
||||
BeginGesture = 19,
|
||||
EndGesture = 20,
|
||||
|
||||
SmartMagnify = 32,
|
||||
QuickLook = 33,
|
||||
Pressure = 34,
|
||||
DirectTouch = 37,
|
||||
|
||||
ChangeMode = 38
|
||||
}
|
||||
|
|
|
@ -41,21 +41,41 @@ impl NSArray {
|
|||
unsafe { msg_send![&*self.0, count] }
|
||||
}
|
||||
|
||||
/// A helper method for mapping over the backing `NSArray` items and producing a Rust `Vec<T>`.
|
||||
/// Often times we need to map in this framework to convert between Rust types, so isolating
|
||||
/// this out makes life much easier.
|
||||
pub fn map<T, F: Fn(id) -> T>(&self, transform: F) -> Vec<T> {
|
||||
let count = self.count();
|
||||
let objc = &*self.0;
|
||||
/// Returns an iterator over the `NSArray`
|
||||
pub fn iter<'a>(&'a self) -> NSArrayIterator<'a> {
|
||||
NSArrayIterator {
|
||||
next_index: 0,
|
||||
count: self.count(),
|
||||
array: self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NSArrayIterator<'a> {
|
||||
next_index: usize,
|
||||
count: usize,
|
||||
|
||||
array: &'a NSArray
|
||||
}
|
||||
|
||||
impl Iterator for NSArrayIterator<'_> {
|
||||
type Item = id;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// I don't know if it's worth trying to get in with NSFastEnumeration here. I'm content to
|
||||
// just rely on Rust, but someone is free to profile it if they want.
|
||||
(0..count)
|
||||
.map(|index| {
|
||||
let item: id = unsafe { msg_send![objc, objectAtIndex: index] };
|
||||
transform(item)
|
||||
})
|
||||
.collect()
|
||||
if self.next_index < self.count {
|
||||
let objc = &*self.array.0;
|
||||
let index = self.next_index;
|
||||
|
||||
let item: id = unsafe { msg_send![objc, objectAtIndex: index] };
|
||||
|
||||
self.next_index += 1;
|
||||
Some(item)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,3 +86,5 @@ pub type NSInteger = libc::c_long;
|
|||
/// Platform-specific.
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pub type NSUInteger = libc::c_ulong;
|
||||
|
||||
pub type NSPoint = core_graphics::geometry::CGPoint;
|
||||
|
|
|
@ -74,36 +74,62 @@ impl MacSystemIcon {
|
|||
#[derive(Debug)]
|
||||
pub enum SFSymbol {
|
||||
AtSymbol,
|
||||
ArrowClockwise,
|
||||
Bell,
|
||||
BellFill,
|
||||
BellBadge,
|
||||
BellBadgeFill,
|
||||
GearShape,
|
||||
FolderFilled,
|
||||
ListAndFilm,
|
||||
PaperPlane,
|
||||
PaperPlaneFilled,
|
||||
Plus,
|
||||
Minus,
|
||||
Message,
|
||||
MessageFill,
|
||||
MessageBadge,
|
||||
MessageBadgeFill,
|
||||
MessageBadgeFilledFill,
|
||||
PersonCropCircle,
|
||||
SliderVertical3,
|
||||
SquareAndArrowUpOnSquare,
|
||||
SquareAndArrowUpOnSquareFill,
|
||||
SquareAndArrowDownOnSquare,
|
||||
SquareAndArrowDownOnSquareFill,
|
||||
SquareDashed
|
||||
SquareDashed,
|
||||
SquareAndPencil
|
||||
}
|
||||
|
||||
impl SFSymbol {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AtSymbol => "at",
|
||||
Self::ArrowClockwise => "arrow.clockwise",
|
||||
Self::GearShape => "gearshape",
|
||||
Self::FolderFilled => "folder.fill",
|
||||
Self::ListAndFilm => "list.and.film",
|
||||
Self::Bell => "bell",
|
||||
Self::BellFill => "bell.fill",
|
||||
Self::BellBadge => "bell.badge",
|
||||
Self::BellBadgeFill => "bell.badge.fill",
|
||||
Self::PaperPlane => "paperplane",
|
||||
Self::PaperPlaneFilled => "paperplane.fill",
|
||||
Self::Plus => "plus",
|
||||
Self::Minus => "minus",
|
||||
Self::Message => "message",
|
||||
Self::MessageFill => "message.fill",
|
||||
Self::MessageBadge => "message.badge",
|
||||
Self::MessageBadgeFill => "message.badge.fill",
|
||||
Self::MessageBadgeFilledFill => "message.badge.filled.fill",
|
||||
Self::PersonCropCircle => "person.crop.circle",
|
||||
Self::SliderVertical3 => "slider.vertical.3",
|
||||
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
|
||||
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
|
||||
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
|
||||
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill",
|
||||
Self::SquareDashed => "square.dashed"
|
||||
Self::SquareDashed => "square.dashed",
|
||||
Self::SquareAndPencil => "square.and.pencil"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use core_graphics::{
|
|||
};
|
||||
|
||||
use super::icons::*;
|
||||
use crate::foundation::{id, NSData, NSString, NO, YES};
|
||||
use crate::foundation::{id, NSData, NSString, NO, NSURL, YES};
|
||||
use crate::utils::os;
|
||||
|
||||
/// Specifies resizing behavior for image drawing.
|
||||
|
@ -146,6 +146,14 @@ impl Image {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn with_contents_of_url(url: NSURL) -> Self {
|
||||
Image(unsafe {
|
||||
let alloc: id = msg_send![Self::class(), alloc];
|
||||
ShareId::from_ptr(msg_send![alloc, initWithContentsOfURL: url.objc])
|
||||
})
|
||||
}
|
||||
|
||||
/// Given a Vec of data, will transform it into an Image by passing it through NSData.
|
||||
/// This can be useful for when you need to include_bytes!() something into your binary.
|
||||
pub fn with_data(data: &[u8]) -> Self {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
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
|
||||
);
|
||||
})
|
||||
}
|
|
@ -27,7 +27,7 @@ pub static ACTION_CALLBACK_PTR: &str = "rstTargetActionPtr";
|
|||
/// Point is, Button aren't created that much in the grand scheme of things,
|
||||
/// and the heap isn't our enemy in a GUI framework anyway. If someone knows
|
||||
/// a better way to do this that doesn't require double-boxing, I'm all ears.
|
||||
pub struct Action(Box<dyn Fn() + Send + Sync + 'static>);
|
||||
pub struct Action(Box<dyn Fn(*const Object) + Send + Sync + 'static>);
|
||||
|
||||
impl fmt::Debug for Action {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
|
@ -51,7 +51,7 @@ pub struct TargetActionHandler {
|
|||
|
||||
impl TargetActionHandler {
|
||||
/// Returns a new TargetEventHandler.
|
||||
pub fn new<F: Fn() + Send + Sync + 'static>(control: &Object, action: F) -> Self {
|
||||
pub fn new<F: Fn(*const Object) + Send + Sync + 'static>(control: &Object, action: F) -> Self {
|
||||
let block = Box::new(Action(Box::new(action)));
|
||||
let ptr = Box::into_raw(block);
|
||||
|
||||
|
@ -74,9 +74,9 @@ impl TargetActionHandler {
|
|||
}
|
||||
|
||||
/// This will fire for an NSButton callback.
|
||||
extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id) {
|
||||
extern "C" fn perform<F: Fn(*const Object) + 'static>(this: &mut Object, _: Sel, sender: id) {
|
||||
let action = load::<Action>(this, ACTION_CALLBACK_PTR);
|
||||
(action.0)();
|
||||
(action.0)(sender.cast_const());
|
||||
}
|
||||
|
||||
/// Due to the way that Rust and Objective-C live... very different lifestyles,
|
||||
|
@ -91,7 +91,7 @@ extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id)
|
|||
/// The `NSButton` owns this object on instantiation, and will release it
|
||||
/// on drop. We handle the heap copy on the Rust side, so setting the block
|
||||
/// is just an ivar.
|
||||
pub(crate) fn register_invoker_class<F: Fn() + 'static>() -> *const Class {
|
||||
pub(crate) fn register_invoker_class<F: Fn(*const Object) + 'static>() -> *const Class {
|
||||
load_or_register_class("NSObject", "RSTTargetActionHandler", |decl| unsafe {
|
||||
decl.add_ivar::<usize>(ACTION_CALLBACK_PTR);
|
||||
decl.add_method(sel!(perform:), perform::<F> as extern "C" fn(&mut Object, _, id));
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -106,7 +106,7 @@ impl Pasteboard {
|
|||
}));
|
||||
}
|
||||
|
||||
let urls = NSArray::retain(contents).map(|url| NSURL::retain(url)).into_iter().collect();
|
||||
let urls = NSArray::retain(contents).iter().map(|url| NSURL::retain(url)).collect();
|
||||
|
||||
Ok(urls)
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 destination’s 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
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 }
|
||||
}
|
|
@ -140,7 +140,7 @@ impl Select {
|
|||
/// Really, this is not ideal.
|
||||
///
|
||||
/// I cannot stress this enough.
|
||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||
pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
|
||||
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
|
||||
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
|
||||
let handler = TargetActionHandler::new(&*this, action);
|
||||
|
|
|
@ -130,7 +130,7 @@ impl Switch {
|
|||
|
||||
/// Attaches a callback for button press events. Don't get too creative now...
|
||||
/// best just to message pass or something.
|
||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||
pub fn set_action<F: Fn(*const Object) + Send + Sync + 'static>(&mut self, action: F) {
|
||||
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
|
||||
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
|
||||
let handler = TargetActionHandler::new(&*this, action);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -421,7 +417,10 @@ impl<T> Label<T> {
|
|||
/// Sets the maximum number of lines.
|
||||
pub fn set_max_number_of_lines(&self, num: NSInteger) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
#[cfg(feature = "appkit")]
|
||||
let _: () = msg_send![obj, setMaximumNumberOfLines: num];
|
||||
#[cfg(feature = "uikit")]
|
||||
let _: () = msg_send![obj, setNumberOfLines: num];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -467,3 +466,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
|
||||
/// `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();
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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;
|
||||
|
@ -398,3 +400,10 @@ impl LayerContentsRedrawPolicy {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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