Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ryanmcgrath/cacao
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 437c539ebf339774974cecf0b93aa3c3b02c833e
Choose a base ref
..
head repository: ryanmcgrath/cacao
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 87fe0e3e122d2e7a290c3386b90264c3ddc5fb58
Choose a head ref
20 changes: 20 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: 2
updates:

- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every weekday
interval: "daily"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"] # ignore patch updates

# Maintain dependencies for Cargo
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-patch"] # ignore patch updates
87 changes: 85 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -2,15 +2,15 @@ name: CI

on:
push:
branches: [master]
branches: [master, trunk]
pull_request:

jobs:
fmt:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
@@ -21,3 +21,86 @@ jobs:
with:
command: fmt
args: -- --check
test:
name: Check that examples build
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
build:
name: Check that the code builds
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: build
examples:
name: Check that examples build
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: build
args: --features webview --example webview_custom_protocol
- 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@v3
- 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
54 changes: 50 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cacao"
description = "Rust bindings for AppKit (macOS/Airyx/GNUStep, beta) and UIKit (iOS/tvOS, alpha)."
version = "0.3.0"
version = "0.3.2"
edition = "2018"
authors = ["Ryan McGrath <ryan@rymc.io>"]
build = "build.rs"
@@ -23,15 +23,15 @@ objc = { version = "=0.3.0-beta.1", package = "objc2" }
block = { version = "=0.2.0-alpha.5", package = "block2" }
# Temporary: Patched versions that implement `Encode` for common types
# Branch: `objc2`
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c506e7d70da010c0070048e9471ff0b441506e65", features = ["with-chrono"] }
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c506e7d70da010c0070048e9471ff0b441506e65" }
core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c506e7d70da010c0070048e9471ff0b441506e65" }
dispatch = "0.2.0"
infer = { version = "0.4", optional = true }
infer = { version = "0.9", optional = true }
lazy_static = "1.4.0"
libc = "0.2"
os_info = "3.0.1"
url = "2.1.1"
uuid = { version = "0.8", features = ["v4"], optional = true }
uuid = { version = "1.1", features = ["v4"], optional = true }

[dev-dependencies]
eval = "0.4"
@@ -54,3 +54,49 @@ identifier = "com.cacao.ios-test"
category = "Developer Tool"
short_description = "An example Cacao iOS app."
long_description = "An example Cacao iOS app."

[[example]]
name = "webview_custom_protocol"
required-features = ["webview"]

[[example]]
name = "browser"
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"]
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -83,7 +83,7 @@ Non-Apple platforms that shim or provide a form of AppKit may be able to use a g
| ListView | Reusable list w/ cached rows ||||
| Button | Styling, events, toolbar support ||||
| Label/TextField | Text rendering & input ||||
| Image/ImageView | Loading, drawing, etc || ||
| Image/ImageView | Loading, drawing, etc || ||
| Toolbar | Basic native toolbar ||||
| SplitViewController | Split views (Big Sur friendly) ||||
| WebView | Wrapper for WKWebView ||||
@@ -108,17 +108,17 @@ The following are a list of [Cargo features][cargo-features] that can be enabled
[cargo-features]: https://doc.rust-lang.org/stable/cargo/reference/manifest.html#the-features-section

## General Notes
**Why not extend the existing cocoa-rs crate?**
**Why not extend the existing cocoa-rs crate?**
A good question. At the end of the day, that crate (I believe, and someone can correct me if I'm wrong) is somewhat tied to Servo, and I wanted to experiment with what the best approach for representing the Cocoa UI model in Rust was. This crate doesn't ignore their work entirely, either - `core_foundation` and `core_graphics` are used internally and re-exported for general use.

**Why should I write in Rust, rather than X language?**
**Why should I write in Rust, rather than X language?**
In _my_ case, I want to be able to write native applications for my devices (and the platform I like to build products for) without being locked in to writing in Apple-specific languages... and without writing in C/C++ or JavaScript (note: the _toolchain_, not the language - ES6/Typescript are fine). I want to do this because I'm tired of hitting a mountain of work when I want to port my applications to other ecosystems. I think that Rust offers a (growing, but significant) viable model for sharing code across platforms and ecosystems without sacrificing performance.

_(This is the part where the internet lights up and rants about some combination of Electron, Qt, and so on - we're not bothering here as it's beaten to death elsewhere)_

This crate is useful for people who don't need to go all-in on the Apple ecosystem, but want to port their work there with some relative ease. It's not expected that everyone will suddenly want to rewrite their macOS/iOS/tvOS apps in Rust.

**Isn't Objective-C dead?**
**Isn't Objective-C dead?**
Yes, and no.

It's true that Apple definitely favors Swift, and for good reason (and I say this as an unabashed lover of Objective-C). With that said, I would be surprised if we didn't have another ~5+ years of support; Apple is quick to deprecate, but removing the Objective-C runtime would require a ton of time and effort. Maybe SwiftUI kills it, who knows. A wrapper around this stuff should conceivably make it easier to swap out the underlying UI backend whenever it comes time.
@@ -133,21 +133,21 @@ Some might also decry Objective-C as slow. To that, I'd note the following:

**tl;dr** it's probably fine, and you have Rust for your performance needs.

**Why not just wrap UIKit, and then rely on Catalyst?**
**Why not just wrap UIKit, and then rely on Catalyst?**
I have yet to see a single application where Catalyst felt good. The goal is good, though, and if it got to a point where that just seemed like the way forward (e.g, Apple just kills AppKit) then it's certainly an option.

**You can't possibly wrap all platform-specific behavior here...**
**You can't possibly wrap all platform-specific behavior here...**
Correct! Each UI control contains a `objc` field, which you can use as an escape hatch - if the control doesn't support something, you're free to drop to the Objective-C runtime yourself and handle it.

**Why don't you use bindings to automatically generate this stuff?**
**Why don't you use bindings to automatically generate this stuff?**
For initial exploration purposes I've done most of this by hand, as I wanted to find an approach that fit well in the Rust model before committing to binding generation. This is something I'll likely focus on next now that I've got things "working" well enough.

**Is this related to Cacao, the Swift project?**
**Is this related to Cacao, the Swift project?**
No. The project referred to in this question aimed to map portions of Cocoa and UIKit over to run on Linux, but hasn't seen activity in some time (it was really cool, too!).

Open source project naming in 2020 is like trying to buy a `.com` domain: everything good is taken. Luckily, multiple projects can share a name... so that's what's going to happen here.

**Isn't this kind of cheating the Rust object model?**
**Isn't this kind of cheating the Rust object model?**
Depends on how you look at it. I personally don't care too much - the GUI layer for these platforms is a hard requirement to support for certain classes of products, and giving them up also means giving up battle-tested tools for things like Accessibility and deeper OS integration. With that said, internally there are efforts to try and make things respect Rust's model of how things should work.

You can think of this as similar to gtk-rs. If you want to support or try a more _pure_ model, go check out Druid or something. :)
7 changes: 4 additions & 3 deletions examples/custom_image_drawing.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
//! This example showcases how to do custom drawing on an ImageView
//! with CoreGraphics. Feel free to modify it and play around!
use cacao::appkit::menu::{Menu, MenuItem};
use cacao::appkit::window::Window;
use cacao::appkit::{App, AppDelegate};

use cacao::color::Color;
use cacao::layout::{Layout, LayoutConstraint};
use cacao::view::View;

use cacao::image::{DrawConfig, Image, ImageView};
use cacao::macos::menu::{Menu, MenuItem};
use cacao::macos::window::Window;
use cacao::macos::{App, AppDelegate};

struct BasicApp {
window: Window,
9 changes: 8 additions & 1 deletion examples/ios-beta/main.rs
Original file line number Diff line number Diff line change
@@ -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};

@@ -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 {
@@ -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.),
2 changes: 1 addition & 1 deletion examples/ios-beta/readme.md
Original file line number Diff line number Diff line change
@@ -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
10 changes: 5 additions & 5 deletions examples/webview_custom_protocol.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! 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::webview::{WebView, WebViewConfig, WebViewDelegate};
use cacao::appkit::menu::{Menu, MenuItem};
use cacao::appkit::toolbar::Toolbar;
use cacao::appkit::window::{Window, WindowConfig, WindowDelegate, WindowToolbarStyle};
use cacao::appkit::{App, AppDelegate};

use cacao::macos::menu::{Menu, MenuItem};
use cacao::macos::toolbar::Toolbar;
use cacao::macos::window::{Window, WindowConfig, WindowDelegate, WindowToolbarStyle};
use cacao::macos::{App, AppDelegate};
use cacao::webview::{WebView, WebViewConfig, WebViewDelegate};

struct BasicApp {
window: Window<AppWindow>
4 changes: 2 additions & 2 deletions src/appkit/alert.rs
Original file line number Diff line number Diff line change
@@ -7,13 +7,13 @@
//! If you want to show a complex view in an alert-esque fashion, you may consider looking at
//! `Sheet`.
//!
//! ```rust
//! ```rust,no_run
//! use cacao::appkit::{App, AppDelegate, Alert};
//!
//! #[derive(Default)]
//! struct ExampleApp;
//!
//! impl AppDelegate {
//! impl AppDelegate for ExampleApp {
//! fn did_finish_launching(&self) {
//!
//! }
4 changes: 2 additions & 2 deletions src/appkit/app/mod.rs
Original file line number Diff line number Diff line change
@@ -4,8 +4,8 @@
//! heavily by lifecycle events - in this case, your boilerplate would look something like this:
//!
//! ```rust,no_run
//! use cacao::app::{App, AppDelegate};
//! use cacao::window::Window;
//! use cacao::appkit::{App, AppDelegate};
//! use cacao::appkit::window::Window;
//!
//! #[derive(Default)]
//! struct BasicApp;
Loading