Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image support for uikit feature backends #48

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm back and forth on whether this one should disable autolayout - it's feature flagged for platforms that might not have that, but can still do frame-based layout. I think if autolayout is disabled it may also break the example at the moment, so I can PR this one after this is merged.

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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that this is the way the iOS simulator app looks but it does show that it graphically works.

image

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha, I can write up some autolayout hell for this to make it look "nicer" once this is merged. I'd really like to get labels and listview support in, so it'd be cool to get a true "kitchen sink" iOS demo at some point here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It'd be nice if it could be use similar to the other views of LayoutConstraint::activate. I agree that it should be out of the scope of this PR though.


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