Skip to content

Commit

Permalink
Merge pull request #48 from simlay/simlay/add-image-support-and-simpl…
Browse files Browse the repository at this point in the history
…e-unit-test

Add image support for uikit feature backends
  • Loading branch information
ryanmcgrath committed Aug 22, 2022
2 parents ca676b4 + 3425f83 commit 6b69c1d
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 49 deletions.
52 changes: 46 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,49 @@ jobs:
with:
command: build
args: --features webview --example webview_custom_protocol
# fails because it needs uikit, which is not
# building for me
#- uses: actions-rs/cargo@v1
# with:
# command: build
# args: --example ios-beta
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: x86_64-apple-ios
# Since it's all Objective-C message passing under the hood, we're
# really just looking for whether we've broken the iOS build. It is likely
# that more robust tests/checking infrastructure should exist for this side
# of things as the iOS portion gets iterated on.
#
# (e.g, this at the moment will not catch invalid selector calls, like if an appkit-specific
# selector is used for something on iOS)
- uses: actions-rs/cargo@v1
with:
command: build
args: --target x86_64-apple-ios --example ios-beta --no-default-features --features uikit,autolayout

ios:
name: Check that iOS tests pass via dinghy.
runs-on: macos-latest
steps:

- name: Install cargo-dinghy
uses: baptiste0928/cargo-install@v1
with:
crate: cargo-dinghy

- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: x86_64-apple-ios

- name: Launch XCode Simulator and prepare Dinghy
run: |
# Get system info
xcrun simctl list runtimes
# Launch the simulator
RUNTIME_ID=$(xcrun simctl list runtimes | grep iOS | cut -d ' ' -f 7 | tail -1)
SIM_ID=$(xcrun simctl create My-iphone-se com.apple.CoreSimulator.SimDeviceType.iPhone-SE $RUNTIME_ID)
xcrun simctl boot $SIM_ID
- name: Dinghy test
run: |
cargo dinghy --platform auto-ios-x86_64 test --no-default-features --features uikit,autolayout
34 changes: 34 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,37 @@ required-features = ["webview"]
[[example]]
name = "ios-beta"
required-features = ["uikit", "autolayout"]

[[example]]
name = "calculator"
required-features = ["appkit"]
[[example]]
name = "todos_list"
required-features = ["appkit"]
[[example]]
name = "animation"
required-features = ["appkit"]
[[example]]
name = "autolayout"
required-features = ["appkit"]
[[example]]
name = "custom_image_drawing"
required-features = ["appkit"]
[[example]]
name = "text_input"
required-features = ["appkit"]
[[example]]
name = "defaults"
required-features = ["appkit"]
[[example]]
name = "frame_layout"
required-features = ["appkit"]
[[example]]
name = "window"
required-features = ["appkit"]
[[example]]
name = "window_delegate"
required-features = ["appkit"]
[[example]]
name = "window_controller"
required-features = ["appkit"]
9 changes: 8 additions & 1 deletion examples/ios-beta/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::RwLock;
use cacao::uikit::{App, AppDelegate, Scene, SceneConfig, SceneConnectionOptions, SceneSession, Window, WindowSceneDelegate};

use cacao::color::Color;
use cacao::image::{Image, ImageView};
use cacao::layout::{Layout, LayoutConstraint};
use cacao::view::{View, ViewController, ViewDelegate};

Expand All @@ -19,7 +20,8 @@ impl AppDelegate for TestApp {
pub struct RootView {
pub red: View,
pub green: View,
pub blue: View
pub blue: View,
pub image: ImageView
}

impl ViewDelegate for RootView {
Expand All @@ -36,6 +38,11 @@ impl ViewDelegate for RootView {
self.blue.set_background_color(Color::SystemBlue);
view.add_subview(&self.blue);

let image_bytes = include_bytes!("../../test-data/favicon.ico");
self.image = ImageView::new();
self.image.set_image(&Image::with_data(image_bytes));
view.add_subview(&self.image);

LayoutConstraint::activate(&[
self.red.top.constraint_equal_to(&view.top).offset(16.),
self.red.leading.constraint_equal_to(&view.leading).offset(16.),
Expand Down
2 changes: 1 addition & 1 deletion examples/ios-beta/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Since this needs to run in an iOS simulator or on a device, you can't run it lik
- Start a simulator (Simulator.app).
- `cargo install cargo-bundle`
- `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-beta.app`
- `xcrun simctl install booted target/x86_64-apple-ios/debug/examples/bundle/ios/ios-beta.app`
- `xcrun simctl launch --console booted com.cacao.ios-test`

## Current Support
Expand Down
37 changes: 29 additions & 8 deletions src/image/image.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use objc::runtime::Object;
use objc::runtime::{Class, Object};
use objc_id::ShareId;

use objc::{class, msg_send, sel, sel_impl};
Expand Down Expand Up @@ -122,6 +122,15 @@ pub struct DrawConfig {
pub struct Image(pub ShareId<Object>);

impl Image {
fn class() -> &'static Class {
#[cfg(feature = "appkit")]
let class = class!(NSImage);
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
let class = class!(UIImage);

class
}

/// Wraps a system-returned image, e.g from QuickLook previews.
pub fn with(image: id) -> Self {
Image(unsafe { ShareId::from_ptr(image) })
Expand All @@ -132,7 +141,7 @@ impl Image {
let file_path = NSString::new(path);

Image(unsafe {
let alloc: id = msg_send![class!(NSImage), alloc];
let alloc: id = msg_send![Self::class(), alloc];
ShareId::from_ptr(msg_send![alloc, initWithContentsOfFile: file_path])
})
}
Expand All @@ -143,7 +152,7 @@ impl Image {
let data = NSData::with_slice(data);

Image(unsafe {
let alloc: id = msg_send![class!(NSImage), alloc];
let alloc: id = msg_send![Self::class(), alloc];
ShareId::from_ptr(msg_send![alloc, initWithData: data])
})
}
Expand All @@ -157,7 +166,7 @@ impl Image {
Image(unsafe {
ShareId::from_ptr({
let icon = icon.to_id();
msg_send![class!(NSImage), imageNamed: icon]
msg_send![Self::class(), imageNamed: icon]
})
})
}
Expand All @@ -179,13 +188,13 @@ impl Image {
true => {
let icon = NSString::new(icon.to_sfsymbol_str());
let desc = NSString::new(accessibility_description);
msg_send![class!(NSImage), imageWithSystemSymbolName:&*icon
msg_send![Self::class(), imageWithSystemSymbolName:&*icon
accessibilityDescription:&*desc]
},

false => {
let icon = icon.to_id();
msg_send![class!(NSImage), imageNamed: icon]
msg_send![Self::class(), imageNamed: icon]
}
})
})
Expand Down Expand Up @@ -213,7 +222,7 @@ impl Image {
true => {
let icon = NSString::new(symbol.to_str());
let desc = NSString::new(accessibility_description);
msg_send![class!(NSImage), imageWithSystemSymbolName:&*icon
msg_send![Self::class(), imageWithSystemSymbolName:&*icon
accessibilityDescription:&*desc]
},

Expand Down Expand Up @@ -267,7 +276,7 @@ impl Image {
let block = block.copy();

Image(unsafe {
let img: id = msg_send![class!(NSImage), imageWithSize:target_frame.size
let img: id = msg_send![Self::class(), imageWithSize:target_frame.size
flipped:YES
drawingHandler:block
];
Expand All @@ -276,3 +285,15 @@ impl Image {
})
}
}

#[test]
fn test_image_from_bytes() {
let image_bytes = include_bytes!("../../test-data/favicon.ico");
let image = Image::with_data(image_bytes);
}
// It's unclear where the file is on the ios simulator.
#[test]
#[cfg(target_os = "macos")]
fn test_image_from_file() {
let image = Image::with_contents_of_file("./test-data/favicon.ico");
}
17 changes: 13 additions & 4 deletions src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ mod appkit;
#[cfg(feature = "appkit")]
use appkit::register_image_view_class;

//#[cfg(feature = "uikit")]
//mod uikit;
#[cfg(feature = "uikit")]
mod uikit;

//#[cfg(feature = "uikit")]
//use uikit::register_image_view_class;
#[cfg(all(feature = "uikit", not(feature = "appkit")))]
use uikit::register_image_view_class;

mod image;
pub use image::{DrawConfig, Image, ResizeBehavior};
Expand Down Expand Up @@ -194,3 +194,12 @@ impl Drop for ImageView {
}*/
}
}

#[test]
fn test_image() {
let image_view = ImageView::new();
image_view.set_background_color(Color::SystemBlue);
let image_bytes = include_bytes!("../../test-data/favicon.ico");
let image = Image::with_data(image_bytes);
image_view.set_image(&image);
}
29 changes: 5 additions & 24 deletions src/image/uikit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,22 @@ 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::foundation::{id, NSUInteger, NO, YES};
use crate::utils::load;
use crate::view::{ViewDelegate, VIEW_DELEGATE_PTR};

/// 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 {
pub(crate) fn register_image_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();
let superclass = class!(UIImageView);
let mut decl = ClassDecl::new("RSTImageView", superclass).expect("Failed to get RSTVIEW");
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
}
}
13 changes: 9 additions & 4 deletions src/layout/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use objc_id::ShareId;

use crate::foundation::{id, NO, YES};

#[cfg(all(feature = "appkit", target_os = "macos"))]
use super::LayoutConstraintAnimatorProxy;

/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
Expand All @@ -31,14 +32,18 @@ pub struct LayoutConstraint {
pub priority: f64,

/// An animator proxy that can be used inside animation contexts.
/// This is currently only supported on macOS with the `appkit` feature.
#[cfg(all(feature = "appkit", target_os = "macos"))]
pub animator: LayoutConstraintAnimatorProxy
}

impl LayoutConstraint {
/// An internal method for wrapping existing constraints.
pub(crate) fn new(object: id) -> Self {
LayoutConstraint {
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: LayoutConstraintAnimatorProxy::new(object),

constraint: unsafe { ShareId::from_ptr(object) },
offset: 0.0,
multiplier: 0.0,
Expand All @@ -55,7 +60,9 @@ impl LayoutConstraint {
}

LayoutConstraint {
#[cfg(all(feature = "appkit", target_os = "macos"))]
animator: self.animator,

constraint: self.constraint,
offset: offset,
multiplier: self.multiplier,
Expand Down Expand Up @@ -94,18 +101,16 @@ impl LayoutConstraint {
//
// I regret nothing, lol. If you have a better solution I'm all ears.
pub fn activate(constraints: &[LayoutConstraint]) {
let ids: Vec<&Object> = constraints.into_iter().map(|constraint| &*constraint.constraint).collect();
unsafe {
let ids: Vec<&Object> = constraints.into_iter().map(|constraint| &*constraint.constraint).collect();

let constraints: id = msg_send![class!(NSArray), arrayWithObjects:ids.as_ptr() count:ids.len()];
let _: () = msg_send![class!(NSLayoutConstraint), activateConstraints: constraints];
}
}

pub fn deactivate(constraints: &[LayoutConstraint]) {
let ids: Vec<&Object> = constraints.into_iter().map(|constraint| &*constraint.constraint).collect();
unsafe {
let ids: Vec<&Object> = constraints.into_iter().map(|constraint| &*constraint.constraint).collect();

let constraints: id = msg_send![class!(NSArray), arrayWithObjects:ids.as_ptr() count:ids.len()];
let _: () = msg_send![class!(NSLayoutConstraint), deactivateConstraints: constraints];
}
Expand Down
3 changes: 3 additions & 0 deletions src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
mod traits;
pub use traits::Layout;

#[cfg(all(feature = "appkit", target_os = "macos"))]
mod animator;

#[cfg(all(feature = "appkit", target_os = "macos"))]
pub use animator::LayoutConstraintAnimatorProxy;

#[cfg(feature = "autolayout")]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub mod filesystem;
pub mod foundation;
pub mod geometry;

#[cfg(feature = "appkit")]
#[cfg(any(feature = "appkit", feature = "uikit"))]
pub mod image;

#[cfg(feature = "appkit")]
Expand Down
Loading

0 comments on commit 6b69c1d

Please sign in to comment.