Merge pull request #31 from ryanmcgrath/airyx-appkit-uikit-features
This commit is contained in:
commit
a790eb95b7
15
Cargo.toml
15
Cargo.toml
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "cacao"
|
name = "cacao"
|
||||||
description = "Rust bindings for AppKit (macOS, beta) and UIKit (iOS/tvOS, alpha)."
|
description = "Rust bindings for AppKit (macOS/Airyx/GNUStep, beta) and UIKit (iOS/tvOS, alpha)."
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
authors = ["Ryan McGrath <ryan@rymc.io>"]
|
authors = ["Ryan McGrath <ryan@rymc.io>"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
@ -20,23 +20,26 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
block = "0.1.6"
|
block = "0.1.6"
|
||||||
core-foundation = { version = "0.9", features = ["with-chrono", "mac_os_10_8_features"] }
|
core-foundation = { version = "0.9", features = ["with-chrono"] }
|
||||||
core-graphics = "0.22"
|
core-graphics = "0.22"
|
||||||
dispatch = "0.2.0"
|
dispatch = "0.2.0"
|
||||||
|
infer = { version = "0.4", optional = true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
objc_id = "0.1.1"
|
objc_id = "0.1.1"
|
||||||
os_info = "3.0.1"
|
os_info = "3.0.1"
|
||||||
uuid = { version = "0.8", features = ["v4"], optional = true }
|
|
||||||
url = "2.1.1"
|
url = "2.1.1"
|
||||||
infer = { version = "0.4", optional = true }
|
uuid = { version = "0.8", features = ["v4"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
eval = "0.4"
|
eval = "0.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
appkit = ["core-foundation/mac_os_10_8_features"]
|
||||||
|
uikit = []
|
||||||
|
autolayout = []
|
||||||
|
default = ["appkit", "autolayout"]
|
||||||
cloudkit = []
|
cloudkit = []
|
||||||
color_fallbacks = []
|
color_fallbacks = []
|
||||||
quicklook = []
|
quicklook = []
|
||||||
|
|
57
README.md
57
README.md
|
@ -5,11 +5,17 @@ It tries to do so in a way that, if you've done programming for the framework be
|
||||||
Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
||||||
creative coding and assumptions can get us pretty far.
|
creative coding and assumptions can get us pretty far.
|
||||||
|
|
||||||
`0.2.0` exists on crates.io in part to enable the project to see wider usage, which can
|
This exists on crates.io in part to enable the project to see wider usage, which can
|
||||||
inform development. That said, this library is currently early stages and may have bugs - your usage of it is at
|
inform development. That said, this library is currently early stages and may have bugs - your usage of it is at
|
||||||
your own risk. However, provided you follow the rules (regarding memory/ownership) it's
|
your own risk. However, provided you follow the rules (regarding memory/ownership) it's
|
||||||
already fine for some apps. The core repository has a wealth of examples to help you get started.
|
already fine for some apps. The core repository has a wealth of examples to help you get started.
|
||||||
|
|
||||||
|
> **Important**
|
||||||
|
>
|
||||||
|
> If you are migrating from 0.2 to 0.3, you should elect either `appkit` or `uikit` as a feature in your `Cargo.toml`. This change was made to
|
||||||
|
> support platforms that aren't just macOS/iOS/tvOS (e.g, gnustep, airyx). One of these features is required to work; `appkit` is defaulted for
|
||||||
|
> ease of development.
|
||||||
|
|
||||||
>_Note that this crate relies on the Objective-C runtime. Interfacing with the runtime **requires**
|
>_Note that this crate relies on the Objective-C runtime. Interfacing with the runtime **requires**
|
||||||
unsafe blocks; this crate handles those unsafe interactions for you and provides a safe wrapper,
|
unsafe blocks; this crate handles those unsafe interactions for you and provides a safe wrapper,
|
||||||
but by using this crate you understand that usage of `unsafe` is a given and will be somewhat
|
but by using this crate you understand that usage of `unsafe` is a given and will be somewhat
|
||||||
|
@ -17,11 +23,16 @@ rampant for wrapped controls. This does **not** mean you can't assess, review, o
|
||||||
usage - just know it's happening, and in large part it's not going away. Issues pertaining to the mere
|
usage - just know it's happening, and in large part it's not going away. Issues pertaining to the mere
|
||||||
existence of unsafe will be closed without comment._
|
existence of unsafe will be closed without comment._
|
||||||
|
|
||||||
|
If you're looking to build the docs for this on your local machine, you'll want the following due to the way feature flags work
|
||||||
|
with `cargo doc`:
|
||||||
|
|
||||||
|
`RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --open`
|
||||||
|
|
||||||
# Hello World
|
# Hello World
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::Window;
|
use cacao::appkit::window::Window;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
|
@ -56,22 +67,28 @@ ensures the application has had time to initialize and do any housekeeping neces
|
||||||
scenes.
|
scenes.
|
||||||
|
|
||||||
## Currently Supported
|
## Currently Supported
|
||||||
In terms of mostly working pieces, the following currently work. This list is not exhaustive and you're encouraged to check out the documentation for more info:
|
In terms of mostly working pieces, the table below showcases the level of support for varying features. This list is not exhaustive just by virtue of documentation updating being hell - so you're encouraged to check out the code-built documentation for more info:
|
||||||
|
|
||||||
- `App` initialization and event delegation
|
Note that while iOS has green checkmarks, some components still aren't as well defined (e.g, Views/ViewControllers are still very alpha there).
|
||||||
- `Window` construction, handling, and event delegation
|
|
||||||
- `View` construction, basic styling, some event delegation
|
Non-Apple platforms that shim or provide a form of AppKit may be able to use a good chunk of the AppKit support in this library.
|
||||||
- `ViewController` construction, lifecycle delegation
|
|
||||||
- `Color`, for handling system-established color types
|
| Component | Description | AppKit | iOS | tvOS |
|
||||||
- `ListView` support, including cell reuse (still needs testing)
|
| ------------------- | ----------------------------------- | ------ | --- | ---- |
|
||||||
- `Button` support, as well as enabling them in Toolbars
|
| App | Initialization & events | ✅ | ✅ | ❌ |
|
||||||
- `Label` and `TextField` support for basic text handling.
|
| Window | Construction, handling, events | ✅ | ✅ | ❌ |
|
||||||
- `Image`, `ImageView` and `SystemIcon` for image usage. Images can use a custom draw handler, and draw graphics with the [core_graphics]() crate
|
| View | Construction, styling, events | ✅ | ✅ | ❌ |
|
||||||
- `Toolbar` construction and basic API
|
| ViewController | Construction, lifecycle events | ✅ | ✅ | ❌ |
|
||||||
- `SplitViewController` support, including some Big-Sur-only additions
|
| Color | System-backed colors, theming | ✅ | ✅ | ❌ |
|
||||||
- `WebView` with a basic API for handling callbacks
|
| ListView | Reusable list w/ cached rows | ✅ | ❌ | ❌ |
|
||||||
- `UserDefaults` for persisting small pieces of data per-application
|
| Button | Styling, events, toolbar support | ✅ | ❌ | ❌ |
|
||||||
- `Autolayout` for View layout and such
|
| Label/TextField | Text rendering & input | ✅ | ❌ | ❌ |
|
||||||
|
| Image/ImageView | Loading, drawing, etc | ✅ | ❌ | ❌ |
|
||||||
|
| Toolbar | Basic native toolbar | ✅ | ❌ | ❌ |
|
||||||
|
| SplitViewController | Split views (Big Sur friendly) | ✅ | ❌ | ❌ |
|
||||||
|
| WebView | Wrapper for WKWebView | ✅ | ❌ | ❌ |
|
||||||
|
| UserDefaults | Persisting small data | ✅ | ✅ | ❌ |
|
||||||
|
| Autolayout | View layout for varying screens | ✅ | ✅ | ❌ |
|
||||||
|
|
||||||
## Optional Features
|
## Optional Features
|
||||||
|
|
||||||
|
@ -79,11 +96,13 @@ The following are a list of [Cargo features][cargo-features] that can be enabled
|
||||||
|
|
||||||
The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
||||||
|
|
||||||
|
- `appkit`: Links `AppKit.framework`.
|
||||||
|
- `uikit`: Links `UIKit.framework` (iOS/tvOS only).
|
||||||
- `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit functionality. Currently not feature complete.
|
- `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit functionality. Currently not feature complete.
|
||||||
- `color_fallbacks`: Provides fallback colors for older systems where `systemColor` types don't exist. This feature is very uncommon and you probably don't need it.
|
- `color_fallbacks`: Provides fallback colors for older systems where `systemColor` types don't exist. This feature is very uncommon and you probably don't need it.
|
||||||
- `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for files.
|
- `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for files.
|
||||||
- `user-notifications`: Links `UserNotifications.framework` and provides functionality for emitting notifications on macOS and iOS. Note that this _requires_ your application be code-signed, and will not work without it.
|
- `user-notifications`: Links `UserNotifications.framework` and provides functionality for emitting notifications on macOS and iOS. Note that this _requires_ your application be code-signed, and will not work without it.
|
||||||
- `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`. This feature is not supported on tvOS, as the platform has no webview control.
|
- `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`. This feature is not supported on tvOS, as the platform has no webview control. This feature is also potentially only supported for macOS/iOS due to the WKWebView control and varying support on non-Apple platforms.
|
||||||
- `webview-downloading-macos`: Enables downloading files from the `WebView` via a private interface. This is not an App-Store-safe feature, so be aware of that before enabling. This feature is not supported on iOS (a user would handle downloads very differently) or tvOS (there's no web browser there at all).
|
- `webview-downloading-macos`: Enables downloading files from the `WebView` via a private interface. This is not an App-Store-safe feature, so be aware of that before enabling. This feature is not supported on iOS (a user would handle downloads very differently) or tvOS (there's no web browser there at all).
|
||||||
|
|
||||||
[cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
[cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
||||||
|
|
12
build.rs
12
build.rs
|
@ -1,17 +1,13 @@
|
||||||
//! Emits linker flags depending on platforms and features.
|
//! Emits linker flags depending on platforms and features.
|
||||||
//!
|
|
||||||
//! (iOS/macOS only right now... maybe tvOS one day?)
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let target = std::env::var("TARGET").unwrap();
|
|
||||||
|
|
||||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||||
|
|
||||||
if target.contains("-ios") {
|
#[cfg(feature = "appkit")]
|
||||||
println!("cargo:rustc-link-lib=framework=UIKit");
|
|
||||||
} else {
|
|
||||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||||
}
|
|
||||||
|
#[cfg(feature = "uikit")]
|
||||||
|
println!("cargo:rustc-link-lib=framework=UIKit");
|
||||||
|
|
||||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||||
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
||||||
|
|
179
examples/animation.rs
Normal file
179
examples/animation.rs
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
//! This example builds on the AutoLayout example, but adds in animation
|
||||||
|
//! via `AnimationContext`. Views and layout anchors have special proxy objects that can be cloned
|
||||||
|
//! into handlers, enabling basic animation support within `AnimationContext`.
|
||||||
|
//!
|
||||||
|
//! This one is a bit kludgier than some other examples, but the comments throughout this should
|
||||||
|
//! clarify why that is.
|
||||||
|
|
||||||
|
use cacao::color::Color;
|
||||||
|
use cacao::layout::{Layout, LayoutConstraint, LayoutConstraintAnimatorProxy};
|
||||||
|
use cacao::view::{View, ViewAnimatorProxy};
|
||||||
|
|
||||||
|
use cacao::appkit::{App, AppDelegate, AnimationContext};
|
||||||
|
use cacao::appkit::{Event, EventMask, EventMonitor};
|
||||||
|
use cacao::appkit::menu::Menu;
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||||
|
|
||||||
|
struct BasicApp {
|
||||||
|
window: Window<AppWindow>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppDelegate for BasicApp {
|
||||||
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(Menu::standard());
|
||||||
|
App::activate();
|
||||||
|
|
||||||
|
self.window.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This map is the four different animation frames that we display, per view type.
|
||||||
|
/// Why do we have this here?
|
||||||
|
///
|
||||||
|
/// Well, it's because there's no random number generator in the standard library, and I really
|
||||||
|
/// dislike when examples need crates attached to 'em.
|
||||||
|
///
|
||||||
|
/// The basic mapping logic is this: each entry is a view's frame(s), and each frame is an array
|
||||||
|
/// of:
|
||||||
|
///
|
||||||
|
/// [top, left, width, height, alpha]
|
||||||
|
///
|
||||||
|
/// We then treat each frame index as follows:
|
||||||
|
///
|
||||||
|
/// w: 0
|
||||||
|
/// a: 1
|
||||||
|
/// s: 2
|
||||||
|
/// d: 3
|
||||||
|
const ANIMATIONS: [[[f64; 5]; 4]; 3] = [
|
||||||
|
// Blue
|
||||||
|
[
|
||||||
|
[44., 16., 100., 100., 1.],
|
||||||
|
[128., 84., 144., 124., 1.],
|
||||||
|
[32., 32., 44., 44., 0.7],
|
||||||
|
[328., 157., 200., 200., 0.7],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Red
|
||||||
|
[
|
||||||
|
[44., 132., 100., 100., 1.],
|
||||||
|
[40., 47., 80., 64., 0.7],
|
||||||
|
[84., 220., 600., 109., 1.0],
|
||||||
|
[48., 600., 340., 44., 0.7],
|
||||||
|
],
|
||||||
|
|
||||||
|
// Green
|
||||||
|
[
|
||||||
|
[44., 248., 100., 100., 1.],
|
||||||
|
[420., 232., 420., 244., 0.7],
|
||||||
|
[310., 440., 150., 238., 0.7],
|
||||||
|
[32., 32., 44., 44., 1.],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
/// A helper method for generating frame constraints that we want to be animating.
|
||||||
|
fn apply_styles(
|
||||||
|
view: &View,
|
||||||
|
parent: &View,
|
||||||
|
background_color: Color,
|
||||||
|
animation_table_index: usize
|
||||||
|
) -> [LayoutConstraint; 4] {
|
||||||
|
view.set_background_color(background_color);
|
||||||
|
view.layer.set_corner_radius(16.);
|
||||||
|
parent.add_subview(view);
|
||||||
|
|
||||||
|
let animation = ANIMATIONS[animation_table_index][0];
|
||||||
|
|
||||||
|
[
|
||||||
|
view.top.constraint_equal_to(&parent.top).offset(animation[0]),
|
||||||
|
view.left.constraint_equal_to(&parent.left).offset(animation[1]),
|
||||||
|
view.width.constraint_equal_to_constant(animation[2]),
|
||||||
|
view.height.constraint_equal_to_constant(animation[3])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AppWindow {
|
||||||
|
content: View,
|
||||||
|
blue: View,
|
||||||
|
red: View,
|
||||||
|
green: View,
|
||||||
|
key_monitor: Option<EventMonitor>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowDelegate for AppWindow {
|
||||||
|
const NAME: &'static str = "WindowDelegate";
|
||||||
|
|
||||||
|
fn did_load(&mut self, window: Window) {
|
||||||
|
window.set_title("Animation Example (Use W/A/S/D to change state!)");
|
||||||
|
window.set_minimum_content_size(300., 300.);
|
||||||
|
|
||||||
|
window.set_content_view(&self.content);
|
||||||
|
|
||||||
|
let blue_frame = apply_styles(&self.blue, &self.content, Color::SystemBlue, 0);
|
||||||
|
let red_frame = apply_styles(&self.red, &self.content, Color::SystemRed, 1);
|
||||||
|
let green_frame = apply_styles(&self.green, &self.content, Color::SystemGreen, 2);
|
||||||
|
|
||||||
|
let alpha_animators = [&self.blue, &self.red, &self.green].iter().map(|view| {
|
||||||
|
view.animator.clone()
|
||||||
|
}).collect::<Vec<ViewAnimatorProxy>>();
|
||||||
|
|
||||||
|
let constraint_animators = [blue_frame, red_frame, green_frame].iter().map(|frame| {
|
||||||
|
LayoutConstraint::activate(frame);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
frame[0].animator.clone(),
|
||||||
|
frame[1].animator.clone(),
|
||||||
|
frame[2].animator.clone(),
|
||||||
|
frame[3].animator.clone(),
|
||||||
|
]
|
||||||
|
}).collect::<Vec<Vec<LayoutConstraintAnimatorProxy>>>();
|
||||||
|
|
||||||
|
// Monitor key change events for w/a/s/d, and then animate each view to their correct
|
||||||
|
// frame and alpha value.
|
||||||
|
self.key_monitor = Some(Event::local_monitor(EventMask::KeyDown, move |evt| {
|
||||||
|
let characters = evt.characters();
|
||||||
|
|
||||||
|
let animation_index = match characters.as_ref() {
|
||||||
|
"w" => 0,
|
||||||
|
"a" => 1,
|
||||||
|
"s" => 2,
|
||||||
|
"d" => 3,
|
||||||
|
_ => 4
|
||||||
|
};
|
||||||
|
|
||||||
|
if animation_index == 4 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let alpha_animators = alpha_animators.clone();
|
||||||
|
let constraint_animators = constraint_animators.clone();
|
||||||
|
|
||||||
|
AnimationContext::run(move |_ctx| {
|
||||||
|
alpha_animators.iter().enumerate().for_each(move |(index, view)| {
|
||||||
|
let animation = ANIMATIONS[index][animation_index];
|
||||||
|
view.set_alpha(animation[4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
constraint_animators.iter().enumerate().for_each(move |(index, frame)| {
|
||||||
|
let animation = ANIMATIONS[index][animation_index];
|
||||||
|
frame[0].set_offset(animation[0]);
|
||||||
|
frame[1].set_offset(animation[1]);
|
||||||
|
frame[2].set_offset(animation[2]);
|
||||||
|
frame[3].set_offset(animation[3]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
None
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new("com.test.window", BasicApp {
|
||||||
|
window: Window::with(WindowConfig::default(), AppWindow::default())
|
||||||
|
}).run();
|
||||||
|
}
|
|
@ -5,8 +5,9 @@ use cacao::color::{Color, Theme};
|
||||||
use cacao::layout::{Layout, LayoutConstraint};
|
use cacao::layout::{Layout, LayoutConstraint};
|
||||||
use cacao::view::View;
|
use cacao::view::View;
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowDelegate};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||||
|
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
window: Window<AppWindow>
|
window: Window<AppWindow>
|
||||||
|
@ -14,9 +15,41 @@ struct BasicApp {
|
||||||
|
|
||||||
impl AppDelegate for BasicApp {
|
impl AppDelegate for BasicApp {
|
||||||
fn did_finish_launching(&self) {
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
App::activate();
|
App::activate();
|
||||||
|
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
use cacao::notification_center::Dispatcher;
|
use cacao::notification_center::Dispatcher;
|
||||||
use cacao::webview::{WebView, WebViewConfig, WebViewDelegate};
|
use cacao::webview::{WebView, WebViewConfig, WebViewDelegate};
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::menu::{Menu, MenuItem};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
use cacao::macos::toolbar::Toolbar;
|
use cacao::appkit::toolbar::Toolbar;
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowDelegate, WindowToolbarStyle};
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate, WindowToolbarStyle};
|
||||||
|
|
||||||
mod toolbar;
|
mod toolbar;
|
||||||
use toolbar::BrowserToolbar;
|
use toolbar::BrowserToolbar;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use cacao::objc::{msg_send, sel, sel_impl};
|
||||||
use cacao::button::Button;
|
use cacao::button::Button;
|
||||||
use cacao::input::{TextField, TextFieldDelegate};
|
use cacao::input::{TextField, TextFieldDelegate};
|
||||||
|
|
||||||
use cacao::macos::toolbar::{Toolbar, ToolbarDisplayMode, ToolbarItem, ItemIdentifier, ToolbarDelegate};
|
use cacao::appkit::toolbar::{Toolbar, ToolbarDisplayMode, ToolbarItem, ItemIdentifier, ToolbarDelegate};
|
||||||
|
|
||||||
use super::Action;
|
use super::Action;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use cacao::lazy_static::lazy_static;
|
use cacao::lazy_static::lazy_static;
|
||||||
use cacao::macos::App;
|
use cacao::appkit::App;
|
||||||
|
|
||||||
use crate::CalculatorApp;
|
use crate::CalculatorApp;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use cacao::text::{Font, Label, TextAlign};
|
||||||
use cacao::layout::{LayoutConstraint, Layout};
|
use cacao::layout::{LayoutConstraint, Layout};
|
||||||
use cacao::button::{Button, BezelStyle};
|
use cacao::button::{Button, BezelStyle};
|
||||||
use cacao::color::Color;
|
use cacao::color::Color;
|
||||||
use cacao::macos::FocusRingType;
|
use cacao::appkit::FocusRingType;
|
||||||
use cacao::view::{View, ViewDelegate};
|
use cacao::view::{View, ViewDelegate};
|
||||||
|
|
||||||
use crate::button_row::ButtonRow;
|
use crate::button_row::ButtonRow;
|
||||||
|
@ -17,7 +17,7 @@ pub fn button(text: &str, msg: Msg) -> Button {
|
||||||
button.set_bezel_style(BezelStyle::SmallSquare);
|
button.set_bezel_style(BezelStyle::SmallSquare);
|
||||||
button.set_focus_ring_type(FocusRingType::None);
|
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());
|
button.set_key_equivalent(&*text.to_lowercase());
|
||||||
|
|
||||||
let font = Font::system(22.);
|
let font = Font::system(22.);
|
||||||
button.set_font(&font);
|
button.set_font(&font);
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::{Window, WindowConfig, TitleVisibility};
|
use cacao::appkit::window::{Window, WindowConfig, TitleVisibility};
|
||||||
use cacao::macos::{Event, EventMask, EventMonitor};
|
use cacao::appkit::{Event, EventMask, EventMonitor};
|
||||||
use cacao::color::Color;
|
use cacao::color::Color;
|
||||||
use cacao::notification_center::Dispatcher;
|
use cacao::notification_center::Dispatcher;
|
||||||
use cacao::view::View;
|
use cacao::view::View;
|
||||||
|
@ -37,7 +37,7 @@ impl AppDelegate for CalculatorApp {
|
||||||
// Event Monitors need to be started after the App has been activated.
|
// Event Monitors need to be started after the App has been activated.
|
||||||
// We use an RwLock here, but it's possible this entire method can be
|
// We use an RwLock here, but it's possible this entire method can be
|
||||||
// &mut self and you wouldn't need these kinds of shenanigans.
|
// &mut self and you wouldn't need these kinds of shenanigans.
|
||||||
self.start_monitoring();
|
//self.start_monitoring();
|
||||||
|
|
||||||
self.window.set_title("Calculator");
|
self.window.set_title("Calculator");
|
||||||
self.window.set_background_color(Color::rgb(49,49,49));
|
self.window.set_background_color(Color::rgb(49,49,49));
|
||||||
|
@ -48,6 +48,7 @@ impl AppDelegate for CalculatorApp {
|
||||||
self.window.set_content_view(&self.content);
|
self.window.set_content_view(&self.content);
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_terminate_after_last_window_closed(&self) -> bool {
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::defaults::{UserDefaults, Value};
|
use cacao::defaults::{UserDefaults, Value};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
119
examples/frame_layout.rs
Normal file
119
examples/frame_layout.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
//! This example showcases setting up a basic application and window, setting up some views to
|
||||||
|
//! work with autolayout, and some basic ways to handle colors.
|
||||||
|
|
||||||
|
use cacao::color::Color;
|
||||||
|
use cacao::geometry::Rect;
|
||||||
|
use cacao::layout::Layout;
|
||||||
|
use cacao::view::View;
|
||||||
|
|
||||||
|
use cacao::appkit::{App, AppDelegate};
|
||||||
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||||
|
|
||||||
|
const CORNER_RADIUS: f64 = 16.;
|
||||||
|
const SPACING: f64 = 10.;
|
||||||
|
const TOP: f64 = 40.;
|
||||||
|
const WIDTH: f64 = 100.;
|
||||||
|
const HEIGHT: f64 = 100.;
|
||||||
|
|
||||||
|
struct BasicApp {
|
||||||
|
window: Window<AppWindow>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppDelegate for BasicApp {
|
||||||
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
|
App::activate();
|
||||||
|
|
||||||
|
self.window.show();
|
||||||
|
self.window.delegate.as_ref().unwrap().layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AppWindow {
|
||||||
|
content: View,
|
||||||
|
blue: View,
|
||||||
|
red: View,
|
||||||
|
green: View
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWindow {
|
||||||
|
pub fn layout(&self) {
|
||||||
|
self.blue.set_background_color(Color::SystemBlue);
|
||||||
|
self.blue.set_frame(Rect {
|
||||||
|
top: TOP,
|
||||||
|
left: SPACING,
|
||||||
|
width: WIDTH,
|
||||||
|
height: HEIGHT
|
||||||
|
});
|
||||||
|
self.blue.layer.set_corner_radius(CORNER_RADIUS);
|
||||||
|
self.content.add_subview(&self.blue);
|
||||||
|
|
||||||
|
self.red.set_background_color(Color::SystemRed);
|
||||||
|
self.red.set_frame(Rect {
|
||||||
|
top: TOP,
|
||||||
|
left: WIDTH + (SPACING * 2.),
|
||||||
|
width: WIDTH,
|
||||||
|
height: HEIGHT
|
||||||
|
});
|
||||||
|
self.red.layer.set_corner_radius(CORNER_RADIUS);
|
||||||
|
self.content.add_subview(&self.red);
|
||||||
|
|
||||||
|
self.green.set_background_color(Color::SystemGreen);
|
||||||
|
self.green.set_frame(Rect {
|
||||||
|
top: TOP,
|
||||||
|
left: (WIDTH * 2.) + (SPACING * 3.),
|
||||||
|
width: WIDTH,
|
||||||
|
height: HEIGHT
|
||||||
|
});
|
||||||
|
self.green.layer.set_corner_radius(CORNER_RADIUS);
|
||||||
|
self.content.add_subview(&self.green);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowDelegate for AppWindow {
|
||||||
|
const NAME: &'static str = "WindowDelegate";
|
||||||
|
|
||||||
|
fn did_load(&mut self, window: Window) {
|
||||||
|
window.set_title("Frame Layout Example");
|
||||||
|
window.set_minimum_content_size(300., 300.);
|
||||||
|
window.set_content_view(&self.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new("com.test.window", BasicApp {
|
||||||
|
window: Window::with(WindowConfig::default(), AppWindow::default())
|
||||||
|
}).run();
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use cacao::ios::{
|
use cacao::uikit::{
|
||||||
App, AppDelegate, Scene, SceneConfig, SceneSession,
|
App, AppDelegate, Scene, SceneConfig, SceneSession,
|
||||||
SceneConnectionOptions, WindowSceneDelegate, Window
|
SceneConnectionOptions, WindowSceneDelegate, Window
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,8 +6,8 @@ Since this needs to run in an iOS simulator or on a device, you can't run it lik
|
||||||
|
|
||||||
- Start a simulator (Simulator.app).
|
- Start a simulator (Simulator.app).
|
||||||
- `cargo install cargo-bundle`
|
- `cargo install cargo-bundle`
|
||||||
- `cargo bundle --example ios-beta --target x86_64-apple-ios`
|
- `cargo bundle --example ios-beta --no-default-features --features uikit,autolayout --target x86_64-apple-ios`
|
||||||
- `xcrun simctl install booted target/x86_64-apple-ios/debug/examples/bundle/ios/cacao-ios-test.app`
|
- `xcrun simctl install booted target/x86_64-apple-ios/debug/examples/bundle/ios/cacao-ios-beta.app`
|
||||||
- `xcrun simctl launch --console booted com.cacao.ios-test`
|
- `xcrun simctl launch --console booted com.cacao.ios-test`
|
||||||
|
|
||||||
## Current Support
|
## Current Support
|
||||||
|
|
64
examples/readme.md
Normal file
64
examples/readme.md
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
# Cacao Examples
|
||||||
|
This directory contains example code for apps written in cacao. To run an example, check out the list of commands below - some require certain features to be enabled.
|
||||||
|
|
||||||
|
## AutoLayout
|
||||||
|
An example that showcases layout out a view with AutoLayout. This requires the feature flag `autolayout` to be enabled, but it's defaulted for ease of use so doesn't need to be specified here. Platforms where AutoLayout is not supported will likely not work with this example.
|
||||||
|
|
||||||
|
`cargo run --example autolayout`
|
||||||
|
|
||||||
|
## Frame Layout
|
||||||
|
An example that showcases laying out with a more old school Frame-based approach. Platforms where AutoLayout are not supported will want to try this instead of the AutoLayout example.
|
||||||
|
|
||||||
|
**macOS:**
|
||||||
|
`cargo run --example frame_layout`
|
||||||
|
|
||||||
|
**Platforms lacking AutoLayout:**
|
||||||
|
`cargo run --example frame_layout --no-default-features --features appkit`
|
||||||
|
|
||||||
|
## Defaults
|
||||||
|
This example isn't GUI-specific, but showcases accessing `NSUserDefaults` from Rust for persisting basic data.
|
||||||
|
|
||||||
|
`cargo run --example defaults`
|
||||||
|
|
||||||
|
## Window
|
||||||
|
This example showcases creating a basic `Window`. This should run on all AppKit-supporting platforms.
|
||||||
|
|
||||||
|
`cargo run --example window`
|
||||||
|
|
||||||
|
## Window Controller
|
||||||
|
This example showcases creating a basic `WindowController`. This may run on all AppKit-supporting platforms.
|
||||||
|
|
||||||
|
`cargo run --example window_controller`
|
||||||
|
|
||||||
|
## Window Delegate
|
||||||
|
This example showcases creating a basic `WindowDelegate` to receive and handle events. This may run on all AppKit-supporting platforms.
|
||||||
|
|
||||||
|
`cargo run --example window_delegate`
|
||||||
|
|
||||||
|
## Text Input
|
||||||
|
This example showcases text input, and logs it to the underlying console. It's mostly a testbed to ensure that the backing widget for input behaves as expected.
|
||||||
|
|
||||||
|
`cargo run --example text_input`
|
||||||
|
|
||||||
|
## Calculator
|
||||||
|
A Rust-rendition of the macOS Calculator app.
|
||||||
|
|
||||||
|
`cargo run --example calculator`
|
||||||
|
|
||||||
|
## To-Do List
|
||||||
|
A "kitchen sink" example that showcases how to do more advanced things, such as cached reusable ListView components.
|
||||||
|
|
||||||
|
`cargo run --example todos_list`
|
||||||
|
|
||||||
|
## Browser
|
||||||
|
A _very_ basic web browser. Platforms that don't support WKWebView will likely not work with this example.
|
||||||
|
|
||||||
|
`cargo run --example browser --features webview`
|
||||||
|
|
||||||
|
## Webview Custom Protocol
|
||||||
|
This example showcases a custom protocol for the webview feature. Platforms that don't support WKWebView will likely not work with this example.
|
||||||
|
|
||||||
|
`cargo run --example webview_custom_protocol --features webview`
|
||||||
|
|
||||||
|
## iOS (Beta)
|
||||||
|
This example showcases how to build and run an iOS app in Rust. See the README in the `ios-beta` folder for instructions on how to run.
|
|
@ -5,9 +5,9 @@ use cacao::layout::{Layout, LayoutConstraint};
|
||||||
use cacao::input::{TextField, TextFieldDelegate};
|
use cacao::input::{TextField, TextFieldDelegate};
|
||||||
use cacao::view::View;
|
use cacao::view::View;
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::menu::{Menu, MenuItem};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowDelegate};
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||||
|
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
window: Window<AppWindow>
|
window: Window<AppWindow>
|
||||||
|
@ -41,7 +41,6 @@ impl AppDelegate for BasicApp {
|
||||||
MenuItem::SelectAll
|
MenuItem::SelectAll
|
||||||
]),
|
]),
|
||||||
|
|
||||||
// Sidebar option is 11.0+ only.
|
|
||||||
Menu::new("View", vec![
|
Menu::new("View", vec![
|
||||||
MenuItem::EnterFullScreen
|
MenuItem::EnterFullScreen
|
||||||
]),
|
]),
|
||||||
|
@ -59,6 +58,10 @@ impl AppDelegate for BasicApp {
|
||||||
App::activate();
|
App::activate();
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Implements a window for adding a new Todo.
|
//! Implements a window for adding a new Todo.
|
||||||
|
|
||||||
use cacao::macos::window::{Window, WindowDelegate};
|
use cacao::appkit::window::{Window, WindowDelegate};
|
||||||
use cacao::view::ViewController;
|
use cacao::view::ViewController;
|
||||||
|
|
||||||
use crate::storage::{dispatch_ui, Message};
|
use crate::storage::{dispatch_ui, Message};
|
||||||
|
|
|
@ -49,6 +49,7 @@ impl ViewDelegate for AddNewTodoContentView {
|
||||||
let input = TextField::new();
|
let input = TextField::new();
|
||||||
|
|
||||||
let mut button = Button::new("Add");
|
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(&instructions);
|
||||||
|
@ -65,7 +66,8 @@ impl ViewDelegate for AddNewTodoContentView {
|
||||||
input.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
input.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
||||||
|
|
||||||
button.top.constraint_equal_to(&input.bottom).offset(8.),
|
button.top.constraint_equal_to(&input.bottom).offset(8.),
|
||||||
button.trailing.constraint_equal_to(&view.trailing).offset(-16.)
|
button.trailing.constraint_equal_to(&view.trailing).offset(-16.),
|
||||||
|
button.bottom.constraint_equal_to(&view.bottom).offset(-16.)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
self.view = Some(view);
|
self.view = Some(view);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Implements the start of the App lifecycle. Handles creating the required menu and window
|
//! Implements the start of the App lifecycle. Handles creating the required menu and window
|
||||||
//! components and message dispatching.
|
//! components and message dispatching.
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::notification_center::Dispatcher;
|
use cacao::notification_center::Dispatcher;
|
||||||
|
|
||||||
use crate::menu::menu;
|
use crate::menu::menu;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//!
|
//!
|
||||||
//! This may get extracted into a different repo some day in the future.
|
//! This may get extracted into a different repo some day in the future.
|
||||||
|
|
||||||
use cacao::macos::App;
|
use cacao::appkit::App;
|
||||||
|
|
||||||
mod add;
|
mod add;
|
||||||
mod app;
|
mod app;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//! Correctly functioning menus are a key part of what makes a macOS app feel right, though, so
|
//! Correctly functioning menus are a key part of what makes a macOS app feel right, though, so
|
||||||
//! this is here for those who might want to use this todos example as a starting point.
|
//! this is here for those who might want to use this todos example as a starting point.
|
||||||
|
|
||||||
use cacao::macos::menu::{Menu, MenuItem};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
|
||||||
use crate::storage::{dispatch_ui, Message};
|
use crate::storage::{dispatch_ui, Message};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Implements a stock-ish Preferences window.
|
//! Implements a stock-ish Preferences window.
|
||||||
|
|
||||||
use cacao::macos::window::{Window, WindowDelegate};
|
use cacao::appkit::window::{Window, WindowDelegate};
|
||||||
use cacao::macos::toolbar::Toolbar;
|
use cacao::appkit::toolbar::Toolbar;
|
||||||
use cacao::view::ViewController;
|
use cacao::view::ViewController;
|
||||||
|
|
||||||
use crate::storage::Message;
|
use crate::storage::Message;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Implements an example toolbar for a Preferences app. Could be cleaner, probably worth cleaning
|
//! Implements an example toolbar for a Preferences app. Could be cleaner, probably worth cleaning
|
||||||
//! up at some point.
|
//! up at some point.
|
||||||
|
|
||||||
use cacao::macos::toolbar::{Toolbar, ToolbarDelegate, ToolbarItem, ItemIdentifier};
|
use cacao::appkit::toolbar::{Toolbar, ToolbarDelegate, ToolbarItem, ItemIdentifier};
|
||||||
use cacao::image::{Image, MacSystemIcon};
|
use cacao::image::{Image, MacSystemIcon};
|
||||||
|
|
||||||
use crate::storage::{dispatch_ui, Message};
|
use crate::storage::{dispatch_ui, Message};
|
||||||
|
@ -15,7 +15,7 @@ impl Default for PreferencesToolbar {
|
||||||
let mut item = ToolbarItem::new("general");
|
let mut item = ToolbarItem::new("general");
|
||||||
item.set_title("General");
|
item.set_title("General");
|
||||||
|
|
||||||
let icon = Image::system_icon(MacSystemIcon::PreferencesGeneral, "General");
|
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesGeneral, "General");
|
||||||
item.set_image(icon);
|
item.set_image(icon);
|
||||||
|
|
||||||
item.set_action(|| {
|
item.set_action(|| {
|
||||||
|
@ -27,7 +27,7 @@ impl Default for PreferencesToolbar {
|
||||||
let mut item = ToolbarItem::new("advanced");
|
let mut item = ToolbarItem::new("advanced");
|
||||||
item.set_title("Advanced");
|
item.set_title("Advanced");
|
||||||
|
|
||||||
let icon = Image::system_icon(MacSystemIcon::PreferencesAdvanced, "Advanced");
|
let icon = Image::toolbar_icon(MacSystemIcon::PreferencesAdvanced, "Advanced");
|
||||||
item.set_image(icon);
|
item.set_image(icon);
|
||||||
|
|
||||||
item.set_action(|| {
|
item.set_action(|| {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Messages that we used to thread control throughout the application.
|
//! Messages that we used to thread control throughout the application.
|
||||||
//! If you come from React/Redux, you can liken it to that world.
|
//! If you come from React/Redux, you can liken it to that world.
|
||||||
|
|
||||||
use cacao::macos::App;
|
use cacao::appkit::App;
|
||||||
|
|
||||||
use crate::app::TodosApp;
|
use crate::app::TodosApp;
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl ListViewDelegate for TodosListView {
|
||||||
fn did_load(&mut self, view: ListView) {
|
fn did_load(&mut self, view: ListView) {
|
||||||
view.register(TODO_ROW, TodoViewRow::default);
|
view.register(TODO_ROW, TodoViewRow::default);
|
||||||
view.set_uses_alternating_backgrounds(true);
|
view.set_uses_alternating_backgrounds(true);
|
||||||
|
view.set_row_height(64.);
|
||||||
self.view = Some(view);
|
self.view = Some(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! The main Todos window.
|
//! The main Todos window.
|
||||||
|
|
||||||
use cacao::macos::window::{Window, WindowDelegate};
|
use cacao::appkit::window::{Window, WindowDelegate};
|
||||||
use cacao::macos::toolbar::Toolbar;
|
use cacao::appkit::toolbar::Toolbar;
|
||||||
use cacao::view::ViewController;
|
use cacao::view::ViewController;
|
||||||
|
|
||||||
use crate::storage::Message;
|
use crate::storage::Message;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! The main Todos window toolbar. Contains a button to enable adding a new task.
|
//! The main Todos window toolbar. Contains a button to enable adding a new task.
|
||||||
|
|
||||||
use cacao::button::Button;
|
use cacao::button::Button;
|
||||||
use cacao::macos::toolbar::{
|
use cacao::appkit::toolbar::{
|
||||||
Toolbar, ToolbarDelegate, ToolbarItem,
|
Toolbar, ToolbarDelegate, ToolbarItem,
|
||||||
ToolbarDisplayMode, ItemIdentifier
|
ToolbarDisplayMode, ItemIdentifier
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowStyle, WindowDelegate, WindowToolbarStyle};
|
use cacao::appkit::window::{Window, WindowConfig, WindowStyle, WindowDelegate, WindowToolbarStyle};
|
||||||
use cacao::notification_center::Dispatcher;
|
use cacao::notification_center::Dispatcher;
|
||||||
|
|
||||||
use crate::storage::Message;
|
use crate::storage::Message;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
//! This example showcases setting up a basic application and window.
|
//! This example showcases setting up a basic application and window.
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::Window;
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::Window;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
|
@ -10,12 +11,43 @@ struct BasicApp {
|
||||||
|
|
||||||
impl AppDelegate for BasicApp {
|
impl AppDelegate for BasicApp {
|
||||||
fn did_finish_launching(&self) {
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
App::activate();
|
App::activate();
|
||||||
|
|
||||||
self.window.set_minimum_content_size(400., 400.);
|
self.window.set_minimum_content_size(400., 400.);
|
||||||
self.window.set_title("A Basic Window");
|
self.window.set_title("A Basic Window");
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -4,8 +4,9 @@
|
||||||
//!
|
//!
|
||||||
//! If you're not using that, you can probably get by fine with a standard `NSWindow`.
|
//! If you're not using that, you can probably get by fine with a standard `NSWindow`.
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowController, WindowDelegate};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowController, WindowDelegate};
|
||||||
|
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
window: WindowController<MyWindow>
|
window: WindowController<MyWindow>
|
||||||
|
@ -13,10 +14,41 @@ struct BasicApp {
|
||||||
|
|
||||||
impl AppDelegate for BasicApp {
|
impl AppDelegate for BasicApp {
|
||||||
fn did_finish_launching(&self) {
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
App::activate();
|
App::activate();
|
||||||
|
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
//! This example showcases setting up a basic application and window delegate.
|
//! This example showcases setting up a basic application and window delegate.
|
||||||
//! Window Delegate's give you lifecycle methods that you can respond to.
|
//! Window Delegate's give you lifecycle methods that you can respond to.
|
||||||
|
|
||||||
use cacao::macos::{App, AppDelegate};
|
use cacao::appkit::{App, AppDelegate};
|
||||||
use cacao::macos::window::{Window, WindowConfig, WindowDelegate};
|
use cacao::appkit::menu::{Menu, MenuItem};
|
||||||
|
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate};
|
||||||
|
|
||||||
struct BasicApp {
|
struct BasicApp {
|
||||||
window: Window<MyWindow>
|
window: Window<MyWindow>
|
||||||
|
@ -10,9 +11,41 @@ struct BasicApp {
|
||||||
|
|
||||||
impl AppDelegate for BasicApp {
|
impl AppDelegate for BasicApp {
|
||||||
fn did_finish_launching(&self) {
|
fn did_finish_launching(&self) {
|
||||||
|
App::set_menu(vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
|
||||||
App::activate();
|
App::activate();
|
||||||
|
|
||||||
self.window.show();
|
self.window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_terminate_after_last_window_closed(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! A wrapper for `NSAlert`.
|
//! A wrapper for `NSAlert`.
|
||||||
//!
|
//!
|
||||||
//! This is housed inside `macos` as it's a useful tool for a few cases, but it doesn't match the
|
//! This is housed inside `appkit` as it's a useful tool for a few cases, but it doesn't match the
|
||||||
//! iOS API, so we make no guarantees about it being a universal control. In general this also
|
//! iOS API, so we make no guarantees about it being a universal control. In general this also
|
||||||
//! doesn't produce an amazing user experience, and you may want to shy away from using it.
|
//! doesn't produce an amazing user experience, and you may want to shy away from using it.
|
||||||
//!
|
//!
|
||||||
|
@ -8,7 +8,7 @@
|
||||||
//! `Sheet`.
|
//! `Sheet`.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use cacao::macos::{App, AppDelegate, Alert};
|
//! use cacao::appkit::{App, AppDelegate, Alert};
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
//! struct ExampleApp;
|
//! struct ExampleApp;
|
73
src/appkit/animation.rs
Normal file
73
src/appkit/animation.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use block::ConcreteBlock;
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
|
||||||
|
/// A very, very basic wrapper around NSAnimationContext. 100% subject to change.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AnimationContext(id);
|
||||||
|
|
||||||
|
impl AnimationContext {
|
||||||
|
/// Wraps an NSAnimationContext pointer.
|
||||||
|
pub fn new(ctx: id) -> Self {
|
||||||
|
Self(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the animation duration.
|
||||||
|
pub fn set_duration(&mut self, duration: f64) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![self.0, setDuration:duration];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass it a block, and the changes in that block will be animated, provided they're
|
||||||
|
/// properties that support animation.
|
||||||
|
///
|
||||||
|
/// [https://developer.apple.com/documentation/appkit/nsanimationcontext?language=objc]
|
||||||
|
///
|
||||||
|
/// For more information, you should consult the documentation for NSAnimationContext, then skim
|
||||||
|
/// the supported methods here.
|
||||||
|
pub fn run<F>(animation: F)
|
||||||
|
where
|
||||||
|
F: Fn(&mut AnimationContext) + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
let block = ConcreteBlock::new(move |ctx| {
|
||||||
|
let mut context = AnimationContext(ctx);
|
||||||
|
animation(&mut context);
|
||||||
|
});
|
||||||
|
let block = block.copy();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
//let context: id = msg_send![class!(NSAnimationContext), currentContext];
|
||||||
|
let _: () = msg_send![class!(NSAnimationContext), runAnimationGroup:block];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pass it a block, and the changes in that block will be animated, provided they're
|
||||||
|
/// properties that support animation.
|
||||||
|
///
|
||||||
|
/// [https://developer.apple.com/documentation/appkit/nsanimationcontext?language=objc]
|
||||||
|
///
|
||||||
|
/// For more information, you should consult the documentation for NSAnimationContext, then skim
|
||||||
|
/// the supported methods here.
|
||||||
|
pub fn run_with_completion_handler<F, C>(animation: F, completion_handler: C)
|
||||||
|
where
|
||||||
|
F: Fn(&mut AnimationContext) + Send + Sync + 'static,
|
||||||
|
C: Fn() + Send + Sync + 'static
|
||||||
|
{
|
||||||
|
let block = ConcreteBlock::new(move |ctx| {
|
||||||
|
let mut context = AnimationContext(ctx);
|
||||||
|
animation(&mut context);
|
||||||
|
});
|
||||||
|
let block = block.copy();
|
||||||
|
|
||||||
|
let completion_block = ConcreteBlock::new(completion_handler);
|
||||||
|
let completion_block = completion_block.copy();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
//let context: id = msg_send![class!(NSAnimationContext), currentContext];
|
||||||
|
let _: () = msg_send![class!(NSAnimationContext), runAnimationGroup:block
|
||||||
|
completionHandler:completion_block];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,8 +15,8 @@ use url::Url;
|
||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::foundation::{id, nil, to_bool, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
use crate::foundation::{id, nil, to_bool, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
||||||
use crate::macos::app::{APP_PTR, AppDelegate};
|
use crate::appkit::app::{APP_PTR, AppDelegate};
|
||||||
use crate::macos::printing::PrintSettings;
|
use crate::appkit::printing::PrintSettings;
|
||||||
use crate::user_activity::UserActivity;
|
use crate::user_activity::UserActivity;
|
||||||
|
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(feature = "cloudkit")]
|
|
@ -45,10 +45,12 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSUInteger, AutoReleasePool};
|
use crate::foundation::{id, nil, YES, NO, NSUInteger, AutoReleasePool};
|
||||||
use crate::invoker::TargetActionHandler;
|
use crate::invoker::TargetActionHandler;
|
||||||
use crate::macos::menu::Menu;
|
use crate::appkit::menu::Menu;
|
||||||
use crate::notification_center::Dispatcher;
|
use crate::notification_center::Dispatcher;
|
||||||
use crate::utils::activate_cocoa_multithreading;
|
use crate::utils::activate_cocoa_multithreading;
|
||||||
|
|
||||||
|
//use crate::bundle::set_bundle_id;
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
use class::register_app_class;
|
use class::register_app_class;
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ fn shared_application<F: Fn(id)>(handler: F) {
|
||||||
handler(app);
|
handler(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A wrapper for `NSApplication` on macOS, and `UIApplication` on iOS.
|
/// A wrapper for `NSApplication` in AppKit/Cocoa, and `UIApplication` in UIKit/Cocoa Touch.
|
||||||
///
|
///
|
||||||
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as
|
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as
|
||||||
/// handles setting up a few necessary pieces:
|
/// handles setting up a few necessary pieces:
|
||||||
|
@ -131,7 +133,7 @@ impl<T> App<T> where T: AppDelegate + 'static {
|
||||||
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
||||||
/// Objective-C side of things.
|
/// Objective-C side of things.
|
||||||
pub fn new(_bundle_id: &str, delegate: T) -> Self {
|
pub fn new(_bundle_id: &str, delegate: T) -> Self {
|
||||||
// set_bundle_id(bundle_id);
|
//set_bundle_id(bundle_id);
|
||||||
|
|
||||||
activate_cocoa_multithreading();
|
activate_cocoa_multithreading();
|
||||||
|
|
|
@ -6,10 +6,10 @@ use url::Url;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::user_activity::UserActivity;
|
use crate::user_activity::UserActivity;
|
||||||
|
|
||||||
use crate::macos::app::enums::TerminateResponse;
|
use crate::appkit::app::enums::TerminateResponse;
|
||||||
use crate::macos::menu::Menu;
|
use crate::appkit::menu::Menu;
|
||||||
use crate::macos::printing::enums::PrintResponse;
|
use crate::appkit::printing::enums::PrintResponse;
|
||||||
use crate::macos::printing::settings::PrintSettings;
|
use crate::appkit::printing::settings::PrintSettings;
|
||||||
|
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(feature = "cloudkit")]
|
||||||
use crate::cloudkit::share::CKShareMetaData;
|
use crate::cloudkit::share::CKShareMetaData;
|
|
@ -3,7 +3,6 @@
|
||||||
use crate::foundation::{NSUInteger};
|
use crate::foundation::{NSUInteger};
|
||||||
|
|
||||||
/// Used to set whether and/or how a view or cell draws a focus ring.
|
/// Used to set whether and/or how a view or cell draws a focus ring.
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum FocusRingType {
|
pub enum FocusRingType {
|
||||||
/// Whatever the default is.
|
/// Whatever the default is.
|
||||||
|
@ -20,7 +19,6 @@ pub enum FocusRingType {
|
||||||
Unknown(NSUInteger)
|
Unknown(NSUInteger)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
impl From<FocusRingType> for NSUInteger {
|
impl From<FocusRingType> for NSUInteger {
|
||||||
fn from(ring_type: FocusRingType) -> Self {
|
fn from(ring_type: FocusRingType) -> Self {
|
||||||
match ring_type {
|
match ring_type {
|
|
@ -32,7 +32,7 @@ impl Event {
|
||||||
// @TODO: Check here if key event, invalid otherwise.
|
// @TODO: Check here if key event, invalid otherwise.
|
||||||
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
// @TODO: Figure out if we can just return &str here, since the Objective-C side
|
||||||
// should... make it work, I think.
|
// should... make it work, I think.
|
||||||
let characters = NSString::from_retained(unsafe {
|
let characters = NSString::retain(unsafe {
|
||||||
msg_send![&*self.0, characters]
|
msg_send![&*self.0, characters]
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@ use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, NSInteger, NSString};
|
use crate::foundation::{id, NSInteger, NSString};
|
||||||
use crate::macos::menu::item::MenuItem;
|
use crate::appkit::menu::item::MenuItem;
|
||||||
|
|
||||||
/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
|
/// A struct that represents an `NSMenu`. It takes ownership of items, and handles instrumenting
|
||||||
/// them throughout the application lifecycle.
|
/// them throughout the application lifecycle.
|
||||||
|
@ -70,4 +70,46 @@ impl Menu {
|
||||||
|
|
||||||
menu
|
menu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience method for the bare-minimum NSMenu structure that "just works" for all
|
||||||
|
/// applications, as expected.
|
||||||
|
pub fn standard() -> Vec<Menu> {
|
||||||
|
vec![
|
||||||
|
Menu::new("", vec![
|
||||||
|
MenuItem::Services,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Hide,
|
||||||
|
MenuItem::HideOthers,
|
||||||
|
MenuItem::ShowAll,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Quit
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("File", vec![
|
||||||
|
MenuItem::CloseWindow
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Edit", vec![
|
||||||
|
MenuItem::Undo,
|
||||||
|
MenuItem::Redo,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::Cut,
|
||||||
|
MenuItem::Copy,
|
||||||
|
MenuItem::Paste,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::SelectAll
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("View", vec![
|
||||||
|
MenuItem::EnterFullScreen
|
||||||
|
]),
|
||||||
|
|
||||||
|
Menu::new("Window", vec![
|
||||||
|
MenuItem::Minimize,
|
||||||
|
MenuItem::Zoom,
|
||||||
|
MenuItem::Separator,
|
||||||
|
MenuItem::new("Bring All to Front")
|
||||||
|
])
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,9 @@
|
||||||
mod alert;
|
mod alert;
|
||||||
pub use alert::Alert;
|
pub use alert::Alert;
|
||||||
|
|
||||||
|
mod animation;
|
||||||
|
pub use animation::AnimationContext;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
|
|
|
@ -7,7 +7,7 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{class, sel, sel_impl, msg_send};
|
use objc::{class, sel, sel_impl, msg_send};
|
||||||
|
|
||||||
use crate::foundation::{load_or_register_class, id, BOOL, NSArray, NSString};
|
use crate::foundation::{load_or_register_class, id, BOOL, NSArray, NSString};
|
||||||
use crate::macos::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
|
use crate::appkit::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
|
||||||
use crate::utils::load;
|
use crate::utils::load;
|
||||||
|
|
||||||
/// Retrieves and passes the allowed item identifiers for this toolbar.
|
/// Retrieves and passes the allowed item identifiers for this toolbar.
|
|
@ -1,4 +1,4 @@
|
||||||
//! Implements an NSToolbar wrapper, which is one of those macOS niceties
|
//! Implements an NSToolbar wrapper, which is one of those AppKit niceties
|
||||||
//! that makes it feel... "proper".
|
//! that makes it feel... "proper".
|
||||||
//!
|
//!
|
||||||
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
|
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
|
|
@ -23,7 +23,7 @@ pub use traits::ToolbarDelegate;
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode, ItemIdentifier};
|
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode, ItemIdentifier};
|
||||||
|
|
||||||
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
pub(crate) static TOOLBAR_PTR: &str = "cacaoToolbarPtr";
|
||||||
|
|
||||||
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
|
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
|
||||||
/// where our `NSToolbar` and associated delegate live.
|
/// where our `NSToolbar` and associated delegate live.
|
||||||
|
@ -62,7 +62,7 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
|
||||||
(ShareId::from_ptr(toolbar), ShareId::from_ptr(objc_delegate))
|
(ShareId::from_ptr(toolbar), ShareId::from_ptr(objc_delegate))
|
||||||
};
|
};
|
||||||
|
|
||||||
&mut delegate.did_load(Toolbar {
|
let _ret = &mut delegate.did_load(Toolbar {
|
||||||
objc: objc.clone(),
|
objc: objc.clone(),
|
||||||
objc_delegate: objc_delegate.clone(),
|
objc_delegate: objc_delegate.clone(),
|
||||||
identifier: identifier.clone(),
|
identifier: identifier.clone(),
|
|
@ -2,7 +2,7 @@
|
||||||
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
|
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
|
||||||
//! to pull request it.
|
//! to pull request it.
|
||||||
|
|
||||||
use crate::macos::toolbar::{Toolbar, ToolbarItem, ItemIdentifier};
|
use crate::appkit::toolbar::{Toolbar, ToolbarItem, ItemIdentifier};
|
||||||
|
|
||||||
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
|
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
|
||||||
pub trait ToolbarDelegate {
|
pub trait ToolbarDelegate {
|
|
@ -11,7 +11,7 @@ use objc::{class, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{load_or_register_class, id, BOOL, YES, NO, NSUInteger};
|
use crate::foundation::{load_or_register_class, id, BOOL, YES, NO, NSUInteger};
|
||||||
use crate::utils::{load, CGSize};
|
use crate::utils::{load, CGSize};
|
||||||
use crate::macos::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
|
use crate::appkit::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
|
||||||
|
|
||||||
/// Called when an `NSWindowDelegate` receives a `windowWillClose:` event.
|
/// Called when an `NSWindowDelegate` receives a `windowWillClose:` event.
|
||||||
/// Good place to clean up memory and what not.
|
/// Good place to clean up memory and what not.
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
use crate::foundation::NSUInteger;
|
use crate::foundation::NSUInteger;
|
||||||
use crate::geometry::Rect;
|
use crate::geometry::Rect;
|
||||||
use crate::macos::window::enums::{WindowStyle, WindowToolbarStyle};
|
use crate::appkit::window::enums::{WindowStyle, WindowToolbarStyle};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct WindowConfig {
|
pub struct WindowConfig {
|
|
@ -7,7 +7,7 @@ use objc::declare::ClassDecl;
|
||||||
use objc::runtime::Class;
|
use objc::runtime::Class;
|
||||||
use objc::class;
|
use objc::class;
|
||||||
|
|
||||||
use crate::macos::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
|
use crate::appkit::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
|
||||||
|
|
||||||
/// Injects an `NSWindowController` subclass, with some callback and pointer ivars for what we
|
/// Injects an `NSWindowController` subclass, with some callback and pointer ivars for what we
|
||||||
/// need to do.
|
/// need to do.
|
|
@ -12,8 +12,8 @@
|
||||||
//! # How to use
|
//! # How to use
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use cacao::macos::app::AppDelegate;
|
//! use cacao::appkit::app::AppDelegate;
|
||||||
//! use cacao::macos::window::{WindowController, WindowDelegate};
|
//! use cacao::appkit::window::{WindowController, WindowDelegate};
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
//! struct MyWindow;
|
//! struct MyWindow;
|
||||||
|
@ -35,7 +35,7 @@ use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil};
|
use crate::foundation::{id, nil};
|
||||||
use crate::utils::Controller;
|
use crate::utils::Controller;
|
||||||
use crate::macos::window::{Window, WindowConfig, WindowDelegate, WINDOW_DELEGATE_PTR};
|
use crate::appkit::window::{Window, WindowConfig, WindowDelegate, WINDOW_DELEGATE_PTR};
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
use class::register_window_controller_class;
|
use class::register_window_controller_class;
|
||||||
|
@ -54,7 +54,7 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
|
||||||
/// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over
|
/// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over
|
||||||
/// to your supplied delegate.
|
/// to your supplied delegate.
|
||||||
pub fn with(config: WindowConfig, delegate: T) -> Self {
|
pub fn with(config: WindowConfig, delegate: T) -> Self {
|
||||||
let mut window = Window::with(config, delegate);
|
let window = Window::with(config, delegate);
|
||||||
|
|
||||||
let objc = unsafe {
|
let objc = unsafe {
|
||||||
let window_controller_class = register_window_controller_class::<T>();
|
let window_controller_class = register_window_controller_class::<T>();
|
||||||
|
@ -69,13 +69,6 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
|
||||||
Id::from_ptr(controller)
|
Id::from_ptr(controller)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(delegate) = &mut window.delegate {
|
|
||||||
(*delegate).did_load(Window {
|
|
||||||
delegate: None,
|
|
||||||
objc: window.objc.clone()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowController { objc, window }
|
WindowController { objc, window }
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,11 @@ use objc::{msg_send, sel, sel_impl, class};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
|
use crate::appkit::toolbar::{Toolbar, ToolbarDelegate};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::foundation::{id, nil, to_bool, YES, NO, NSString, NSInteger, NSUInteger};
|
use crate::foundation::{id, nil, to_bool, YES, NO, NSString, NSInteger, NSUInteger};
|
||||||
use crate::layout::traits::Layout;
|
use crate::layout::Layout;
|
||||||
use crate::macos::toolbar::{Toolbar, ToolbarDelegate};
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::{os, Controller};
|
use crate::utils::{os, Controller};
|
||||||
|
|
||||||
mod class;
|
mod class;
|
||||||
|
@ -289,7 +290,7 @@ impl<T> Window<T> {
|
||||||
|
|
||||||
/// Given a view, sets it as the content view for this window.
|
/// Given a view, sets it as the content view for this window.
|
||||||
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
||||||
view.with_backing_node(|backing_node| unsafe {
|
view.with_backing_obj_mut(|backing_node| unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setContentView:&*backing_node];
|
let _: () = msg_send![&*self.objc, setContentView:&*backing_node];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -446,6 +447,7 @@ impl<T> Window<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the separator style for this window.
|
||||||
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
|
@ -2,8 +2,8 @@
|
||||||
//! module. There's a few different ones, and it's just... cleaner, if
|
//! module. There's a few different ones, and it's just... cleaner, if
|
||||||
//! it's organized here.
|
//! it's organized here.
|
||||||
|
|
||||||
use crate::macos::app::PresentationOption;
|
use crate::appkit::app::PresentationOption;
|
||||||
use crate::macos::window::Window;
|
use crate::appkit::window::Window;
|
||||||
|
|
||||||
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
|
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
|
||||||
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
|
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
|
|
@ -66,9 +66,7 @@ extern fn get_bundle_id(this: &Object, s: Sel, v: id) -> id {
|
||||||
let url: id = msg_send![main_bundle, bundleURL];
|
let url: id = msg_send![main_bundle, bundleURL];
|
||||||
let x: id = msg_send![url, absoluteString];
|
let x: id = msg_send![url, absoluteString];
|
||||||
println!("Got here? {:?}", x);
|
println!("Got here? {:?}", x);
|
||||||
unsafe {
|
NSString::new("com.test.user_notifications").into()
|
||||||
NSString::alloc(nil).init_str("com.secretkeys.subatomic")
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
msg_send![this, __bundleIdentifier]
|
msg_send![this, __bundleIdentifier]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::foundation::NSUInteger;
|
||||||
|
|
||||||
/// Represents a bezel style for a button. This is a macOS-specific control, and has no effect
|
/// Represents a bezel style for a button. This is a macOS-specific control, and has no effect
|
||||||
/// under iOS or tvOS.
|
/// under iOS or tvOS.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BezelStyle {
|
pub enum BezelStyle {
|
||||||
/// A standard circular button.
|
/// A standard circular button.
|
||||||
|
@ -49,7 +49,7 @@ pub enum BezelStyle {
|
||||||
Unknown(NSUInteger)
|
Unknown(NSUInteger)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
impl From<BezelStyle> for NSUInteger {
|
impl From<BezelStyle> for NSUInteger {
|
||||||
fn from(style: BezelStyle) -> Self {
|
fn from(style: BezelStyle) -> Self {
|
||||||
match style {
|
match style {
|
||||||
|
@ -71,7 +71,7 @@ impl From<BezelStyle> for NSUInteger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
impl From<NSUInteger> for BezelStyle {
|
impl From<NSUInteger> for BezelStyle {
|
||||||
fn from(i: NSUInteger) -> Self {
|
fn from(i: NSUInteger) -> Self {
|
||||||
match i {
|
match i {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Wraps `NSButton` on macOS, and `UIButton` on iOS and tvOS.
|
//! 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
|
//! 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.
|
//! a number of ways, and support setting a callback to fire when they're clicked or tapped.
|
||||||
|
@ -29,20 +29,26 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
use crate::control::Control;
|
||||||
use crate::image::Image;
|
use crate::image::Image;
|
||||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
||||||
use crate::invoker::TargetActionHandler;
|
use crate::invoker::TargetActionHandler;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::keys::Key;
|
||||||
|
use crate::layout::Layout;
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::text::{AttributedString, Font};
|
use crate::text::{AttributedString, Font};
|
||||||
use crate::utils::{load, properties::ObjcProperty};
|
use crate::utils::{load, properties::ObjcProperty};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "autolayout")]
|
||||||
use crate::macos::FocusRingType;
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
|
use crate::appkit::FocusRingType;
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::*;
|
pub use enums::*;
|
||||||
|
|
||||||
/// Wraps `NSButton` on macOS, and `UIButton` on iOS and tvOS.
|
/// 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
|
/// 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.
|
/// a number of ways, and support setting a callback to fire when they're clicked or tapped.
|
||||||
|
@ -71,33 +77,43 @@ pub struct Button {
|
||||||
handler: Option<TargetActionHandler>,
|
handler: Option<TargetActionHandler>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,23 +130,47 @@ impl Button {
|
||||||
];
|
];
|
||||||
|
|
||||||
let _: () = msg_send![button, setWantsLayer:YES];
|
let _: () = msg_send![button, setWantsLayer:YES];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
button
|
button
|
||||||
};
|
};
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
handler: None,
|
handler: None,
|
||||||
image: None,
|
image: None,
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(view),
|
top: LayoutAnchorY::top(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: LayoutAnchorX::left(view),
|
left: LayoutAnchorX::left(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: LayoutAnchorX::leading(view),
|
leading: LayoutAnchorX::leading(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: LayoutAnchorX::right(view),
|
right: LayoutAnchorX::right(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
trailing: LayoutAnchorX::trailing(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
bottom: LayoutAnchorY::bottom(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: LayoutAnchorDimension::width(view),
|
width: LayoutAnchorDimension::width(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: LayoutAnchorDimension::height(view),
|
height: LayoutAnchorDimension::height(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: LayoutAnchorX::center(view),
|
center_x: LayoutAnchorX::center(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,8 +184,8 @@ impl Button {
|
||||||
self.image = Some(image);
|
self.image = Some(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the bezel style for this button. Only supported on macOS.
|
/// Sets the bezel style for this button. Only supported on appkit.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
|
||||||
let style: NSUInteger = bezel_style.into();
|
let style: NSUInteger = bezel_style.into();
|
||||||
|
|
||||||
|
@ -167,7 +207,7 @@ impl Button {
|
||||||
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
||||||
let color: id = color.as_ref().into();
|
let color: id = color.as_ref().into();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let cell: id = msg_send![obj, cell];
|
let cell: id = msg_send![obj, cell];
|
||||||
let _: () = msg_send![cell, setBackgroundColor:color];
|
let _: () = msg_send![cell, setBackgroundColor:color];
|
||||||
|
@ -176,19 +216,29 @@ impl Button {
|
||||||
|
|
||||||
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
|
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
|
||||||
/// button will fire.
|
/// button will fire.
|
||||||
pub fn set_key_equivalent(&self, key: &str) {
|
pub fn set_key_equivalent<'a, K>(&self, key: K)
|
||||||
let key = NSString::new(key);
|
where
|
||||||
|
K: Into<Key<'a>>
|
||||||
|
{
|
||||||
|
let key: Key<'a> = key.into();
|
||||||
|
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| {
|
||||||
let _: () = msg_send![obj, setKeyEquivalent:&*key];
|
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.
|
/// Sets the text color for this button.
|
||||||
///
|
///
|
||||||
/// On macOS, this is done by way of an `AttributedString` under the hood.
|
/// On appkit, this is done by way of an `AttributedString` under the hood.
|
||||||
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
|
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.objc.with_mut(move |obj| unsafe {
|
self.objc.with_mut(move |obj| unsafe {
|
||||||
let text: id = msg_send![obj, attributedTitle];
|
let text: id = msg_send![obj, attributedTitle];
|
||||||
let len: isize = msg_send![text, length];
|
let len: isize = msg_send![text, length];
|
||||||
|
@ -201,8 +251,8 @@ impl Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Figure out how to handle oddities like this.
|
// @TODO: Figure out how to handle oddities like this.
|
||||||
/// For buttons on macOS, one might need to disable the border. This does that.
|
/// For buttons on appkit, one might need to disable the border. This does that.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub fn set_bordered(&self, is_bordered: bool) {
|
pub fn set_bordered(&self, is_bordered: bool) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setBordered:match is_bordered {
|
let _: () = msg_send![obj, setBordered:match is_bordered {
|
||||||
|
@ -223,8 +273,8 @@ impl Button {
|
||||||
|
|
||||||
/// Sets how the control should draw a focus ring when a user is focused on it.
|
/// Sets how the control should draw a focus ring when a user is focused on it.
|
||||||
///
|
///
|
||||||
/// This is a macOS-only method.
|
/// This is an appkit-only method.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
|
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
|
||||||
let ring_type: NSUInteger = focus_ring_type.into();
|
let ring_type: NSUInteger = focus_ring_type.into();
|
||||||
|
|
||||||
|
@ -244,26 +294,32 @@ impl Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for Button {
|
impl ObjcAccess for Button {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for &Button {
|
impl Layout for Button {}
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
impl Control for Button {}
|
||||||
|
|
||||||
|
impl ObjcAccess for &Button {
|
||||||
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Layout for &Button {}
|
||||||
|
impl Control for &Button {}
|
||||||
|
|
||||||
impl Drop for Button {
|
impl Drop for Button {
|
||||||
/// Nils out references on the Objective-C side and removes this from the backing view.
|
/// 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,
|
// Just to be sure, let's... nil these out. They should be weak references,
|
||||||
|
|
|
@ -27,6 +27,9 @@ pub(crate) const AQUA_LIGHT_COLOR_HIGH_CONTRAST: &'static str = "AQUA_LIGHT_COLO
|
||||||
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
|
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
|
||||||
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
|
||||||
|
|
||||||
|
// Certain platforms we're interested in supporting (airyx) might not have these yet, so this
|
||||||
|
// will just force the Aqua appearance on that platform.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
static NSAppearanceNameAqua: id;
|
static NSAppearanceNameAqua: id;
|
||||||
static NSAppearanceNameAccessibilityHighContrastAqua: id;
|
static NSAppearanceNameAccessibilityHighContrastAqua: id;
|
||||||
|
@ -39,8 +42,11 @@ extern "C" {
|
||||||
/// contrast checking on systems prior to 10.14: it's not that it couldn't be supported, but the
|
/// contrast checking on systems prior to 10.14: it's not that it couldn't be supported, but the
|
||||||
/// ongoing question of how far back to support makes this not worth bothering with right now.
|
/// ongoing question of how far back to support makes this not worth bothering with right now.
|
||||||
///
|
///
|
||||||
|
/// On non-Apple systems, this returns the light aqua color at all times.
|
||||||
|
///
|
||||||
/// Pull requests to implement that check would be welcome.
|
/// Pull requests to implement that check would be welcome.
|
||||||
fn get_effective_color(this: &Object) -> id {
|
fn get_effective_color(this: &Object) -> id {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
if os::is_minimum_semversion(10, 14, 0) {
|
if os::is_minimum_semversion(10, 14, 0) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut appearance: id = msg_send![class!(NSAppearance), currentAppearance];
|
let mut appearance: id = msg_send![class!(NSAppearance), currentAppearance];
|
|
@ -26,11 +26,11 @@ use objc_id::Id;
|
||||||
use crate::foundation::id;
|
use crate::foundation::id;
|
||||||
use crate::utils::os;
|
use crate::utils::os;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
mod macos_dynamic_color;
|
mod appkit_dynamic_color;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use macos_dynamic_color::{
|
use appkit_dynamic_color::{
|
||||||
AQUA_LIGHT_COLOR_NORMAL_CONTRAST, AQUA_LIGHT_COLOR_HIGH_CONTRAST,
|
AQUA_LIGHT_COLOR_NORMAL_CONTRAST, AQUA_LIGHT_COLOR_HIGH_CONTRAST,
|
||||||
AQUA_DARK_COLOR_NORMAL_CONTRAST, AQUA_DARK_COLOR_HIGH_CONTRAST
|
AQUA_DARK_COLOR_NORMAL_CONTRAST, AQUA_DARK_COLOR_HIGH_CONTRAST
|
||||||
};
|
};
|
||||||
|
@ -41,7 +41,7 @@ use macos_dynamic_color::{
|
||||||
/// default to the `Light` theme.
|
/// default to the `Light` theme.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum Theme {
|
pub enum Theme {
|
||||||
/// The "default" theme on a platform. On macOS, this is Aqua.
|
/// The "default" theme on a platform. On macOS/Airyx, this is Aqua.
|
||||||
/// On iOS and tvOS, this is whatever you call the system defined theme.
|
/// On iOS and tvOS, this is whatever you call the system defined theme.
|
||||||
Light,
|
Light,
|
||||||
|
|
||||||
|
@ -217,6 +217,7 @@ pub enum Color {
|
||||||
/// The default color to use for thin separators/lines that
|
/// The default color to use for thin separators/lines that
|
||||||
/// do not allow content underneath to be visible.
|
/// do not allow content underneath to be visible.
|
||||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||||
|
#[cfg(feature = "uikit")]
|
||||||
OpaqueSeparator,
|
OpaqueSeparator,
|
||||||
|
|
||||||
/// The default color to use for rendering links.
|
/// The default color to use for rendering links.
|
||||||
|
@ -230,11 +231,11 @@ pub enum Color {
|
||||||
LightText,
|
LightText,
|
||||||
|
|
||||||
/// The background color for a given window in the system theme.
|
/// The background color for a given window in the system theme.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
MacOSWindowBackgroundColor,
|
MacOSWindowBackgroundColor,
|
||||||
|
|
||||||
/// The background color that should appear under a page per the system theme.
|
/// The background color that should appear under a page per the system theme.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
MacOSUnderPageBackgroundColor
|
MacOSUnderPageBackgroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,10 +249,10 @@ impl Color {
|
||||||
let a = alpha as CGFloat / 255.0;
|
let a = alpha as CGFloat / 255.0;
|
||||||
|
|
||||||
Color::Custom(Arc::new(RwLock::new(unsafe {
|
Color::Custom(Arc::new(RwLock::new(unsafe {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedRed:r green:g blue:b alpha:a]) }
|
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedRed:r green:g blue:b alpha:a]) }
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "uikit")]
|
||||||
{ Id::from_ptr(msg_send![class!(UIColor), colorWithRed:r green:g blue:b alpha:a]) }
|
{ Id::from_ptr(msg_send![class!(UIColor), colorWithRed:r green:g blue:b alpha:a]) }
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -271,10 +272,10 @@ impl Color {
|
||||||
let a = alpha as CGFloat / 255.0;
|
let a = alpha as CGFloat / 255.0;
|
||||||
|
|
||||||
Color::Custom(Arc::new(RwLock::new(unsafe {
|
Color::Custom(Arc::new(RwLock::new(unsafe {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedHue:h saturation:s brightness:b alpha:a]) }
|
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedHue:h saturation:s brightness:b alpha:a]) }
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "uikit")]
|
||||||
{ Id::from_ptr(msg_send![class!(UIColor), colorWithHue:h saturation:s brightness:b alpha:a]) }
|
{ Id::from_ptr(msg_send![class!(UIColor), colorWithHue:h saturation:s brightness:b alpha:a]) }
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -289,10 +290,10 @@ impl Color {
|
||||||
/// specified alpha.
|
/// specified alpha.
|
||||||
pub fn white_alpha(level: CGFloat, alpha: CGFloat) -> Self {
|
pub fn white_alpha(level: CGFloat, alpha: CGFloat) -> Self {
|
||||||
Color::Custom(Arc::new(RwLock::new(unsafe {
|
Color::Custom(Arc::new(RwLock::new(unsafe {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedWhite:level alpha:alpha]) }
|
{ Id::from_ptr(msg_send![class!(NSColor), colorWithCalibratedWhite:level alpha:alpha]) }
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "uikit")]
|
||||||
{ Id::from_ptr(msg_send![class!(UIColor), colorWithWhite:level alpha:alpha]) }
|
{ Id::from_ptr(msg_send![class!(UIColor), colorWithWhite:level alpha:alpha]) }
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -319,6 +320,7 @@ impl Color {
|
||||||
Color::hexa(hex, 255)
|
Color::hexa(hex, 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @TODO: This is currently appkit-only but should be for uikit as well.
|
||||||
/// Creates and returns a dynamic color, which stores a handler and enables returning specific
|
/// Creates and returns a dynamic color, which stores a handler and enables returning specific
|
||||||
/// colors at appearance time based on device traits (i.e, dark mode vs light mode, contrast
|
/// colors at appearance time based on device traits (i.e, dark mode vs light mode, contrast
|
||||||
/// settings, etc).
|
/// settings, etc).
|
||||||
|
@ -327,7 +329,7 @@ impl Color {
|
||||||
/// "default" or "light" color.
|
/// "default" or "light" color.
|
||||||
///
|
///
|
||||||
/// Returning a dynamic color in your handler is unsupported and may panic.
|
/// Returning a dynamic color in your handler is unsupported and may panic.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub fn dynamic<F>(handler: F) -> Self
|
pub fn dynamic<F>(handler: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(Style) -> Color + 'static
|
F: Fn(Style) -> Color + 'static
|
||||||
|
@ -337,9 +339,8 @@ impl Color {
|
||||||
// not entirely clear on how expensive the dynamic allocation would be pre-10.15/11.0 and
|
// not entirely clear on how expensive the dynamic allocation would be pre-10.15/11.0 and
|
||||||
// am happy to do this for now and let someone who needs true dynamic allocation look into
|
// am happy to do this for now and let someone who needs true dynamic allocation look into
|
||||||
// it and PR it.
|
// it and PR it.
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
Color::Custom(Arc::new(RwLock::new(unsafe {
|
Color::Custom(Arc::new(RwLock::new(unsafe {
|
||||||
let color: id = msg_send![macos_dynamic_color::register_class(), new];
|
let color: id = msg_send![appkit_dynamic_color::register_class(), new];
|
||||||
|
|
||||||
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST, {
|
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST, {
|
||||||
let color: id = handler(Style {
|
let color: id = handler(Style {
|
||||||
|
@ -421,7 +422,7 @@ impl From<&Color> for id {
|
||||||
/// Handles color fallback for system-provided colors.
|
/// Handles color fallback for system-provided colors.
|
||||||
macro_rules! system_color_with_fallback {
|
macro_rules! system_color_with_fallback {
|
||||||
($class:ident, $color:ident, $fallback:ident) => ({
|
($class:ident, $color:ident, $fallback:ident) => ({
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{
|
{
|
||||||
#[cfg(feature = "color-fallbacks")]
|
#[cfg(feature = "color-fallbacks")]
|
||||||
if os::minimum_semversion(10, 10, 0) {
|
if os::minimum_semversion(10, 10, 0) {
|
||||||
|
@ -434,7 +435,7 @@ macro_rules! system_color_with_fallback {
|
||||||
msg_send![$class, $color]
|
msg_send![$class, $color]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "uikit")]
|
||||||
{
|
{
|
||||||
msg_send![$class, $color]
|
msg_send![$class, $color]
|
||||||
}
|
}
|
||||||
|
@ -450,10 +451,10 @@ macro_rules! system_color_with_fallback {
|
||||||
/// The goal here is to make sure that this can't reasonably break on OS's, as `Color` is kind of
|
/// The goal here is to make sure that this can't reasonably break on OS's, as `Color` is kind of
|
||||||
/// an important piece. It's not on the framework to make your app look good, though.
|
/// an important piece. It's not on the framework to make your app look good, though.
|
||||||
unsafe fn to_objc(obj: &Color) -> id {
|
unsafe fn to_objc(obj: &Color) -> id {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let color = class!(NSColor);
|
let color = class!(NSColor);
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "uikit")]
|
||||||
let color = class!(UIColor);
|
let color = class!(UIColor);
|
||||||
|
|
||||||
match obj {
|
match obj {
|
||||||
|
@ -495,15 +496,18 @@ unsafe fn to_objc(obj: &Color) -> id {
|
||||||
Color::SystemBackgroundSecondary => system_color_with_fallback!(color, secondarySystemBackgroundColor, clearColor),
|
Color::SystemBackgroundSecondary => system_color_with_fallback!(color, secondarySystemBackgroundColor, clearColor),
|
||||||
Color::SystemBackgroundTertiary => system_color_with_fallback!(color, tertiarySystemBackgroundColor, clearColor),
|
Color::SystemBackgroundTertiary => system_color_with_fallback!(color, tertiarySystemBackgroundColor, clearColor),
|
||||||
Color::Separator => system_color_with_fallback!(color, separatorColor, lightGrayColor),
|
Color::Separator => system_color_with_fallback!(color, separatorColor, lightGrayColor),
|
||||||
|
|
||||||
|
#[cfg(feature = "uikit")]
|
||||||
Color::OpaqueSeparator => system_color_with_fallback!(color, opaqueSeparatorColor, darkGrayColor),
|
Color::OpaqueSeparator => system_color_with_fallback!(color, opaqueSeparatorColor, darkGrayColor),
|
||||||
|
|
||||||
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
||||||
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
||||||
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
Color::MacOSWindowBackgroundColor => system_color_with_fallback!(color, windowBackgroundColor, clearColor),
|
Color::MacOSWindowBackgroundColor => system_color_with_fallback!(color, windowBackgroundColor, clearColor),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
Color::MacOSUnderPageBackgroundColor => system_color_with_fallback!(color, underPageBackgroundColor, clearColor),
|
Color::MacOSUnderPageBackgroundColor => system_color_with_fallback!(color, underPageBackgroundColor, clearColor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
56
src/control/mod.rs
Normal file
56
src/control/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
|
||||||
|
use crate::foundation::{id, YES, NO, NSUInteger};
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
|
|
||||||
|
/// Use this enum for specifying NSControl size types.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum ControlSize {
|
||||||
|
/// The smallest control size.
|
||||||
|
Mini,
|
||||||
|
|
||||||
|
/// A smaller control size.
|
||||||
|
Small,
|
||||||
|
|
||||||
|
/// The default, regular, size.
|
||||||
|
Regular,
|
||||||
|
|
||||||
|
/// A large control. Only available on macOS 11.0+.
|
||||||
|
/// If you pass this to the `set_control_size` method on the `Control` trait, it will
|
||||||
|
/// transparently map to `Regular` on 10.15 and below.
|
||||||
|
Large
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait that view wrappers must conform to. Enables managing the subview tree.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait Control: ObjcAccess {
|
||||||
|
/// Whether this control is enabled or not.
|
||||||
|
fn set_enabled(&self, is_enabled: bool) {
|
||||||
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setEnabled:match is_enabled {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the underlying control size.
|
||||||
|
fn set_control_size(&self, size: ControlSize) {
|
||||||
|
let control_size: NSUInteger = match size {
|
||||||
|
ControlSize::Mini => 2,
|
||||||
|
ControlSize::Small => 1,
|
||||||
|
ControlSize::Regular => 0,
|
||||||
|
|
||||||
|
ControlSize::Large => match crate::utils::os::is_minimum_version(11) {
|
||||||
|
true => 3,
|
||||||
|
false => 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setControlSize:control_size];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,10 +10,11 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::{id, YES, NO, NSInteger, NSString};
|
use crate::foundation::{id, nil, YES, NO, NSInteger, NSString, NSURL};
|
||||||
use crate::filesystem::enums::ModalResponse;
|
use crate::filesystem::enums::ModalResponse;
|
||||||
|
|
||||||
use crate::macos::window::{Window, WindowDelegate};
|
#[cfg(feature = "appkit")]
|
||||||
|
use crate::appkit::window::{Window, WindowDelegate};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FileSelectPanel {
|
pub struct FileSelectPanel {
|
||||||
|
@ -126,8 +127,7 @@ impl FileSelectPanel {
|
||||||
self.allows_multiple_selection = allows;
|
self.allows_multiple_selection = allows;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the panel as a modal. Currently sheets are not supported, but you're free (and able
|
/// Shows the panel as a modal.
|
||||||
/// to) thread the Objective C calls yourself by using the panel field on this struct.
|
|
||||||
///
|
///
|
||||||
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
||||||
/// the system runs and manages that in another process, and we're still abiding by the general
|
/// the system runs and manages that in another process, and we're still abiding by the general
|
||||||
|
@ -137,7 +137,7 @@ impl FileSelectPanel {
|
||||||
/// script) or can't easily pass one to use as a sheet.
|
/// script) or can't easily pass one to use as a sheet.
|
||||||
pub fn show<F>(&self, handler: F)
|
pub fn show<F>(&self, handler: F)
|
||||||
where
|
where
|
||||||
F: Fn(Vec<PathBuf>) + 'static
|
F: Fn(Vec<NSURL>) + 'static
|
||||||
{
|
{
|
||||||
let panel = self.panel.clone();
|
let panel = self.panel.clone();
|
||||||
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||||
|
@ -154,6 +154,16 @@ impl FileSelectPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// As panels descend behind the scenes from `NSWindow`, we can call through to close it.
|
||||||
|
///
|
||||||
|
/// You should really prefer to utilize sheets to display panels; this is offered as a
|
||||||
|
/// convenience for rare cases where you might need to retain a panel and close it later on.
|
||||||
|
pub fn close(&self) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.panel, close];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Shows the panel as a modal. Currently, this method accepts `Window`s which use a delegate.
|
/// Shows the panel as a modal. Currently, this method accepts `Window`s which use a delegate.
|
||||||
/// If you're using a `Window` without a delegate, you may need to opt to use the `show()`
|
/// If you're using a `Window` without a delegate, you may need to opt to use the `show()`
|
||||||
/// method.
|
/// method.
|
||||||
|
@ -163,7 +173,7 @@ impl FileSelectPanel {
|
||||||
/// retain/ownership rules here.
|
/// retain/ownership rules here.
|
||||||
pub fn begin_sheet<T, F>(&self, window: &Window<T>, handler: F)
|
pub fn begin_sheet<T, F>(&self, window: &Window<T>, handler: F)
|
||||||
where
|
where
|
||||||
F: Fn(Vec<PathBuf>) + 'static
|
F: Fn(Vec<NSURL>) + 'static
|
||||||
{
|
{
|
||||||
let panel = self.panel.clone();
|
let panel = self.panel.clone();
|
||||||
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||||
|
@ -184,15 +194,16 @@ impl FileSelectPanel {
|
||||||
/// Retrieves the selected URLs from the provided panel.
|
/// Retrieves the selected URLs from the provided panel.
|
||||||
/// This is currently a bit ugly, but it's also not something that needs to be the best thing in
|
/// This is currently a bit ugly, but it's also not something that needs to be the best thing in
|
||||||
/// the world as it (ideally) shouldn't be called repeatedly in hot spots.
|
/// the world as it (ideally) shouldn't be called repeatedly in hot spots.
|
||||||
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
|
///
|
||||||
|
/// (We mostly do this to find the sweet spot between Rust constructs and necessary Foundation
|
||||||
|
/// interaction patterns)
|
||||||
|
fn get_urls(panel: &Object) -> Vec<NSURL> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let urls: id = msg_send![&*panel, URLs];
|
let urls: id = msg_send![&*panel, URLs];
|
||||||
let count: usize = msg_send![urls, count];
|
let count: usize = msg_send![urls, count];
|
||||||
|
|
||||||
(0..count).map(|index| {
|
(0..count).map(|index| {
|
||||||
let url: id = msg_send![urls, objectAtIndex:index];
|
NSURL::retain(msg_send![urls, objectAtIndex:index])
|
||||||
let path = NSString::retain(msg_send![url, path]).to_string();
|
|
||||||
path.into()
|
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,21 @@ impl NSData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given a slice of bytes, creates, retains, and returns a wrapped `NSData`.
|
||||||
|
///
|
||||||
|
/// This method is borrowed straight out of [objc-foundation](objc-foundation) by the amazing
|
||||||
|
/// Steven Sheldon, and just tweaked slightly to fit the desired API semantics here.
|
||||||
|
///
|
||||||
|
/// [objc-foundation]: https://crates.io/crates/objc-foundation
|
||||||
|
pub fn with_slice(bytes: &[u8]) -> Self {
|
||||||
|
let bytes_ptr = bytes.as_ptr() as *mut c_void;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let obj: id = msg_send![class!(NSData), dataWithBytes:bytes_ptr length:bytes.len()];
|
||||||
|
NSData(Id::from_ptr(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a (presumably) `NSData`, wraps and retains it.
|
/// Given a (presumably) `NSData`, wraps and retains it.
|
||||||
pub fn retain(data: id) -> Self {
|
pub fn retain(data: id) -> Self {
|
||||||
NSData(unsafe {
|
NSData(unsafe {
|
||||||
|
|
|
@ -42,6 +42,10 @@ pub use number::NSNumber;
|
||||||
mod string;
|
mod string;
|
||||||
pub use string::NSString;
|
pub use string::NSString;
|
||||||
|
|
||||||
|
// Separate named module to not conflict with the `url` crate. Go figure.
|
||||||
|
mod urls;
|
||||||
|
pub use urls::{NSURL, NSURLBookmarkCreationOption, NSURLBookmarkResolutionOption};
|
||||||
|
|
||||||
/// Bool mapping types differ between ARM and x64. There's a number of places that we need to check
|
/// Bool mapping types differ between ARM and x64. There's a number of places that we need to check
|
||||||
/// against BOOL results throughout the framework, and this just simplifies some mismatches.
|
/// against BOOL results throughout the framework, and this just simplifies some mismatches.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
|
66
src/foundation/urls/bookmark_options.rs
Normal file
66
src/foundation/urls/bookmark_options.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::foundation::NSUInteger;
|
||||||
|
|
||||||
|
/// Options used when creating bookmark data.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum NSURLBookmarkCreationOption {
|
||||||
|
/// Specifies that a bookmark created with this option should be created with minimal information.
|
||||||
|
Minimal,
|
||||||
|
|
||||||
|
/// Specifies that the bookmark data should include properties required to create Finder alias files.
|
||||||
|
SuitableForBookmarkFile,
|
||||||
|
|
||||||
|
/// Specifies that you want to create a security-scoped bookmark that, when resolved, provides a
|
||||||
|
/// security-scoped URL allowing read/write access to a file-system resource.
|
||||||
|
SecurityScoped,
|
||||||
|
|
||||||
|
/// When combined with the NSURLBookmarkCreationOptions::SecurityScoped option, specifies that you
|
||||||
|
/// want to create a security-scoped bookmark that, when resolved, provides a security-scoped URL allowing
|
||||||
|
/// read-only access to a file-system resource.
|
||||||
|
SecurityScopedReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSURLBookmarkCreationOption> for NSUInteger {
|
||||||
|
fn from(flag: NSURLBookmarkCreationOption) -> NSUInteger {
|
||||||
|
match flag {
|
||||||
|
NSURLBookmarkCreationOption::Minimal => 1u64 << 9,
|
||||||
|
NSURLBookmarkCreationOption::SuitableForBookmarkFile => 1u64 << 10,
|
||||||
|
NSURLBookmarkCreationOption::SecurityScoped => 1 << 11,
|
||||||
|
NSURLBookmarkCreationOption::SecurityScopedReadOnly => 1 << 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&NSURLBookmarkCreationOption> for NSUInteger {
|
||||||
|
fn from(flag: &NSURLBookmarkCreationOption) -> NSUInteger {
|
||||||
|
match flag {
|
||||||
|
NSURLBookmarkCreationOption::Minimal => 1u64 << 9,
|
||||||
|
NSURLBookmarkCreationOption::SuitableForBookmarkFile => 1u64 << 10,
|
||||||
|
NSURLBookmarkCreationOption::SecurityScoped => 1 << 11,
|
||||||
|
NSURLBookmarkCreationOption::SecurityScopedReadOnly => 1 << 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Options used when resolving bookmark data.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NSURLBookmarkResolutionOption {
|
||||||
|
/// Specifies that no UI feedback should accompany resolution of the bookmark data.
|
||||||
|
WithoutUI,
|
||||||
|
|
||||||
|
/// Specifies that no volume should be mounted during resolution of the bookmark data.
|
||||||
|
WithoutMounting,
|
||||||
|
|
||||||
|
/// Specifies that the security scope, applied to the bookmark when it was created, should
|
||||||
|
/// be used during resolution of the bookmark data.
|
||||||
|
SecurityScoped
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSURLBookmarkResolutionOption> for NSUInteger {
|
||||||
|
fn from(flag: NSURLBookmarkResolutionOption) -> NSUInteger {
|
||||||
|
match flag {
|
||||||
|
NSURLBookmarkResolutionOption::WithoutUI => 1u64 << 8,
|
||||||
|
NSURLBookmarkResolutionOption::WithoutMounting => 1u64 << 9,
|
||||||
|
NSURLBookmarkResolutionOption::SecurityScoped => 1 << 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
src/foundation/urls/mod.rs
Normal file
181
src/foundation/urls/mod.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::ShareId;
|
||||||
|
|
||||||
|
use crate::foundation::{id, nil, NSData, NSString, NSUInteger};
|
||||||
|
|
||||||
|
mod bookmark_options;
|
||||||
|
pub use bookmark_options::{NSURLBookmarkCreationOption, NSURLBookmarkResolutionOption};
|
||||||
|
|
||||||
|
mod resource_keys;
|
||||||
|
pub use resource_keys::{NSURLResourceKey, NSURLFileResource, NSUbiquitousItemDownloadingStatus};
|
||||||
|
|
||||||
|
/// Wraps `NSURL` for use throughout the framework.
|
||||||
|
///
|
||||||
|
/// This type may also be returned to users in some callbacks (e.g, file manager/selectors) as it's
|
||||||
|
/// a core part of the macOS/iOS experience and bridging around it is arguably blocking people from
|
||||||
|
/// being able to actually build useful things.
|
||||||
|
///
|
||||||
|
/// For pure-Rust developers who have no interest in the Objective-C underpinnings, there's a
|
||||||
|
/// `pathbuf()` method that returns an `std::path::PathBuf` for working with. Note that this will
|
||||||
|
/// prove less useful in sandboxed applications, and if the underlying file that the PathBuf points
|
||||||
|
/// to moves, you'll be responsible for figuring out exactly what you do there.
|
||||||
|
///
|
||||||
|
/// Otherwise, this struct bridges enough of NSURL to be useful (loading, using, and bookmarks).
|
||||||
|
/// Pull requests for additional functionality are welcome.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NSURL<'a> {
|
||||||
|
/// A reference to the backing `NSURL`.
|
||||||
|
pub objc: ShareId<Object>,
|
||||||
|
phantom: PhantomData<&'a ()>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NSURL<'a> {
|
||||||
|
/// In cases where we're vended an `NSURL` by the system, this can be used to wrap and
|
||||||
|
/// retain it.
|
||||||
|
pub fn retain(object: id) -> Self {
|
||||||
|
NSURL {
|
||||||
|
objc: unsafe { ShareId::from_ptr(object) },
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In some cases, we want to wrap a system-provided NSURL without retaining it.
|
||||||
|
pub fn from_retained(object: id) -> Self {
|
||||||
|
NSURL {
|
||||||
|
objc: unsafe { ShareId::from_retained_ptr(object) },
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and returns a URL object by calling through to `[NSURL URLWithString]`.
|
||||||
|
pub fn with_str(url: &str) -> Self {
|
||||||
|
let url = NSString::new(url);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
objc: unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSURL), URLWithString:&*url])
|
||||||
|
},
|
||||||
|
|
||||||
|
phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the absolute string path that this URL points to.
|
||||||
|
///
|
||||||
|
/// Note that if the underlying file moved, this won't be accurate - you likely want to
|
||||||
|
/// research URL bookmarks.
|
||||||
|
pub fn absolute_string(&self) -> String {
|
||||||
|
let abs_str = NSString::retain(unsafe {
|
||||||
|
msg_send![&*self.objc, absoluteString]
|
||||||
|
});
|
||||||
|
|
||||||
|
abs_str.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates and returns a Rust `PathBuf`, for users who don't need the extra pieces of NSURL
|
||||||
|
/// and just want to write Rust code.
|
||||||
|
pub fn pathbuf(&self) -> PathBuf {
|
||||||
|
let path = NSString::retain(unsafe {
|
||||||
|
msg_send![&*self.objc, path]
|
||||||
|
});
|
||||||
|
|
||||||
|
path.to_str().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns bookmark data for this URL. Will error if the underlying API errors.
|
||||||
|
///
|
||||||
|
/// Bookmarks are useful for sandboxed applications, as well as situations where you might want
|
||||||
|
/// to later resolve the true location of a file (e.g, if the user moved it between when you
|
||||||
|
/// got the URL and when you need to use it).
|
||||||
|
pub fn bookmark_data(
|
||||||
|
&self,
|
||||||
|
options: &[NSURLBookmarkCreationOption],
|
||||||
|
resource_value_keys: &[NSURLResourceKey],
|
||||||
|
relative_to_url: Option<NSURL>
|
||||||
|
) -> Result<NSData, Box<dyn Error>> {
|
||||||
|
let mut opts: NSUInteger = 0;
|
||||||
|
for mask in options {
|
||||||
|
let i: NSUInteger = mask.into();
|
||||||
|
opts = opts | i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build NSArray of resource keys
|
||||||
|
let resource_keys = nil;
|
||||||
|
|
||||||
|
// Mutability woes mean we just go through a match here to satisfy message passing needs.
|
||||||
|
let bookmark_data = NSData::retain(match relative_to_url {
|
||||||
|
Some(relative_url) => unsafe {
|
||||||
|
msg_send![&*self.objc, bookmarkDataWithOptions:opts
|
||||||
|
includingResourceValuesForKeys:resource_keys
|
||||||
|
relativeToURL:relative_url
|
||||||
|
error:nil
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
None => unsafe {
|
||||||
|
msg_send![&*self.objc, bookmarkDataWithOptions:opts
|
||||||
|
includingResourceValuesForKeys:resource_keys
|
||||||
|
relativeToURL:nil
|
||||||
|
error:nil
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for errors...
|
||||||
|
//Err("LOL".into())
|
||||||
|
|
||||||
|
Ok(bookmark_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts bookmark data into a URL.
|
||||||
|
pub fn from_bookmark_data(
|
||||||
|
data: NSData,
|
||||||
|
options: &[NSURLBookmarkResolutionOption],
|
||||||
|
relative_to_url: Option<NSURL>,
|
||||||
|
data_is_stale: bool
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
Err("LOL".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In an app that has adopted App Sandbox, makes the resource pointed to by a security-scoped URL available to the app.
|
||||||
|
///
|
||||||
|
/// More information can be found at:
|
||||||
|
/// [https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso?language=objc]
|
||||||
|
pub fn start_accessing_security_scoped_resource(&self) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, startAccessingSecurityScopedResource];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In an app that adopts App Sandbox, revokes access to the resource pointed to by a security-scoped URL.
|
||||||
|
///
|
||||||
|
/// More information can be found at:
|
||||||
|
/// [https://developer.apple.com/documentation/foundation/nsurl/1413736-stopaccessingsecurityscopedresou?language=objc]
|
||||||
|
pub fn stop_accessing_security_scoped_resource(&self) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.objc, stopAccessingSecurityScopedResource];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*impl From<NSString<'_>> for id {
|
||||||
|
/// Consumes and returns the pointer to the underlying NSString instance.
|
||||||
|
fn from(mut string: NSString) -> Self {
|
||||||
|
&mut *string.objc
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
impl Deref for NSURL<'_> {
|
||||||
|
type Target = Object;
|
||||||
|
|
||||||
|
/// Derefs to the underlying Objective-C Object.
|
||||||
|
fn deref(&self) -> &Object {
|
||||||
|
&*self.objc
|
||||||
|
}
|
||||||
|
}
|
170
src/foundation/urls/resource_keys.rs
Normal file
170
src/foundation/urls/resource_keys.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use crate::foundation::id;
|
||||||
|
|
||||||
|
/// Possible values for the `NSURLResourceKey::FileResourceType` key.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NSURLFileResource {
|
||||||
|
/// The resource is a named pipe.
|
||||||
|
NamedPipe,
|
||||||
|
|
||||||
|
/// The resource is a character special file.
|
||||||
|
CharacterSpecial,
|
||||||
|
|
||||||
|
/// The resource is a directory.
|
||||||
|
Directory,
|
||||||
|
|
||||||
|
/// The resource is a block special file.
|
||||||
|
BlockSpecial,
|
||||||
|
|
||||||
|
/// The resource is a regular file.
|
||||||
|
Regular,
|
||||||
|
|
||||||
|
/// The resource is a symbolic link.
|
||||||
|
SymbolicLink,
|
||||||
|
|
||||||
|
/// The resource is a socket.
|
||||||
|
Socket,
|
||||||
|
|
||||||
|
/// The resource’s type is unknown.
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Values that describe the iCloud storage state of a file.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NSUbiquitousItemDownloadingStatus {
|
||||||
|
/// A local copy of this item exists and is the most up-to-date version known to the device.
|
||||||
|
Current,
|
||||||
|
|
||||||
|
/// A local copy of this item exists, but it is stale. The most recent version will be downloaded as soon as possible.
|
||||||
|
Downloaded,
|
||||||
|
|
||||||
|
/// This item has not been downloaded yet. Initiate a download.
|
||||||
|
NotDownloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NSURLResourceKey {
|
||||||
|
IsApplication,
|
||||||
|
IsScriptable,
|
||||||
|
IsDirectory,
|
||||||
|
ParentDirectoryURL,
|
||||||
|
FileAllocatedSize,
|
||||||
|
FileProtection,
|
||||||
|
FileProtectionType,
|
||||||
|
FileResourceIdentifier,
|
||||||
|
FileResourceType(NSURLFileResource),
|
||||||
|
FileSecurity,
|
||||||
|
FileSize,
|
||||||
|
IsAliasFile,
|
||||||
|
IsPackage,
|
||||||
|
IsRegularFile,
|
||||||
|
PreferredIOBlockSize,
|
||||||
|
TotalFileAllocatedSize,
|
||||||
|
TotalFileSize,
|
||||||
|
|
||||||
|
VolumeAvailableCapacity,
|
||||||
|
VolumeAvailableCapacityForImportantUsage,
|
||||||
|
VolumeAvailableCapacityForOpportunisticUsage,
|
||||||
|
VolumeTotalCapacity,
|
||||||
|
VolumeIsAutomounted,
|
||||||
|
VolumeIsBrowsable,
|
||||||
|
VolumeIsEjectable,
|
||||||
|
VolumeIsEncrypted,
|
||||||
|
VolumeIsInternal,
|
||||||
|
VolumeIsJournaling,
|
||||||
|
VolumeIsLocal,
|
||||||
|
VolumeIsReadOnly,
|
||||||
|
VolumeIsRemovable,
|
||||||
|
VolumeIsRootFileSystem,
|
||||||
|
|
||||||
|
IsMountTrigger,
|
||||||
|
IsVolume,
|
||||||
|
VolumeCreationDate,
|
||||||
|
VolumeIdentifier,
|
||||||
|
VolumeLocalizedFormatDescription,
|
||||||
|
VolumeLocalizedName,
|
||||||
|
VolumeMaximumFileSize,
|
||||||
|
VolumeName,
|
||||||
|
VolumeResourceCount,
|
||||||
|
VolumeSupportsAccessPermissions,
|
||||||
|
VolumeSupportsAdvisoryFileLocking,
|
||||||
|
VolumeSupportsCasePreservedNames,
|
||||||
|
VolumeSupportsCaseSensitiveNames,
|
||||||
|
VolumeSupportsCompression,
|
||||||
|
VolumeSupportsExclusiveRenaming,
|
||||||
|
VolumeSupportsExtendedSecurity,
|
||||||
|
VolumeSupportsFileCloning,
|
||||||
|
VolumeSupportsHardLinks,
|
||||||
|
VolumeSupportsImmutableFiles,
|
||||||
|
VolumeSupportsJournaling,
|
||||||
|
VolumeSupportsPersistentIDs,
|
||||||
|
VolumeSupportsRenaming,
|
||||||
|
VolumeSupportsRootDirectoryDates,
|
||||||
|
VolumeSupportsSparseFiles,
|
||||||
|
VolumeSupportsSwapRenaming,
|
||||||
|
VolumeSupportsSymbolicLinks,
|
||||||
|
VolumeSupportsVolumeSizes,
|
||||||
|
VolumeSupportsZeroRuns,
|
||||||
|
VolumeURLForRemounting,
|
||||||
|
VolumeURL,
|
||||||
|
VolumeUUIDString,
|
||||||
|
|
||||||
|
IsUbiquitousItem,
|
||||||
|
UbiquitousSharedItemMostRecentEditorNameComponents,
|
||||||
|
UbiquitousItemDownloadRequested,
|
||||||
|
UbiquitousItemIsDownloading,
|
||||||
|
UbiquitousItemDownloadingError,
|
||||||
|
UbiquitousItemDownloadingStatus(NSUbiquitousItemDownloadingStatus),
|
||||||
|
UbiquitousItemIsUploaded,
|
||||||
|
UbiquitousItemIsUploading,
|
||||||
|
UbiquitousItemUploadingError,
|
||||||
|
UbiquitousItemHasUnresolvedConflicts,
|
||||||
|
UbiquitousItemContainerDisplayName,
|
||||||
|
UbiquitousSharedItemOwnerNameComponents,
|
||||||
|
UbiquitousSharedItemCurrentUserPermissions,
|
||||||
|
UbiquitousSharedItemCurrentUserRole,
|
||||||
|
UbiquitousItemIsShared,
|
||||||
|
UbiquitousSharedItemRole,
|
||||||
|
UbiquitousSharedItemPermissions,
|
||||||
|
|
||||||
|
ThumbnailDictionaryItem,
|
||||||
|
|
||||||
|
KeysOfUnsetValues,
|
||||||
|
QuarantineProperties,
|
||||||
|
AddedToDirectoryDate,
|
||||||
|
AttributeModificationDate,
|
||||||
|
ContentAccessDate,
|
||||||
|
ContentModificationDate,
|
||||||
|
CreationDate,
|
||||||
|
CustomIcon,
|
||||||
|
DocumentIdentifier,
|
||||||
|
EffectiveIcon,
|
||||||
|
GenerationIdentifier,
|
||||||
|
HasHiddenExtension,
|
||||||
|
IsExcludedFromBackup,
|
||||||
|
IsExecutable,
|
||||||
|
IsHidden,
|
||||||
|
IsReadable,
|
||||||
|
IsSymbolicLink,
|
||||||
|
IsSystemImmutable,
|
||||||
|
IsUserImmutable,
|
||||||
|
IsWritable,
|
||||||
|
LabelColor,
|
||||||
|
LabelNumber,
|
||||||
|
LinkCount,
|
||||||
|
LocalizedLabel,
|
||||||
|
LocalizedName,
|
||||||
|
LocalizedTypeDescription,
|
||||||
|
Name,
|
||||||
|
Path,
|
||||||
|
CanonicalPath,
|
||||||
|
TagNames,
|
||||||
|
ContentType,
|
||||||
|
|
||||||
|
FileContentIdentifier,
|
||||||
|
IsPurgeable,
|
||||||
|
IsSparse,
|
||||||
|
MayHaveExtendedAttributes,
|
||||||
|
MayShareFileContent,
|
||||||
|
UbiquitousItemIsExcludedFromSync,
|
||||||
|
VolumeSupportsFileProtection
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ impl Rect {
|
||||||
impl From<Rect> for CGRect {
|
impl From<Rect> for CGRect {
|
||||||
fn from(rect: Rect) -> CGRect {
|
fn from(rect: Rect) -> CGRect {
|
||||||
CGRect::new(
|
CGRect::new(
|
||||||
&CGPoint::new(rect.top, rect.left),
|
&CGPoint::new(rect.left, rect.top),
|
||||||
&CGSize::new(rect.width, rect.height)
|
&CGSize::new(rect.width, rect.height)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::foundation::id;
|
||||||
|
|
||||||
/// These icons are system-provided icons that are guaranteed to exist in all versions of macOS
|
/// These icons are system-provided icons that are guaranteed to exist in all versions of macOS
|
||||||
/// that Cacao supports. These will use SFSymbols on Big Sur and onwards (11.0+), and the correct
|
/// that Cacao supports. These will use SFSymbols on Big Sur and onwards (11.0+), and the correct
|
||||||
|
@ -23,51 +24,86 @@ pub enum MacSystemIcon {
|
||||||
|
|
||||||
/// Returns a stock "+" icon that's common to the system. Use this for buttons that need the
|
/// Returns a stock "+" icon that's common to the system. Use this for buttons that need the
|
||||||
/// symbol.
|
/// symbol.
|
||||||
Add
|
Add,
|
||||||
|
|
||||||
|
/// A stock "-" icon that's common to the system. Use this for buttons that need the symbol.
|
||||||
|
Remove,
|
||||||
|
|
||||||
|
/// Returns a Folder icon.
|
||||||
|
Folder
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
static NSImageNamePreferencesGeneral: id;
|
||||||
|
static NSImageNameAdvanced: id;
|
||||||
|
static NSImageNameUserAccounts: id;
|
||||||
|
static NSImageNameAddTemplate: id;
|
||||||
|
static NSImageNameFolder: id;
|
||||||
|
static NSImageNameRemoveTemplate: id;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
impl MacSystemIcon {
|
impl MacSystemIcon {
|
||||||
/// Maps system icons to their pre-11.0 framework identifiers.
|
/// Maps system icons to their pre-11.0 framework identifiers.
|
||||||
pub fn to_str(&self) -> &'static str {
|
pub fn to_id(&self) -> id {
|
||||||
|
unsafe {
|
||||||
match self {
|
match self {
|
||||||
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
|
MacSystemIcon::PreferencesGeneral => NSImageNamePreferencesGeneral,
|
||||||
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
|
MacSystemIcon::PreferencesAdvanced => NSImageNameAdvanced,
|
||||||
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
|
MacSystemIcon::PreferencesUserAccounts => NSImageNameUserAccounts,
|
||||||
MacSystemIcon::Add => "NSImageNameAddTemplate"
|
MacSystemIcon::Add => NSImageNameAddTemplate,
|
||||||
|
MacSystemIcon::Remove => NSImageNameRemoveTemplate,
|
||||||
|
MacSystemIcon::Folder => NSImageNameFolder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Maps system icons to their SFSymbols-counterparts for use on 11.0+.
|
/// Maps system icons to their SFSymbols-counterparts for use on 11.0+.
|
||||||
pub fn to_sfsymbol_str(&self) -> &'static str {
|
pub fn to_sfsymbol_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
MacSystemIcon::PreferencesGeneral => "gearshape",
|
MacSystemIcon::PreferencesGeneral => SFSymbol::GearShape.to_str(),
|
||||||
MacSystemIcon::PreferencesAdvanced => "slider.vertical.3",
|
MacSystemIcon::PreferencesAdvanced => SFSymbol::SliderVertical3.to_str(),
|
||||||
MacSystemIcon::PreferencesUserAccounts => "at",
|
MacSystemIcon::PreferencesUserAccounts => SFSymbol::AtSymbol.to_str(),
|
||||||
MacSystemIcon::Add => "plus"
|
MacSystemIcon::Add => SFSymbol::Plus.to_str(),
|
||||||
|
MacSystemIcon::Remove => SFSymbol::Minus.to_str(),
|
||||||
|
MacSystemIcon::Folder => SFSymbol::FolderFilled.to_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SFSymbol {
|
pub enum SFSymbol {
|
||||||
|
AtSymbol,
|
||||||
|
GearShape,
|
||||||
|
FolderFilled,
|
||||||
PaperPlane,
|
PaperPlane,
|
||||||
PaperPlaneFilled,
|
PaperPlaneFilled,
|
||||||
|
Plus,
|
||||||
|
Minus,
|
||||||
|
SliderVertical3,
|
||||||
SquareAndArrowUpOnSquare,
|
SquareAndArrowUpOnSquare,
|
||||||
SquareAndArrowUpOnSquareFill,
|
SquareAndArrowUpOnSquareFill,
|
||||||
SquareAndArrowDownOnSquare,
|
SquareAndArrowDownOnSquare,
|
||||||
SquareAndArrowDownOnSquareFill
|
SquareAndArrowDownOnSquareFill,
|
||||||
|
SquareDashed
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SFSymbol {
|
impl SFSymbol {
|
||||||
pub fn to_str(&self) -> &str {
|
pub fn to_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
Self::AtSymbol => "at",
|
||||||
|
Self::GearShape => "gearshape",
|
||||||
|
Self::FolderFilled => "folder.fill",
|
||||||
Self::PaperPlane => "paperplane",
|
Self::PaperPlane => "paperplane",
|
||||||
Self::PaperPlaneFilled => "paperplane.fill",
|
Self::PaperPlaneFilled => "paperplane.fill",
|
||||||
|
Self::Plus => "plus",
|
||||||
|
Self::Minus => "minus",
|
||||||
|
Self::SliderVertical3 => "slider.vertical.3",
|
||||||
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
|
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
|
||||||
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
|
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
|
||||||
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
|
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
|
||||||
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill"
|
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill",
|
||||||
|
Self::SquareDashed => "square.dashed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use core_graphics::{
|
||||||
};
|
};
|
||||||
use core_graphics::context::{CGContext, CGContextRef};
|
use core_graphics::context::{CGContext, CGContextRef};
|
||||||
|
|
||||||
use crate::foundation::{id, YES, NO, NSString};
|
use crate::foundation::{id, YES, NO, NSString, NSData};
|
||||||
use crate::utils::os;
|
use crate::utils::os;
|
||||||
use super::icons::*;
|
use super::icons::*;
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ pub struct DrawConfig {
|
||||||
pub resize: ResizeBehavior
|
pub resize: ResizeBehavior
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps `NSImage` on macOS, and `UIImage` on iOS and tvOS. Can be used to display images, icons,
|
/// Wraps `NSImage` under AppKit, and `UIImage` on under UIKit (iOS and tvOS). Can be used to display images, icons,
|
||||||
/// and so on.
|
/// and so on.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image(pub ShareId<Object>);
|
pub struct Image(pub ShareId<Object>);
|
||||||
|
@ -131,10 +131,53 @@ impl Image {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads an image from the specified path.
|
||||||
|
pub fn with_contents_of_file(path: &str) -> Self {
|
||||||
|
let file_path = NSString::new(path);
|
||||||
|
|
||||||
|
Image(unsafe {
|
||||||
|
let alloc: id = msg_send![class!(NSImage), alloc];
|
||||||
|
ShareId::from_ptr(msg_send![alloc, initWithContentsOfFile:file_path])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
||||||
|
let data = NSData::with_slice(data);
|
||||||
|
|
||||||
|
Image(unsafe {
|
||||||
|
let alloc: id = msg_send![class!(NSImage), alloc];
|
||||||
|
ShareId::from_ptr(msg_send![alloc, initWithData:data])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: for Airyx, unsure if this is supported - and it's somewhat modern macOS-specific, so
|
||||||
|
// let's keep the os flag here for now.
|
||||||
/// Returns a stock system icon. These are guaranteed to exist across all versions of macOS
|
/// Returns a stock system icon. These are guaranteed to exist across all versions of macOS
|
||||||
/// supported.
|
/// supported.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn system_icon(icon: MacSystemIcon, accessibility_description: &str) -> Self {
|
pub fn system_icon(icon: MacSystemIcon) -> Self {
|
||||||
|
Image(unsafe {
|
||||||
|
ShareId::from_ptr({
|
||||||
|
let icon = icon.to_id();
|
||||||
|
msg_send![class!(NSImage), imageNamed:icon]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO: for Airyx, unsure if this is supported - and it's somewhat modern macOS-specific, so
|
||||||
|
// let's keep the os flag here for now.
|
||||||
|
/// The name here can be confusing, I know.
|
||||||
|
///
|
||||||
|
/// A system symbol will swap an SFSymbol in for macOS 11.0+, but return the correct
|
||||||
|
/// MacSystemIcon image type for versions prior to that. This is mostly helpful in situations
|
||||||
|
/// like Preferences windows toolbars, where you want to have the correct modern styling for newer OS
|
||||||
|
/// versions.
|
||||||
|
///
|
||||||
|
/// However, if you need the correct "folder" icon for instance, you probably want `system_icon`.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub fn toolbar_icon(icon: MacSystemIcon, accessibility_description: &str) -> Self {
|
||||||
Image(unsafe {
|
Image(unsafe {
|
||||||
ShareId::from_ptr(match os::is_minimum_version(11) {
|
ShareId::from_ptr(match os::is_minimum_version(11) {
|
||||||
true => {
|
true => {
|
||||||
|
@ -145,8 +188,8 @@ impl Image {
|
||||||
},
|
},
|
||||||
|
|
||||||
false => {
|
false => {
|
||||||
let icon = NSString::new(icon.to_str());
|
let icon = icon.to_id();
|
||||||
msg_send![class!(NSImage), imageNamed:&*icon]
|
msg_send![class!(NSImage), imageNamed:icon]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,20 +4,24 @@ use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::Layout;
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "autolayout")]
|
||||||
mod macos;
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use macos::register_image_view_class;
|
mod appkit;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "appkit")]
|
||||||
mod ios;
|
use appkit::register_image_view_class;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(feature = "uikit")]
|
||||||
use ios::register_image_view_class;
|
//mod uikit;
|
||||||
|
|
||||||
|
//#[cfg(feature = "uikit")]
|
||||||
|
//use uikit::register_image_view_class;
|
||||||
|
|
||||||
mod image;
|
mod image;
|
||||||
pub use image::{Image, DrawConfig, ResizeBehavior};
|
pub use image::{Image, DrawConfig, ResizeBehavior};
|
||||||
|
@ -29,9 +33,11 @@ pub use icons::*;
|
||||||
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
let view: id = msg_send![registration_fn(), new];
|
let view: id = msg_send![registration_fn(), new];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -47,33 +53,43 @@ pub struct ImageView {
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,16 +105,36 @@ impl ImageView {
|
||||||
let view = allocate_view(register_image_view_class);
|
let view = allocate_view(register_image_view_class);
|
||||||
|
|
||||||
ImageView {
|
ImageView {
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(view),
|
top: LayoutAnchorY::top(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: LayoutAnchorX::left(view),
|
left: LayoutAnchorX::left(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: LayoutAnchorX::leading(view),
|
leading: LayoutAnchorX::leading(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: LayoutAnchorX::right(view),
|
right: LayoutAnchorX::right(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
trailing: LayoutAnchorX::trailing(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
bottom: LayoutAnchorY::bottom(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: LayoutAnchorDimension::width(view),
|
width: LayoutAnchorDimension::width(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: LayoutAnchorDimension::height(view),
|
height: LayoutAnchorDimension::height(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: LayoutAnchorX::center(view),
|
center_x: LayoutAnchorX::center(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,23 +148,34 @@ impl ImageView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given an image reference, sets it on the image view. You're currently responsible for
|
||||||
|
/// retaining this yourself.
|
||||||
pub fn set_image(&self, image: &Image) {
|
pub fn set_image(&self, image: &Image) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setImage:&*image.0];
|
let _: () = msg_send![obj, setImage:&*image.0];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*pub fn set_image_scaling(&self, scaling_type: ImageScale) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
|
||||||
|
let _: () = msg_send![obj, setImageScaling:
|
||||||
|
});
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageView {
|
impl ObjcAccess for ImageView {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Layout for ImageView {}
|
||||||
|
|
||||||
impl Drop for ImageView {
|
impl Drop for ImageView {
|
||||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
||||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||||
|
|
204
src/input/mod.rs
204
src/input/mod.rs
|
@ -45,22 +45,27 @@ use objc::{msg_send, sel, sel_impl};
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
use crate::control::Control;
|
||||||
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES};
|
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES};
|
||||||
use crate::layout::{Layout, LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
use crate::layout::Layout;
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::text::{Font, TextAlign};
|
use crate::text::{Font, TextAlign};
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "autolayout")]
|
||||||
mod macos;
|
use crate::layout::{LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use macos::{register_view_class, register_view_class_with_delegate};
|
mod appkit;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "appkit")]
|
||||||
mod ios;
|
use appkit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(feature = "uikit")]
|
||||||
use ios::{register_view_class, register_view_class_with_delegate};
|
//mod uikit;
|
||||||
|
|
||||||
|
//#[cfg(feature = "uikit")]
|
||||||
|
//use uikit::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::TextFieldDelegate;
|
pub use traits::TextFieldDelegate;
|
||||||
|
@ -72,9 +77,10 @@ fn common_init(class: *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
let view: id = msg_send![class, new];
|
let view: id = msg_send![class, new];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO];
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![view, setWantsLayer: YES];
|
let _: () = msg_send![view, setWantsLayer: YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -92,33 +98,43 @@ pub struct TextField<T = ()> {
|
||||||
pub delegate: Option<Box<T>>,
|
pub delegate: Option<Box<T>>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY,
|
pub center_y: LayoutAnchorY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,17 +152,37 @@ impl TextField {
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(view),
|
|
||||||
left: LayoutAnchorX::left(view),
|
|
||||||
leading: LayoutAnchorX::leading(view),
|
|
||||||
right: LayoutAnchorX::right(view),
|
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
|
||||||
width: LayoutAnchorDimension::width(view),
|
|
||||||
height: LayoutAnchorDimension::height(view),
|
|
||||||
center_x: LayoutAnchorX::center(view),
|
|
||||||
center_y: LayoutAnchorY::center(view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
|
||||||
|
#[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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,17 +205,37 @@ where
|
||||||
|
|
||||||
let mut label = TextField {
|
let mut label = TextField {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(label),
|
|
||||||
left: LayoutAnchorX::left(label),
|
|
||||||
leading: LayoutAnchorX::leading(label),
|
|
||||||
right: LayoutAnchorX::right(label),
|
|
||||||
trailing: LayoutAnchorX::trailing(label),
|
|
||||||
bottom: LayoutAnchorY::bottom(label),
|
|
||||||
width: LayoutAnchorDimension::width(label),
|
|
||||||
height: LayoutAnchorDimension::height(label),
|
|
||||||
center_x: LayoutAnchorX::center(label),
|
|
||||||
center_y: LayoutAnchorY::center(label),
|
|
||||||
objc: ObjcProperty::retain(label),
|
objc: ObjcProperty::retain(label),
|
||||||
|
|
||||||
|
#[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),
|
||||||
};
|
};
|
||||||
|
|
||||||
(&mut delegate).did_load(label.clone_as_handle());
|
(&mut delegate).did_load(label.clone_as_handle());
|
||||||
|
@ -196,17 +252,37 @@ impl<T> TextField<T> {
|
||||||
pub(crate) fn clone_as_handle(&self) -> TextField {
|
pub(crate) fn clone_as_handle(&self) -> TextField {
|
||||||
TextField {
|
TextField {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: self.top.clone(),
|
|
||||||
leading: self.leading.clone(),
|
|
||||||
left: self.left.clone(),
|
|
||||||
trailing: self.trailing.clone(),
|
|
||||||
right: self.right.clone(),
|
|
||||||
bottom: self.bottom.clone(),
|
|
||||||
width: self.width.clone(),
|
|
||||||
height: self.height.clone(),
|
|
||||||
center_x: self.center_x.clone(),
|
|
||||||
center_y: self.center_y.clone(),
|
|
||||||
objc: self.objc.clone(),
|
objc: self.objc.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
top: self.top.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
leading: self.leading.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
left: self.left.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
trailing: self.trailing.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
right: self.right.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
bottom: self.bottom.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
width: self.width.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
height: self.height.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_x: self.center_x.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_y: self.center_y.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +311,15 @@ impl<T> TextField<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Call this to set the text for the label.
|
||||||
|
pub fn set_placeholder_text(&self, text: &str) {
|
||||||
|
let s = NSString::new(text);
|
||||||
|
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setPlaceholderString:&*s];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// The the text alignment style for this control.
|
/// The the text alignment style for this control.
|
||||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
@ -243,6 +328,35 @@ impl<T> TextField<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set whether this field operates in single-line mode.
|
||||||
|
pub fn set_uses_single_line(&self, uses_single_line: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let cell: id = msg_send![obj, cell];
|
||||||
|
let _: () = msg_send![cell, setUsesSingleLineMode:match uses_single_line {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether this field operates in single-line mode.
|
||||||
|
pub fn set_wraps(&self, uses_single_line: bool) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let cell: id = msg_send![obj, cell];
|
||||||
|
let _: () = msg_send![cell, setWraps:match uses_single_line {
|
||||||
|
true => YES,
|
||||||
|
false => NO
|
||||||
|
}];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the maximum number of lines.
|
||||||
|
pub fn set_max_number_of_lines(&self, num: NSInteger) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setMaximumNumberOfLines:num];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the font for this input.
|
/// Sets the font for this input.
|
||||||
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
||||||
let font = font.as_ref().clone();
|
let font = font.as_ref().clone();
|
||||||
|
@ -253,16 +367,20 @@ impl<T> TextField<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Layout for TextField<T> {
|
impl<T> ObjcAccess for TextField<T> {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Layout for TextField<T> {}
|
||||||
|
|
||||||
|
impl<T> Control for TextField<T> {}
|
||||||
|
|
||||||
impl<T> Drop for TextField<T> {
|
impl<T> Drop for TextField<T> {
|
||||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `TextField` is being
|
/// A bit of extra cleanup for delegate callback pointers. If the originating `TextField` is being
|
||||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||||
|
|
18
src/keys.rs
Normal file
18
src/keys.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
//! This provides some basic mapping for providing Key characters to controls. It's mostly meant as
|
||||||
|
//! a wrapper to stop magic symbols all over the place.
|
||||||
|
|
||||||
|
/// Represents a Key character.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Key<'a> {
|
||||||
|
/// Behind the scenes, this translates to NSDeleteCharacter (for AppKit).
|
||||||
|
Delete,
|
||||||
|
|
||||||
|
/// Whatever character you want.
|
||||||
|
Char(&'a str)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for Key<'a> {
|
||||||
|
fn from(s: &'a str) -> Self {
|
||||||
|
Key::Char(s)
|
||||||
|
}
|
||||||
|
}
|
27
src/layout/animator.rs
Normal file
27
src/layout/animator.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use core_graphics::base::CGFloat;
|
||||||
|
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::{Class, Object};
|
||||||
|
use objc_id::ShareId;
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
|
||||||
|
/// A wrapper for an animation proxy object in Cocoa that supports basic animations.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LayoutConstraintAnimatorProxy(pub ShareId<Object>);
|
||||||
|
|
||||||
|
impl LayoutConstraintAnimatorProxy {
|
||||||
|
/// Wraps and returns a proxy for animation of layout constraint values.
|
||||||
|
pub fn new(proxy: id) -> Self {
|
||||||
|
Self(unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![proxy, animator])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the constant (usually referred to as `offset` in Cacao) value for the constraint being animated.
|
||||||
|
pub fn set_offset(&self, value: CGFloat) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, setConstant:value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::{id, YES, NO};
|
use crate::foundation::{id, YES, NO};
|
||||||
|
|
||||||
|
use super::LayoutConstraintAnimatorProxy;
|
||||||
|
|
||||||
/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
|
/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
|
||||||
/// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or
|
/// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or
|
||||||
/// height).
|
/// height).
|
||||||
|
@ -27,16 +29,20 @@ pub struct LayoutConstraint {
|
||||||
|
|
||||||
/// The priority used in computing this constraint.
|
/// The priority used in computing this constraint.
|
||||||
pub priority: f64,
|
pub priority: f64,
|
||||||
|
|
||||||
|
/// An animator proxy that can be used inside animation contexts.
|
||||||
|
pub animator: LayoutConstraintAnimatorProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutConstraint {
|
impl LayoutConstraint {
|
||||||
/// An internal method for wrapping existing constraints.
|
/// An internal method for wrapping existing constraints.
|
||||||
pub(crate) fn new(object: id) -> Self {
|
pub(crate) fn new(object: id) -> Self {
|
||||||
LayoutConstraint {
|
LayoutConstraint {
|
||||||
|
animator: LayoutConstraintAnimatorProxy::new(object),
|
||||||
constraint: unsafe { ShareId::from_ptr(object) },
|
constraint: unsafe { ShareId::from_ptr(object) },
|
||||||
offset: 0.0,
|
offset: 0.0,
|
||||||
multiplier: 0.0,
|
multiplier: 0.0,
|
||||||
priority: 0.0
|
priority: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +56,7 @@ impl LayoutConstraint {
|
||||||
|
|
||||||
|
|
||||||
LayoutConstraint {
|
LayoutConstraint {
|
||||||
|
animator: self.animator,
|
||||||
constraint: self.constraint,
|
constraint: self.constraint,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
multiplier: self.multiplier,
|
multiplier: self.multiplier,
|
||||||
|
@ -57,6 +64,7 @@ impl LayoutConstraint {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the offset of a borrowed constraint.
|
||||||
pub fn set_offset<F: Into<f64>>(&self, offset: F) {
|
pub fn set_offset<F: Into<f64>>(&self, offset: F) {
|
||||||
let offset: f64 = offset.into();
|
let offset: f64 = offset.into();
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,46 @@
|
||||||
//! A wrapper for `NSLayoutConstraint`, enabling AutoLayout across views. This does a few things
|
//! This module contains traits and helpers for layout. By default, standard frame-based layouts
|
||||||
//! that might seem weird, but are generally good and rely on the idea that this is all written
|
//! are supported via the `Layout` trait, which all widgets implement. If you opt in to the
|
||||||
//! once and used often.
|
//! `AutoLayout` feature, each widget will default to using AutoLayout, which can be beneficial in
|
||||||
//!
|
//! more complicated views that need to deal with differing screen sizes.
|
||||||
//! Notably: there are 3 structs for wrapping layout constraints; in practice, you likely don't need to
|
|
||||||
//! care. This is because we want to detect at compile time invalid layout items - i.e, you should
|
|
||||||
//! not be able to attach a left-axis to a top-axis. In Rust this is a bit tricky, but by using
|
|
||||||
//! some `impl Trait`'s in the right places we can mostly hide this detail away.
|
|
||||||
|
|
||||||
pub mod attributes;
|
mod traits;
|
||||||
pub use attributes::*;
|
|
||||||
|
|
||||||
pub mod traits;
|
|
||||||
pub use traits::Layout;
|
pub use traits::Layout;
|
||||||
|
|
||||||
pub mod constraint;
|
mod animator;
|
||||||
|
pub use animator::LayoutConstraintAnimatorProxy;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod attributes;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
pub use attributes::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod constraint;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub use constraint::LayoutConstraint;
|
pub use constraint::LayoutConstraint;
|
||||||
|
|
||||||
pub mod dimension;
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod dimension;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub use dimension::LayoutAnchorDimension;
|
pub use dimension::LayoutAnchorDimension;
|
||||||
|
|
||||||
pub mod horizontal;
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod horizontal;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub use horizontal::LayoutAnchorX;
|
pub use horizontal::LayoutAnchorX;
|
||||||
|
|
||||||
pub mod vertical;
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod vertical;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub use vertical::LayoutAnchorY;
|
pub use vertical::LayoutAnchorY;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
mod safe_guide;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
pub use safe_guide::SafeAreaLayoutGuide;
|
||||||
|
|
67
src/layout/safe_guide.rs
Normal file
67
src/layout/safe_guide.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
use crate::utils::os;
|
||||||
|
|
||||||
|
/// A SafeAreaLayoutGuide should exist on all view types, and ensures that there are anchor points
|
||||||
|
/// that work within the system constraints. On macOS 11+, this will ensure you work around system
|
||||||
|
/// padding transprently - on macOS 10.15 and under, this will transparently map to the normal
|
||||||
|
/// edges, as the underlying properties were not supported there.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SafeAreaLayoutGuide {
|
||||||
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
pub center_y: LayoutAnchorY
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SafeAreaLayoutGuide {
|
||||||
|
/// Given a view pointer, will extract the safe area layout guide properties and return a
|
||||||
|
/// `SafeAreaLayoutGuide` composed of them.
|
||||||
|
pub fn new(view: id) -> Self {
|
||||||
|
// For versions prior to Big Sur, we'll just use the default view anchors in place.
|
||||||
|
let guide: id = match os::is_minimum_version(11) {
|
||||||
|
true => unsafe { msg_send![view, layoutMarginsGuide] },
|
||||||
|
false => view
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
top: LayoutAnchorY::top(guide),
|
||||||
|
left: LayoutAnchorX::left(guide),
|
||||||
|
leading: LayoutAnchorX::leading(guide),
|
||||||
|
right: LayoutAnchorX::right(guide),
|
||||||
|
trailing: LayoutAnchorX::trailing(guide),
|
||||||
|
bottom: LayoutAnchorY::bottom(guide),
|
||||||
|
width: LayoutAnchorDimension::width(guide),
|
||||||
|
height: LayoutAnchorDimension::height(guide),
|
||||||
|
center_x: LayoutAnchorX::center(guide),
|
||||||
|
center_y: LayoutAnchorY::center(guide)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
//! Various traits related to controllers opting in to autolayout routines and support for view
|
//! Various traits related to controllers opting in to autolayout routines and support for view
|
||||||
//! heirarchies.
|
//! heirarchies.
|
||||||
|
|
||||||
|
use core_graphics::base::CGFloat;
|
||||||
use core_graphics::geometry::{CGRect, CGPoint, CGSize};
|
use core_graphics::geometry::{CGRect, CGPoint, CGSize};
|
||||||
|
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
@ -9,27 +10,21 @@ use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, to_bool, YES, NO, NSArray, NSString};
|
use crate::foundation::{id, nil, to_bool, YES, NO, NSArray, NSString};
|
||||||
use crate::geometry::Rect;
|
use crate::geometry::Rect;
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
|
|
||||||
/// A trait that view wrappers must conform to. Enables managing the subview tree.
|
/// A trait that view wrappers must conform to. Enables managing the subview tree.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Layout {
|
pub trait Layout: ObjcAccess {
|
||||||
/// Used for mutably interacting with the underlying Objective-C instance.
|
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F);
|
|
||||||
|
|
||||||
/// Used for checking backing properties of the underlying Objective-C instance, without
|
|
||||||
/// needing a mutable borrow.
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R;
|
|
||||||
|
|
||||||
/// Sets whether this needs to be redrawn before being displayed.
|
/// Sets whether this needs to be redrawn before being displayed.
|
||||||
///
|
///
|
||||||
/// If you're updating data that dynamically impacts this view, mark this as true - the next
|
/// If you're updating data that dynamically impacts this view, mark this as true - the next
|
||||||
/// pass from the system will redraw it accordingly, and set the underlying value back to
|
/// pass from the system will redraw it accordingly, and set the underlying value back to
|
||||||
/// `false`.
|
/// `false`.
|
||||||
fn set_needs_display(&self, needs_display: bool) {
|
fn set_needs_display(&self, needs_display: bool) {
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setNeedsDisplay:match needs_display {
|
let _: () = msg_send![obj, setNeedsDisplay:match needs_display {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
|
@ -39,8 +34,8 @@ pub trait Layout {
|
||||||
|
|
||||||
/// Adds another Layout-backed control or view as a subview of this view.
|
/// Adds another Layout-backed control or view as a subview of this view.
|
||||||
fn add_subview<V: Layout>(&self, view: &V) {
|
fn add_subview<V: Layout>(&self, view: &V) {
|
||||||
self.with_backing_node(|backing_node| {
|
self.with_backing_obj_mut(|backing_node| {
|
||||||
view.with_backing_node(|subview_node| unsafe {
|
view.with_backing_obj_mut(|subview_node| unsafe {
|
||||||
let _: () = msg_send![backing_node, addSubview:subview_node];
|
let _: () = msg_send![backing_node, addSubview:subview_node];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -48,7 +43,7 @@ pub trait Layout {
|
||||||
|
|
||||||
/// Removes a control or view from the superview.
|
/// Removes a control or view from the superview.
|
||||||
fn remove_from_superview(&self) {
|
fn remove_from_superview(&self) {
|
||||||
self.with_backing_node(|backing_node| unsafe {
|
self.with_backing_obj_mut(|backing_node| unsafe {
|
||||||
let _: () = msg_send![backing_node, removeFromSuperview];
|
let _: () = msg_send![backing_node, removeFromSuperview];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -61,7 +56,7 @@ pub trait Layout {
|
||||||
fn set_frame<R: Into<CGRect>>(&self, rect: R) {
|
fn set_frame<R: Into<CGRect>>(&self, rect: R) {
|
||||||
let frame: CGRect = rect.into();
|
let frame: CGRect = rect.into();
|
||||||
|
|
||||||
self.with_backing_node(move |backing_node| unsafe {
|
self.with_backing_obj_mut(move |backing_node| unsafe {
|
||||||
let _: () = msg_send![backing_node, setFrame:frame];
|
let _: () = msg_send![backing_node, setFrame:frame];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -71,8 +66,9 @@ pub trait Layout {
|
||||||
///
|
///
|
||||||
/// Cacao defaults this to `false`; if you need to set frame-based layout pieces,
|
/// Cacao defaults this to `false`; if you need to set frame-based layout pieces,
|
||||||
/// then you should set this to `true` (or use an appropriate initializer that does it for you).
|
/// then you should set this to `true` (or use an appropriate initializer that does it for you).
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
fn set_translates_autoresizing_mask_into_constraints(&self, translates: bool) {
|
fn set_translates_autoresizing_mask_into_constraints(&self, translates: bool) {
|
||||||
self.with_backing_node(|backing_node| unsafe {
|
self.with_backing_obj_mut(|backing_node| unsafe {
|
||||||
let _: () = msg_send![backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
|
let _: () = msg_send![backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
|
@ -84,7 +80,7 @@ pub trait Layout {
|
||||||
///
|
///
|
||||||
/// When hidden, widgets don't receive events and is not visible.
|
/// When hidden, widgets don't receive events and is not visible.
|
||||||
fn set_hidden(&self, hide: bool) {
|
fn set_hidden(&self, hide: bool) {
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setHidden:match hide {
|
let _: () = msg_send![obj, setHidden:match hide {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
|
@ -97,7 +93,7 @@ pub trait Layout {
|
||||||
/// Note that this can report `false` if an ancestor widget is hidden, thus hiding this - to check in
|
/// Note that this can report `false` if an ancestor widget is hidden, thus hiding this - to check in
|
||||||
/// that case, you may want `is_hidden_or_ancestor_is_hidden()`.
|
/// that case, you may want `is_hidden_or_ancestor_is_hidden()`.
|
||||||
fn is_hidden(&self) -> bool {
|
fn is_hidden(&self) -> bool {
|
||||||
self.get_from_backing_node(|obj| {
|
self.get_from_backing_obj(|obj| {
|
||||||
to_bool(unsafe {
|
to_bool(unsafe {
|
||||||
msg_send![obj, isHidden]
|
msg_send![obj, isHidden]
|
||||||
})
|
})
|
||||||
|
@ -105,9 +101,9 @@ pub trait Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether this is hidden, *or* whether an ancestor view is hidden.
|
/// Returns whether this is hidden, *or* whether an ancestor view is hidden.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
fn is_hidden_or_ancestor_is_hidden(&self) -> bool {
|
fn is_hidden_or_ancestor_is_hidden(&self) -> bool {
|
||||||
self.get_from_backing_node(|obj| {
|
self.get_from_backing_obj(|obj| {
|
||||||
to_bool(unsafe {
|
to_bool(unsafe {
|
||||||
msg_send![obj, isHiddenOrHasHiddenAncestor]
|
msg_send![obj, isHiddenOrHasHiddenAncestor]
|
||||||
})
|
})
|
||||||
|
@ -115,22 +111,28 @@ pub trait Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register this view for drag and drop operations.
|
/// Register this view for drag and drop operations.
|
||||||
#[cfg(target_os = "macos")]
|
///
|
||||||
|
/// This should be supported under UIKit as well, but is featured gated under AppKit
|
||||||
|
/// currently to avoid compile issues.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
||||||
let types: NSArray = types.into_iter().map(|t| {
|
let types: NSArray = types.into_iter().map(|t| {
|
||||||
let x: NSString = (*t).into();
|
let x: NSString = (*t).into();
|
||||||
x.into()
|
x.into()
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
|
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregisters this as a target for drag and drop operations.
|
/// Unregisters this as a target for drag and drop operations.
|
||||||
#[cfg(target_os = "macos")]
|
///
|
||||||
|
/// This should be supported under UIKit as well, but is featured gated under AppKit
|
||||||
|
/// currently to avoid compile issues.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
fn unregister_dragged_types(&self) {
|
fn unregister_dragged_types(&self) {
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, unregisterDraggedTypes];
|
let _: () = msg_send![obj, unregisterDraggedTypes];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -139,9 +141,9 @@ pub trait Layout {
|
||||||
///
|
///
|
||||||
/// If you have a high performance tableview or collectionview that has issues, disabling these
|
/// If you have a high performance tableview or collectionview that has issues, disabling these
|
||||||
/// can be helpful - but always test!
|
/// can be helpful - but always test!
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
fn set_posts_frame_change_notifications(&self, posts: bool) {
|
fn set_posts_frame_change_notifications(&self, posts: bool) {
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setPostsFrameChangedNotifications:match posts {
|
let _: () = msg_send![obj, setPostsFrameChangedNotifications:match posts {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
|
@ -153,13 +155,24 @@ pub trait Layout {
|
||||||
///
|
///
|
||||||
/// If you have a high performance tableview or collectionview that has issues, disabling these
|
/// If you have a high performance tableview or collectionview that has issues, disabling these
|
||||||
/// can be helpful - but always test!
|
/// can be helpful - but always test!
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
fn set_posts_bounds_change_notifications(&self, posts: bool) {
|
fn set_posts_bounds_change_notifications(&self, posts: bool) {
|
||||||
self.with_backing_node(|obj| unsafe {
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setPostsBoundsChangedNotifications:match posts {
|
let _: () = msg_send![obj, setPostsBoundsChangedNotifications:match posts {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}];
|
}];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Theoretically this belongs elsewhere, but we want to enable this on all view layers, since
|
||||||
|
/// it's common enough anyway.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
|
fn set_alpha(&self, value: f64) {
|
||||||
|
let value: CGFloat = value.into();
|
||||||
|
|
||||||
|
self.with_backing_obj_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setAlphaValue:value];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
71
src/lib.rs
71
src/lib.rs
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
//! # Cacao
|
//! # Cacao
|
||||||
//!
|
//!
|
||||||
//! This library provides safe Rust bindings for `AppKit` on macOS and (eventually) `UIKit` on iOS and tvOS.
|
//! This library provides safe Rust bindings for `AppKit` on appkit and (eventually) `UIKit` on uikit and tvOS.
|
||||||
//! It tries to do so in a way that, if you've done programming for the framework before (in Swift or
|
//! It tries to do so in a way that, if you've done programming for the framework before (in Swift or
|
||||||
//! Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
//! Objective-C), will feel familiar. This is tricky in Rust due to the ownership model, but some
|
||||||
//! creative coding and assumptions can get us pretty far.
|
//! creative coding and assumptions can get us pretty far.
|
||||||
|
@ -28,8 +28,8 @@
|
||||||
//! # Hello World
|
//! # Hello World
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use cacao::macos::app::{App, AppDelegate};
|
//! use cacao::appkit::app::{App, AppDelegate};
|
||||||
//! use cacao::macos::window::Window;
|
//! use cacao::appkit::window::Window;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Default)]
|
//! #[derive(Default)]
|
||||||
//! struct BasicApp {
|
//! struct BasicApp {
|
||||||
|
@ -59,9 +59,8 @@
|
||||||
//! Note that, in order for this framework to be useful, you must always elect one of the following
|
//! Note that, in order for this framework to be useful, you must always elect one of the following
|
||||||
//! features:
|
//! features:
|
||||||
//!
|
//!
|
||||||
//! - `macos`: Implements macOS-specific APIs (menus, toolbars, windowing, and so on).
|
//! - `appkit`: Implements appkit-specific APIs (menus, toolbars, windowing, and so on).
|
||||||
//! - `ios`: Implements iOS-specific APIs (scenes, navigation controllers, and so on).
|
//! - `uikit`: Implements uikit-specific APIs (scenes, navigation controllers, and so on).
|
||||||
//! - `tvos`: Implements tvOS-specific APIs. Currently barely implemented.
|
|
||||||
//!
|
//!
|
||||||
//! The rest of the features in this framework attempt to expose a common API across the three
|
//! The rest of the features in this framework attempt to expose a common API across the three
|
||||||
//! aforementioned feature platforms; if you need something else, you can often implement it
|
//! aforementioned feature platforms; if you need something else, you can often implement it
|
||||||
|
@ -72,6 +71,8 @@
|
||||||
//!
|
//!
|
||||||
//! The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
//! The following are a list of [Cargo features][cargo-features] that can be enabled or disabled.
|
||||||
//!
|
//!
|
||||||
|
//! - `autolayout`: Enables the use of AutoLayout across all widget types. This is a default
|
||||||
|
//! feature, but is gated to enable platforms that might shim AppKit without AutoLayout support.
|
||||||
//! - `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit
|
//! - `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit
|
||||||
//! functionality. Currently not feature complete.
|
//! functionality. Currently not feature complete.
|
||||||
//! - `color_fallbacks`: Provides fallback colors for older systems where `systemColor` types don't
|
//! - `color_fallbacks`: Provides fallback colors for older systems where `systemColor` types don't
|
||||||
|
@ -79,13 +80,13 @@
|
||||||
//! - `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for
|
//! - `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for
|
||||||
//! files.
|
//! files.
|
||||||
//! - `user-notifications`: Links `UserNotifications.framework` and provides functionality for
|
//! - `user-notifications`: Links `UserNotifications.framework` and provides functionality for
|
||||||
//! emitting notifications on macOS and iOS. Note that this _requires_ your application be
|
//! emitting notifications on appkit and uikit. Note that this _requires_ your application be
|
||||||
//! code-signed, and will not work without it.
|
//! code-signed, and will not work without it.
|
||||||
//! - `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
//! - `webview`: Links `WebKit.framework` and provides a `WebView` control backed by `WKWebView`.
|
||||||
//! This feature is not supported on tvOS, as the platform has no webview control.
|
//! This feature will not be supported on tvOS, as the platform has no webview control.
|
||||||
//! - `webview-downloading-macos`: Enables downloading files from the `WebView` via a private
|
//! - `webview-downloading-macos`: Enables downloading files from the `WebView` via a private
|
||||||
//! interface. This is not an App-Store-safe feature, so be aware of that before enabling. This
|
//! interface. This is not an App-Store-safe feature, so be aware of that before enabling. This
|
||||||
//! feature is not supported on iOS (a user would handle downloads very differently) or tvOS
|
//! feature is not supported on uikit (a user would handle downloads very differently) or tvOS
|
||||||
//! (there's no web browser there at all).
|
//! (there's no web browser there at all).
|
||||||
//!
|
//!
|
||||||
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
//! [cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section
|
||||||
|
@ -96,15 +97,20 @@ pub use objc;
|
||||||
pub use url;
|
pub use url;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
//#[cfg(all(feature = "appkit", feature = "uikit", not(feature = "doc_cfg")))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(target_os = "macos")))]
|
//compile_error!("The \"appkit\" and \"uikit\" features cannot be enabled together. Pick one. :)");
|
||||||
pub mod macos;
|
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "appkit")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(target_os = "ios")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "appkit")))]
|
||||||
pub mod ios;
|
pub mod appkit;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
//pub mod bundle;
|
||||||
|
|
||||||
|
#[cfg(feature = "uikit")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "uikit")))]
|
||||||
|
pub mod uikit;
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub mod button;
|
pub mod button;
|
||||||
|
|
||||||
#[cfg(any(feature = "cloudkit", doc))]
|
#[cfg(any(feature = "cloudkit", doc))]
|
||||||
|
@ -113,50 +119,59 @@ pub mod cloudkit;
|
||||||
|
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
|
pub mod control;
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub mod dragdrop;
|
pub mod dragdrop;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
|
||||||
pub mod defaults;
|
pub mod defaults;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod filesystem;
|
pub mod filesystem;
|
||||||
|
|
||||||
pub mod foundation;
|
pub mod foundation;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub(crate) mod invoker;
|
||||||
|
|
||||||
|
pub mod keys;
|
||||||
|
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub(crate) mod invoker;
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod listview;
|
pub mod listview;
|
||||||
pub mod networking;
|
pub mod networking;
|
||||||
|
pub(crate) mod objc_access;
|
||||||
pub mod notification_center;
|
pub mod notification_center;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod pasteboard;
|
pub mod pasteboard;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod scrollview;
|
pub mod scrollview;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub mod switch;
|
pub mod switch;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
|
pub mod select;
|
||||||
|
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
#[cfg(feature = "quicklook")]
|
#[cfg(feature = "quicklook")]
|
||||||
|
|
|
@ -14,7 +14,7 @@ use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
use objc::{class, sel, sel_impl, msg_send};
|
use objc::{class, sel, sel_impl, msg_send};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::macos::menu::{Menu, MenuItem};
|
use crate::appkit::menu::{Menu, MenuItem};
|
||||||
use crate::foundation::{load_or_register_class, id, nil, YES, NO, NSArray, NSInteger, NSUInteger};
|
use crate::foundation::{load_or_register_class, id, nil, YES, NO, NSArray, NSInteger, NSUInteger};
|
||||||
use crate::dragdrop::DragInfo;
|
use crate::dragdrop::DragInfo;
|
||||||
use crate::listview::{
|
use crate::listview::{
|
||||||
|
@ -37,9 +37,16 @@ extern fn view_for_column<T: ListViewDelegate>(
|
||||||
this: &Object,
|
this: &Object,
|
||||||
_: Sel,
|
_: Sel,
|
||||||
_table_view: id,
|
_table_view: id,
|
||||||
_: id,
|
_table_column: id,
|
||||||
item: NSInteger
|
item: NSInteger
|
||||||
) -> id {
|
) -> id {
|
||||||
|
/*use core_graphics::geometry::CGRect;
|
||||||
|
unsafe {
|
||||||
|
//let superview: id = msg_send![table_view, superview];
|
||||||
|
let frame: CGRect = msg_send![table_view, frame];
|
||||||
|
let _: () = msg_send![table_column, setWidth:frame.size.width];
|
||||||
|
}*/
|
||||||
|
|
||||||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
let item = view.item_for(item as usize);
|
let item = view.item_for(item as usize);
|
||||||
|
|
||||||
|
@ -79,7 +86,7 @@ extern fn menu_needs_update<T: ListViewDelegate>(
|
||||||
let _ = Menu::append(menu, items);
|
let _ = Menu::append(menu, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NSTableView requires listening to an observer to detect row selection changes, but that is...
|
/*/// NSTableView requires listening to an observer to detect row selection changes, but that is...
|
||||||
/// even clunkier than what we do in this framework.
|
/// even clunkier than what we do in this framework.
|
||||||
///
|
///
|
||||||
/// The other less obvious way is to subclass and override the `shouldSelectRow:` method; here, we
|
/// The other less obvious way is to subclass and override the `shouldSelectRow:` method; here, we
|
||||||
|
@ -94,6 +101,24 @@ extern fn select_row<T: ListViewDelegate>(
|
||||||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
view.item_selected(item as usize);
|
view.item_selected(item as usize);
|
||||||
YES
|
YES
|
||||||
|
}*/
|
||||||
|
|
||||||
|
extern fn selection_did_change<T: ListViewDelegate>(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
notification: id
|
||||||
|
) {
|
||||||
|
let selected_row: NSInteger = unsafe {
|
||||||
|
let tableview: id = msg_send![notification, object];
|
||||||
|
msg_send![tableview, selectedRow]
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||||
|
if selected_row == -1 {
|
||||||
|
view.item_selected(None);
|
||||||
|
} else {
|
||||||
|
view.item_selected(Some(selected_row as usize));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn row_actions_for_row<T: ListViewDelegate>(
|
extern fn row_actions_for_row<T: ListViewDelegate>(
|
||||||
|
@ -203,7 +228,7 @@ pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instanc
|
||||||
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, id) -> NSInteger);
|
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, id) -> NSInteger);
|
||||||
decl.add_method(sel!(tableView:willDisplayCell:forTableColumn:row:), will_display_cell::<T> as extern fn(&Object, _, id, id, id, NSInteger));
|
decl.add_method(sel!(tableView:willDisplayCell:forTableColumn:row:), will_display_cell::<T> as extern fn(&Object, _, id, id, id, NSInteger));
|
||||||
decl.add_method(sel!(tableView:viewForTableColumn:row:), view_for_column::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
|
decl.add_method(sel!(tableView:viewForTableColumn:row:), view_for_column::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
|
||||||
decl.add_method(sel!(tableView:shouldSelectRow:), select_row::<T> as extern fn(&Object, _, id, NSInteger) -> BOOL);
|
decl.add_method(sel!(tableViewSelectionDidChange:), selection_did_change::<T> as extern fn(&Object, _, id));
|
||||||
decl.add_method(sel!(tableView:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
|
decl.add_method(sel!(tableView:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
|
||||||
|
|
||||||
// A slot for some menu handling; we just let it be done here for now rather than do the
|
// A slot for some menu handling; we just let it be done here for now rather than do the
|
|
@ -50,26 +50,31 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger};
|
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::Layout;
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::scrollview::ScrollView;
|
use crate::scrollview::ScrollView;
|
||||||
use crate::utils::{os, CellFactory, CGSize};
|
use crate::utils::{os, CellFactory, CGSize};
|
||||||
use crate::utils::properties::{ObjcProperty, PropertyNullable};
|
use crate::utils::properties::{ObjcProperty, PropertyNullable};
|
||||||
use crate::view::ViewDelegate;
|
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use crate::macos::menu::MenuItem;
|
use crate::appkit::menu::MenuItem;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
mod macos;
|
mod appkit;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use macos::{register_listview_class, register_listview_class_with_delegate};
|
use appkit::{register_listview_class, register_listview_class_with_delegate};
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(target_os = "ios")]
|
||||||
mod ios;
|
//mod ios;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(target_os = "ios")]
|
||||||
use ios::{register_view_class, register_view_class_with_delegate};
|
//use ios::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::{RowAnimation, RowEdge};
|
pub use enums::{RowAnimation, RowEdge};
|
||||||
|
@ -94,11 +99,12 @@ use std::cell::RefCell;
|
||||||
/// A helper method for instantiating view classes and applying default settings to them.
|
/// A helper method for instantiating view classes and applying default settings to them.
|
||||||
fn common_init(class: *const Class) -> id {
|
fn common_init(class: *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// Note: we do *not* enable AutoLayout here as we're by default placing this in a scroll
|
||||||
|
// view, and we want it to just do its thing.
|
||||||
let tableview: id = msg_send![class, new];
|
let tableview: id = msg_send![class, new];
|
||||||
let _: () = msg_send![tableview, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
||||||
|
|
||||||
// Let's... make NSTableView into UITableView-ish.
|
// Let's... make NSTableView into UITableView-ish.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{
|
{
|
||||||
// @TODO: Clean this up in a dealloc method.
|
// @TODO: Clean this up in a dealloc method.
|
||||||
let menu: id = msg_send![class!(NSMenu), new];
|
let menu: id = msg_send![class!(NSMenu), new];
|
||||||
|
@ -137,42 +143,56 @@ pub struct ListView<T = ()> {
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
/// On macOS, we need to manage the NSScrollView ourselves. It's a bit
|
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||||
|
pub animator: ViewAnimatorProxy,
|
||||||
|
|
||||||
|
/// In AppKit, we need to manage the NSScrollView ourselves. It's a bit
|
||||||
/// more old school like that...
|
/// more old school like that...
|
||||||
#[cfg(target_os = "macos")]
|
///
|
||||||
|
/// In iOS, this is a pointer to the UITableView-owned UIScrollView.
|
||||||
pub scrollview: ScrollView,
|
pub scrollview: ScrollView,
|
||||||
|
|
||||||
/// A pointer to the delegate for this view.
|
/// A pointer to the delegate for this view.
|
||||||
pub delegate: Option<Box<T>>,
|
pub delegate: Option<Box<T>>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +208,7 @@ impl ListView {
|
||||||
let class = register_listview_class();
|
let class = register_listview_class();
|
||||||
let view = common_init(class);
|
let view = common_init(class);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let scrollview = {
|
let scrollview = {
|
||||||
let sview = ScrollView::new();
|
let sview = ScrollView::new();
|
||||||
|
|
||||||
|
@ -199,34 +219,59 @@ impl ListView {
|
||||||
sview
|
sview
|
||||||
};
|
};
|
||||||
|
|
||||||
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
|
// For AppKit, we need to use the NSScrollView anchor points, not the NSTableView.
|
||||||
// @TODO: Fix this with proper mutable access.
|
// @TODO: Fix this with proper mutable access.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(all(feature = "appkit", feature = "autolayout"))]
|
||||||
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
|
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
|
||||||
msg_send![obj, self]
|
msg_send![obj, self]
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(all(feature = "uikit", feature = "autolayout"))]
|
||||||
let anchor_view: id = view;
|
//let anchor_view: id = view;
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
cell_factory: CellFactory::new(),
|
cell_factory: CellFactory::new(),
|
||||||
menu: PropertyNullable::default(),
|
menu: PropertyNullable::default(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(anchor_view),
|
top: LayoutAnchorY::top(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: LayoutAnchorX::left(anchor_view),
|
left: LayoutAnchorX::left(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: LayoutAnchorX::leading(anchor_view),
|
leading: LayoutAnchorX::leading(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: LayoutAnchorX::right(anchor_view),
|
right: LayoutAnchorX::right(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: LayoutAnchorX::trailing(anchor_view),
|
trailing: LayoutAnchorX::trailing(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: LayoutAnchorY::bottom(anchor_view),
|
bottom: LayoutAnchorY::bottom(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: LayoutAnchorDimension::width(anchor_view),
|
width: LayoutAnchorDimension::width(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: LayoutAnchorDimension::height(anchor_view),
|
height: LayoutAnchorDimension::height(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: LayoutAnchorX::center(anchor_view),
|
center_x: LayoutAnchorX::center(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(anchor_view),
|
center_y: LayoutAnchorY::center(anchor_view),
|
||||||
|
|
||||||
|
// Note that AppKit needs this to be the ScrollView!
|
||||||
|
// @TODO: Figure out if there's a use case for exposing the inner tableview animator
|
||||||
|
// property...
|
||||||
|
animator: ViewAnimatorProxy::new(anchor_view),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
scrollview
|
||||||
scrollview: scrollview
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,15 +286,13 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
|
||||||
let cell = CellFactory::new();
|
let cell = CellFactory::new();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
|
||||||
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
||||||
let delegate_ptr: *const T = &*delegate;
|
let delegate_ptr: *const T = &*delegate;
|
||||||
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, delegate_ptr as usize);
|
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, delegate_ptr as usize);
|
||||||
let _: () = msg_send![view, setDelegate:view];
|
let _: () = msg_send![view, setDelegate:view];
|
||||||
let _: () = msg_send![view, setDataSource:view];
|
let _: () = msg_send![view, setDataSource:view];
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let scrollview = {
|
let scrollview = {
|
||||||
let sview = ScrollView::new();
|
let sview = ScrollView::new();
|
||||||
|
|
||||||
|
@ -260,33 +303,53 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
|
||||||
sview
|
sview
|
||||||
};
|
};
|
||||||
|
|
||||||
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
|
// For AppKit, we need to use the NSScrollView anchor points, not the NSTableView.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(all(feature = "appkit", feature = "autolayout"))]
|
||||||
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
|
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
|
||||||
msg_send![obj, self]
|
msg_send![obj, self]
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(feature = "uikit")]
|
||||||
let anchor_view = view;
|
//let anchor_view = view;
|
||||||
|
|
||||||
let mut view = ListView {
|
let mut view = ListView {
|
||||||
cell_factory: cell,
|
cell_factory: cell,
|
||||||
menu: PropertyNullable::default(),
|
menu: PropertyNullable::default(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(anchor_view),
|
|
||||||
left: LayoutAnchorX::left(anchor_view),
|
|
||||||
leading: LayoutAnchorX::leading(anchor_view),
|
|
||||||
right: LayoutAnchorX::right(anchor_view),
|
|
||||||
trailing: LayoutAnchorX::trailing(anchor_view),
|
|
||||||
bottom: LayoutAnchorY::bottom(anchor_view),
|
|
||||||
width: LayoutAnchorDimension::width(anchor_view),
|
|
||||||
height: LayoutAnchorDimension::height(anchor_view),
|
|
||||||
center_x: LayoutAnchorX::center(anchor_view),
|
|
||||||
center_y: LayoutAnchorY::center(anchor_view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
animator: ViewAnimatorProxy::new(anchor_view),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "autolayout")]
|
||||||
scrollview: scrollview
|
top: LayoutAnchorY::top(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
left: LayoutAnchorX::left(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
leading: LayoutAnchorX::leading(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
right: LayoutAnchorX::right(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
trailing: LayoutAnchorX::trailing(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
bottom: LayoutAnchorY::bottom(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
width: LayoutAnchorDimension::width(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
height: LayoutAnchorDimension::height(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_x: LayoutAnchorX::center(anchor_view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_y: LayoutAnchorY::center(anchor_view),
|
||||||
|
|
||||||
|
scrollview
|
||||||
};
|
};
|
||||||
|
|
||||||
(&mut delegate).did_load(view.clone_as_handle());
|
(&mut delegate).did_load(view.clone_as_handle());
|
||||||
|
@ -300,24 +363,44 @@ impl<T> ListView<T> {
|
||||||
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
|
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
|
||||||
/// reference, customize and use the view but without the trickery of holding pieces of the
|
/// reference, customize and use the view but without the trickery of holding pieces of the
|
||||||
/// delegate - the `View` is the only true holder of those.
|
/// delegate - the `View` is the only true holder of those.
|
||||||
pub(crate) fn clone_as_handle(&self) -> ListView {
|
pub fn clone_as_handle(&self) -> ListView {
|
||||||
ListView {
|
ListView {
|
||||||
cell_factory: CellFactory::new(),
|
cell_factory: CellFactory::new(),
|
||||||
menu: self.menu.clone(),
|
menu: self.menu.clone(),
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: self.top.clone(),
|
|
||||||
leading: self.leading.clone(),
|
|
||||||
left: self.left.clone(),
|
|
||||||
trailing: self.trailing.clone(),
|
|
||||||
right: self.right.clone(),
|
|
||||||
bottom: self.bottom.clone(),
|
|
||||||
width: self.width.clone(),
|
|
||||||
height: self.height.clone(),
|
|
||||||
center_x: self.center_x.clone(),
|
|
||||||
center_y: self.center_y.clone(),
|
|
||||||
objc: self.objc.clone(),
|
objc: self.objc.clone(),
|
||||||
|
animator: self.animator.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
top: self.top.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
leading: self.leading.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
left: self.left.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
trailing: self.trailing.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
right: self.right.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
bottom: self.bottom.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
width: self.width.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
height: self.height.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_x: self.center_x.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
center_y: self.center_y.clone(),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
scrollview: self.scrollview.clone_as_handle()
|
scrollview: self.scrollview.clone_as_handle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,7 +417,7 @@ impl<T> ListView<T> {
|
||||||
|
|
||||||
/// Dequeue a reusable cell. If one is not in the queue, will create and cache one for reuse.
|
/// Dequeue a reusable cell. If one is not in the queue, will create and cache one for reuse.
|
||||||
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
|
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{
|
{
|
||||||
let key = NSString::new(identifier);
|
let key = NSString::new(identifier);
|
||||||
let cell: id = self.objc.get(|obj| unsafe {
|
let cell: id = self.objc.get(|obj| unsafe {
|
||||||
|
@ -364,8 +447,11 @@ impl<T> ListView<T> {
|
||||||
|
|
||||||
/// Sets the style for the underlying NSTableView. This property is only supported on macOS
|
/// Sets the style for the underlying NSTableView. This property is only supported on macOS
|
||||||
/// 11.0+, and will always be `FullWidth` on anything older.
|
/// 11.0+, and will always be `FullWidth` on anything older.
|
||||||
#[cfg(target_os = "macos")]
|
///
|
||||||
|
/// On non-macOS platforms, this method is a noop.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
pub fn set_style(&self, style: crate::foundation::NSInteger) {
|
pub fn set_style(&self, style: crate::foundation::NSInteger) {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
if os::is_minimum_version(11) {
|
if os::is_minimum_version(11) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setStyle:style];
|
let _: () = msg_send![obj, setStyle:style];
|
||||||
|
@ -375,10 +461,10 @@ impl<T> ListView<T> {
|
||||||
|
|
||||||
/// Set whether this control can appear with no row selected.
|
/// Set whether this control can appear with no row selected.
|
||||||
///
|
///
|
||||||
/// This defaults to `true`, but some macOS pieces (e.g, a sidebar) may want this set to
|
/// This defaults to `true`, but some AppKit pieces (e.g, a sidebar) may want this set to
|
||||||
/// `false`. This can be particularly useful when implementing a Source List style sidebar
|
/// `false`. This can be particularly useful when implementing a Source List style sidebar
|
||||||
/// view for navigation purposes.
|
/// view for navigation purposes.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
pub fn set_allows_empty_selection(&self, allows: bool) {
|
pub fn set_allows_empty_selection(&self, allows: bool) {
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setAllowsEmptySelection:match allows {
|
let _: () = msg_send![obj, setAllowsEmptySelection:match allows {
|
||||||
|
@ -413,6 +499,34 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This hack exists to avoid a bug with how Rust's model isn't really friendly with more
|
||||||
|
/// old-school GUI models. The tl;dr is that we unfortunately have to cheat a bit to gracefully
|
||||||
|
/// handle two conditions.
|
||||||
|
///
|
||||||
|
/// The gist of it is that there are two situations (`perform_batch_updates` and `insert_rows`)
|
||||||
|
/// where we call over to the list view to, well, perform updates. This causes the internal
|
||||||
|
/// machinery of AppKit to call to the delegate, and the delegate then - rightfully - calls to
|
||||||
|
/// dequeue a cell.
|
||||||
|
///
|
||||||
|
/// The problem is then that dequeue'ing a cell requires borrowing the underlying cell handler,
|
||||||
|
/// per Rust's model. We haven't been able to drop our existing lock though! Thus it winds up
|
||||||
|
/// panic'ing and all hell breaks loose.
|
||||||
|
///
|
||||||
|
/// For now, we just drop to Objective-C and message pass directly to avoid a
|
||||||
|
/// double-locking-attempt on the Rust side of things. This is explicitly not ideal, and if
|
||||||
|
/// you're reading this and rightfully going "WTF?", I encourage you to contribute a solution
|
||||||
|
/// if you can come up with one.
|
||||||
|
///
|
||||||
|
/// In practice, this hack isn't that bad - at least, no worse than existing Objective-C code.
|
||||||
|
/// The behavior is relatively well understood and documented in the above paragraph, so I'm
|
||||||
|
/// comfortable with the hack for now.
|
||||||
|
///
|
||||||
|
/// To be ultra-clear: the hack is that we don't `borrow_mut` before sending a message. It just
|
||||||
|
/// feels dirty, hence the novel. ;P
|
||||||
|
fn hack_avoid_dequeue_loop<F: Fn(&Object)>(&self, handler: F) {
|
||||||
|
self.objc.get(handler);
|
||||||
|
}
|
||||||
|
|
||||||
/// This method should be used when inserting or removing multiple rows at once. Under the
|
/// This method should be used when inserting or removing multiple rows at once. Under the
|
||||||
/// hood, it batches the changes and tries to ensure things are done properly. The provided
|
/// hood, it batches the changes and tries to ensure things are done properly. The provided
|
||||||
/// `ListView` for the handler is your `ListView`, and you can call `insert_rows`,
|
/// `ListView` for the handler is your `ListView`, and you can call `insert_rows`,
|
||||||
|
@ -425,7 +539,7 @@ impl<T> ListView<T> {
|
||||||
/// ```
|
/// ```
|
||||||
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
|
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
|
||||||
// Note that we need to thread the `with_mut` calls carefully, to avoid deadlocking.
|
// Note that we need to thread the `with_mut` calls carefully, to avoid deadlocking.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
{
|
{
|
||||||
self.objc.get(|obj| unsafe {
|
self.objc.get(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, beginUpdates];
|
let _: () = msg_send![obj, beginUpdates];
|
||||||
|
@ -434,14 +548,9 @@ impl<T> ListView<T> {
|
||||||
let handle = self.clone_as_handle();
|
let handle = self.clone_as_handle();
|
||||||
update(handle);
|
update(handle);
|
||||||
|
|
||||||
// This is cheating, but there's no good way around it at the moment. If we (mutably) lock in
|
// This is done for a very explicit reason; see the comments on the method itself for
|
||||||
// Rust here, firing this call will loop back around into `dequeue`, which will then
|
// an explanation.
|
||||||
// hit a double lock.
|
self.hack_avoid_dequeue_loop(|obj| unsafe {
|
||||||
//
|
|
||||||
// Personally, I can live with this - `endUpdates` is effectively just flushing the
|
|
||||||
// already added updates, so with this small hack here we're able to keep the mutable
|
|
||||||
// borrow structure everywhere else, which feels "correct".
|
|
||||||
self.objc.get(|obj| unsafe {
|
|
||||||
let _: () = msg_send![obj, endUpdates];
|
let _: () = msg_send![obj, endUpdates];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -453,7 +562,7 @@ impl<T> ListView<T> {
|
||||||
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
|
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
|
||||||
/// optimize things accordingly.
|
/// optimize things accordingly.
|
||||||
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
|
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
|
@ -468,7 +577,9 @@ impl<T> ListView<T> {
|
||||||
// has also retained it.
|
// has also retained it.
|
||||||
let x = ShareId::from_ptr(index_set);
|
let x = ShareId::from_ptr(index_set);
|
||||||
|
|
||||||
self.objc.with_mut(|obj| {
|
// This is done for a very explicit reason; see the comments on the method itself for
|
||||||
|
// an explanation.
|
||||||
|
self.hack_avoid_dequeue_loop(|obj| {
|
||||||
let _: () = msg_send![obj, insertRowsAtIndexes:&*x withAnimation:animation_options];
|
let _: () = msg_send![obj, insertRowsAtIndexes:&*x withAnimation:animation_options];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -476,7 +587,7 @@ impl<T> ListView<T> {
|
||||||
|
|
||||||
/// Reload the rows at the specified indexes.
|
/// Reload the rows at the specified indexes.
|
||||||
pub fn reload_rows(&self, indexes: &[usize]) {
|
pub fn reload_rows(&self, indexes: &[usize]) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
|
@ -503,7 +614,7 @@ impl<T> ListView<T> {
|
||||||
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
|
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
|
||||||
/// optimize things accordingly.
|
/// optimize things accordingly.
|
||||||
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
|
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
||||||
|
|
||||||
|
@ -537,7 +648,7 @@ impl<T> ListView<T> {
|
||||||
///
|
///
|
||||||
/// It can make some scrolling situations much smoother.
|
/// It can make some scrolling situations much smoother.
|
||||||
pub fn set_uses_automatic_row_heights(&self, uses: bool) {
|
pub fn set_uses_automatic_row_heights(&self, uses: bool) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setUsesAutomaticRowHeights:match uses {
|
let _: () = msg_send![obj, setUsesAutomaticRowHeights:match uses {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -546,11 +657,11 @@ impl<T> ListView<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// On macOS, this will instruct the underlying NSTableView to alternate
|
/// In AppKit, this will instruct the underlying NSTableView to alternate
|
||||||
/// background colors automatically. If you set this, you possibly want
|
/// background colors automatically. If you set this, you possibly want
|
||||||
/// to hard-set a row height as well.
|
/// to hard-set a row height as well.
|
||||||
pub fn set_uses_alternating_backgrounds(&self, uses: bool) {
|
pub fn set_uses_alternating_backgrounds(&self, uses: bool) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setUsesAlternatingRowBackgroundColors:match uses {
|
let _: () = msg_send![obj, setUsesAlternatingRowBackgroundColors:match uses {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -561,7 +672,7 @@ impl<T> ListView<T> {
|
||||||
|
|
||||||
/// End actions for a row. API subject to change.
|
/// End actions for a row. API subject to change.
|
||||||
pub fn set_row_actions_visible(&self, visible: bool) {
|
pub fn set_row_actions_visible(&self, visible: bool) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.objc.with_mut(|obj| unsafe {
|
self.objc.with_mut(|obj| unsafe {
|
||||||
let _: () = msg_send![obj, setRowActionsVisible:match visible {
|
let _: () = msg_send![obj, setRowActionsVisible:match visible {
|
||||||
true => YES,
|
true => YES,
|
||||||
|
@ -570,6 +681,15 @@ impl<T> ListView<T> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes this table view the first responder.
|
||||||
|
#[cfg(feature = "appkit")]
|
||||||
|
pub fn make_first_responder(&self) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let window: id = msg_send![&*obj, window];
|
||||||
|
let _: () = msg_send![window, makeFirstResponder:&*obj];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
|
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
|
||||||
/// calls yourself, but often easier to implement.
|
/// calls yourself, but often easier to implement.
|
||||||
///
|
///
|
||||||
|
@ -586,7 +706,7 @@ impl<T> ListView<T> {
|
||||||
self.objc.get(|obj| unsafe { msg_send![obj, selectedRow] })
|
self.objc.get(|obj| unsafe { msg_send![obj, selectedRow] })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the currently clicked row. This is macOS-specific, and is generally used in context
|
/// Returns the currently clicked row. This is AppKit-specific, and is generally used in context
|
||||||
/// menu generation to determine what item the context menu should be for. If the clicked area
|
/// menu generation to determine what item the context menu should be for. If the clicked area
|
||||||
/// is not an actual row, this will return `-1`.
|
/// is not an actual row, this will return `-1`.
|
||||||
///
|
///
|
||||||
|
@ -612,25 +732,27 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Layout for ListView<T> {
|
impl<T> ObjcAccess for ListView<T> {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
// On macOS, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
// In AppKit, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
||||||
// what to do normally.
|
// what to do normally.
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.scrollview.objc.with_mut(handler);
|
self.scrollview.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
// On macOS, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
// In AppKit, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
||||||
// what to do normally.
|
// what to do normally.
|
||||||
//
|
//
|
||||||
// @TODO: Review this, as property access isn't really used in the same place as layout
|
// @TODO: Review this, as property access isn't really used in the same place as layout
|
||||||
// stuff... hmm...
|
// stuff... hmm...
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
self.scrollview.objc.get(handler)
|
self.scrollview.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Layout for ListView<T> {}
|
||||||
|
|
||||||
impl<T> Drop for ListView<T> {
|
impl<T> Drop for ListView<T> {
|
||||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
||||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||||
|
|
|
@ -51,32 +51,38 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layer::Layer;
|
use crate::layer::Layer;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::Layout;
|
||||||
use crate::view::ViewDelegate;
|
use crate::objc_access::ObjcAccess;
|
||||||
|
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "autolayout")]
|
||||||
mod macos;
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension, SafeAreaLayoutGuide};
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
use macos::{register_listview_row_class, register_listview_row_class_with_delegate};
|
mod appkit;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "appkit")]
|
||||||
mod ios;
|
use appkit::{register_listview_row_class, register_listview_row_class_with_delegate};
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
//#[cfg(feature = "uikit")]
|
||||||
use ios::{register_listview_row_view_class, register_listview_row_class_with_delegate};
|
//mod ios;
|
||||||
|
|
||||||
pub(crate) static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
//#[cfg(feature = "uikit")]
|
||||||
pub(crate) static LISTVIEW_ROW_DELEGATE_PTR: &str = "rstListViewRowDelegatePtr";
|
//use ios::{register_listview_row_view_class, register_listview_row_class_with_delegate};
|
||||||
|
|
||||||
|
pub(crate) static BACKGROUND_COLOR: &str = "cacaoBackgroundColor";
|
||||||
|
pub(crate) static LISTVIEW_ROW_DELEGATE_PTR: &str = "cacaoListViewRowDelegatePtr";
|
||||||
|
|
||||||
/// A helper method for instantiating view classes and applying default settings to them.
|
/// A helper method for instantiating view classes and applying default settings to them.
|
||||||
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
let view: id = msg_send![registration_fn(), new];
|
let view: id = msg_send![registration_fn(), new];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -88,40 +94,57 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
/// side anyway.
|
/// side anyway.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ListViewRow<T = ()> {
|
pub struct ListViewRow<T = ()> {
|
||||||
|
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||||
|
pub animator: ViewAnimatorProxy,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
/// A pointer to the delegate for this view.
|
/// A pointer to the delegate for this view.
|
||||||
pub delegate: Option<Box<T>>,
|
pub delegate: Option<Box<T>>,
|
||||||
|
|
||||||
|
/// A safe layout guide property.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
pub safe_layout_guide: SafeAreaLayoutGuide,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,17 +161,41 @@ impl ListViewRow {
|
||||||
|
|
||||||
ListViewRow {
|
ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(view),
|
|
||||||
left: LayoutAnchorX::left(view),
|
|
||||||
leading: LayoutAnchorX::leading(view),
|
|
||||||
right: LayoutAnchorX::right(view),
|
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
|
||||||
width: LayoutAnchorDimension::width(view),
|
|
||||||
height: LayoutAnchorDimension::height(view),
|
|
||||||
center_x: LayoutAnchorX::center(view),
|
|
||||||
center_y: LayoutAnchorY::center(view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||||
|
|
||||||
|
#[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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,17 +222,41 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
||||||
|
|
||||||
let view = ListViewRow {
|
let view = ListViewRow {
|
||||||
delegate: Some(delegate),
|
delegate: Some(delegate),
|
||||||
top: LayoutAnchorY::top(view),
|
|
||||||
left: LayoutAnchorX::left(view),
|
|
||||||
leading: LayoutAnchorX::leading(view),
|
|
||||||
right: LayoutAnchorX::right(view),
|
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
|
||||||
width: LayoutAnchorDimension::width(view),
|
|
||||||
height: LayoutAnchorDimension::height(view),
|
|
||||||
center_x: LayoutAnchorX::center(view),
|
|
||||||
center_y: LayoutAnchorY::center(view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||||
|
|
||||||
|
#[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),
|
||||||
};
|
};
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -201,25 +272,47 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
||||||
pub fn with_boxed(mut delegate: Box<T>) -> ListViewRow<T> {
|
pub fn with_boxed(mut delegate: Box<T>) -> ListViewRow<T> {
|
||||||
let view = allocate_view(register_listview_row_class_with_delegate::<T>);
|
let view = allocate_view(register_listview_row_class_with_delegate::<T>);
|
||||||
unsafe {
|
unsafe {
|
||||||
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
|
||||||
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
||||||
let ptr: *const T = &*delegate;
|
let ptr: *const T = &*delegate;
|
||||||
(&mut *view).set_ivar(LISTVIEW_ROW_DELEGATE_PTR, ptr as usize);
|
(&mut *view).set_ivar(LISTVIEW_ROW_DELEGATE_PTR, ptr as usize);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut view = ListViewRow {
|
let mut view = ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(view),
|
|
||||||
left: LayoutAnchorX::left(view),
|
|
||||||
leading: LayoutAnchorX::leading(view),
|
|
||||||
right: LayoutAnchorX::right(view),
|
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
|
||||||
width: LayoutAnchorDimension::width(view),
|
|
||||||
height: LayoutAnchorDimension::height(view),
|
|
||||||
center_x: LayoutAnchorX::center(view),
|
|
||||||
center_y: LayoutAnchorY::center(view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
|
animator: ViewAnimatorProxy::new(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||||
|
|
||||||
|
#[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),
|
||||||
};
|
};
|
||||||
|
|
||||||
(&mut delegate).did_load(view.clone_as_handle());
|
(&mut delegate).did_load(view.clone_as_handle());
|
||||||
|
@ -237,17 +330,41 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
||||||
|
|
||||||
ListViewRow {
|
ListViewRow {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
|
objc: self.objc.clone(),
|
||||||
|
animator: self.animator.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
safe_layout_guide: self.safe_layout_guide.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: self.top.clone(),
|
top: self.top.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: self.leading.clone(),
|
leading: self.leading.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: self.left.clone(),
|
left: self.left.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: self.trailing.clone(),
|
trailing: self.trailing.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: self.right.clone(),
|
right: self.right.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: self.bottom.clone(),
|
bottom: self.bottom.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: self.width.clone(),
|
width: self.width.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: self.height.clone(),
|
height: self.height.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: self.center_x.clone(),
|
center_x: self.center_x.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: self.center_y.clone(),
|
center_y: self.center_y.clone(),
|
||||||
objc: self.objc.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,18 +378,42 @@ impl<T> ListViewRow<T> {
|
||||||
crate::view::View {
|
crate::view::View {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
is_handle: true,
|
is_handle: true,
|
||||||
layer: Layer::new(),
|
layer: Layer::new(), // @TODO: Fix & return cloned true layer for this row.
|
||||||
|
objc: self.objc.clone(),
|
||||||
|
animator: self.animator.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
|
safe_layout_guide: self.safe_layout_guide.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: self.top.clone(),
|
top: self.top.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: self.leading.clone(),
|
leading: self.leading.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: self.left.clone(),
|
left: self.left.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: self.trailing.clone(),
|
trailing: self.trailing.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: self.right.clone(),
|
right: self.right.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: self.bottom.clone(),
|
bottom: self.bottom.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: self.width.clone(),
|
width: self.width.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: self.height.clone(),
|
height: self.height.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: self.center_x.clone(),
|
center_x: self.center_x.clone(),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: self.center_y.clone(),
|
center_y: self.center_y.clone(),
|
||||||
objc: self.objc.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,16 +436,18 @@ impl<T> ListViewRow<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Layout for ListViewRow<T> {
|
impl<T> ObjcAccess for ListViewRow<T> {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Layout for ListViewRow<T> {}
|
||||||
|
|
||||||
impl<T> Drop for ListViewRow<T> {
|
impl<T> Drop for ListViewRow<T> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Various traits used for Views.
|
//! Various traits used for Views.
|
||||||
|
|
||||||
use crate::macos::menu::MenuItem;
|
use crate::appkit::menu::MenuItem;
|
||||||
use crate::dragdrop::{DragInfo, DragOperation};
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
|
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
|
||||||
use crate::layout::Layout;
|
use crate::layout::Layout;
|
||||||
|
@ -36,8 +36,9 @@ pub trait ListViewDelegate {
|
||||||
/// had time to sit down and figure them out properly yet.
|
/// had time to sit down and figure them out properly yet.
|
||||||
fn item_for(&self, row: usize) -> ListViewRow;
|
fn item_for(&self, row: usize) -> ListViewRow;
|
||||||
|
|
||||||
/// Called when an item has been selected (clicked/tapped on).
|
/// Called when an item has been selected (clicked/tapped on). If the selection was cleared,
|
||||||
fn item_selected(&self, row: usize) {}
|
/// then this will be called with `None`.
|
||||||
|
fn item_selected(&self, row: Option<usize>) {}
|
||||||
|
|
||||||
/// Called when the menu for the tableview is about to be shown. You can update the menu here
|
/// Called when the menu for the tableview is about to be shown. You can update the menu here
|
||||||
/// depending on, say, what the user has context-clicked on. You should avoid any expensive
|
/// depending on, say, what the user has context-clicked on. You should avoid any expensive
|
||||||
|
|
23
src/objc_access.rs
Normal file
23
src/objc_access.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//! Implements a parent trait for the various sub-traits we use throughout Cacao. The methods
|
||||||
|
//! defined on here provide access handlers for common properties that the sub-traits need to
|
||||||
|
//! enable modifying.
|
||||||
|
|
||||||
|
use objc::runtime::Object;
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
|
||||||
|
/// Types that implement this should provide access to their underlying root node type (e.g, the
|
||||||
|
/// view or control). Traits that have this as their super-trait can rely on this to ensure access
|
||||||
|
/// without needing to derive or do extra work elsewhere.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait ObjcAccess {
|
||||||
|
/// Used for mutably interacting with the underlying Objective-C instance.
|
||||||
|
/// Setters should use this.
|
||||||
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F);
|
||||||
|
|
||||||
|
/// Used for checking backing properties of the underlying Objective-C instance, without
|
||||||
|
/// needing a mutable borrow.
|
||||||
|
///
|
||||||
|
/// Getters should use this.
|
||||||
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R;
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NSString, NSArray};
|
use crate::foundation::{id, nil, NSString, NSArray, NSURL};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
|
@ -93,7 +93,7 @@ impl Pasteboard {
|
||||||
/// _Note that this method returns a list of `Url` entities, in an attempt to be closer to how
|
/// _Note that this method returns a list of `Url` entities, in an attempt to be closer to how
|
||||||
/// Cocoa & co operate. This method may go away in the future if it's determined that people
|
/// Cocoa & co operate. This method may go away in the future if it's determined that people
|
||||||
/// wind up just using `get_file_paths()`._
|
/// wind up just using `get_file_paths()`._
|
||||||
pub fn get_file_urls(&self) -> Result<Vec<Url>, Box<dyn std::error::Error>> {
|
pub fn get_file_urls(&self) -> Result<Vec<NSURL>, Box<dyn std::error::Error>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let class: id = msg_send![class!(NSURL), class];
|
let class: id = msg_send![class!(NSURL), class];
|
||||||
let classes = NSArray::new(&[class]);
|
let classes = NSArray::new(&[class]);
|
||||||
|
@ -113,40 +113,7 @@ impl Pasteboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
let urls = NSArray::retain(contents).map(|url| {
|
let urls = NSArray::retain(contents).map(|url| {
|
||||||
let path = NSString::retain(msg_send![url, path]);
|
NSURL::retain(url)
|
||||||
Url::parse(&format!("file://{}", path.to_str()))
|
|
||||||
}).into_iter().filter_map(|r| r.ok()).collect();
|
|
||||||
|
|
||||||
Ok(urls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Looks inside the pasteboard contents and extracts what FileURLs are there, if any.
|
|
||||||
///
|
|
||||||
/// Note that this method operates on file paths, as opposed to URLs, and returns a list of
|
|
||||||
/// results in a format more Rust-y.
|
|
||||||
pub fn get_file_paths(&self) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
|
|
||||||
unsafe {
|
|
||||||
let class: id = msg_send![class!(NSURL), class];
|
|
||||||
let classes = NSArray::new(&[class]);
|
|
||||||
let contents: id = msg_send![&*self.0, readObjectsForClasses:classes options:nil];
|
|
||||||
|
|
||||||
// This can happen if the Pasteboard server has an error in returning items.
|
|
||||||
// In our case, we'll bubble up an error by checking the pasteboard.
|
|
||||||
if contents == nil {
|
|
||||||
// This error is not necessarily "correct", but in the event of an error in
|
|
||||||
// Pasteboard server retrieval I'm not sure where to check... and this stuff is
|
|
||||||
// kinda ancient and has conflicting docs in places. ;P
|
|
||||||
return Err(Box::new(Error {
|
|
||||||
code: 666,
|
|
||||||
domain: "com.cacao-rs.pasteboard".to_string(),
|
|
||||||
description: "Pasteboard server returned no data.".to_string()
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let urls = NSArray::retain(contents).map(|url| {
|
|
||||||
let path = NSString::retain(msg_send![url, path]).to_str().to_string();
|
|
||||||
PathBuf::from(path)
|
|
||||||
}).into_iter().collect();
|
}).into_iter().collect();
|
||||||
|
|
||||||
Ok(urls)
|
Ok(urls)
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
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, YES, NO, NSUInteger};
|
|
||||||
use crate::dragdrop::DragInfo;
|
|
||||||
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
|
||||||
use crate::utils::load;
|
|
||||||
|
|
||||||
/// Injects an `NSView` 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!(UIView);
|
|
||||||
let mut decl = ClassDecl::new("RSTView", 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_view_class_with_delegate<T: ViewDelegate>() -> *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("RSTViewWithDelegate", superclass).unwrap();
|
|
||||||
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
|
||||||
VIEW_CLASS = decl.register();
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
VIEW_CLASS
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! A progress indicator widget.
|
//! A progress indicator widget.
|
||||||
//!
|
//!
|
||||||
//! This control wraps `NSProgressIndicator` on macOS, and
|
//! This control wraps `NSProgressIndicator` in AppKit, and
|
||||||
//! `UIProgressView+UIActivityIndicatorView` on iOS and tvOS. It operates in two modes: determinate
|
//! `UIProgressView+UIActivityIndicatorView` in iOS/tvOS. It operates in two modes: determinate
|
||||||
//! (where you have a fixed start and end) and indeterminate (infinite; it will go and go until you
|
//! (where you have a fixed start and end) and indeterminate (infinite; it will go and go until you
|
||||||
//! tell it to stop).
|
//! tell it to stop).
|
||||||
//!
|
//!
|
||||||
|
@ -19,14 +19,12 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSUInteger};
|
use crate::foundation::{id, nil, YES, NO, NSUInteger};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::Layout;
|
||||||
|
use crate::objc_access::ObjcAccess;
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(feature = "autolayout")]
|
||||||
mod ios;
|
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
use ios::register_progress_indicator_class;
|
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::ProgressIndicatorStyle;
|
pub use enums::ProgressIndicatorStyle;
|
||||||
|
@ -38,33 +36,43 @@ pub struct ProgressIndicator {
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub top: LayoutAnchorY,
|
pub top: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub leading: LayoutAnchorX,
|
pub leading: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime left layout constraint.
|
/// A pointer to the Objective-C runtime left layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub left: LayoutAnchorX,
|
pub left: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub trailing: LayoutAnchorX,
|
pub trailing: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime right layout constraint.
|
/// A pointer to the Objective-C runtime right layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub right: LayoutAnchorX,
|
pub right: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub bottom: LayoutAnchorY,
|
pub bottom: LayoutAnchorY,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime width layout constraint.
|
/// A pointer to the Objective-C runtime width layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub width: LayoutAnchorDimension,
|
pub width: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime height layout constraint.
|
/// A pointer to the Objective-C runtime height layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub height: LayoutAnchorDimension,
|
pub height: LayoutAnchorDimension,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_x: LayoutAnchorX,
|
pub center_x: LayoutAnchorX,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,27 +87,49 @@ impl ProgressIndicator {
|
||||||
/// need it to stay around.
|
/// need it to stay around.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let view = unsafe {
|
let view = unsafe {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let view: id = msg_send![class!(NSProgressIndicator), new];
|
let view: id = msg_send![class!(NSProgressIndicator), new];
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "appkit")]
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
};
|
};
|
||||||
|
|
||||||
ProgressIndicator {
|
ProgressIndicator {
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
top: LayoutAnchorY::top(view),
|
top: LayoutAnchorY::top(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
left: LayoutAnchorX::left(view),
|
left: LayoutAnchorX::left(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
leading: LayoutAnchorX::leading(view),
|
leading: LayoutAnchorX::leading(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
right: LayoutAnchorX::right(view),
|
right: LayoutAnchorX::right(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
trailing: LayoutAnchorX::trailing(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
bottom: LayoutAnchorY::bottom(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
width: LayoutAnchorDimension::width(view),
|
width: LayoutAnchorDimension::width(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
height: LayoutAnchorDimension::height(view),
|
height: LayoutAnchorDimension::height(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_x: LayoutAnchorX::center(view),
|
center_x: LayoutAnchorX::center(view),
|
||||||
|
|
||||||
|
#[cfg(feature = "autolayout")]
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,16 +202,18 @@ impl ProgressIndicator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ProgressIndicator {
|
impl ObjcAccess for ProgressIndicator {
|
||||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||||
self.objc.with_mut(handler);
|
self.objc.with_mut(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||||
self.objc.get(handler)
|
self.objc.get(handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Layout for ProgressIndicator {}
|
||||||
|
|
||||||
impl Drop for ProgressIndicator {
|
impl Drop for ProgressIndicator {
|
||||||
/// A bit of extra cleanup for delegate callback pointers.
|
/// A bit of extra cleanup for delegate callback pointers.
|
||||||
/// If the originating `ProgressIndicator` is being
|
/// If the originating `ProgressIndicator` is being
|
||||||
|
|
|
@ -83,7 +83,7 @@ impl Default for ThumbnailConfig {
|
||||||
|
|
||||||
// #TODO: Should query the current screen size maybe? 2x is fairly safe
|
// #TODO: Should query the current screen size maybe? 2x is fairly safe
|
||||||
// for most moderns Macs right now.
|
// for most moderns Macs right now.
|
||||||
scale: 2.,
|
scale: 1.,
|
||||||
|
|
||||||
minimum_dimension: 0.,
|
minimum_dimension: 0.,
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use objc::runtime::{Object};
|
use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
|
@ -17,13 +17,19 @@ pub use config::{ThumbnailConfig, ThumbnailQuality};
|
||||||
pub struct ThumbnailGenerator(pub ShareId<Object>);
|
pub struct ThumbnailGenerator(pub ShareId<Object>);
|
||||||
|
|
||||||
impl ThumbnailGenerator {
|
impl ThumbnailGenerator {
|
||||||
|
/// Returns the global shared, wrapped, QLThumbnailGenerator.
|
||||||
pub fn shared() -> Self {
|
pub fn shared() -> Self {
|
||||||
ThumbnailGenerator(unsafe {
|
ThumbnailGenerator(unsafe {
|
||||||
ShareId::from_ptr(msg_send![class!(QLThumbnailGenerator), sharedGenerator])
|
ShareId::from_ptr(msg_send![class!(QLThumbnailGenerator), sharedGenerator])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate<F>(&self, path: &Path, config: ThumbnailConfig, callback: F)
|
/// Given a path and config, will generate a preview image, calling back on the provided
|
||||||
|
/// callback closure.
|
||||||
|
///
|
||||||
|
/// Note that this callback can come back on a separate thread, so react accordingly to get to
|
||||||
|
/// the main thread if you need to.
|
||||||
|
pub fn generate_from_path<F>(&self, path: &Path, config: ThumbnailConfig, callback: F)
|
||||||
where
|
where
|
||||||
F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static
|
F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue