diff --git a/.gitignore b/.gitignore index 211d1fa57..feae384ac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,13 @@ node_modules target/ vendor/ -pax-chassis-macos/pax-dev-harness-macos/build +.swiftpm +.build + + +xcuserdata + +pax-chassis-macos/interface/build pax-example/build pax-example/.pax/ pax-create-sandbox/ @@ -16,5 +22,5 @@ Cargo.lock ## VScode project configuration files .vscode/ -pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.xcworkspace/xcuserdata/**/* -pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcuserdata/**/* +pax-chassis-macos/interface/interface.xcodeproj/project.xcworkspace/xcuserdata/**/* +pax-chassis-macos/interface/interface.xcodeproj/xcuserdata/**/* diff --git a/.idea/vcs.xml b/.idea/vcs.xml index b8454501e..c3c11088e 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,6 +3,8 @@ + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6922e2702..8e4602afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ exclude = [ "pax-compiler/new-project-template", "pax-example", "pax-properties-coproduct", - "pax-chassis-macos/pax-dev-harness-macos", + "pax-chassis-macos/interface", "pax-create-sandbox", "pax-language-server", ] diff --git a/README.md b/README.md index 9447469d0..a41308ee4 100644 --- a/README.md +++ b/README.md @@ -16,33 +16,70 @@ Writing Pax is intended to feel familiar and the language borrows many ideas fro Following is a simple Pax component called `IncrementMe`: ```rust -//File: increment-me.rs - +//File: lib.rs use pax_lang::*; -use pax_std::{Text}; -use pax_std::forms::{Button, ArgsButtonSubmit}; -use pax_std::layout::{Stacker}; +use pax_lang::api::*; +use pax_std::primitives::*; +use pax_std::types::*; +use pax_std::types::text::*; +use pax_std::components::Stacker; /// Defines the Pax component `IncrementMe`, with template & settings specified in `increment-me.pax`. #[derive(Pax)] -#[file("increment-me.pax")] +#[main] +#[file("increment-me.pax")] pub struct IncrementMe { - pub num_clicks: Property + pub num_clicks: Property, + pub message: Property, } + impl IncrementMe { - pub async fn increment(&self, args: ArgsButtonSubmit) { - let old_num_clicks = self.num_clicks.get(); - self.num_clicks.set(old_num_clicks + 1); - } -} + pub fn handle_did_mount(&mut self, ctx: RuntimeContext) { + self.num_clicks.set(0); + self.message.set("0 clicks".to_string()); + } + pub fn increment(&mut self, ctx: RuntimeContext, args: ArgsClick){ + let old_num_clicks = self.num_clicks.get(); + self.num_clicks.set(old_num_clicks + 1); + self.message.set(format!("{} clicks", self.num_clicks.get())); + } + +} ``` ```rust -//File: increment-me.pax +//increment-me.pax + + + +@handlers{ + did_mount:handle_did_mount +} - - - - +@settings { + .centered { + x: 50% + y: 50% + anchor_x: 50% + anchor_y: 50% + } + .small { + width: 120px + height: 120px + } + #text { + style: { + font: {Font::system("Times New Roman", FontStyle::Normal, FontWeight::Bold)}, + font_size: 32px, + fill: {Color::rgba(1.0, 1.0, 1.0, 1.0)}, + align_vertical: TextAlignVertical::Center, + align_horizontal: TextAlignHorizontal::Center, + align_multiline: TextAlignHorizontal::Center + } + } +} ``` Any Pax component like the example above may be included inside other Pax components, or may be mounted as the root of a stand-alone app. @@ -107,24 +144,38 @@ Read more in [The Pax Docs](https://docs.pax.dev/) ### To build Pax projects as native macOS apps - Building macOS apps requires running a Mac with macOS. This is a constraint enforced technically and legally by Apple. -- Install xcode `>=14.3` and Xcode command line utils: `xcode-select --install` -- SDK Version `macosx13.3`, Xcode version `>=14.3` +- Install xcode `>=15.0` and Xcode command line utils: `xcode-select --install` +- Make sure to accept Xcode's license agreement (prompted during Xcode startup for the first time) +- SDK Version `macosx13.3`, Xcode version `>=15.0` - Current Minimum Deployment `13.0` +- Install all necessary build architectures for Rust, so that binaries can be built for both Intel and Apple Silicon macs + ``` + rustup target add aarch64-apple-darwin x86_64-apple-darwin + ``` + +### To build Pax projects as native iOS apps + +- Follow instructions for building native macOS apps, above +- Install all necessary build architectures for Rust, so that binaries can be built for iOS and simulator targets: + ``` + rustup target add aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + ``` +- Install [ios simulator through Xcode](https://developer.apple.com/documentation/safari-developer-tools/adding-additional-simulators) #### Support matrix: | | Web browsers | Native iOS | Native Android | Native macOS | Native Windows | Native Linux | |-----------------------------------------|---------------|---------------------|-------------------|---------------------|-----------------------------|--------------| -| Development harness & chassis | ✅ | ⏲ | ⏲ | ✅ | ⏲ | ⏲ | -| 2D rendering and UIs | ✅
Canvas | ⏲
CoreGraphics | ⏲
Cairo | ✅
CoreGraphics | ⏲
Direct2D | ⏲
Cairo | +| Development harness & chassis | ✅ | ✅ | ⏲ | ✅ | ⏲ | ⏲ | +| 2D rendering and UIs | ✅
Canvas | ✅
CoreGraphics | ⏲
Cairo | ✅
CoreGraphics | ⏲
Direct2D | ⏲
Cairo | | 3D rendering and UIs | ⏲ | ⏲ | ⏲ | ⏲ | ⏲ | ⏲ | -| Vector graphics APIs | ✅ | ⏲ | ⏲ | ✅ | ⏲ | ⏲ | -| 2D layouts | ✅ | ⏲ | ⏲ | ✅ | ⏲ | ⏲ | -| Animation APIs | ✅ | ⏲ | ⏲ | ✅ | ⏲ | ⏲ | -| Native text rendering | ✅
DOM | ⏲
UIKit | ⏲
android:\* | ✅
SwiftUI | ⏲
System.Windows.Forms | ⏲
GTK | -| Native form elements | ⏲
DOM | ⏲
UIKit | ⏲
android:\* | ⏲
SwiftUI | ⏲
System.Windows.Forms | ⏲
GTK | -| Native event handling (e.g. Click, Tap) | ✅ | ⏲ | ⏲ | ✅ | ⏲ | ⏲ | -| Rust host language | ✅
WASM | ⏲
LLVM | ⏲
LLVM | ✅
LLVM | ⏲
LLVM | ⏲
LLVM | +| Vector graphics APIs | ✅ | ✅ | ⏲ | ✅ | ⏲ | ⏲ | +| 2D layouts | ✅ | ✅ | ⏲ | ✅ | ⏲ | ⏲ | +| Animation APIs | ✅ | ✅ | ⏲ | ✅ | ⏲ | ⏲ | +| Native text rendering | ✅
DOM | ✅
SwiftUI | ⏲
android:\* | ✅
SwiftUI | ⏲
System.Windows.Forms | ⏲
GTK | +| Native form elements | ⏲
DOM | ⏲
SwiftUI | ⏲
android:\* | ⏲
SwiftUI | ⏲
System.Windows.Forms | ⏲
GTK | +| Native event handling (e.g. Click, Tap) | ✅ | ✅ | ⏲ | ✅ | ⏲ | ⏲ | +| Rust host language | ✅
WASM | ✅
LLVM | ⏲
LLVM | ✅
LLVM | ⏲
LLVM | ⏲
LLVM | | JS/TypeScript host language | ⏲ | ⏲ | ⏲ | ⏲ | ⏲ | ⏲ | | Legend: | diff --git a/lab-journal-zb.md b/lab-journal-zb.md index e9e612104..b3a7f9d7f 100644 --- a/lab-journal-zb.md +++ b/lab-journal-zb.md @@ -2780,9 +2780,9 @@ focused on enabling multi-arch builds, partly to chase down the errors coming from xcodebuild: ``` -Ld /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS normal (in target 'Pax macOS' from project 'pax-dev-harness-macos') - cd /Users/zack/code/pax/pax-example/.pax/chassis/MacOS/pax-dev-harness-macos - /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -target x86_64-apple-macos12.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -L/Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/EagerLinkingTBDs -L/Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/BuildProductsPath/Debug -L/Users/zack/code/pax/pax-example/.pax/chassis/MacOS/pax-dev-harness-macos/../target/debug -F/Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/EagerLinkingTBDs -F/Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/BuildProductsPath/Debug -filelist /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/pax-dev-harness-macos.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS.LinkFileList -Xlinker -rpath -Xlinker @executable_path/../Frameworks -Xlinker -object_path_lto -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/pax-dev-harness-macos.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS_lto.o -Xlinker -export_dynamic -Xlinker -no_deduplicate -Xlinker -final_output -Xlinker /Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS -fobjc-link-runtime -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -L/usr/lib/swift -Xlinker -add_ast_path -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/pax-dev-harness-macos.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax_macOS.swiftmodule -lpaxchassismacos -lpaxchassismacos -Xlinker -dependency_info -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/pax-dev-harness-macos.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS_dependency_info.dat -o /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS +Ld /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS normal (in target 'Pax macOS' from project 'interface') + cd /Users/zack/code/pax/pax-example/.pax/chassis/MacOS/interface + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -target x86_64-apple-macos12.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk -L/Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/EagerLinkingTBDs -L/Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/BuildProductsPath/Debug -L/Users/zack/code/pax/pax-example/.pax/chassis/MacOS/interface/../target/debug -F/Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/EagerLinkingTBDs -F/Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/BuildProductsPath/Debug -filelist /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/interface.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS.LinkFileList -Xlinker -rpath -Xlinker @executable_path/../Frameworks -Xlinker -object_path_lto -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/interface.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS_lto.o -Xlinker -export_dynamic -Xlinker -no_deduplicate -Xlinker -final_output -Xlinker /Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS -fobjc-link-runtime -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx -L/usr/lib/swift -Xlinker -add_ast_path -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/interface.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax_macOS.swiftmodule -lpaxchassismacos -lpaxchassismacos -Xlinker -dependency_info -Xlinker /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/IntermediateBuildFilesPath/interface.build/Debug/Pax\ macOS.build/Objects-normal/x86_64/Pax\ macOS_dependency_info.dat -o /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS ld: warning: ignoring file /Users/zack/code/pax/pax-example/.pax/chassis/MacOS/target/debug/libpaxchassismacos.dylib, building for macOS-x86_64 but attempting to link with file built for macOS-arm64 Undefined symbols for architecture x86_64: "_pax_dealloc_message_queue", referenced from: @@ -2800,7 +2800,7 @@ clang: error: linker command failed with exit code 1 (use -v to see invocation) The following build commands failed: - Ld /Users/zack/Library/Developer/Xcode/DerivedData/pax-dev-harness-macos-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS normal (in target 'Pax macOS' from project 'pax-dev-harness-macos') + Ld /Users/zack/Library/Developer/Xcode/DerivedData/interface-ceupzrmwryakhafqxlfbcvhpgytw/Build/Intermediates.noindex/ArchiveIntermediates/Pax\ macOS/InstallationBuildProductsLocation/Applications/Pax\ macOS.app/Contents/MacOS/Pax\ macOS normal (in target 'Pax macOS' from project 'interface') (1 failure) ``` @@ -3481,3 +3481,98 @@ Sketch addtl. functionality needed to support "edit this website in playground" [-] Publish (can start with manual) [-] Manifest for files [-] upload relevant files (lib.rs, website_desktop, website_mobile + + + +### On native iOS builds + +We are 90% of the way to supporting iOS, given the shared Swift + CoreGraphics layers with macOS. Speccing out what's needed to achieve alpha iOS support: + +[x] Split out shared logic into SPM packages + [x] Configure macOS project to consume shared packages; get build working +[x] For proactive cleanliness, break out the cartridge (dylib) into a SPM package, so that consuming it is streamlined and simple across macOS / iOS + [x] Wrap all the way from .dylib -> SwiftUI View; this includes creating a .Framework + [x] Consume that exposed View in both macOS & iOS +[x] Refactor (or redo, as needed) macos project to consume new deps +[ ] Compiler work + [x] Refactor compiler internals to adapt to new cartridge / framework / swift package structure: macOS + [x] Configure pax-chassis-macos/interface/pax-app-macos to load the swift packages as relative dirs + [x] Get e2e build working in-place with pax-chassis-macos (no codegen); embed a placeholder cartridge + resources (bouncing logo?) + [x] After copying everything into .pax, patch (1) resources and (2) the dylib into pax-chassis-common/pax-swift-cartridge, then + [x] Build the resulting, patched, codegenned macOS project + [x] Handle architectures: aarch64 and x86_64; + [x] Bundle targets into xcframework + [x] Create default carts for xcframework along the way + [x] Multithread the dylib builds + [x] Handle release vs. debug + [x] Handle updates to assets; bundle into pax-swift-cartridge Resources + [X] Extend pax CLI + compiler to support --target=ios, firing up simulator if present on machine + [x] Refactor macOS-specific deps, e.g. NS* and CVDisplayLink + [x] set up xcframework manually and achieve hello world build of xcodeproject + [x] add ios target + [x] Handle build architectures: aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim + [x] automate copying & lipo of dylibs + [x] manage xcodebuild shell command + firing up simulator + [ ] Handle relative paths + inter-dylib deps + [ ] Manually update xcframework structure + [ ] use `install_name_tool` to change Rust-generated absolute paths into relative paths + [ ] get iOS project building with manual changes + [ ] +[ ] Make better default cartridge for default bundling (e.g. bouncing Pax logo) + [ ] Clean xcframework binaries from git history for leaner clones; replace with lighter-weight release builds if straight-forward +[ ] Fix undefined out-of-canvas pixels on iOS (either lock scrolling into bounds with a simple check, or handle canvas clearing ?) + +From Apple developer forums: +``` +If you want to build a framework that supports iOS and the iOS Simulator, construct an XCFramework with: + +One element for iOS containing just the arm64 architecture + +Another element for the iOS Simulator containing both arm64 and x86_64 architectures +``` + + +Issue with hard-coded vestigial paths in dylib — our built dylibs include absolute paths pointing to ....../libpaxchassismacos.dylib + + +``` +otool -L PaxCartridge +PaxCartridge (architecture x86_64): + /Users/zack/code/pax/pax-example/.pax/pkg/pax-chassis-macos/target/x86_64-apple-ios/release/deps/libpaxchassismacos.dylib (compatibility version 0.0.0, current version 0.0.0) + /System/Library/Frameworks/CoreText.framework/CoreText (compatibility version 1.0.0, current version 1.0.0) + /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 1774.0.1) + /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 2048.1.101) + /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0) + /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0) + /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) + /System/Library/Frameworks/Security.framework/Security (compatibility version 1.0.0, current version 61040.2.2) + /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 2048.1.101) +PaxCartridge (architecture arm64): + /Users/zack/code/pax/pax-example/.pax/pkg/pax-chassis-macos/target/aarch64-apple-ios-sim/release/deps/libpaxchassismacos.dylib (compatibility version 0.0.0, current version 0.0.0) + /System/Library/Frameworks/CoreText.framework/CoreText (compatibility version 1.0.0, current version 1.0.0) + /System/Library/Frameworks/CoreGraphics.framework/CoreGraphics (compatibility version 64.0.0, current version 1774.0.1) + /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 2048.1.101) + /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0) + /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0) + /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) + /System/Library/Frameworks/Security.framework/Security (compatibility version 1.0.0, current version 61040.2.2) + /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 2048.1.101) +``` + +1. These pax-chassis-macos paths almost certainly come from the "pre-built" PaxCartridges that I made manually to get to iOS hello world. + These should be replaced with correctly prebuilt packages, from pax-chassis-ios. They should also use @rpath instead of hard-coded dep paths. + +2. current best-bet approach to handle the nested dylib deps: create a nested framework (for each arch target, also consider whether lipo is necessary; probably is) + this allows embedding the dep .dylib alongside the entrypoint .dylib. Then in the entrypoint .dylib, use `install_name_tool` or similar to + update the path to an @rpath/ relative path, using some trial and error to get to a place that xcode respects the provided path. Something like: + +``` +MyLibrary.xcframework +├── ios-arm64 +│ ├── MyLibrary.framework +│ ├── Dependency1.framework +│ └── Dependency2.framework +└── ... +``` + +Get this working entirely manually first, then automate in pax-compiler. (validate by `pax clean` and ensuring that builds still run.) \ No newline at end of file diff --git a/pax-chassis-common/Cargo.toml b/pax-chassis-common/Cargo.toml new file mode 100644 index 000000000..5ec99cafe --- /dev/null +++ b/pax-chassis-common/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "pax-chassis-common" +version = "0.9.9" +authors = ["Zack Brown "] +edition = "2021" +description = "Shared resources for Pax Chassis" +license = "MIT OR Apache-2.0" +homepage = "https://pax.dev/" +repository = "https://www.github.com/pax-lang/pax" +include = ["src/**/*","pax-swift-common/**/*"] + +[workspace] + +[lib] + +[dependencies] +piet = "0.6.0" +piet-coregraphics = "0.6.0" +pax-core = { path = "../pax-core", version="0.9.9" } +pax-cartridge = {path="../pax-cartridge", version="0.9.9"} +pax-message = {path = "../pax-message", version="0.9.9"} +pax-runtime-api = {path = "../pax-runtime-api", version="0.9.9"} +pax-properties-coproduct = {path="../pax-properties-coproduct", version="0.9.9"} +lazy_static = "1.4.0" +mut_static = "5.0.0" +#be cautious about core-graphics' version number -- +#ideally this would be locked with `piet` (the specified version should exactly match the version used +#internally by piet-coregraphics, e.g. 0.6.0 => 0.22.3) +core-graphics = "0.22.3" +serde = "1.0.159" +flexbuffers = "2.0.0" diff --git a/pax-chassis-common/pax-swift-cartridge/Package.swift b/pax-chassis-common/pax-swift-cartridge/Package.swift new file mode 100644 index 000000000..a21d3328b --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "PaxSwiftCartridge", + platforms: [ + .macOS(.v12), + .iOS(.v13) + ], + products: [ + .library( + name: "PaxCartridge", + targets: ["PaxCartridge", "PaxCartridgeAssets"] + ), + .library( + name: "PaxCartridgeAssets", + targets: ["PaxCartridgeAssets"] + ), + ], + targets: [ + .binaryTarget( + name: "PaxCartridge", + path: "PaxCartridge.xcframework" + ), + .target( + name: "PaxCartridgeAssets", + resources: [.process("Resources")] + ) + ] +) diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/Info.plist b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/Info.plist new file mode 100644 index 000000000..225d58a20 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/Info.plist @@ -0,0 +1,59 @@ + + + + + AvailableLibraries + + + BinaryPath + PaxCartridge.framework/PaxCartridge + LibraryIdentifier + ios-arm64_x86_64-simulator + LibraryPath + PaxCartridge.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + BinaryPath + PaxCartridge.framework/PaxCartridge + LibraryIdentifier + macos-arm64_x86_64 + LibraryPath + PaxCartridge.framework + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + macos + + + BinaryPath + PaxCartridge.framework/PaxCartridge + LibraryIdentifier + ios-arm64 + LibraryPath + PaxCartridge.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/pax-chassis-macos/src/paxchassismacos.h b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Headers/PaxCartridge.h similarity index 67% rename from pax-chassis-macos/src/paxchassismacos.h rename to pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Headers/PaxCartridge.h index 2120e0398..e7b6f013f 100644 --- a/pax-chassis-macos/src/paxchassismacos.h +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Headers/PaxCartridge.h @@ -1,6 +1,7 @@ -// +// // Header to expose Rust data & logic via FFI -- if it isn't here, it doesn't exist to Swift. -// See also: `pax-chassis-macos/src/lib.rs` where this logic is exposed +// +// SEE ALSO: `pax-chassis-macos/src/lib.rs` where this logic is managed. #include #include @@ -8,13 +9,13 @@ #include typedef struct NativeMessageQueue { - uint8_t *data_ptr; - uint64_t length; + uint8_t *data_ptr; + uint64_t length; } NativeMessageQueue; typedef struct InterruptBuffer { - const void *data_ptr; - uint64_t length; + const void *data_ptr; + uint64_t length; } InterruptBuffer; typedef struct PaxEngineContainer PaxEngineContainer; @@ -26,8 +27,8 @@ void pax_dealloc_engine(struct PaxEngineContainer * container); void pax_interrupt(struct PaxEngineContainer *engine_container, const void * interrupt); struct NativeMessageQueue *pax_tick(struct PaxEngineContainer *engine_container, - void *cgContext, - float width, - float height); + void *cgContext, + float width, + float height); void pax_dealloc_message_queue(struct NativeMessageQueue *queue); diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Info.plist b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Info.plist new file mode 100644 index 000000000..29273b3c2 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Info.plist @@ -0,0 +1,44 @@ + + + + + + CFBundleName + PaxCartridge + + + CFBundleIdentifier + dev.pax.PaxCartridge + + + CFBundleShortVersionString + 1.0.0 + + CFBundleVersion + 1 + + + CFBundlePackageType + FMWK + + + CFBundleSupportedPlatforms + + iOS + + + + + MinimumOSVersion + 15.0 + + + CFBundleExecutable + PaxCartridge + + + CFBundleDevelopmentRegion + en + + + diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Modules/module.modulemap b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Modules/module.modulemap new file mode 100644 index 000000000..1f802a9fe --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module PaxCartridge { + umbrella header "../Headers/PaxCartridge.h" + export * + module * { export * } +} diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/PaxCartridge b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/PaxCartridge new file mode 100755 index 000000000..dce7ca695 Binary files /dev/null and b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64/PaxCartridge.framework/PaxCartridge differ diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Headers/PaxCartridge.h b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Headers/PaxCartridge.h new file mode 100644 index 000000000..e7b6f013f --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Headers/PaxCartridge.h @@ -0,0 +1,34 @@ +// +// Header to expose Rust data & logic via FFI -- if it isn't here, it doesn't exist to Swift. +// +// SEE ALSO: `pax-chassis-macos/src/lib.rs` where this logic is managed. + +#include +#include +#include +#include + +typedef struct NativeMessageQueue { + uint8_t *data_ptr; + uint64_t length; +} NativeMessageQueue; + +typedef struct InterruptBuffer { + const void *data_ptr; + uint64_t length; +} InterruptBuffer; + +typedef struct PaxEngineContainer PaxEngineContainer; + +struct PaxEngineContainer *pax_init(void (*logger)(const char*)); + +void pax_dealloc_engine(struct PaxEngineContainer * container); + +void pax_interrupt(struct PaxEngineContainer *engine_container, const void * interrupt); + +struct NativeMessageQueue *pax_tick(struct PaxEngineContainer *engine_container, + void *cgContext, + float width, + float height); + +void pax_dealloc_message_queue(struct NativeMessageQueue *queue); diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Info.plist b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Info.plist new file mode 100644 index 000000000..ef27f3120 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Info.plist @@ -0,0 +1,44 @@ + + + + + + CFBundleName + PaxCartridge + + + CFBundleIdentifier + dev.pax.PaxCartridge + + + CFBundleShortVersionString + 1.0.0 + + CFBundleVersion + 1 + + + CFBundlePackageType + FMWK + + + CFBundleSupportedPlatforms + + iossimulator + + + + + MinimumOSVersion + 15.0 + + + CFBundleExecutable + PaxCartridge + + + CFBundleDevelopmentRegion + en + + + diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Modules/module.modulemap b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Modules/module.modulemap new file mode 100644 index 000000000..1f802a9fe --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module PaxCartridge { + umbrella header "../Headers/PaxCartridge.h" + export * + module * { export * } +} diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/PaxCartridge b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/PaxCartridge new file mode 100755 index 000000000..77dad22d8 Binary files /dev/null and b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ios-arm64_x86_64-simulator/PaxCartridge.framework/PaxCartridge differ diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Headers/PaxCartridge.h b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Headers/PaxCartridge.h new file mode 100644 index 000000000..e7b6f013f --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Headers/PaxCartridge.h @@ -0,0 +1,34 @@ +// +// Header to expose Rust data & logic via FFI -- if it isn't here, it doesn't exist to Swift. +// +// SEE ALSO: `pax-chassis-macos/src/lib.rs` where this logic is managed. + +#include +#include +#include +#include + +typedef struct NativeMessageQueue { + uint8_t *data_ptr; + uint64_t length; +} NativeMessageQueue; + +typedef struct InterruptBuffer { + const void *data_ptr; + uint64_t length; +} InterruptBuffer; + +typedef struct PaxEngineContainer PaxEngineContainer; + +struct PaxEngineContainer *pax_init(void (*logger)(const char*)); + +void pax_dealloc_engine(struct PaxEngineContainer * container); + +void pax_interrupt(struct PaxEngineContainer *engine_container, const void * interrupt); + +struct NativeMessageQueue *pax_tick(struct PaxEngineContainer *engine_container, + void *cgContext, + float width, + float height); + +void pax_dealloc_message_queue(struct NativeMessageQueue *queue); diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Info.plist b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Info.plist new file mode 100644 index 000000000..a070a1c6e --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Info.plist @@ -0,0 +1,44 @@ + + + + + + CFBundleName + PaxCartridge + + + CFBundleIdentifier + dev.pax.PaxCartridge + + + CFBundleShortVersionString + 1.0.0 + + CFBundleVersion + 1 + + + CFBundlePackageType + FMWK + + + CFBundleSupportedPlatforms + + macOS + + + + + MinimumOSVersion + 12.0 + + + CFBundleExecutable + PaxCartridge + + + CFBundleDevelopmentRegion + en + + + diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Modules/module.modulemap b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Modules/module.modulemap new file mode 100644 index 000000000..1f802a9fe --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module PaxCartridge { + umbrella header "../Headers/PaxCartridge.h" + export * + module * { export * } +} diff --git a/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/PaxCartridge b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/PaxCartridge new file mode 100755 index 000000000..d09735f62 Binary files /dev/null and b/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/macos-arm64_x86_64/PaxCartridge.framework/PaxCartridge differ diff --git a/pax-chassis-common/pax-swift-cartridge/README.md b/pax-chassis-common/pax-swift-cartridge/README.md new file mode 100644 index 000000000..2f690d7c4 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/README.md @@ -0,0 +1,11 @@ +# pax-swift-cartridge + +Holds the Swift Package Manager package + configuration for a Pax cartridge. + +This is used by both macOS and iOS builds. + + 1. Configures dylib dependency (built Pax+Rust cartridge) + 2. Exposes a SwiftUI View for consumption by developers or full-app chassis + +This directory is used as a codegen template — the final built and dylib-patched version of this Swift package will +sit inside userland `.pax/pkg/pax-chassis-common/pax-swift-cartridge` \ No newline at end of file diff --git a/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Empty.swift b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Empty.swift new file mode 100644 index 000000000..3b9ebedc6 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Empty.swift @@ -0,0 +1 @@ +public class PaxCartridgeBundleLocator {} \ No newline at end of file diff --git a/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo-light.png b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo-light.png new file mode 100644 index 000000000..0aacb3190 Binary files /dev/null and b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo-light.png differ diff --git a/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo.png b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo.png new file mode 100644 index 000000000..cfbc54f54 Binary files /dev/null and b/pax-chassis-common/pax-swift-cartridge/Sources/PaxCartridgeAssets/Resources/assets/images/pax-logo.png differ diff --git a/pax-chassis-common/pax-swift-cartridge/copy-dylibs-from-pax-example-build.sh b/pax-chassis-common/pax-swift-cartridge/copy-dylibs-from-pax-example-build.sh new file mode 100755 index 000000000..51c4e7c73 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/copy-dylibs-from-pax-example-build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Extracts .frameworks from xcframework, rebuilds xcframework. +# Used to rely on xcodebuild's sanitary bundling process for the fragile xcframework structure + +set -e + +cp -r ../../pax-example/.pax/pkg/pax-chassis-common/pax-swift-cartridge/PaxCartridge.xcframework/ ./PaxCartridge.xcframework/ \ No newline at end of file diff --git a/pax-chassis-common/pax-swift-cartridge/rebuild-xcframework.sh b/pax-chassis-common/pax-swift-cartridge/rebuild-xcframework.sh new file mode 100755 index 000000000..0c0f70256 --- /dev/null +++ b/pax-chassis-common/pax-swift-cartridge/rebuild-xcframework.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Extracts .frameworks from xcframework, rebuilds xcframework. +# Used to rely on xcodebuild's sanitary bundling process for the fragile xcframework structure + +set -e + +# An array of architectures +archs=("ios-arm64" "iossimulator-multiarch" "macos-multiarch") + +# Iterate over each architecture and perform the operations +for arch in "${archs[@]}"; do + mkdir -p $arch/PaxCartridge.framework + cp -r PaxCartridge.xcframework/$arch/PaxCartridge.framework $arch/ +done + +rm -rf PaxCartridge.xcframework/ + +# Construct the xcodebuild command +cmd="xcodebuild -create-xcframework" + +for arch in "${archs[@]}"; do + cmd="$cmd -framework $arch/PaxCartridge.framework" +done + +cmd="$cmd -output PaxCartridge.xcframework" + +# Execute the xcodebuild command +eval $cmd + +# Cleanup the temporary directories +for arch in "${archs[@]}"; do + rm -rf $arch/ +done \ No newline at end of file diff --git a/pax-chassis-common/pax-swift-common/.gitignore b/pax-chassis-common/pax-swift-common/.gitignore new file mode 100644 index 000000000..3b2981208 --- /dev/null +++ b/pax-chassis-common/pax-swift-common/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/pax-chassis-common/pax-swift-common/Package.swift b/pax-chassis-common/pax-swift-common/Package.swift new file mode 100644 index 000000000..beb09b6bc --- /dev/null +++ b/pax-chassis-common/pax-swift-common/Package.swift @@ -0,0 +1,37 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "PaxSwiftCommon", + platforms: [ + .macOS(.v12), + .iOS(.v15) + ], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Messages", + targets: ["Messages"]), + .library( + name: "Rendering", + targets: ["Rendering"]), + .library( + name: "FlexBuffers", + targets: ["FlexBuffers"]), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + + .target( + name: "FlexBuffers"), + .target( + name: "Rendering", + dependencies: ["Messages"]), + .target( + name: "Messages", + dependencies: ["FlexBuffers"]), + ] +) diff --git a/pax-chassis-common/pax-swift-common/README.md b/pax-chassis-common/pax-swift-common/README.md new file mode 100644 index 000000000..6061f3d8d --- /dev/null +++ b/pax-chassis-common/pax-swift-common/README.md @@ -0,0 +1,4 @@ +# pax-swift-common + +Common swift logic, packaged as an SPM package, shared by both iOS and macOS (e.g. FlexBuffers and Message structs) + diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBufferBuilder.swift b/pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBufferBuilder.swift similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBufferBuilder.swift rename to pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBufferBuilder.swift diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBuffers.h b/pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBuffers.h similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBuffers.h rename to pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBuffers.h diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBuffers.swift b/pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBuffers.swift similarity index 98% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBuffers.swift rename to pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBuffers.swift index af34a9856..6d8aaef06 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/FlexBuffers/FlexBuffers.swift +++ b/pax-chassis-common/pax-swift-common/Sources/FlexBuffers/FlexBuffers.swift @@ -11,6 +11,26 @@ import CoreGraphics // MARK: FlexBuffers Builder logic + +protocol WritableValueType {} + +extension Int: WritableValueType {} +extension Int8: WritableValueType {} +extension Int16: WritableValueType {} +extension Int32: WritableValueType {} +extension Int64: WritableValueType {} + +extension UInt: WritableValueType {} +extension UInt8: WritableValueType {} +extension UInt16: WritableValueType {} +extension UInt32: WritableValueType {} +extension UInt64: WritableValueType {} + +extension Float32: WritableValueType {} +extension Float64: WritableValueType {} + +extension Bool: WritableValueType {} + public class FlexBuffer { let initialSize : Int @@ -250,7 +270,8 @@ public class FlexBuffer { write(value: length, size: byteWidth) sloc = offset for c in buffer { - write(value: c, size: 1) + let ci = UInt8(c) + write(value: ci, size: 1) } write(value: UInt8(0), size: 1) return bitWidth @@ -414,8 +435,10 @@ public class FlexBuffer { offset += paddingSize(bufSize: offset, scalarSize: UInt8(byteWidth)) return UInt8(byteWidth) } + + - fileprivate func write(value : T, size : UInt8) { + fileprivate func write(value : T, size : UInt8) { var v = value let newOffest = offset + Int(size) let prevSize = currentSize @@ -1099,7 +1122,8 @@ public extension FlexBuffer { _ = value.withUnsafeBytes { (byte) -> Bool in for i in 0...allocate(capacity: tokenPointerCurrent) tokenNamePointerCapacity = tokenPointerCurrent } - tokenNamePointerStart.moveAssign(from: tokenPointerStart, count: tokenPointerCurrent) + tokenNamePointerStart.moveUpdate(from: tokenPointerStart, count: tokenPointerCurrent) tokenNamePointerCurrent = tokenPointerCurrent tokenPointerCurrent = 0 keyIsPresent = true diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Messages.swift b/pax-chassis-common/pax-swift-common/Sources/Messages/Messages.swift similarity index 79% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Messages.swift rename to pax-chassis-common/pax-swift-common/Sources/Messages/Messages.swift index 2c9e9cac6..7b29b1428 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Messages.swift +++ b/pax-chassis-common/pax-swift-common/Sources/Messages/Messages.swift @@ -1,21 +1,22 @@ // // Messages.swift -// pax-dev-harness-macos +// interface // // Created by Zachary Brown on 5/7/22. // import Foundation import SwiftUI +import FlexBuffers /// Agnostic of the type of element, this patch contains only an `id_chain` field, suitable for looking up a NativeElement (e.g. for deletion) -class AnyCreatePatch { - var id_chain: [UInt64] +public class AnyCreatePatch { + public var id_chain: [UInt64] /// Used for clipping -- each `[UInt64]` is an `id_chain` for an associated clipping mask (`Frame`) - var clipping_ids: [[UInt64]] + public var clipping_ids: [[UInt64]] - init(fb:FlxbReference) { + public init(fb:FlxbReference) { self.id_chain = fb["id_chain"]!.asVector!.makeIterator().map({ fb in fb.asUInt64! }) @@ -29,10 +30,10 @@ class AnyCreatePatch { } -class AnyDeletePatch { - var id_chain: [UInt64] +public class AnyDeletePatch { + public var id_chain: [UInt64] - init(fb:FlxbReference) { + public init(fb:FlxbReference) { self.id_chain = fb.asVector!.makeIterator().map({ fb in fb.asUInt64! }) @@ -40,15 +41,15 @@ class AnyDeletePatch { } } -class TextStyle { - var font: PaxFont - var fill: Color - var alignmentMultiline: TextAlignment - var alignment: Alignment - var font_size: CGFloat - var underline: Bool +public class TextStyle { + public var font: PaxFont + public var fill: Color + public var alignmentMultiline: TextAlignment + public var alignment: Alignment + public var font_size: CGFloat + public var underline: Bool - init(font: PaxFont, fill: Color, alignmentMultiline: TextAlignment, alignment: Alignment, font_size: CGFloat, underline: Bool) { + public init(font: PaxFont, fill: Color, alignmentMultiline: TextAlignment, alignment: Alignment, font_size: CGFloat, underline: Bool) { self.font = font self.fill = fill self.alignmentMultiline = alignmentMultiline @@ -57,7 +58,7 @@ class TextStyle { self.underline = underline } - func applyPatch(from patch: TextStyleMessage) { + public func applyPatch(from patch: TextStyleMessage) { self.font.applyPatch(fb: patch.font) @@ -84,18 +85,18 @@ class TextStyle { } } -class TextElement { - var id_chain: [UInt64] - var clipping_ids: [[UInt64]] - var content: String - var transform: [Float] - var size_x: Float - var size_y: Float - var textStyle: TextStyle - var depth: UInt? - var style_link: TextStyle? +public class TextElement { + public var id_chain: [UInt64] + public var clipping_ids: [[UInt64]] + public var content: String + public var transform: [Float] + public var size_x: Float + public var size_y: Float + public var textStyle: TextStyle + public var depth: UInt? + public var style_link: TextStyle? - init(id_chain: [UInt64], clipping_ids: [[UInt64]], content: String, transform: [Float], size_x: Float, size_y: Float, textStyle: TextStyle, depth: UInt?, style_link: TextStyle?) { + public init(id_chain: [UInt64], clipping_ids: [[UInt64]], content: String, transform: [Float], size_x: Float, size_y: Float, textStyle: TextStyle, depth: UInt?, style_link: TextStyle?) { self.id_chain = id_chain self.clipping_ids = clipping_ids self.content = content @@ -107,12 +108,12 @@ class TextElement { self.style_link = style_link } - static func makeDefault(id_chain: [UInt64], clipping_ids: [[UInt64]]) -> TextElement { + public static func makeDefault(id_chain: [UInt64], clipping_ids: [[UInt64]]) -> TextElement { let defaultTextStyle = TextStyle(font: PaxFont.makeDefault(), fill: Color(.black), alignmentMultiline: .leading, alignment: .topLeading, font_size: 5.0, underline: false) return TextElement(id_chain: id_chain, clipping_ids: clipping_ids, content: "", transform: [1,0,0,1,0,0], size_x: 0.0, size_y: 0.0, textStyle: defaultTextStyle, depth: nil, style_link: nil) } - func applyPatch(patch: TextUpdatePatch) { + public func applyPatch(patch: TextUpdatePatch) { //no-op to ID, as it is primary key if let content = patch.content { @@ -143,13 +144,13 @@ class TextElement { } } -enum TextAlignHorizontal { +public enum TextAlignHorizontal { case center case left case right } -extension TextAlignHorizontal { +public extension TextAlignHorizontal { func toTextAlignment() -> TextAlignment { switch self { case .center: @@ -162,14 +163,14 @@ extension TextAlignHorizontal { } } -enum TextAlignVertical { +public enum TextAlignVertical { case top case center case bottom } -func toAlignment(horizontalAlignment: TextAlignHorizontal, verticalAlignment: TextAlignVertical) -> Alignment { +public func toAlignment(horizontalAlignment: TextAlignHorizontal, verticalAlignment: TextAlignVertical) -> Alignment { let horizontal: HorizontalAlignment let vertical: VerticalAlignment @@ -195,11 +196,11 @@ func toAlignment(horizontalAlignment: TextAlignHorizontal, verticalAlignment: Te /// A patch representing an image load request from a given id_chain -class ImageLoadPatch { - var id_chain: [UInt64] - var path: String? +public class ImageLoadPatch { + public var id_chain: [UInt64] + public var path: String? - init(fb:FlxbReference) { + public init(fb:FlxbReference) { self.id_chain = fb["id_chain"]!.asVector!.makeIterator().map({ fb in fb.asUInt64! }) @@ -208,16 +209,16 @@ class ImageLoadPatch { } -class TextStyleMessage { - var font: FlxbReference - var fill: Color? - var font_size: CGFloat? - var underline: Bool? - var align_multiline: TextAlignHorizontal? - var align_horizontal: TextAlignHorizontal? - var align_vertical: TextAlignVertical? +public class TextStyleMessage { + public var font: FlxbReference + public var fill: Color? + public var font_size: CGFloat? + public var underline: Bool? + public var align_multiline: TextAlignHorizontal? + public var align_horizontal: TextAlignHorizontal? + public var align_vertical: TextAlignVertical? - init(_ buffer: FlxbReference) { + public init(_ buffer: FlxbReference) { self.font = buffer["font"]! self.font_size = buffer["font_size"]?.asFloat.map { CGFloat($0) } @@ -269,17 +270,17 @@ class TextStyleMessage { } -class TextUpdatePatch { - var id_chain: [UInt64] - var content: String? - var transform: [Float]? - var size_x: Float? - var size_y: Float? - var depth: UInt? - var style: TextStyleMessage? - var style_link: TextStyleMessage? +public class TextUpdatePatch { + public var id_chain: [UInt64] + public var content: String? + public var transform: [Float]? + public var size_x: Float? + public var size_y: Float? + public var depth: UInt? + public var style: TextStyleMessage? + public var style_link: TextStyleMessage? - init(fb: FlxbReference) { + public init(fb: FlxbReference) { self.id_chain = fb["id_chain"]!.asVector!.makeIterator().map({ fb in fb.asUInt64! }) @@ -302,20 +303,20 @@ class TextUpdatePatch { } ///// A patch containing optional fields, representing an update action for the NativeElement of the given id_chain -//class TextUpdatePatch { -// var id_chain: [UInt64] -// var content: String? -// var transform: [Float]? -// var size_x: Float? -// var size_y: Float? -// var fontBuffer: FlxbReference -// var fill: Color? -// var align_multiline: TextAlignHorizontal? -// var align_vertical: TextAlignVertical? -// var align_horizontal: TextAlignHorizontal? +//public class TextUpdatePatch { +// public var id_chain: [UInt64] +// public var content: String? +// public var transform: [Float]? +// public var size_x: Float? +// public var size_y: Float? +// public var fontBuffer: FlxbReference +// public var fill: Color? +// public var align_multiline: TextAlignHorizontal? +// public var align_vertical: TextAlignVertical? +// public var align_horizontal: TextAlignHorizontal? // // New properties -// var size: CGFloat? -// var style_link: LinkStyle? +// public var size: CGFloat? +// public var style_link: LinkStyle? // // init(fb: FlxbReference) { // self.id_chain = fb["id_chain"]!.asVector!.makeIterator().map({ fb in @@ -382,7 +383,7 @@ class TextUpdatePatch { //} -func extractColorFromBuffer(_ fillBuffer: FlxbReference) -> Color { +public func extractColorFromBuffer(_ fillBuffer: FlxbReference) -> Color { if let rgba = fillBuffer["Rgba"], !rgba.isNull { let stub = fillBuffer["Rgba"]! return Color( @@ -404,18 +405,18 @@ func extractColorFromBuffer(_ fillBuffer: FlxbReference) -> Color { } } -enum TextAlignHorizontalMessage: String { +public enum TextAlignHorizontalMessage: String { case Left, Center, Right } -enum FontStyle: String { +public enum FontStyle: String { case normal = "Normal" case italic = "Italic" case oblique = "Oblique" } extension FontWeight { - func fontWeight() -> Font.Weight { + public func fontWeight() -> Font.Weight { switch self { case .thin: return .thin case .extraLight: return .ultraLight @@ -430,7 +431,7 @@ extension FontWeight { } } -enum FontWeight: String { +public enum FontWeight: String { case thin = "Thin" case extraLight = "ExtraLight" case light = "Light" @@ -442,48 +443,48 @@ enum FontWeight: String { case black = "Black" } -class PaxFont { - enum PaxFontType { +public class PaxFont { + public enum PaxFontType { case system(SystemFont) case web(WebFont) case local(LocalFont) } - struct SystemFont { + public struct SystemFont { let family: String let style: FontStyle let weight: FontWeight } - struct WebFont { + public struct WebFont { let family: String let url: URL let style: FontStyle let weight: FontWeight } - struct LocalFont { + public struct LocalFont { let family: String let path: URL let style: FontStyle let weight: FontWeight } - var type: PaxFontType - var cachedFont: Font? - var currentSize: CGFloat + public var type: PaxFontType + public var cachedFont: Font? + public var currentSize: CGFloat - init(type: PaxFontType) { + public init(type: PaxFontType) { self.type = type self.currentSize = 12 } - static func makeDefault() -> PaxFont { + public static func makeDefault() -> PaxFont { let defaultSystemFont = SystemFont(family: "Helvetica", style: .normal, weight: .normal) return PaxFont(type: .system(defaultSystemFont)) } - func getFont(size: CGFloat) -> Font { + public func getFont(size: CGFloat) -> Font { if let cachedFont = cachedFont, currentSize == size { return cachedFont } @@ -534,7 +535,7 @@ class PaxFont { - func applyPatch(fb: FlxbReference) { + public func applyPatch(fb: FlxbReference) { if let systemFontMessage = fb["System"] { if let family = systemFontMessage["family"]?.asString { let styleMessage = FontStyle(rawValue: systemFontMessage["style"]?.asString ?? "normal") ?? .normal @@ -562,7 +563,8 @@ class PaxFont { } } - static func isFontRegistered(fontFamily: String) -> Bool { + #if os(macOS) + public static func isFontRegistered(fontFamily: String) -> Bool { let fontFamilies = CTFontManagerCopyAvailableFontFamilyNames() as! [String] if fontFamilies.contains(fontFamily) { @@ -583,15 +585,22 @@ class PaxFont { } } } - return false + + } + #elseif os(iOS) || os(tvOS) || os(watchOS) + public static func isFontRegistered(fontFamily: String) -> Bool { + let availableFontFamilies = UIFont.familyNames + + return availableFontFamilies.contains(fontFamily) } + #endif } // -//class FontFactory { -//// var family: String -//// var variant: String -//// var size: Float +//public class FontFactory { +//// public var family: String +//// public var public variant: String +//// public var size: Float // // func applyPatch(fb: FlxbReference) -> Font { // print("MAKING FONT") @@ -640,24 +649,24 @@ class PaxFont { -class FrameElement { - var id_chain: [UInt64] - var transform: [Float] - var size_x: Float - var size_y: Float +public class FrameElement { + public var id_chain: [UInt64] + public var transform: [Float] + public var size_x: Float + public var size_y: Float - init(id_chain: [UInt64], transform: [Float], size_x: Float, size_y: Float) { + public init(id_chain: [UInt64], transform: [Float], size_x: Float, size_y: Float) { self.id_chain = id_chain self.transform = transform self.size_x = size_x self.size_y = size_y } - static func makeDefault(id_chain: [UInt64]) -> FrameElement { + public static func makeDefault(id_chain: [UInt64]) -> FrameElement { FrameElement(id_chain: id_chain, transform: [1,0,0,1,0,0], size_x: 0.0, size_y: 0.0) } - func applyPatch(patch: FrameUpdatePatch) { + public func applyPatch(patch: FrameUpdatePatch) { //no-op to ID, as it is primary key if patch.transform != nil { @@ -675,13 +684,13 @@ class FrameElement { /// A patch containing optional fields, representing an update action for the NativeElement of the given id_chain -class FrameUpdatePatch { - var id_chain: [UInt64] - var transform: [Float]? - var size_x: Float? - var size_y: Float? +public class FrameUpdatePatch { + public var id_chain: [UInt64] + public var transform: [Float]? + public var size_x: Float? + public var size_y: Float? - init(fb: FlxbReference) { + public init(fb: FlxbReference) { self.id_chain = fb["id_chain"]!.asVector!.makeIterator().map({ fb in fb.asUInt64! }) diff --git a/pax-chassis-common/pax-swift-common/Sources/Rendering/Rendering.swift b/pax-chassis-common/pax-swift-common/Sources/Rendering/Rendering.swift new file mode 100644 index 000000000..d4436e687 --- /dev/null +++ b/pax-chassis-common/pax-swift-common/Sources/Rendering/Rendering.swift @@ -0,0 +1,118 @@ +import SwiftUI +import Messages + +public struct NativeRenderingLayer: View { + + public init() {} + + @ObservedObject var textElements : TextElements = TextElements.singleton + @ObservedObject var frameElements : FrameElements = FrameElements.singleton + + public func getClippingMask(clippingIds: [[UInt64]]) -> some View { + + var elements : [FrameElement] = [] + + clippingIds.makeIterator().forEach( { id_chain in + elements.insert(self.frameElements.elements[id_chain]!, at: 0) + }) + + return ZStack { ForEach(elements, id: \.id_chain) { frameElement in + Rectangle() + .frame(width: CGFloat(frameElement.size_x), height: CGFloat(frameElement.size_y)) + .position(x: CGFloat(frameElement.size_x / 2.0), y: CGFloat(frameElement.size_y / 2.0)) + .transformEffect(CGAffineTransform.init( + a: CGFloat(frameElement.transform[0]), + b: CGFloat(frameElement.transform[1]), + c: CGFloat(frameElement.transform[2]), + d: CGFloat(frameElement.transform[3]), + tx: CGFloat(frameElement.transform[4]), + ty: CGFloat(frameElement.transform[5])) + ) + } } + } + + @ViewBuilder + public func getPositionedTextGroup(textElement: TextElement) -> some View { + let transform = CGAffineTransform.init( + a: CGFloat(textElement.transform[0]), + b: CGFloat(textElement.transform[1]), + c: CGFloat(textElement.transform[2]), + d: CGFloat(textElement.transform[3]), + tx: CGFloat(textElement.transform[4]), + ty: CGFloat(textElement.transform[5]) + ) + var text: AttributedString { + var attributedString: AttributedString = try! AttributedString(markdown: textElement.content, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) + + for run in attributedString.runs { + if run.link != nil { + if let linkStyle = textElement.style_link { + attributedString[run.range].font = linkStyle.font.getFont(size: linkStyle.font_size) + if(linkStyle.underline){ + attributedString[run.range].underlineStyle = .single + } else { + attributedString[run.range].underlineStyle = .none + } + attributedString[run.range].foregroundColor = linkStyle.fill + } + } + } + return attributedString + + } + let textView : some View = + Text(text) + .foregroundColor(textElement.textStyle.fill) + .font(textElement.textStyle.font.getFont(size: textElement.textStyle.font_size)) + .frame(width: CGFloat(textElement.size_x), height: CGFloat(textElement.size_y), alignment: textElement.textStyle.alignment) + .position(x: CGFloat(textElement.size_x / 2.0), y: CGFloat(textElement.size_y / 2.0)) + .transformEffect(transform) + .textSelection(.enabled) + +// +// if !textElement.clipping_ids.isEmpty { +// textView.mask(getClippingMask(clippingIds: textElement.clipping_ids)) +// } else { +// textView +// } + + textView + } + + public var body: some View { + ZStack{ + ForEach(Array(self.textElements.elements.values), id: \.id_chain) { textElement in + getPositionedTextGroup(textElement: textElement) + } + } + } +} + +public class TextElements: ObservableObject { + public static let singleton : TextElements = TextElements() + + @Published public var elements : [[UInt64]: TextElement] = [:] + + public func add(element: TextElement) { + self.elements[element.id_chain] = element + } + public func remove(id: [UInt64]) { + self.elements.removeValue(forKey: id) + } +} + +public class FrameElements: ObservableObject { + public static let singleton : FrameElements = FrameElements() + + @Published public var elements : [[UInt64]: FrameElement] = [:] + + public func add(element: FrameElement) { + self.elements[element.id_chain] = element + } + public func remove(id: [UInt64]) { + self.elements.removeValue(forKey: id) + } + public func get(id: [UInt64]) -> FrameElement? { + return self.elements[id] + } +} diff --git a/pax-chassis-common/pax-swift-common/Tests/PaxSwiftCommonTests/PaxSwiftCommonTests.swift b/pax-chassis-common/pax-swift-common/Tests/PaxSwiftCommonTests/PaxSwiftCommonTests.swift new file mode 100644 index 000000000..811910067 --- /dev/null +++ b/pax-chassis-common/pax-swift-common/Tests/PaxSwiftCommonTests/PaxSwiftCommonTests.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import PaxSwiftCommon + +final class PaxSwiftCommonTests: XCTestCase { + func testExample() throws { + // XCTest Documenation + // https://developer.apple.com/documentation/xctest + + // Defining Test Cases and Test Methods + // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods + } +} diff --git a/pax-chassis-common/src/core_graphics_c_bridge.rs b/pax-chassis-common/src/core_graphics_c_bridge.rs new file mode 100644 index 000000000..94e0ab20e --- /dev/null +++ b/pax-chassis-common/src/core_graphics_c_bridge.rs @@ -0,0 +1,211 @@ +#![allow(non_snake_case)] //Non-snake-case is used here to help denote foreign structs, e.g. from Swift via C + +extern crate core; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::ffi::c_void; +use std::rc::Rc; + +use std::mem::{transmute, ManuallyDrop}; +use std::os::raw::c_char; + +use core_graphics::context::CGContext; +use piet_coregraphics::CoreGraphicsContext; + +use flexbuffers; +use flexbuffers::DeserializationError; +use serde::Serialize; + +use pax_cartridge; +use pax_core::{InstanceRegistry, PaxEngine}; + +//Re-export all native message types; used by Swift via FFI. +//Note that any types exposed by pax_message must ALSO be added to `PaxCartridge.h` +//in order to be visible to Swift +pub use pax_message::*; +use pax_runtime_api::{ArgsClick, ArgsScroll, ModifierKey, MouseButton, MouseEventArgs}; + +/// Container data structure for PaxEngine, aggregated to support passing across C bridge +#[repr(C)] //Exposed to Swift via PaxCartridge.h +pub struct PaxEngineContainer { + _engine: *mut PaxEngine>, + //NOTE: since that has become a single field, this data structure may be be retired and `*mut PaxEngine` could be passed directly. +} + +/// Allocate an instance of the Pax engine, with a specified root/main component from the loaded `pax_cartridge`. +#[no_mangle] //Exposed to Swift via PaxCartridge.h +pub extern "C" fn pax_init(logger: extern "C" fn(*const c_char)) -> *mut PaxEngineContainer { + //Initialize a ManuallyDrop-contained PaxEngine, so that a pointer to that + //engine can be passed back to Swift via the C (FFI) bridge + //This could presumably be cleaned up -- see `pax_dealloc_engine` + let instance_registry: Rc>>> = + Rc::new(RefCell::new(InstanceRegistry::new())); + let main_component_instance = + pax_cartridge::instantiate_main_component(Rc::clone(&instance_registry)); + let expression_table = pax_cartridge::instantiate_expression_table(); + + let engine: ManuallyDrop>>> = + ManuallyDrop::new(Box::new(PaxEngine::new( + main_component_instance, + expression_table, + pax_runtime_api::PlatformSpecificLogger::MacOS(logger), + (1.0, 1.0), + instance_registry, + ))); + + let container = ManuallyDrop::new(Box::new(PaxEngineContainer { + _engine: Box::into_raw(ManuallyDrop::into_inner(engine)), + })); + + Box::into_raw(ManuallyDrop::into_inner(container)) +} + +/// Destroy `engine` and clean up the `ManuallyDrop` container surround it. +#[no_mangle] +pub extern "C" fn pax_dealloc_engine(_container: *mut PaxEngineContainer) { + //particularly for when we need to support elegant clean-up from attached harness + unimplemented!(); +} + +/// Send `interrupt`s from the chassis, for example: user input +/// Note that in any single-threaded environment, these interrupts will happen +/// synchronously between engine ticks, allowing for safe unwrapping / borrowing +/// of engine and runtime here. +#[no_mangle] +pub extern "C" fn pax_interrupt( + engine_container: *mut PaxEngineContainer, + buffer: *const InterruptBuffer, +) { + let mut engine = unsafe { Box::from_raw((*engine_container)._engine) }; + // let slice = unsafe { buffer.as_ref().unwrap() }; + + let length: u64 = unsafe { + (*buffer).length.try_into().unwrap() // length negative or overflowed + }; + + let slice = unsafe { + if (*buffer).data_ptr.is_null() { + &mut [] + } else { + std::slice::from_raw_parts((*buffer).data_ptr, length.try_into().unwrap()) + } + }; + + let interrupt_wrapped: Result = + flexbuffers::from_slice(slice); + let interrupt = interrupt_wrapped.unwrap(); + match interrupt { + NativeInterrupt::Click(args) => { + let prospective_hit = engine.get_topmost_element_beneath_ray((args.x, args.y)); + match prospective_hit { + Some(topmost_node) => { + let modifiers = args + .modifiers + .iter() + .map(|x| ModifierKey::from(x)) + .collect(); + let args_click = ArgsClick { + mouse: MouseEventArgs { + x: args.x, + y: args.y, + button: MouseButton::from(args.button), + modifiers, + }, + }; + topmost_node.dispatch_click(args_click); + } + _ => {} + }; + }, + NativeInterrupt::Scroll(args) => { + let prospective_hit = engine.get_focused_element(); + match prospective_hit { + Some(topmost_node) => { + let args_scroll = ArgsScroll { + delta_x: args.delta_x, + delta_y: args.delta_y, + }; + topmost_node.dispatch_scroll(args_scroll); + } + _ => {} + }; + }, + NativeInterrupt::Image(args) => match args { + ImageLoadInterruptArgs::Reference(ref_args) => { + let ptr = ref_args.image_data as *const u8; + let slice = unsafe { std::slice::from_raw_parts(ptr, ref_args.image_data_length) }; + let owned_data: Vec = slice.to_vec(); + engine.load_image( + ref_args.id_chain, + owned_data, + ref_args.width, + ref_args.height, + ); + } + ImageLoadInterruptArgs::Data(_) => {} + }, + _ => {} + } + + unsafe { (*engine_container)._engine = Box::into_raw(engine) }; +} + +/// Perform full tick of engine, including property computation, lifecycle event handling, and rendering side-effects. +/// Returns a message queue of native rendering actions encoded as a Flexbuffer via FFI to Swift. +/// The returned message queue requires explicit deallocation: `pax_deallocate_message_queue` +#[no_mangle] //Exposed to Swift via PaxCartridge.h +pub extern "C" fn pax_tick( + engine_container: *mut PaxEngineContainer, + cgContext: *mut c_void, + width: f32, + height: f32, +) -> *mut NativeMessageQueue { + // note that f32 is essentially `CFloat`, per: https://doc.rust-lang.org/std/os/raw/type.c_float.html + let mut engine = unsafe { Box::from_raw((*engine_container)._engine) }; + + let will_cast_cgContext = cgContext as *mut CGContext; + let ctx = unsafe { &mut *will_cast_cgContext }; + let render_context = CoreGraphicsContext::new_y_up(ctx, height as f64, None); + (*engine).set_viewport_size((width as f64, height as f64)); + + let mut render_contexts = HashMap::new(); + render_contexts.insert(format!("{}", 0), render_context); + + let messages = (*engine).tick(&mut render_contexts); + + let wrapped_queue = MessageQueue { messages }; + let mut serializer = flexbuffers::FlexbufferSerializer::new(); + + //side-effectfully serialize, mutating `serializer` + wrapped_queue.serialize(&mut serializer).unwrap(); + + let data_buffer = serializer.take_buffer(); + let length = data_buffer.len(); + + let leaked_data: ManuallyDrop> = ManuallyDrop::new(data_buffer.into_boxed_slice()); + + let queue_container = unsafe { + transmute(Box::new(NativeMessageQueue { + data_ptr: Box::into_raw(ManuallyDrop::into_inner(leaked_data)), + length: length as u64, + })) + }; + + //`Box::into_raw` is our necessary manual clean-up, acting as a trigger to drop all of the RefCell::borrow_mut's throughout the tick lifecycle + unsafe { (*engine_container)._engine = Box::into_raw(engine) }; + + queue_container +} + +/// Required manual cleanup callback from Swift after reading a frame's message queue. +/// If this is not called after `pax_tick` is invoked, we will have a memory leak. +#[no_mangle] //Exposed to Swift via PaxCartridge.h +pub extern "C" fn pax_dealloc_message_queue(queue: *mut NativeMessageQueue) { + unsafe { + let queue_container = Box::from_raw(queue); + let data_buffer = Box::from_raw(queue_container.data_ptr); + drop(data_buffer); + drop(queue_container); + } +} diff --git a/pax-chassis-common/src/lib.rs b/pax-chassis-common/src/lib.rs new file mode 100644 index 000000000..46c08d95f --- /dev/null +++ b/pax-chassis-common/src/lib.rs @@ -0,0 +1 @@ +pub mod core_graphics_c_bridge; \ No newline at end of file diff --git a/pax-chassis-ios/Cargo.toml b/pax-chassis-ios/Cargo.toml new file mode 100644 index 000000000..6a0652c01 --- /dev/null +++ b/pax-chassis-ios/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "pax-chassis-ios" +edition = "2021" +version = "0.9.9" +authors = ["Zack Brown "] +license = "MIT OR Apache-2.0" +homepage = "https://pax.dev/" +repository = "https://www.github.com/pax-lang/pax" +description = "Platform-specific chassis allowing Pax cartridges to be executed as native iOS apps" + +[lib] +name = "paxchassisios" +crate-type = ["cdylib"] + +[dependencies] +pax-chassis-common = {version = "0.9.9", path="../pax-chassis-common"} + +# The following empty [workspace] directive is a workaround to satisfy +# a cargo workspace bug, as documented: https://github.com/rust-lang/cargo/issues/6745 +[workspace] \ No newline at end of file diff --git a/pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.pbxproj b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cc4627bcb --- /dev/null +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.pbxproj @@ -0,0 +1,414 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 1405D1202ACF5B1C006EBEF1 /* PaxViewIos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1405D11F2ACF5B1C006EBEF1 /* PaxViewIos.swift */; }; + 1405D1222ACF5B1C006EBEF1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1405D1212ACF5B1C006EBEF1 /* ContentView.swift */; }; + 1405D1242ACF5B1D006EBEF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1405D1232ACF5B1D006EBEF1 /* Assets.xcassets */; }; + 1405D1272ACF5B1D006EBEF1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1405D1262ACF5B1D006EBEF1 /* Preview Assets.xcassets */; }; + 141DEF6D2AD4E82D000EFF82 /* PaxCartridge in Frameworks */ = {isa = PBXBuildFile; productRef = 141DEF6C2AD4E82D000EFF82 /* PaxCartridge */; }; + 141DEF6F2AD4E82D000EFF82 /* PaxCartridgeAssets in Frameworks */ = {isa = PBXBuildFile; productRef = 141DEF6E2AD4E82D000EFF82 /* PaxCartridgeAssets */; }; + 141DEF722AD4E83E000EFF82 /* FlexBuffers in Frameworks */ = {isa = PBXBuildFile; productRef = 141DEF712AD4E83E000EFF82 /* FlexBuffers */; }; + 141DEF742AD4E83E000EFF82 /* Messages in Frameworks */ = {isa = PBXBuildFile; productRef = 141DEF732AD4E83E000EFF82 /* Messages */; }; + 141DEF762AD4E83E000EFF82 /* Rendering in Frameworks */ = {isa = PBXBuildFile; productRef = 141DEF752AD4E83E000EFF82 /* Rendering */; }; + 14BA75902AD4C4DE001B3A61 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14BA758F2AD4C4DE001B3A61 /* Main.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 1405D11C2ACF5B1C006EBEF1 /* Pax iOS (Development).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pax iOS (Development).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1405D11F2ACF5B1C006EBEF1 /* PaxViewIos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaxViewIos.swift; sourceTree = ""; }; + 1405D1212ACF5B1C006EBEF1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 1405D1232ACF5B1D006EBEF1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1405D1262ACF5B1D006EBEF1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 14BA758F2AD4C4DE001B3A61 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 1405D1192ACF5B1C006EBEF1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 141DEF742AD4E83E000EFF82 /* Messages in Frameworks */, + 141DEF762AD4E83E000EFF82 /* Rendering in Frameworks */, + 141DEF6D2AD4E82D000EFF82 /* PaxCartridge in Frameworks */, + 141DEF722AD4E83E000EFF82 /* FlexBuffers in Frameworks */, + 141DEF6F2AD4E82D000EFF82 /* PaxCartridgeAssets in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1405D1132ACF5B1C006EBEF1 = { + isa = PBXGroup; + children = ( + 1405D11E2ACF5B1C006EBEF1 /* pax-app-ios */, + 1405D11D2ACF5B1C006EBEF1 /* Products */, + ); + sourceTree = ""; + }; + 1405D11D2ACF5B1C006EBEF1 /* Products */ = { + isa = PBXGroup; + children = ( + 1405D11C2ACF5B1C006EBEF1 /* Pax iOS (Development).app */, + ); + name = Products; + sourceTree = ""; + }; + 1405D11E2ACF5B1C006EBEF1 /* pax-app-ios */ = { + isa = PBXGroup; + children = ( + 1405D11F2ACF5B1C006EBEF1 /* PaxViewIos.swift */, + 1405D1212ACF5B1C006EBEF1 /* ContentView.swift */, + 1405D1232ACF5B1D006EBEF1 /* Assets.xcassets */, + 1405D1252ACF5B1D006EBEF1 /* Preview Content */, + 14BA758F2AD4C4DE001B3A61 /* Main.swift */, + ); + path = "pax-app-ios"; + sourceTree = ""; + }; + 1405D1252ACF5B1D006EBEF1 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 1405D1262ACF5B1D006EBEF1 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1405D11B2ACF5B1C006EBEF1 /* Pax iOS (Development) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1405D12A2ACF5B1D006EBEF1 /* Build configuration list for PBXNativeTarget "Pax iOS (Development)" */; + buildPhases = ( + 1405D1182ACF5B1C006EBEF1 /* Sources */, + 1405D1192ACF5B1C006EBEF1 /* Frameworks */, + 1405D11A2ACF5B1C006EBEF1 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Pax iOS (Development)"; + packageProductDependencies = ( + 141DEF6C2AD4E82D000EFF82 /* PaxCartridge */, + 141DEF6E2AD4E82D000EFF82 /* PaxCartridgeAssets */, + 141DEF712AD4E83E000EFF82 /* FlexBuffers */, + 141DEF732AD4E83E000EFF82 /* Messages */, + 141DEF752AD4E83E000EFF82 /* Rendering */, + ); + productName = "pax-app-ios"; + productReference = 1405D11C2ACF5B1C006EBEF1 /* Pax iOS (Development).app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 1405D1142ACF5B1C006EBEF1 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 1405D11B2ACF5B1C006EBEF1 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = 1405D1172ACF5B1C006EBEF1 /* Build configuration list for PBXProject "pax-app-ios" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 1405D1132ACF5B1C006EBEF1; + packageReferences = ( + 141DEF6B2AD4E82D000EFF82 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */, + 141DEF702AD4E83E000EFF82 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-common" */, + ); + productRefGroup = 1405D11D2ACF5B1C006EBEF1 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 1405D11B2ACF5B1C006EBEF1 /* Pax iOS (Development) */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 1405D11A2ACF5B1C006EBEF1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1405D1272ACF5B1D006EBEF1 /* Preview Assets.xcassets in Resources */, + 1405D1242ACF5B1D006EBEF1 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 1405D1182ACF5B1C006EBEF1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14BA75902AD4C4DE001B3A61 /* Main.swift in Sources */, + 1405D1222ACF5B1C006EBEF1 /* ContentView.swift in Sources */, + 1405D1202ACF5B1C006EBEF1 /* PaxViewIos.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 1405D1282ACF5B1D006EBEF1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 1405D1292ACF5B1D006EBEF1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 1405D12B2ACF5B1D006EBEF1 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-ios/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-ios"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 1405D12C2ACF5B1D006EBEF1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Don't Code Sign"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-ios/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-ios"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1405D1172ACF5B1C006EBEF1 /* Build configuration list for PBXProject "pax-app-ios" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1405D1282ACF5B1D006EBEF1 /* Debug */, + 1405D1292ACF5B1D006EBEF1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1405D12A2ACF5B1D006EBEF1 /* Build configuration list for PBXNativeTarget "Pax iOS (Development)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1405D12B2ACF5B1D006EBEF1 /* Debug */, + 1405D12C2ACF5B1D006EBEF1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 141DEF6B2AD4E82D000EFF82 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../pax-chassis-common/pax-swift-cartridge"; + }; + 141DEF702AD4E83E000EFF82 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-common" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../pax-chassis-common/pax-swift-common"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 141DEF6C2AD4E82D000EFF82 /* PaxCartridge */ = { + isa = XCSwiftPackageProductDependency; + productName = PaxCartridge; + }; + 141DEF6E2AD4E82D000EFF82 /* PaxCartridgeAssets */ = { + isa = XCSwiftPackageProductDependency; + productName = PaxCartridgeAssets; + }; + 141DEF712AD4E83E000EFF82 /* FlexBuffers */ = { + isa = XCSwiftPackageProductDependency; + productName = FlexBuffers; + }; + 141DEF732AD4E83E000EFF82 /* Messages */ = { + isa = XCSwiftPackageProductDependency; + productName = Messages; + }; + 141DEF752AD4E83E000EFF82 /* Rendering */ = { + isa = XCSwiftPackageProductDependency; + productName = Rendering; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 1405D1142ACF5B1C006EBEF1 /* Project object */; +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to pax-chassis-ios/interface/pax-app-ios/pax-app-ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AccentColor.colorset/Contents.json b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 56% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AccentColor.colorset/Contents.json rename to pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AccentColor.colorset/Contents.json index 2f0f228e3..eb8789700 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,10 +1,6 @@ { "colors" : [ { - "color" : { - "platform" : "universal", - "reference" : "labelColor" - }, "idiom" : "universal" } ], diff --git a/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..13613e3ee --- /dev/null +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/Contents.json b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/Contents.json similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/Contents.json rename to pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Assets.xcassets/Contents.json diff --git a/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/ContentView.swift b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/ContentView.swift new file mode 100644 index 000000000..151706093 --- /dev/null +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/ContentView.swift @@ -0,0 +1,24 @@ +// +// ContentView.swift +// pax-app-ios +// +// Created by Zack Brown on 10/5/23. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Main.swift b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Main.swift new file mode 100644 index 000000000..3b95a10ee --- /dev/null +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Main.swift @@ -0,0 +1,17 @@ +// +// Main.swift +// pax-app-macos +// +// Created by Zack Brown on 10/3/23. +// + +import SwiftUI + +@main +struct pax_app_iosApp: App { + var body: some Scene { + WindowGroup { + PaxViewIos().edgesIgnoringSafeArea(.all) + } + } +} diff --git a/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/PaxViewIos.swift b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/PaxViewIos.swift new file mode 100644 index 000000000..21b0d0753 --- /dev/null +++ b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/PaxViewIos.swift @@ -0,0 +1,371 @@ +// +// ContentView.swift +// interface +// +// Created by Zachary Brown on 4/6/22. +// + +import SwiftUI +import Foundation +import FlexBuffers +import Messages +import Rendering +import PaxCartridgeAssets +import PaxCartridge + +struct PaxViewIos: View { + + var canvasView : some View = PaxCanvasViewRepresentable() + .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) + + @State private var previousScrollLocation: CGPoint? = nil + + var body: some View { + ZStack { + self.canvasView + NativeRenderingLayer() + } + .onAppear { + registerFonts() + } + .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .local) + .onChanged { dragGesture in + if let previous = self.previousScrollLocation { + let deltaX = dragGesture.location.x - previous.x + let deltaY = dragGesture.location.y - previous.y + + let json = String(format: "{\"Scroll\": {\"x\": %f, \"y\": %f, \"delta_x\": %f, \"delta_y\": %f} }", + dragGesture.location.x, + dragGesture.location.y, + -deltaX, + -deltaY) + sendInterrupt(with: json) + } + + self.previousScrollLocation = dragGesture.location + } + .onEnded { dragGesture in + //Reset scroll tracking position + self.previousScrollLocation = nil + + // Handle "Click" events — note that we should probably check to ensure that a maximum distance has not been crossed + // to rightly handle this as a "click". Currently this is more of a `touchend`. + let json = String(format: "{\"Click\": {\"x\": %f, \"y\": %f, \"button\": \"Left\", \"modifiers\":[] } }", dragGesture.location.x, dragGesture.location.y) + sendInterrupt(with: json) + } + ) + } + + func sendInterrupt(with json: String) { + let buffer = try! FlexBufferBuilder.fromJSON(json) + buffer.data.withUnsafeBytes { ptr in + var ffi_container = InterruptBuffer(data_ptr: ptr.baseAddress!, length: UInt64(ptr.count)) + withUnsafePointer(to: &ffi_container) { ffi_container_ptr in + pax_interrupt(PaxEngineContainer.paxEngineContainer!, ffi_container_ptr) + } + } + } + + func registerFonts() { + + let nestedBundleURL = Bundle.main.url(forResource: "PaxSwiftCartridge_PaxCartridgeAssets", withExtension: "bundle")! + + let resourceBundle = Bundle(url: nestedBundleURL)! + + let resourceURL = resourceBundle.resourceURL! + + let fontFileExtensions = ["ttf", "otf"] + + do { + let resourceFiles = try FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil, options: []) + for fileURL in resourceFiles { + let fileExtension = fileURL.pathExtension.lowercased() + if fontFileExtensions.contains(fileExtension) { + let fontDescriptors = CTFontManagerCreateFontDescriptorsFromURL(fileURL as CFURL) as! [CTFontDescriptor] + if let fontDescriptor = fontDescriptors.first, + let postscriptName = CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontNameAttribute) as? String, + let fontFamily = CTFontDescriptorCopyAttribute(fontDescriptor, kCTFontFamilyNameAttribute) as? String { + if !PaxFont.isFontRegistered(fontFamily: postscriptName) { + var errorRef: Unmanaged? + if !CTFontManagerRegisterFontsForURL(fileURL as CFURL, .process, &errorRef) { + print("Error registering font: \(fontFamily) - PostScript name: \(postscriptName) - \(String(describing: errorRef))") + } + } else { + print("Font already registered: \(fontFamily) - PostScript name: \(postscriptName)") + } + } + } + } + } catch { + print("Error reading font files from resources: \(error)") + } + } + + + class PaxEngineContainer { + static var paxEngineContainer : OpaquePointer? = nil + } + + + + struct PaxCanvasViewRepresentable: UIViewRepresentable { + typealias UIViewType = PaxCanvasViewIos + + func makeUIView(context: Context) -> PaxCanvasViewIos { + let view = PaxCanvasViewIos() + return view + } + + func updateUIView(_ uiView: PaxCanvasViewIos, context: Context) { + } + } + + + class PaxCanvasViewIos: UIView { + + @ObservedObject var textElements = TextElements.singleton + @ObservedObject var frameElements = FrameElements.singleton + private var displayLink: CADisplayLink? + + override init(frame: CGRect) { + super.init(frame: frame) + createDisplayLink() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + createDisplayLink() + } + + + private var requestAnimationFrameQueue: [() -> Void] = [] + + private func processRequestAnimationFrameQueue() { + // Execute and remove each closure in the array + while !requestAnimationFrameQueue.isEmpty { + let closure = requestAnimationFrameQueue.removeFirst() + closure() + } + } + + func requestAnimationFrame(_ closure: @escaping () -> Void) { + requestAnimationFrameQueue.append(closure) + } + + + private func createDisplayLink() { + displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink)) + displayLink?.add(to: .current, forMode: .common) + } + + @objc private func handleDisplayLink() { + DispatchQueue.main.async { + self.setNeedsDisplay() + self.processRequestAnimationFrameQueue() + } + } + + deinit { + displayLink?.invalidate() + } + + override func draw(_ rect: CGRect) { + super.draw(rect) + guard let cgContext = UIGraphicsGetCurrentContext() else { return } + + // Apply affine transform to cgContext to emulate macOS's "y-up" coordinate space. + cgContext.translateBy(x: 0, y: rect.height) // Move the origin to the bottom-left + cgContext.scaleBy(x: 1.0, y: -1.0) // Reflect over x axis + + if PaxEngineContainer.paxEngineContainer == nil { + let swiftLoggerCallback : @convention(c) (UnsafePointer?) -> () = { + (msg) -> () in + let outputString = String(cString: msg!) + print(outputString) + } + + PaxEngineContainer.paxEngineContainer = pax_init(swiftLoggerCallback) + } else { + guard var mutableCGContext = UIGraphicsGetCurrentContext() else { return } + let nativeMessageQueue = pax_tick(PaxEngineContainer.paxEngineContainer!, &mutableCGContext, Float(rect.width), Float(rect.height)) + processNativeMessageQueue(queue: nativeMessageQueue.unsafelyUnwrapped.pointee) + pax_dealloc_message_queue(nativeMessageQueue) + } + + if currentTickWorkItem != nil { + currentTickWorkItem!.cancel() + } + + currentTickWorkItem = DispatchWorkItem { + self.setNeedsDisplay(rect) + self.setNeedsLayout() + } + + } + + var currentTickWorkItem : DispatchWorkItem? = nil + + func handleTextCreate(patch: AnyCreatePatch) { + textElements.add(element: TextElement.makeDefault(id_chain: patch.id_chain, clipping_ids: patch.clipping_ids)) + } + + func handleTextUpdate(patch: TextUpdatePatch) { + textElements.elements[patch.id_chain]?.applyPatch(patch: patch) + textElements.objectWillChange.send() + } + + func handleTextDelete(patch: AnyDeletePatch) { + self.textElements.remove(id: patch.id_chain) + } + + func handleFrameCreate(patch: AnyCreatePatch) { + frameElements.add(element: FrameElement.makeDefault(id_chain: patch.id_chain)) + } + + func handleFrameUpdate(patch: FrameUpdatePatch) { + frameElements.elements[patch.id_chain]?.applyPatch(patch: patch) + frameElements.objectWillChange.send() + } + + func handleFrameDelete(patch: AnyDeletePatch) { + frameElements.remove(id: patch.id_chain) + } + + func printAllFilesInBundle() { + let bundleURL = Bundle.main.bundleURL + + do { + let resourceURLs = try FileManager.default.contentsOfDirectory(at: bundleURL, includingPropertiesForKeys: nil, options: []) + for url in resourceURLs { + print(url.lastPathComponent) + } + } catch { + print("Error: \(error)") + } + } + + func handleImageLoad(patch: ImageLoadPatch) { + Task { + do { + let fullPatchPath = patch.path! + let url = URL(fileURLWithPath: fullPatchPath) + let fileNameWithExtension = url.lastPathComponent + let fileExtension = url.pathExtension + let fileName = String(fileNameWithExtension.prefix(fileNameWithExtension.count - fileExtension.count - 1)) + + guard let nestedBundleURL = Bundle.main.url(forResource: "PaxSwiftCartridge_PaxCartridgeAssets", withExtension: "bundle") else { + throw NSError(domain: "", code: 99, userInfo: [NSLocalizedDescriptionKey : "PaxCartridgeAssets bundle not found in main bundle. Make sure you have imported PaxCartridgeAssets in Swift."]) + } + + let assetsBundle = Bundle(url: nestedBundleURL) + + guard let imageURL = assetsBundle?.url(forResource: fileName, withExtension: fileExtension) else { + throw NSError(domain: "", code: 100, userInfo: [NSLocalizedDescriptionKey : "Image file not found in nested bundle"]) + } + + guard let image = UIImage(contentsOfFile: imageURL.path) else { + throw NSError(domain: "", code: 101, userInfo: [NSLocalizedDescriptionKey : "Could not create UIImage from data"]) + } + + guard let cgImage = image.cgImage else { + throw NSError(domain: "", code: 102, userInfo: [NSLocalizedDescriptionKey : "Could not retrieve CGImage from UIImage"]) + } + + let width = cgImage.width + let height = cgImage.height + let bitsPerComponent = cgImage.bitsPerComponent + let bytesPerRow = cgImage.bytesPerRow + let totalBytes = height * bytesPerRow + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue + + guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else { + throw NSError(domain: "", code: 103, userInfo: [NSLocalizedDescriptionKey : "Could not create CGContext"]) + } + + context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height)) + + guard let data = context.data else { + throw NSError(domain: "", code: 104, userInfo: [NSLocalizedDescriptionKey : "Could not retrieve pixel data from context"]) + } + + let byteBuffer = data.assumingMemoryBound(to: UInt8.self) + + let id_chain : FlxbValueVector = FlxbValueVector.init(values: patch.id_chain.map { (number) -> FlxbValue in + return number as FlxbValue + }) + let raw_pointer_uint = UInt(bitPattern: byteBuffer) + + let buffer = try! FlexBufferBuilder.encode( + [ "Image": [ "Reference": [ + "id_chain": id_chain, + "image_data": raw_pointer_uint, + "image_data_length": totalBytes, + "width": width, + "height": height, + ] as FlxbValueMap] as FlxbValueMap ] as FlxbValueMap) + + buffer.data.withUnsafeBytes { ptr in + var ffi_container = InterruptBuffer(data_ptr: ptr.baseAddress!, length: UInt64(ptr.count)) + withUnsafePointer(to: &ffi_container) { ffi_container_ptr in + pax_interrupt(PaxEngineContainer.paxEngineContainer!, ffi_container_ptr) + } + } + } catch { + print("Failed to load image data: \(error)") + } + } + } + + + + func processNativeMessageQueue(queue: NativeMessageQueue) { + + let buffer = UnsafeBufferPointer(start: queue.data_ptr!, count: Int(queue.length)) + let root = FlexBuffer.decode(data: Data.init(buffer: buffer))! + + root["messages"]?.asVector?.makeIterator().forEach( { message in + + let textCreateMessage = message["TextCreate"] + if textCreateMessage != nil { + handleTextCreate(patch: AnyCreatePatch(fb: textCreateMessage!)) + } + + let textUpdateMessage = message["TextUpdate"] + if textUpdateMessage != nil { + handleTextUpdate(patch: TextUpdatePatch(fb: textUpdateMessage!)) + } + + let textDeleteMessage = message["TextDelete"] + if textDeleteMessage != nil { + handleTextDelete(patch: AnyDeletePatch(fb: textDeleteMessage!)) + } + + let frameCreateMessage = message["FrameCreate"] + if frameCreateMessage != nil { + handleFrameCreate(patch: AnyCreatePatch(fb: frameCreateMessage!)) + } + + let frameUpdateMessage = message["FrameUpdate"] + if frameUpdateMessage != nil { + handleFrameUpdate(patch: FrameUpdatePatch(fb: frameUpdateMessage!)) + } + + let frameDeleteMessage = message["FrameDelete"] + if frameDeleteMessage != nil { + handleFrameDelete(patch: AnyDeletePatch(fb: frameDeleteMessage!)) + } + + let imageLoadMessage = message["ImageLoad"] + if imageLoadMessage != nil { + handleImageLoad(patch: ImageLoadPatch(fb: imageLoadMessage!)) + } + + //^ Add new message-receive handlers here ^ + }) + + } + + } +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Preview Content/Preview Assets.xcassets/Contents.json b/pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Preview Content/Preview Assets.xcassets/Contents.json rename to pax-chassis-ios/interface/pax-app-ios/pax-app-ios/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/pax-chassis-ios/src/lib.rs b/pax-chassis-ios/src/lib.rs new file mode 100644 index 000000000..f8fbd4464 --- /dev/null +++ b/pax-chassis-ios/src/lib.rs @@ -0,0 +1 @@ +pub use pax_chassis_common::core_graphics_c_bridge::*; \ No newline at end of file diff --git a/pax-chassis-macos/Cargo.toml b/pax-chassis-macos/Cargo.toml index f5c5e32d3..10fabf811 100644 --- a/pax-chassis-macos/Cargo.toml +++ b/pax-chassis-macos/Cargo.toml @@ -10,11 +10,12 @@ description = "Platform-specific chassis allowing Pax cartridges to be executed [lib] name = "paxchassismacos" -crate-type = ["staticlib", "cdylib"] +crate-type = ["cdylib"] [dependencies] piet = "0.6.0" piet-coregraphics = "0.6.0" +pax-chassis-common = { path = "../pax-chassis-common", version="0.9.9" } pax-core = { path = "../pax-core", version="0.9.9" } pax-cartridge = {path="../pax-cartridge", version="0.9.9"} pax-message = {path = "../pax-message", version="0.9.9"} diff --git a/pax-chassis-macos/README.md b/pax-chassis-macos/README.md index cd5514a1c..880ddea85 100644 --- a/pax-chassis-macos/README.md +++ b/pax-chassis-macos/README.md @@ -9,7 +9,7 @@ Handles: This directory also includes: -## pax-dev-harness-macos +## interface Simple macOS app for developing Pax projects. Also usable as a template for packaging full-window Pax apps for macOS diff --git a/pax-chassis-macos/interface/README.md b/pax-chassis-macos/interface/README.md new file mode 100644 index 000000000..212c0a075 --- /dev/null +++ b/pax-chassis-macos/interface/README.md @@ -0,0 +1,11 @@ +# pax-chassis-macos/interface + +Much of the interface for macOS is described in `pax-chassis-common`, and is common across iOS and macOS. + +Thus, the interface for pax-chassis-macos includes only the relative complement of (`macOS \ iOS`), namely: a macOS full-app wrapper for a Pax component. + +This is useful for local development on a Mac or for publishing full-app Pax-mac projects. + +If you wish to use a Pax project as a SwiftUI view inside e.g. an existing Swift project, +look to the SPM package created inside `pax-chassis-common/pax-swift-cartridge`. That same SPM package +is consumed by pax-app-macos. \ No newline at end of file diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.pbxproj b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3dd779093 --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.pbxproj @@ -0,0 +1,564 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 60; + objects = { + +/* Begin PBXBuildFile section */ + 1486C80F2ACE4E48003AB8B8 /* PaxCartridge in Frameworks */ = {isa = PBXBuildFile; productRef = 1486C80E2ACE4E48003AB8B8 /* PaxCartridge */; }; + 1486C8112ACE4E48003AB8B8 /* PaxCartridgeAssets in Frameworks */ = {isa = PBXBuildFile; productRef = 1486C8102ACE4E48003AB8B8 /* PaxCartridgeAssets */; }; + 14A8ED632ADE1A7C00292406 /* PaxViewMacos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FDCD4A2ACC9D690060F532 /* PaxViewMacos.swift */; }; + 14A8ED642ADE1A7C00292406 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FDCD482ACC9D690060F532 /* Main.swift */; }; + 14A8ED662ADE1A7C00292406 /* Messages in Frameworks */ = {isa = PBXBuildFile; productRef = 14A8ED5D2ADE1A7C00292406 /* Messages */; }; + 14A8ED672ADE1A7C00292406 /* FlexBuffers in Frameworks */ = {isa = PBXBuildFile; productRef = 14A8ED5C2ADE1A7C00292406 /* FlexBuffers */; }; + 14A8ED682ADE1A7C00292406 /* PaxCartridge in Frameworks */ = {isa = PBXBuildFile; productRef = 14A8ED5F2ADE1A7C00292406 /* PaxCartridge */; }; + 14A8ED692ADE1A7C00292406 /* PaxCartridgeAssets in Frameworks */ = {isa = PBXBuildFile; productRef = 14A8ED612ADE1A7C00292406 /* PaxCartridgeAssets */; }; + 14A8ED6A2ADE1A7C00292406 /* Rendering in Frameworks */ = {isa = PBXBuildFile; productRef = 14A8ED5E2ADE1A7C00292406 /* Rendering */; }; + 14A8ED6C2ADE1A7C00292406 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14FDCD4F2ACC9D6A0060F532 /* Preview Assets.xcassets */; }; + 14A8ED6D2ADE1A7C00292406 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14FDCD4C2ACC9D6A0060F532 /* Assets.xcassets */; }; + 14FDCD492ACC9D690060F532 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FDCD482ACC9D690060F532 /* Main.swift */; }; + 14FDCD4B2ACC9D690060F532 /* PaxViewMacos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FDCD4A2ACC9D690060F532 /* PaxViewMacos.swift */; }; + 14FDCD4D2ACC9D6A0060F532 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14FDCD4C2ACC9D6A0060F532 /* Assets.xcassets */; }; + 14FDCD502ACC9D6A0060F532 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14FDCD4F2ACC9D6A0060F532 /* Preview Assets.xcassets */; }; + 14FDCD592ACC9E7B0060F532 /* FlexBuffers in Frameworks */ = {isa = PBXBuildFile; productRef = 14FDCD582ACC9E7B0060F532 /* FlexBuffers */; }; + 14FDCD5B2ACC9E7B0060F532 /* Messages in Frameworks */ = {isa = PBXBuildFile; productRef = 14FDCD5A2ACC9E7B0060F532 /* Messages */; }; + 14FDCD5D2ACC9E7B0060F532 /* Rendering in Frameworks */ = {isa = PBXBuildFile; productRef = 14FDCD5C2ACC9E7B0060F532 /* Rendering */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 14A8ED712ADE1A7C00292406 /* Pax macOS (Release).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pax macOS (Release).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14FDCD452ACC9D690060F532 /* Pax macOS (Development).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pax macOS (Development).app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 14FDCD482ACC9D690060F532 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; }; + 14FDCD4A2ACC9D690060F532 /* PaxViewMacos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaxViewMacos.swift; sourceTree = ""; }; + 14FDCD4C2ACC9D6A0060F532 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 14FDCD4F2ACC9D6A0060F532 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 14FDCD512ACC9D6A0060F532 /* pax_app_macos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pax_app_macos.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 14A8ED652ADE1A7C00292406 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 14A8ED662ADE1A7C00292406 /* Messages in Frameworks */, + 14A8ED672ADE1A7C00292406 /* FlexBuffers in Frameworks */, + 14A8ED682ADE1A7C00292406 /* PaxCartridge in Frameworks */, + 14A8ED692ADE1A7C00292406 /* PaxCartridgeAssets in Frameworks */, + 14A8ED6A2ADE1A7C00292406 /* Rendering in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14FDCD422ACC9D690060F532 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 14FDCD5B2ACC9E7B0060F532 /* Messages in Frameworks */, + 14FDCD592ACC9E7B0060F532 /* FlexBuffers in Frameworks */, + 1486C80F2ACE4E48003AB8B8 /* PaxCartridge in Frameworks */, + 1486C8112ACE4E48003AB8B8 /* PaxCartridgeAssets in Frameworks */, + 14FDCD5D2ACC9E7B0060F532 /* Rendering in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1486C80D2ACE4E48003AB8B8 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 14FDCD3C2ACC9D690060F532 = { + isa = PBXGroup; + children = ( + 14FDCD472ACC9D690060F532 /* pax-app-macos */, + 14FDCD462ACC9D690060F532 /* Products */, + 1486C80D2ACE4E48003AB8B8 /* Frameworks */, + ); + sourceTree = ""; + }; + 14FDCD462ACC9D690060F532 /* Products */ = { + isa = PBXGroup; + children = ( + 14FDCD452ACC9D690060F532 /* Pax macOS (Development).app */, + 14A8ED712ADE1A7C00292406 /* Pax macOS (Release).app */, + ); + name = Products; + sourceTree = ""; + }; + 14FDCD472ACC9D690060F532 /* pax-app-macos */ = { + isa = PBXGroup; + children = ( + 14FDCD482ACC9D690060F532 /* Main.swift */, + 14FDCD4A2ACC9D690060F532 /* PaxViewMacos.swift */, + 14FDCD4C2ACC9D6A0060F532 /* Assets.xcassets */, + 14FDCD512ACC9D6A0060F532 /* pax_app_macos.entitlements */, + 14FDCD4E2ACC9D6A0060F532 /* Preview Content */, + ); + path = "pax-app-macos"; + sourceTree = ""; + }; + 14FDCD4E2ACC9D6A0060F532 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 14FDCD4F2ACC9D6A0060F532 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 14A8ED5B2ADE1A7C00292406 /* Pax macOS (Release) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14A8ED6E2ADE1A7C00292406 /* Build configuration list for PBXNativeTarget "Pax macOS (Release)" */; + buildPhases = ( + 14A8ED622ADE1A7C00292406 /* Sources */, + 14A8ED652ADE1A7C00292406 /* Frameworks */, + 14A8ED6B2ADE1A7C00292406 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Pax macOS (Release)"; + packageProductDependencies = ( + 14A8ED5C2ADE1A7C00292406 /* FlexBuffers */, + 14A8ED5D2ADE1A7C00292406 /* Messages */, + 14A8ED5E2ADE1A7C00292406 /* Rendering */, + 14A8ED5F2ADE1A7C00292406 /* PaxCartridge */, + 14A8ED612ADE1A7C00292406 /* PaxCartridgeAssets */, + ); + productName = "pax-app-macos"; + productReference = 14A8ED712ADE1A7C00292406 /* Pax macOS (Release).app */; + productType = "com.apple.product-type.application"; + }; + 14FDCD442ACC9D690060F532 /* Pax macOS (Development) */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14FDCD542ACC9D6A0060F532 /* Build configuration list for PBXNativeTarget "Pax macOS (Development)" */; + buildPhases = ( + 14FDCD412ACC9D690060F532 /* Sources */, + 14FDCD422ACC9D690060F532 /* Frameworks */, + 14FDCD432ACC9D690060F532 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Pax macOS (Development)"; + packageProductDependencies = ( + 14FDCD582ACC9E7B0060F532 /* FlexBuffers */, + 14FDCD5A2ACC9E7B0060F532 /* Messages */, + 14FDCD5C2ACC9E7B0060F532 /* Rendering */, + 1486C80E2ACE4E48003AB8B8 /* PaxCartridge */, + 1486C8102ACE4E48003AB8B8 /* PaxCartridgeAssets */, + ); + productName = "pax-app-macos"; + productReference = 14FDCD452ACC9D690060F532 /* Pax macOS (Development).app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 14FDCD3D2ACC9D690060F532 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + 14FDCD442ACC9D690060F532 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = 14FDCD402ACC9D690060F532 /* Build configuration list for PBXProject "pax-app-macos" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 14FDCD3C2ACC9D690060F532; + packageReferences = ( + 14FDCD572ACC9E7B0060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-common" */, + 14FDCD5E2ACC9EB80060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */, + ); + productRefGroup = 14FDCD462ACC9D690060F532 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 14FDCD442ACC9D690060F532 /* Pax macOS (Development) */, + 14A8ED5B2ADE1A7C00292406 /* Pax macOS (Release) */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 14A8ED6B2ADE1A7C00292406 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14A8ED6C2ADE1A7C00292406 /* Preview Assets.xcassets in Resources */, + 14A8ED6D2ADE1A7C00292406 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14FDCD432ACC9D690060F532 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14FDCD502ACC9D6A0060F532 /* Preview Assets.xcassets in Resources */, + 14FDCD4D2ACC9D6A0060F532 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 14A8ED622ADE1A7C00292406 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14A8ED632ADE1A7C00292406 /* PaxViewMacos.swift in Sources */, + 14A8ED642ADE1A7C00292406 /* Main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 14FDCD412ACC9D690060F532 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14FDCD4B2ACC9D690060F532 /* PaxViewMacos.swift in Sources */, + 14FDCD492ACC9D690060F532 /* Main.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 14A8ED6F2ADE1A7C00292406 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "pax-app-macos/pax_app_macos.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-macos/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 14A8ED702ADE1A7C00292406 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "pax-app-macos/pax_app_macos.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-macos/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 14FDCD522ACC9D6A0060F532 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 14FDCD532ACC9D6A0060F532 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 14FDCD552ACC9D6A0060F532 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "pax-app-macos/pax_app_macos.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-macos/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 14FDCD562ACC9D6A0060F532 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = "pax-app-macos/pax_app_macos.entitlements"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"pax-app-macos/Preview Content\""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.pax.pax-app-macos"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 14A8ED6E2ADE1A7C00292406 /* Build configuration list for PBXNativeTarget "Pax macOS (Release)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14A8ED6F2ADE1A7C00292406 /* Debug */, + 14A8ED702ADE1A7C00292406 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14FDCD402ACC9D690060F532 /* Build configuration list for PBXProject "pax-app-macos" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14FDCD522ACC9D6A0060F532 /* Debug */, + 14FDCD532ACC9D6A0060F532 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 14FDCD542ACC9D6A0060F532 /* Build configuration list for PBXNativeTarget "Pax macOS (Development)" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 14FDCD552ACC9D6A0060F532 /* Debug */, + 14FDCD562ACC9D6A0060F532 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCLocalSwiftPackageReference section */ + 14A8ED602ADE1A7C00292406 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../pax-chassis-common/pax-swift-cartridge"; + }; + 14FDCD572ACC9E7B0060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-common" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../pax-chassis-common/pax-swift-common"; + }; + 14FDCD5E2ACC9EB80060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = "../../../pax-chassis-common/pax-swift-cartridge"; + }; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 1486C80E2ACE4E48003AB8B8 /* PaxCartridge */ = { + isa = XCSwiftPackageProductDependency; + package = 14FDCD5E2ACC9EB80060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */; + productName = PaxCartridge; + }; + 1486C8102ACE4E48003AB8B8 /* PaxCartridgeAssets */ = { + isa = XCSwiftPackageProductDependency; + package = 14FDCD5E2ACC9EB80060F532 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */; + productName = PaxCartridgeAssets; + }; + 14A8ED5C2ADE1A7C00292406 /* FlexBuffers */ = { + isa = XCSwiftPackageProductDependency; + productName = FlexBuffers; + }; + 14A8ED5D2ADE1A7C00292406 /* Messages */ = { + isa = XCSwiftPackageProductDependency; + productName = Messages; + }; + 14A8ED5E2ADE1A7C00292406 /* Rendering */ = { + isa = XCSwiftPackageProductDependency; + productName = Rendering; + }; + 14A8ED5F2ADE1A7C00292406 /* PaxCartridge */ = { + isa = XCSwiftPackageProductDependency; + package = 14A8ED602ADE1A7C00292406 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */; + productName = PaxCartridge; + }; + 14A8ED612ADE1A7C00292406 /* PaxCartridgeAssets */ = { + isa = XCSwiftPackageProductDependency; + package = 14A8ED602ADE1A7C00292406 /* XCLocalSwiftPackageReference "../../../pax-chassis-common/pax-swift-cartridge" */; + productName = PaxCartridgeAssets; + }; + 14FDCD582ACC9E7B0060F532 /* FlexBuffers */ = { + isa = XCSwiftPackageProductDependency; + productName = FlexBuffers; + }; + 14FDCD5A2ACC9E7B0060F532 /* Messages */ = { + isa = XCSwiftPackageProductDependency; + productName = Messages; + }; + 14FDCD5C2ACC9E7B0060F532 /* Rendering */ = { + isa = XCSwiftPackageProductDependency; + productName = Rendering; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 14FDCD3D2ACC9D690060F532 /* Project object */; +} diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Info.plist b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 51% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Info.plist rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist index 196a270ce..18d981003 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Info.plist +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -1,12 +1,8 @@ - - + + IDEDidComputeMac32BitWarning + + diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist similarity index 54% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist index 735c82355..f5cb84b2b 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos.xcodeproj/xcuserdata/zack.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,23 +4,25 @@ SchemeUserState - Pax macOS.xcscheme_^#shared#^_ + Pax macOS (Development) copy.xcscheme_^#shared#^_ + + orderHint + 1 + + Pax macOS (Development).xcscheme_^#shared#^_ orderHint 0 - pax-dev-harness-macos.xcscheme_^#shared#^_ + debug.xcscheme_^#shared#^_ orderHint 0 - - SuppressBuildableAutocreation - - 14965C0027FE3D0F00A38CA1 + pax-app-macos.xcscheme_^#shared#^_ - primary - + orderHint + 0 diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AccentColor.colorset/Contents.json b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 000000000..eb8789700 --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/Contents.json b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/Contents.json rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-1024.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-1024.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-1024.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-1024.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-128.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-128.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-128.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-128.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-16.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-16.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-16.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-16.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256 1.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256 1.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256 1.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256 1.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-256.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32 1.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32 1.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32 1.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32 1.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-32.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512 1.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512 1.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512 1.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512 1.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-512.png diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-64.png b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-64.png similarity index 100% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-64.png rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/AppIcon.appiconset/pax-logo-64.png diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/Contents.json b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Main.swift b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Main.swift new file mode 100644 index 000000000..61b56bde9 --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Main.swift @@ -0,0 +1,17 @@ +// +// Main.swift +// pax-app-macos +// +// Created by Zack Brown on 10/3/23. +// + +import SwiftUI + +@main +struct pax_app_macosApp: App { + var body: some Scene { + WindowGroup { + PaxViewMacos() + } + } +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/PaxView.swift b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/PaxViewMacos.swift similarity index 70% rename from pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/PaxView.swift rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/PaxViewMacos.swift index 46feb310e..e493a0352 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/PaxView.swift +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/PaxViewMacos.swift @@ -1,43 +1,19 @@ // // ContentView.swift -// pax-dev-harness-macos +// interface // // Created by Zachary Brown on 4/6/22. // import SwiftUI import Foundation +import FlexBuffers +import Messages +import Rendering +import PaxCartridgeAssets +import PaxCartridge -class TextElements: ObservableObject { - static let singleton : TextElements = TextElements() - - @Published var elements : [[UInt64]: TextElement] = [:] - - func add(element: TextElement) { - self.elements[element.id_chain] = element - } - func remove(id: [UInt64]) { - self.elements.removeValue(forKey: id) - } -} - -class FrameElements: ObservableObject { - static let singleton : FrameElements = FrameElements() - - @Published var elements : [[UInt64]: FrameElement] = [:] - - func add(element: FrameElement) { - self.elements[element.id_chain] = element - } - func remove(id: [UInt64]) { - self.elements.removeValue(forKey: id) - } - func get(id: [UInt64]) -> FrameElement? { - return self.elements[id] - } -} - -struct PaxView: View { +struct PaxViewMacos: View { var canvasView : some View = PaxCanvasViewRepresentable() .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) @@ -52,22 +28,28 @@ struct PaxView: View { }.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global).onEnded { dragGesture in //FUTURE: especially if parsing is a bottleneck, could use a different encoding than JSON let json = String(format: "{\"Click\": {\"x\": %f, \"y\": %f, \"button\": \"Left\", \"modifiers\":[] } }", dragGesture.location.x, dragGesture.location.y); - let buffer = try! FlexBufferBuilder.fromJSON(json) + let buffer = try! FlexBufferBuilder.fromJSON(json) - //Send `Click` interrupt - buffer.data.withUnsafeBytes({ptr in - var ffi_container = InterruptBuffer( data_ptr: ptr.baseAddress!, length: UInt64(ptr.count) ) + //Send `Click` interrupt + buffer.data.withUnsafeBytes({ptr in + var ffi_container = InterruptBuffer( data_ptr: ptr.baseAddress!, length: UInt64(ptr.count) ) - withUnsafePointer(to: &ffi_container) {ffi_container_ptr in - pax_interrupt(PaxEngineContainer.paxEngineContainer!, ffi_container_ptr) - } - }) + withUnsafePointer(to: &ffi_container) {ffi_container_ptr in + pax_interrupt(PaxEngineContainer.paxEngineContainer!, ffi_container_ptr) + } + }) }) } func registerFonts() { - guard let resourceURL = Bundle.main.resourceURL else { return } + + let nestedBundleURL = Bundle.main.url(forResource: "PaxSwiftCartridge_PaxCartridgeAssets", withExtension: "bundle")! + + let resourceBundle = Bundle(url: nestedBundleURL)! + + let resourceURL = resourceBundle.resourceURL! + let fontFileExtensions = ["ttf", "otf"] do { @@ -96,110 +78,25 @@ struct PaxView: View { } - - class PaxEngineContainer { static var paxEngineContainer : OpaquePointer? = nil } - struct NativeRenderingLayer: View { - - @ObservedObject var textElements : TextElements = TextElements.singleton - @ObservedObject var frameElements : FrameElements = FrameElements.singleton - - func getClippingMask(clippingIds: [[UInt64]]) -> some View { - - var elements : [FrameElement] = [] - - clippingIds.makeIterator().forEach( { id_chain in - elements.insert(self.frameElements.elements[id_chain]!, at: 0) - }) - - return ZStack { ForEach(elements, id: \.id_chain) { frameElement in - Rectangle() - .frame(width: CGFloat(frameElement.size_x), height: CGFloat(frameElement.size_y)) - .position(x: CGFloat(frameElement.size_x / 2.0), y: CGFloat(frameElement.size_y / 2.0)) - .transformEffect(CGAffineTransform.init( - a: CGFloat(frameElement.transform[0]), - b: CGFloat(frameElement.transform[1]), - c: CGFloat(frameElement.transform[2]), - d: CGFloat(frameElement.transform[3]), - tx: CGFloat(frameElement.transform[4]), - ty: CGFloat(frameElement.transform[5])) - ) - } } - } - - @ViewBuilder - func getPositionedTextGroup(textElement: TextElement) -> some View { - let transform = CGAffineTransform.init( - a: CGFloat(textElement.transform[0]), - b: CGFloat(textElement.transform[1]), - c: CGFloat(textElement.transform[2]), - d: CGFloat(textElement.transform[3]), - tx: CGFloat(textElement.transform[4]), - ty: CGFloat(textElement.transform[5]) - ) - var text: AttributedString { - var attributedString: AttributedString = try! AttributedString(markdown: textElement.content, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) - - for run in attributedString.runs { - if run.link != nil { - if let linkStyle = textElement.style_link { - attributedString[run.range].font = linkStyle.font.getFont(size: linkStyle.font_size) - if(linkStyle.underline){ - attributedString[run.range].underlineStyle = .single - } else { - attributedString[run.range].underlineStyle = .none - } - attributedString[run.range].foregroundColor = linkStyle.fill - } - } - } - return attributedString - - } - let textView : some View = - Text(text) - .foregroundColor(textElement.textStyle.fill) - .font(textElement.textStyle.font.getFont(size: textElement.textStyle.font_size)) - .frame(width: CGFloat(textElement.size_x), height: CGFloat(textElement.size_y), alignment: textElement.textStyle.alignment) - .position(x: CGFloat(textElement.size_x / 2.0), y: CGFloat(textElement.size_y / 2.0)) - .transformEffect(transform) - .textSelection(.enabled) - -// -// if !textElement.clipping_ids.isEmpty { -// textView.mask(getClippingMask(clippingIds: textElement.clipping_ids)) -// } else { -// textView -// } - textView - } - - var body: some View { - ZStack{ - ForEach(Array(self.textElements.elements.values), id: \.id_chain) { textElement in - getPositionedTextGroup(textElement: textElement) - } - } - - } - } + struct PaxCanvasViewRepresentable: NSViewRepresentable { - typealias NSViewType = PaxCanvasView + typealias NSViewType = PaxCanvasViewMacos - func makeNSView(context: Context) -> PaxCanvasView { - let view = PaxCanvasView() + func makeNSView(context: Context) -> PaxCanvasViewMacos { + let view = PaxCanvasViewMacos() return view } - func updateNSView(_ canvas: PaxCanvasView, context: Context) { } + func updateNSView(_ canvas: PaxCanvasViewMacos, context: Context) { } } - class PaxCanvasView: NSView { + class PaxCanvasViewMacos: NSView { @ObservedObject var textElements = TextElements.singleton @ObservedObject var frameElements = FrameElements.singleton @@ -207,6 +104,8 @@ struct PaxView: View { private var displayLink: CVDisplayLink? override init(frame frameRect: NSRect) { + + super.init(frame: frameRect) self.wantsLayer = true self.layer?.drawsAsynchronously = true @@ -344,17 +243,23 @@ struct PaxView: View { func handleImageLoad(patch: ImageLoadPatch) { Task { do { - let path = patch.path! - let url = URL(fileURLWithPath: path) + let fullPatchPath = patch.path! + let url = URL(fileURLWithPath: fullPatchPath) let fileNameWithExtension = url.lastPathComponent let fileExtension = url.pathExtension let fileName = String(fileNameWithExtension.prefix(fileNameWithExtension.count - fileExtension.count - 1)) - guard let bundleURL = Bundle.main.url(forResource: fileName, withExtension: fileExtension) else { - throw NSError(domain: "", code: 100, userInfo: [NSLocalizedDescriptionKey : "Image file not found in bundle"]) + guard let nestedBundleURL = Bundle.main.url(forResource: "PaxSwiftCartridge_PaxCartridgeAssets", withExtension: "bundle") else { + throw NSError(domain: "", code: 99, userInfo: [NSLocalizedDescriptionKey : "PaxCartridgeAssets bundle not found in main bundle. Make sure you have imported PaxCartridgeAssets in Swift."]) + } + + let assetsBundle = Bundle(url: nestedBundleURL) + + guard let imageURL = assetsBundle?.url(forResource: fileName, withExtension: fileExtension) else { + throw NSError(domain: "", code: 100, userInfo: [NSLocalizedDescriptionKey : "Image file not found in nested bundle"]) } - guard let image = NSImage(contentsOf: bundleURL) else { + guard let image = NSImage(contentsOf: imageURL) else { throw NSError(domain: "", code: 101, userInfo: [NSLocalizedDescriptionKey : "Could not create NSImage from data"]) } diff --git a/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Preview Content/Preview Assets.xcassets/Contents.json b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/pax-chassis-macos/pax-dev-harness-macos/Pax macOSDebug.entitlements b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/pax_app_macos.entitlements similarity index 63% rename from pax-chassis-macos/pax-dev-harness-macos/Pax macOSDebug.entitlements rename to pax-chassis-macos/interface/pax-app-macos/pax-app-macos/pax_app_macos.entitlements index 71a0adc2c..18aff0ce4 100644 --- a/pax-chassis-macos/pax-dev-harness-macos/Pax macOSDebug.entitlements +++ b/pax-chassis-macos/interface/pax-app-macos/pax-app-macos/pax_app_macos.entitlements @@ -4,13 +4,7 @@ com.apple.security.app-sandbox - com.apple.security.cs.disable-library-validation - com.apple.security.files.user-selected.read-only - com.apple.security.network.client - - com.apple.security.network.server - diff --git a/pax-chassis-macos/pax-dev-harness-macos/Paxchassismacos-Bridging-Header.h b/pax-chassis-macos/pax-dev-harness-macos/Paxchassismacos-Bridging-Header.h deleted file mode 100644 index 4072196e6..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/Paxchassismacos-Bridging-Header.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// Paxchassismacos-Bridging-Header.h -// pax-dev-harness-macos -// -// Created by Zachary Brown on 4/12/22. -// - -#ifndef Paxchassismacos_Bridging_Header_h -#define Paxchassismacos_Bridging_Header_h - -#import "paxchassismacos.h" - -#endif /* Paxchassismacos_Bridging_Header_h */ - diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.pbxproj b/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.pbxproj deleted file mode 100644 index 73991fe3d..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/project.pbxproj +++ /dev/null @@ -1,466 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - 140C6AF92825C99C003BACE3 /* FlexBufferBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140C6AF62825C99B003BACE3 /* FlexBufferBuilder.swift */; }; - 140C6AFA2825C99C003BACE3 /* FlexBuffers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140C6AF72825C99B003BACE3 /* FlexBuffers.swift */; }; - 140C6AFE28276BCD003BACE3 /* Messages.swift in Sources */ = {isa = PBXBuildFile; fileRef = 140C6AFD28276BCD003BACE3 /* Messages.swift */; }; - 1459D7E4280F2922006391F8 /* libpaxchassismacos.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1459D7E1280F2902006391F8 /* libpaxchassismacos.dylib */; }; - 1459D7E5280F2922006391F8 /* libpaxchassismacos.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 1459D7E1280F2902006391F8 /* libpaxchassismacos.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 1459D7E8280F2933006391F8 /* libpaxchassismacos.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1459D7E7280F2922006391F8 /* libpaxchassismacos.a */; }; - 14965C0527FE3D0F00A38CA1 /* Main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14965C0427FE3D0F00A38CA1 /* Main.swift */; }; - 14965C0727FE3D0F00A38CA1 /* PaxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14965C0627FE3D0F00A38CA1 /* PaxView.swift */; }; - 14965C0927FE3D1100A38CA1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14965C0827FE3D1100A38CA1 /* Assets.xcassets */; }; - 14965C0C27FE3D1100A38CA1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14965C0B27FE3D1100A38CA1 /* Preview Assets.xcassets */; }; - 14965C1E2805EAFD00A38CA1 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 14965C1D2805EAF500A38CA1 /* libresolv.tbd */; platformFilter = maccatalyst; }; -/* End PBXBuildFile section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 1459D7E6280F2922006391F8 /* Embed Libraries */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 1459D7E5280F2922006391F8 /* libpaxchassismacos.dylib in Embed Libraries */, - ); - name = "Embed Libraries"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 140C6AF62825C99B003BACE3 /* FlexBufferBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexBufferBuilder.swift; sourceTree = ""; }; - 140C6AF72825C99B003BACE3 /* FlexBuffers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlexBuffers.swift; sourceTree = ""; }; - 140C6AF82825C99C003BACE3 /* FlexBuffers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlexBuffers.h; sourceTree = ""; }; - 140C6AFD28276BCD003BACE3 /* Messages.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Messages.swift; sourceTree = ""; }; - 1459D7E1280F2902006391F8 /* libpaxchassismacos.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpaxchassismacos.dylib; path = ../target/debug/libpaxchassismacos.dylib; sourceTree = ""; }; - 1459D7E7280F2922006391F8 /* libpaxchassismacos.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpaxchassismacos.a; path = ../target/debug/libpaxchassismacos.a; sourceTree = ""; }; - 14619AC429C505180024805D /* Pax macOSDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Pax macOSDebug.entitlements"; sourceTree = ""; }; - 14965C0127FE3D0F00A38CA1 /* Pax macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pax macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 14965C0427FE3D0F00A38CA1 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Main.swift; sourceTree = ""; }; - 14965C0627FE3D0F00A38CA1 /* PaxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaxView.swift; sourceTree = ""; }; - 14965C0827FE3D1100A38CA1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 14965C0B27FE3D1100A38CA1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 14965C0D27FE3D1100A38CA1 /* pax_dev_harness_macos.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = pax_dev_harness_macos.entitlements; sourceTree = ""; }; - 14965C142805E47D00A38CA1 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../src; sourceTree = ""; }; - 14965C152805E4BF00A38CA1 /* paxchassismacos.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = paxchassismacos.h; path = ../src/paxchassismacos.h; sourceTree = ""; }; - 14965C162805E4EF00A38CA1 /* Paxchassismacos-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Paxchassismacos-Bridging-Header.h"; sourceTree = ""; }; - 14965C172805EA3B00A38CA1 /* libpaxchassismacos.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libpaxchassismacos.a; path = ../../target/release/libpaxchassismacos.a; sourceTree = ""; }; - 14965C192805EAA700A38CA1 /* libpaxchassismacos.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpaxchassismacos.dylib; path = ../../target/release/libpaxchassismacos.dylib; sourceTree = ""; }; - 14965C1D2805EAF500A38CA1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 14965BFE27FE3D0F00A38CA1 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1459D7E8280F2933006391F8 /* libpaxchassismacos.a in Frameworks */, - 14965C1E2805EAFD00A38CA1 /* libresolv.tbd in Frameworks */, - 1459D7E4280F2922006391F8 /* libpaxchassismacos.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 140C6AFB28276B89003BACE3 /* FlexBuffers */ = { - isa = PBXGroup; - children = ( - 140C6AF62825C99B003BACE3 /* FlexBufferBuilder.swift */, - 140C6AF82825C99C003BACE3 /* FlexBuffers.h */, - 140C6AF72825C99B003BACE3 /* FlexBuffers.swift */, - ); - path = FlexBuffers; - sourceTree = ""; - }; - 14965BF827FE3D0F00A38CA1 = { - isa = PBXGroup; - children = ( - 14619AC429C505180024805D /* Pax macOSDebug.entitlements */, - 14965C162805E4EF00A38CA1 /* Paxchassismacos-Bridging-Header.h */, - 14965C152805E4BF00A38CA1 /* paxchassismacos.h */, - 14965C0327FE3D0F00A38CA1 /* pax-dev-harness-macos */, - 14965C0227FE3D0F00A38CA1 /* Products */, - 14965C132805E47D00A38CA1 /* Frameworks */, - ); - sourceTree = ""; - }; - 14965C0227FE3D0F00A38CA1 /* Products */ = { - isa = PBXGroup; - children = ( - 14965C0127FE3D0F00A38CA1 /* Pax macOS.app */, - ); - name = Products; - sourceTree = ""; - }; - 14965C0327FE3D0F00A38CA1 /* pax-dev-harness-macos */ = { - isa = PBXGroup; - children = ( - 140C6AFB28276B89003BACE3 /* FlexBuffers */, - 14965C0427FE3D0F00A38CA1 /* Main.swift */, - 140C6AFD28276BCD003BACE3 /* Messages.swift */, - 14965C0627FE3D0F00A38CA1 /* PaxView.swift */, - 14965C0827FE3D1100A38CA1 /* Assets.xcassets */, - 14965C0D27FE3D1100A38CA1 /* pax_dev_harness_macos.entitlements */, - 14965C0A27FE3D1100A38CA1 /* Preview Content */, - ); - path = "pax-dev-harness-macos"; - sourceTree = ""; - }; - 14965C0A27FE3D1100A38CA1 /* Preview Content */ = { - isa = PBXGroup; - children = ( - 14965C0B27FE3D1100A38CA1 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - 14965C132805E47D00A38CA1 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 1459D7E7280F2922006391F8 /* libpaxchassismacos.a */, - 1459D7E1280F2902006391F8 /* libpaxchassismacos.dylib */, - 14965C1D2805EAF500A38CA1 /* libresolv.tbd */, - 14965C192805EAA700A38CA1 /* libpaxchassismacos.dylib */, - 14965C172805EA3B00A38CA1 /* libpaxchassismacos.a */, - 14965C142805E47D00A38CA1 /* src */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 14965C0027FE3D0F00A38CA1 /* Pax macOS */ = { - isa = PBXNativeTarget; - buildConfigurationList = 14965C1027FE3D1100A38CA1 /* Build configuration list for PBXNativeTarget "Pax macOS" */; - buildPhases = ( - 14965BFD27FE3D0F00A38CA1 /* Sources */, - 14965BFE27FE3D0F00A38CA1 /* Frameworks */, - 14965BFF27FE3D0F00A38CA1 /* Resources */, - 1459D7E6280F2922006391F8 /* Embed Libraries */, - D082094129FE19DD006C5651 /* Copy Font & Image Files */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "Pax macOS"; - packageProductDependencies = ( - ); - productName = "pax-dev-harness-macos"; - productReference = 14965C0127FE3D0F00A38CA1 /* Pax macOS.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 14965BF927FE3D0F00A38CA1 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1300; - LastUpgradeCheck = 1430; - TargetAttributes = { - 14965C0027FE3D0F00A38CA1 = { - CreatedOnToolsVersion = 13.0; - }; - }; - }; - buildConfigurationList = 14965BFC27FE3D0F00A38CA1 /* Build configuration list for PBXProject "pax-dev-harness-macos" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 14965BF827FE3D0F00A38CA1; - packageReferences = ( - ); - productRefGroup = 14965C0227FE3D0F00A38CA1 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 14965C0027FE3D0F00A38CA1 /* Pax macOS */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 14965BFF27FE3D0F00A38CA1 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 14965C0C27FE3D1100A38CA1 /* Preview Assets.xcassets in Resources */, - 14965C0927FE3D1100A38CA1 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - D082094129FE19DD006C5651 /* Copy Font & Image Files */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 12; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Copy Font & Image Files"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "# Define the target folder for resources\nRESOURCES_PATH=\"$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH\"\n\n# Find the .pax folder\nPAX_FOLDER_DIR=$(find ../../../../../../ -type d -name \"*.pax\" -print -quit)\n\n# Get the parent directory of the .pax folder\nPAX_PARENT_DIR=$(dirname \"$PAX_FOLDER_DIR\")\n\n# Find the assets folder on the same level as the .pax folder\nASSETS_TARGET_DIR=$(find \"$PAX_PARENT_DIR\" -maxdepth 1 -type d -name \"assets\" -print -quit)\necho \"Copying assets from: $ASSETS_TARGET_DIR to: $RESOURCES_PATH\"\n\n# Copy all the files recursively if the ASSETS_TARGET_DIR is found\nif [ -d \"$ASSETS_TARGET_DIR\" ]; then\n # Using find to locate and copy files including the directory structure\n for ext in ttf otf jpg png gif tif bmp svg; do\n find \"$ASSETS_TARGET_DIR\" -name \"*.$ext\" -exec cp {} \"$RESOURCES_PATH\" \\;\n done\nelse\n echo \"WARNING: ASSETS_TARGET_DIR not found! Skipping assets copy.\"\nfi\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 14965BFD27FE3D0F00A38CA1 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 140C6AFA2825C99C003BACE3 /* FlexBuffers.swift in Sources */, - 14965C0727FE3D0F00A38CA1 /* PaxView.swift in Sources */, - 140C6AFE28276BCD003BACE3 /* Messages.swift in Sources */, - 14965C0527FE3D0F00A38CA1 /* Main.swift in Sources */, - 140C6AF92825C99C003BACE3 /* FlexBufferBuilder.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 14965C0E27FE3D1100A38CA1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; - CLANG_CXX_LIBRARY = ""; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = ""; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - EXCLUDED_ARCHS = ""; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.3; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - NEW_SETTING = ""; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 14965C0F27FE3D1100A38CA1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; - CLANG_CXX_LIBRARY = ""; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = ""; - COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - EXCLUDED_ARCHS = ""; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.3; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - NEW_SETTING = ""; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - 14965C1127FE3D1100A38CA1 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "Pax macOSDebug.entitlements"; - CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_ASSET_PATHS = "\"pax-dev-harness-macos/Preview Content\""; - DEVELOPMENT_TEAM = ""; - ENABLE_HARDENED_RUNTIME = NO; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../target/debug"; - MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "org.pax-lang.pax-dev-harness-macos"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx13.3; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/Paxchassismacos-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 14965C1227FE3D1100A38CA1 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "pax-dev-harness-macos/pax_dev_harness_macos.entitlements"; - CODE_SIGN_IDENTITY = ""; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; - CODE_SIGN_STYLE = Manual; - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; - DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_ASSET_PATHS = "\"pax-dev-harness-macos/Preview Content\""; - DEVELOPMENT_TEAM = ""; - ENABLE_HARDENED_RUNTIME = NO; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/../target/debug"; - MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "org.pax-lang.pax-dev-harness-macos"; - PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx13.3; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/Paxchassismacos-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 14965BFC27FE3D0F00A38CA1 /* Build configuration list for PBXProject "pax-dev-harness-macos" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 14965C0E27FE3D1100A38CA1 /* Debug */, - 14965C0F27FE3D1100A38CA1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 14965C1027FE3D1100A38CA1 /* Build configuration list for PBXNativeTarget "Pax macOS" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 14965C1127FE3D1100A38CA1 /* Debug */, - 14965C1227FE3D1100A38CA1 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 14965BF927FE3D0F00A38CA1 /* Project object */; -} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcshareddata/xcschemes/Pax macOS.xcscheme b/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcshareddata/xcschemes/Pax macOS.xcscheme deleted file mode 100644 index 35a038d92..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos.xcodeproj/xcshareddata/xcschemes/Pax macOS.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Main.swift b/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Main.swift deleted file mode 100644 index 5f464a332..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/Main.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Main.swift -// pax-dev-harness-macos -// -// Created by Zachary Brown on 4/6/22. -// - -import SwiftUI - -@main -struct pax_dev_harness_macosApp: App { - var body: some Scene { - WindowGroup { - PaxView() - } - } -} diff --git a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/pax_dev_harness_macos.entitlements b/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/pax_dev_harness_macos.entitlements deleted file mode 100644 index 71a0adc2c..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/pax-dev-harness-macos/pax_dev_harness_macos.entitlements +++ /dev/null @@ -1,16 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.disable-library-validation - - com.apple.security.files.user-selected.read-only - - com.apple.security.network.client - - com.apple.security.network.server - - - diff --git a/pax-chassis-macos/pax-dev-harness-macos/run-debuggable-mac-app.sh b/pax-chassis-macos/pax-dev-harness-macos/run-debuggable-mac-app.sh deleted file mode 100755 index a0435d793..000000000 --- a/pax-chassis-macos/pax-dev-harness-macos/run-debuggable-mac-app.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -# Expects args: -# 1: VERBOSE ∈ {"true" , "false"} -# 2: EXCLUDE_ARCHS ∈ {"arm64" , "x86_64"} -# 3: SHOULD_ALSO_RUN ∈ {"true" , "false"} -# 4: OUTPUT_PATH : output directory for build -VERBOSE=$1 -EXCLUDE_ARCHS=$2 -SHOULD_ALSO_RUN=$3 -OUTPUT_PATH=$4 - -runbuild () { - xcodebuild archive \ - -configuration Debug \ - -scheme "Pax macOS" \ - -archivePath build/PaxDevHarnessMacos.xcarchive \ - -sdk macosx13.3 \ - SKIP_INSTALL=NO SUPPORTS_MACCATALYST=YES ONLY_ACTIVE_ARCH=YES EXCLUDED_ARCHS="$EXCLUDE_ARCHS" -} - -# Build -if [ "$VERBOSE" == "true" ]; then - # Pipe output to both stdout and stderr - runbuild 2>&1 -else - # Pipe stdout to /dev/null, but pipe stderr to /dev/stderr - runbuild > /dev/null 2>&1 >&2 -fi - -# Clear old build and move to output directory -rm -rf "$OUTPUT_PATH" -mkdir -p "$OUTPUT_PATH" -cp -r "build/PaxDevHarnessMacos.xcarchive/Products/Applications/Pax macOS.app" "$OUTPUT_PATH" -cd "$OUTPUT_PATH" - -if [ "$SHOULD_ALSO_RUN" == "true" ]; then - # Run - Pax\ macOS.app/Contents/MacOS/Pax\ macOS -fi \ No newline at end of file diff --git a/pax-chassis-macos/src/lib.rs b/pax-chassis-macos/src/lib.rs index e6d21ce13..8d4e0df61 100644 --- a/pax-chassis-macos/src/lib.rs +++ b/pax-chassis-macos/src/lib.rs @@ -1,212 +1,2 @@ -#![allow(non_snake_case)] //Non-snake-case is used here to help denote foreign structs, e.g. from Swift via C -extern crate core; - -use std::cell::RefCell; -use std::collections::HashMap; -use std::ffi::c_void; -use std::rc::Rc; - -use std::mem::{transmute, ManuallyDrop}; -use std::os::raw::c_char; - -use core_graphics::context::CGContext; -use piet_coregraphics::CoreGraphicsContext; - -use flexbuffers; -use flexbuffers::DeserializationError; -use serde::Serialize; - -use pax_cartridge; -use pax_core::{InstanceRegistry, PaxEngine}; - -//Re-export all native message types; used by Swift via FFI. -//Note that any types exposed by pax_message must ALSO be added to `paxchassismacos.h` -//in order to be visible to Swift -pub use pax_message::*; -use pax_runtime_api::{ArgsClick, ArgsScroll, ModifierKey, MouseButton, MouseEventArgs}; - -/// Container data structure for PaxEngine, aggregated to support passing across C bridge -#[repr(C)] //Exposed to Swift via paxchassismacos.h -pub struct PaxEngineContainer { - _engine: *mut PaxEngine>, - //NOTE: since that has become a single field, this data structure may be be retired and `*mut PaxEngine` could be passed directly. -} - -/// Allocate an instance of the Pax engine, with a specified root/main component from the loaded `pax_cartridge`. -#[no_mangle] //Exposed to Swift via paxchassismacos.h -pub extern "C" fn pax_init(logger: extern "C" fn(*const c_char)) -> *mut PaxEngineContainer { - //Initialize a ManuallyDrop-contained PaxEngine, so that a pointer to that - //engine can be passed back to Swift via the C (FFI) bridge - //This could presumably be cleaned up -- see `pax_dealloc_engine` - let instance_registry: Rc>>> = - Rc::new(RefCell::new(InstanceRegistry::new())); - let main_component_instance = - pax_cartridge::instantiate_main_component(Rc::clone(&instance_registry)); - let expression_table = pax_cartridge::instantiate_expression_table(); - - let engine: ManuallyDrop>>> = - ManuallyDrop::new(Box::new(PaxEngine::new( - main_component_instance, - expression_table, - pax_runtime_api::PlatformSpecificLogger::MacOS(logger), - (1.0, 1.0), - instance_registry, - ))); - - let container = ManuallyDrop::new(Box::new(PaxEngineContainer { - _engine: Box::into_raw(ManuallyDrop::into_inner(engine)), - })); - - Box::into_raw(ManuallyDrop::into_inner(container)) -} - -/// Destroy `engine` and clean up the `ManuallyDrop` container surround it. -#[no_mangle] -pub extern "C" fn pax_dealloc_engine(_container: *mut PaxEngineContainer) { - //particularly for when we need to support elegant clean-up from attached harness - unimplemented!(); -} - -/// Send `interrupt`s from the chassis, for example: user input -/// Note that in any single-threaded environemnt, these interrupts will happen -/// synchronously between engine ticks, allowing for safe unwrapping / borrowing -/// of engine and runtime here. In short: this happens between ticks. -#[no_mangle] -pub extern "C" fn pax_interrupt( - engine_container: *mut PaxEngineContainer, - buffer: *const InterruptBuffer, -) { - let mut engine = unsafe { Box::from_raw((*engine_container)._engine) }; - // let slice = unsafe { buffer.as_ref().unwrap() }; - - let length: u64 = unsafe { - (*buffer).length.try_into().unwrap() // length negative or overflowed - }; - - let slice = unsafe { - if (*buffer).data_ptr.is_null() { - &mut [] - } else { - std::slice::from_raw_parts((*buffer).data_ptr, length.try_into().unwrap()) - } - }; - - let interrupt_wrapped: Result = - flexbuffers::from_slice(slice); - let interrupt = interrupt_wrapped.unwrap(); - match interrupt { - NativeInterrupt::Click(args) => { - let prospective_hit = engine.get_topmost_element_beneath_ray((args.x, args.y)); - match prospective_hit { - Some(topmost_node) => { - let modifiers = args - .modifiers - .iter() - .map(|x| ModifierKey::from(x)) - .collect(); - let args_click = ArgsClick { - mouse: MouseEventArgs { - x: args.x, - y: args.y, - button: MouseButton::from(args.button), - modifiers, - }, - }; - topmost_node.dispatch_click(args_click); - } - _ => {} - }; - } - - NativeInterrupt::Scroll(args) => { - let prospective_hit = engine.get_focused_element(); - match prospective_hit { - Some(topmost_node) => { - let args_scroll = ArgsScroll { - delta_x: args.delta_x, - delta_y: args.delta_y, - }; - topmost_node.dispatch_scroll(args_scroll); - } - _ => {} - }; - } - NativeInterrupt::Image(args) => match args { - ImageLoadInterruptArgs::Reference(ref_args) => { - let ptr = ref_args.image_data as *const u8; - let slice = unsafe { std::slice::from_raw_parts(ptr, ref_args.image_data_length) }; - let owned_data: Vec = slice.to_vec(); - engine.load_image( - ref_args.id_chain, - owned_data, - ref_args.width, - ref_args.height, - ); - } - ImageLoadInterruptArgs::Data(_) => {} - }, - _ => {} - } - - unsafe { (*engine_container)._engine = Box::into_raw(engine) }; -} - -/// Perform full tick of engine, including property computation, lifecycle event handling, and rendering side-effects. -/// Returns a message queue of native rendering actions encoded as a Flexbuffer via FFI to Swift. -/// The returned message queue requires explicit deallocation: `pax_deallocate_message_queue` -#[no_mangle] //Exposed to Swift via paxchassismacos.h -pub extern "C" fn pax_tick( - engine_container: *mut PaxEngineContainer, - cgContext: *mut c_void, - width: f32, - height: f32, -) -> *mut NativeMessageQueue { - // note that f32 is essentially `CFloat`, per: https://doc.rust-lang.org/std/os/raw/type.c_float.html - let mut engine = unsafe { Box::from_raw((*engine_container)._engine) }; - - let will_cast_cgContext = cgContext as *mut CGContext; - let ctx = unsafe { &mut *will_cast_cgContext }; - let render_context = CoreGraphicsContext::new_y_up(ctx, height as f64, None); - (*engine).set_viewport_size((width as f64, height as f64)); - - let mut render_contexts = HashMap::new(); - render_contexts.insert(format!("{}", 0), render_context); - - let messages = (*engine).tick(&mut render_contexts); - - let wrapped_queue = MessageQueue { messages }; - let mut serializer = flexbuffers::FlexbufferSerializer::new(); - - //side-effectfully serialize, mutating `serializer` - wrapped_queue.serialize(&mut serializer).unwrap(); - - let data_buffer = serializer.take_buffer(); - let length = data_buffer.len(); - - let leaked_data: ManuallyDrop> = ManuallyDrop::new(data_buffer.into_boxed_slice()); - - let queue_container = unsafe { - transmute(Box::new(NativeMessageQueue { - data_ptr: Box::into_raw(ManuallyDrop::into_inner(leaked_data)), - length: length as u64, - })) - }; - - //`Box::into_raw` is our necessary manual clean-up, acting as a trigger to drop all of the RefCell::borrow_mut's throughout the tick lifecycle - unsafe { (*engine_container)._engine = Box::into_raw(engine) }; - - queue_container -} - -/// Required manual cleanup callback from Swift after reading a frame's message queue. -/// If this is not called after `pax_tick` is invoked, we will have a memory leak. -#[no_mangle] //Exposed to Swift via paxchassismacos.h -pub extern "C" fn pax_dealloc_message_queue(queue: *mut NativeMessageQueue) { - unsafe { - let queue_container = Box::from_raw(queue); - let data_buffer = Box::from_raw(queue_container.data_ptr); - drop(data_buffer); - drop(queue_container); - } -} +pub use pax_chassis_common::core_graphics_c_bridge::*; \ No newline at end of file diff --git a/pax-cli/src/main.rs b/pax-cli/src/main.rs index 49b8e56af..f6e2b7d5d 100644 --- a/pax-cli/src/main.rs +++ b/pax-cli/src/main.rs @@ -1,10 +1,9 @@ use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use colored::{ColoredString, Colorize}; use std::io::Write; -use std::path::Path; use std::sync::{Arc, Mutex}; use std::time::Duration; -use std::{fs, process, thread}; +use std::{process, thread}; use pax_compiler::{CreateContext, RunContext, RunTarget}; extern crate pax_language_server; @@ -14,7 +13,9 @@ mod http; use signal_hook::consts::{SIGINT, SIGTERM}; use signal_hook::iterator::Signals; +/// `pax-cli` entrypoint fn main() -> Result<(), ()> { + //Shared state to store child processes keyed by static unique string IDs, for cleanup tracking let process_child_ids: Arc>> = Arc::new(Mutex::new(vec![])); // Shared state to store the new version info if available. @@ -26,20 +27,6 @@ fn main() -> Result<(), ()> { http::check_for_update(cloned_new_version_info); }); - // Create a separate thread to handle signals e.g. via CTRL+C - let mut signals = Signals::new(&[SIGINT, SIGTERM]).unwrap(); - let cloned_version_info = Arc::clone(&new_version_info); - let cloned_process_child_ids = Arc::clone(&process_child_ids); - thread::spawn(move || { - for _sig in signals.forever() { - println!("\nInterrupt received. Cleaning up child processes..."); - perform_cleanup( - Arc::clone(&cloned_version_info), - Arc::clone(&cloned_process_child_ids), - ); - } - }); - #[allow(non_snake_case)] let ARG_PATH = Arg::with_name("path") .short("p") @@ -53,17 +40,21 @@ fn main() -> Result<(), ()> { .long("verbose") .takes_value(false); + const DEFAULT_TARGET : &str = "web"; #[allow(non_snake_case)] let ARG_TARGET = Arg::with_name("target") .short("t") .long("target") - //Default to web -- perhaps the ideal would be to discover host - //platform and run appropriate native harness. Web is a suitable, - //sane default for now. - .default_value("web") + .default_value(DEFAULT_TARGET) .help("Specify the target platform on which to run. Will run in platform-specific demo harness.") .takes_value(true); + #[allow(non_snake_case)] + let ARG_RELEASE = Arg::with_name("release") + .long("release") + .takes_value(false) + .help("Build in Release mode, with appropriate platform-specific optimizations."); + #[allow(non_snake_case)] let ARG_LIBDEV = Arg::with_name("libdev") .long("libdev") @@ -78,7 +69,7 @@ fn main() -> Result<(), ()> { let matches = App::new("pax") .name("pax") - .bin_name("pax") + .bin_name("pax-cli") .about("Pax CLI including compiler and dev tooling") .version(crate_version!()) .setting(AppSettings::SubcommandRequiredElseHelp) @@ -98,6 +89,7 @@ fn main() -> Result<(), ()> { .arg( ARG_TARGET.clone() ) .arg( ARG_VERBOSE.clone() ) .arg( ARG_LIBDEV.clone() ) + .arg( ARG_RELEASE.clone() ) ) .subcommand( App::new("clean") @@ -107,6 +99,7 @@ fn main() -> Result<(), ()> { ) .subcommand( App::new("create") + .about("Creates a new Pax + Rust project at the specified path, including necessary boilerplate and default configuration.") .alias("new") .arg(Arg::with_name("path") .help("File system path where the new project should be created. If not provided with --path, it should directly follow 'create'") @@ -116,12 +109,6 @@ fn main() -> Result<(), ()> { ) .subcommand( App::new("libdev") - .subcommand( - App::new("build-chassis") - .arg( ARG_PATH.clone() ) - .arg( ARG_TARGET.clone() ) - .about("Runs cargo build on the codegenned chassis, within the .pax folder contained within the specified `path`. Useful for core development, e.g. building compiler features or compiler debugging. Expected to fail if the whole compiler has not run at least once.") - ) .subcommand( App::new("parse") .arg( ARG_PATH.clone() ) @@ -132,10 +119,29 @@ fn main() -> Result<(), ()> { .subcommand(ARG_LSP.clone()) .get_matches(); - let _ = perform_nominal_action(matches, Arc::clone(&process_child_ids)); - perform_cleanup(new_version_info, process_child_ids); + // Clap doesn't easily let us check a "global" arg without performing individual `match`es. + // Since we want to know at this top level whether `--libdev` is present, we will parse it manually. + let args: Vec = std::env::args().collect(); + let is_libdev_mode = args.contains(&"--libdev".to_string()); + + // Create a separate thread to handle signals e.g. via CTRL+C + let mut signals = Signals::new(&[SIGINT, SIGTERM]).unwrap(); + let cloned_version_info = Arc::clone(&new_version_info); + let cloned_process_child_ids = Arc::clone(&process_child_ids); + thread::spawn(move || { + for _sig in signals.forever() { + println!("\nInterrupt received. Cleaning up child processes..."); + perform_cleanup( + Arc::clone(&cloned_version_info), + Arc::clone(&cloned_process_child_ids), + is_libdev_mode, + ); + } + }); - Ok(()) + let res = perform_nominal_action(matches, Arc::clone(&process_child_ids)); + perform_cleanup(new_version_info, process_child_ids, is_libdev_mode); + res } fn perform_nominal_action( @@ -156,6 +162,7 @@ fn perform_nominal_action( should_also_run: true, is_libdev_mode, process_child_ids, + is_release: false, }) } ("build", Some(args)) => { @@ -163,6 +170,7 @@ fn perform_nominal_action( let path = args.value_of("path").unwrap().to_string(); //default value "." let verbose = args.is_present("verbose"); let is_libdev_mode = args.is_present("libdev"); + let is_release = args.is_present("release"); pax_compiler::perform_build(&RunContext { target: RunTarget::from(target.as_str()), @@ -171,6 +179,7 @@ fn perform_nominal_action( verbose, is_libdev_mode, process_child_ids, + is_release, }) } ("clean", Some(args)) => { @@ -211,38 +220,6 @@ fn perform_nominal_action( Ok(()) } - ("build-chassis", Some(args)) => { - let target = args.value_of("target").unwrap().to_lowercase(); - let path = args.value_of("path").unwrap().to_string(); //default value "." - - let working_path = Path::new(&path).join(".pax"); - let pax_dir = fs::canonicalize(working_path).unwrap(); - - let ctx = RunContext { - target: RunTarget::from(target.as_str()), - path, - verbose: true, - should_also_run: false, - is_libdev_mode: true, - process_child_ids: Arc::new(Mutex::new(vec![])), - }; - - let output = pax_compiler::build_chassis_with_cartridge( - &pax_dir, - &ctx, - process_child_ids, - ); - - // Forward both stdout and stderr - std::io::stderr() - .write_all(output.stderr.as_slice()) - .unwrap(); - std::io::stdout() - .write_all(output.stdout.as_slice()) - .unwrap(); - - Ok(()) - } _ => { unreachable!() } @@ -259,6 +236,7 @@ fn perform_nominal_action( fn perform_cleanup( new_version_info: Arc>>, process_child_ids: Arc>>, + is_libdev_mode: bool, ) { //1. kill any running child processes if let Ok(process_child_ids_lock) = process_child_ids.lock() { @@ -270,64 +248,66 @@ fn perform_cleanup( //2. print update message if appropriate if let Ok(new_version_lock) = new_version_info.lock() { - if let Some(new_version) = new_version_lock.as_ref() { - if new_version != "" { - //Print our banner if we have a concrete value stored in the new version mutex - const TOTAL_LENGTH: usize = 60; - let stars_line: ColoredString = - "*".repeat(TOTAL_LENGTH).bright_white().on_bright_black(); - let empty_line: ColoredString = - " ".repeat(TOTAL_LENGTH).bright_white().on_bright_black(); - - let new_version_static = " A new version of the Pax CLI is available: "; - let new_version_formatted = format!("{}{}", new_version_static, new_version); - let new_version_line: ColoredString = - format!("{: ( type_id: "isize".to_string(), }; - let scope = HashMap::from([ + let scope: HashMap = HashMap::from([ //`elem` property (by specified name) (elem_id.clone(), property_definition), ]); diff --git a/pax-compiler/src/lib.rs b/pax-compiler/src/lib.rs index c42770963..f24f09123 100644 --- a/pax-compiler/src/lib.rs +++ b/pax-compiler/src/lib.rs @@ -1,21 +1,12 @@ extern crate core; -use lazy_static::lazy_static; - -use include_dir::{include_dir, Dir}; - pub mod expressions; pub mod manifest; pub mod parsing; pub mod templating; -use pax_runtime_api::CommonProperties; - use manifest::PaxManifest; -use rust_format::Formatter; - -use fs_extra::dir::{self, CopyOptions}; -use itertools::Itertools; +use pax_runtime_api::CommonProperties; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; @@ -23,37 +14,77 @@ use std::fs; use std::io::Write; use std::str::FromStr; use std::sync::{Arc, Mutex}; +use std::thread; use actix_web::middleware::Logger; use actix_web::{App, HttpServer}; use env_logger; use flate2::read::GzDecoder; +use fs_extra::dir::{self, CopyOptions}; +use itertools::Itertools; +use rust_format::Formatter; use std::net::TcpListener; use tar::Archive; +use include_dir::{include_dir, Dir}; +use lazy_static::lazy_static; + #[cfg(unix)] -use std::os::unix::process::CommandExt; // For the .pre_exec() method +use std::os::unix::process::CommandExt; use crate::manifest::{ ComponentDefinition, EventDefinition, ExpressionSpec, LiteralBlockDefinition, TemplateNodeDefinition, TypeDefinition, TypeTable, ValueDefinition, }; + use crate::templating::{ press_template_codegen_cartridge_component_factory, press_template_codegen_cartridge_render_node_literal, TemplateArgsCodegenCartridgeComponentFactory, TemplateArgsCodegenCartridgeRenderNodeLiteral, }; + use std::path::{Path, PathBuf}; use std::process::{Command, Output}; use toml_edit::Item; -//relative to pax_dir -pub const REEXPORTS_PARTIAL_RS_PATH: &str = "reexports.partial.rs"; +lazy_static! { + #[allow(non_snake_case)] + static ref PAX_BADGE: ColoredString = "[Pax]".bold().on_black().white(); + + static ref DIR_IGNORE_LIST_MACOS : Vec<&'static str> = vec!["target", ".build", ".git"]; + static ref DIR_IGNORE_LIST_WEB : Vec<&'static str> = vec![".git"]; +} + +static PAX_CREATE_TEMPLATE: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/new-project-template"); + +const PAX_CREATE_TEMPLATE_DIR_NAME: &str = "new-project-template"; +const PKG_DIR_NAME: &str = "pkg"; +const BUILD_DIR_NAME: &str = "build"; +const PUBLIC_DIR_NAME: &str = "public"; +const ASSETS_DIR_NAME: &str = "assets"; +const REEXPORTS_PARTIAL_FILE_NAME: &str = "reexports.partial.rs"; +const RUST_IOS_DYLIB_FILE_NAME: &str = "libpaxchassisios.dylib"; +const RUST_MACOS_DYLIB_FILE_NAME: &str = "libpaxchassismacos.dylib"; +const PORTABLE_DYLIB_INSTALL_NAME: &str = "@rpath/PaxCartridge.framework/PaxCartridge"; + +const XCODE_MACOS_TARGET_DEBUG: &str = "Pax macOS (Development)"; +const XCODE_MACOS_TARGET_RELEASE: &str = "Pax macOS (Release)"; +const XCODE_IOS_TARGET_DEBUG: &str = "Pax iOS (Development)"; +const XCODE_IOS_TARGET_RELEASE: &str = "Pax iOS (Release)"; + +// These package IDs represent the directory / package names inside the xcframework, +const MACOS_MULTIARCH_PACKAGE_ID: &str = "macos-arm64_x86_64"; +const IOS_SIMULATOR_MULTIARCH_PACKAGE_ID: &str = "ios-arm64_x86_64-simulator"; +const IOS_PACKAGE_ID: &str = "ios-arm64"; + +const ERR_SPAWN: &str = "failed to spawn child"; //whitelist of package ids that are relevant to the compiler, e.g. for cloning & patching, for assembling FS paths, //or for looking up package IDs from a userland Cargo.lock. -const ALL_PKGS: [&'static str; 12] = [ +const ALL_PKGS: [&'static str; 14] = [ "pax-cartridge", + "pax-chassis-common", + "pax-chassis-ios", "pax-chassis-macos", "pax-chassis-web", "pax-cli", @@ -67,73 +98,23 @@ const ALL_PKGS: [&'static str; 12] = [ "pax-std", ]; -/// Returns a sorted and de-duped list of combined_reexports. -fn generate_reexports_partial_rs(pax_dir: &PathBuf, manifest: &PaxManifest) { - let imports = manifest.import_paths.clone().into_iter().sorted().collect(); - - let file_contents = &bundle_reexports_into_namespace_string(&imports); - - let path = pax_dir.join(Path::new(REEXPORTS_PARTIAL_RS_PATH)); - fs::write(path, file_contents).unwrap(); -} - -fn bundle_reexports_into_namespace_string(sorted_reexports: &Vec) -> String { - let mut root = NamespaceTrieNode { - node_string: None, - children: Default::default(), - }; - - for s in sorted_reexports { - root.insert(s); - } - - root.serialize_to_reexports() -} - -fn update_property_prefixes_in_place(manifest: &mut PaxManifest, host_crate_info: &HostCrateInfo) { - let mut updated_type_table = HashMap::new(); - manifest.type_table.iter_mut().for_each(|t| { - t.1.type_id_escaped = t.1.type_id_escaped.replace("{PREFIX}", ""); - t.1.type_id = - t.1.type_id - .replace("{PREFIX}", &host_crate_info.import_prefix); - t.1.property_definitions.iter_mut().for_each(|pd| { - pd.type_id = pd - .type_id - .replace("{PREFIX}", &host_crate_info.import_prefix); - }); - updated_type_table.insert( - t.0.replace("{PREFIX}", &host_crate_info.import_prefix), - t.1.clone(), - ); - }); - std::mem::swap(&mut manifest.type_table, &mut updated_type_table); -} - -// The stable output directory for generated / copied files -const PAX_DIR_PKG_PATH: &str = "pkg"; - -fn clone_all_dependencies_to_tmp( - pax_dir: &PathBuf, - pax_version: &Option, - ctx: &RunContext, -) { - let dest_pkg_root = pax_dir.join(PAX_DIR_PKG_PATH); +/// Clone all dependencies to `.pax/pkg`. Similar in spirit to the Cargo package cache, +/// this temp directory enables Pax to codegen and building in the context of the larger monorepo, +/// working around various constraints with Cargo (for example, limits surrounding the `patch` directive.) +/// +/// The packages in `.pax/pkg` are both where we write our codegen (into pax-cartridge and pax-properties-coproduct) +/// and where we build chassis and chassis-interfaces. (for example, running `wasm-pack` inside `.pax/pkg/pax-chassis-web`. +fn clone_all_to_pkg_dir(pax_dir: &PathBuf, pax_version: &Option, ctx: &RunContext) { + let dest_pkg_root = pax_dir.join(PKG_DIR_NAME); for pkg in ALL_PKGS { if ctx.is_libdev_mode { //Copy all packages from monorepo root on every build. this allows us to propagate changes //to a libdev build without "sticky caches." - // - //Note that this may incur a penalty on libdev build times, - //since cargo will want to rebuild the whole workspace from scratch on every build. If we want to optimize this, - //consider a "double buffered" approach, where we copy everything into a fresh new buffer (B), call it `.pax/pkg-tmp`, while leaving (A) `.pax/pkg` - //unchanged on disk. Bytewise check each file found in B against a prospective match in A, and copy only if different. (B) could also be stored on a virtual - //FS in memory, to reduce disk churn. let pax_workspace_root = pax_dir.parent().unwrap().parent().unwrap(); let src = pax_workspace_root.join(pkg); let dest = dest_pkg_root.join(pkg); - copy_dir_to(&src, &dest) + copy_dir_recursively(&src, &dest, &DIR_IGNORE_LIST_MACOS) .expect(&format!("Failed to copy from {:?} to {:?}", src, dest)); } else { let dest = dest_pkg_root.join(pkg); @@ -190,14 +171,55 @@ fn clone_all_dependencies_to_tmp( } } +/// Returns a sorted and de-duped list of combined_reexports. +fn generate_reexports_partial_rs(pax_dir: &PathBuf, manifest: &PaxManifest) { + let imports = manifest.import_paths.clone().into_iter().sorted().collect(); + + let file_contents = &bundle_reexports_into_namespace_string(&imports); + + let path = pax_dir.join(Path::new(REEXPORTS_PARTIAL_FILE_NAME)); + fs::write(path, file_contents).unwrap(); +} + +fn bundle_reexports_into_namespace_string(sorted_reexports: &Vec) -> String { + let mut root = NamespaceTrieNode { + node_string: None, + children: Default::default(), + }; + + for s in sorted_reexports { + root.insert(s); + } + + root.serialize_to_reexports() +} + +fn update_property_prefixes_in_place(manifest: &mut PaxManifest, host_crate_info: &HostCrateInfo) { + let mut updated_type_table = HashMap::new(); + manifest.type_table.iter_mut().for_each(|t| { + t.1.type_id_escaped = t.1.type_id_escaped.replace("{PREFIX}", ""); + t.1.type_id = + t.1.type_id + .replace("{PREFIX}", &host_crate_info.import_prefix); + t.1.property_definitions.iter_mut().for_each(|pd| { + pd.type_id = pd + .type_id + .replace("{PREFIX}", &host_crate_info.import_prefix); + }); + updated_type_table.insert( + t.0.replace("{PREFIX}", &host_crate_info.import_prefix), + t.1.clone(), + ); + }); + std::mem::swap(&mut manifest.type_table, &mut updated_type_table); +} + fn generate_and_overwrite_properties_coproduct( pax_dir: &PathBuf, manifest: &PaxManifest, host_crate_info: &HostCrateInfo, ) { - let target_dir = pax_dir - .join(PAX_DIR_PKG_PATH) - .join("pax-properties-coproduct"); + let target_dir = pax_dir.join(PKG_DIR_NAME).join("pax-properties-coproduct"); let target_cargo_full_path = fs::canonicalize(target_dir.join("Cargo.toml")).unwrap(); let mut target_cargo_toml_contents = @@ -314,7 +336,7 @@ fn generate_and_overwrite_cartridge( manifest: &PaxManifest, host_crate_info: &HostCrateInfo, ) { - let target_dir = pax_dir.join(PAX_DIR_PKG_PATH).join("pax-cartridge"); + let target_dir = pax_dir.join(PKG_DIR_NAME).join("pax-cartridge"); let target_cargo_full_path = fs::canonicalize(target_dir.join("Cargo.toml")).unwrap(); let mut target_cargo_toml_contents = @@ -391,38 +413,6 @@ fn generate_and_overwrite_cartridge( let consts = vec![]; //TODO! - //Traverse component tree starting at root - //build a N/PIT in memory for each component (maybe this can be automatically serialized for component factories?) - // handle each kind of attribute: - // Literal(String), - // inline into N/PIT - // Expression(String), - // pencil in the ID; handle the expression separately (build ExpressionSpec & friends) - // Identifier(String), - // syntactic sugar for an expression with a single dependency, returning that dependency - // EventBindingTarget(String), - // ensure this gets added to the HandlerRegistry for this component; rely on ugly error messages for now - // - // for serialization to RIL, generate InstantiationArgs for each node, special-casing built-ins like Repeat, Slot - // - // Also decide whether to join settings blocks in this work - // - // Compile expressions during traversal, keeping track of "compile-time stack" for symbol resolution - // If `const` is bit off for this work, must first populate symbols via pax_const => PaxManifest - // -- must also choose scoping rules; probably just component-level scoping for now - // - // Throw errors when symbols in expressions cannot be resolved; ensure path forward to developer-friendly error messages - // For reference, Rust's message is: - // error[E0425]: cannot find value `not_defined` in this scope - // --> pax-compiler/src/main.rs:404:13 - // | - // 404 | let y = not_defined + 6; - // | ^^^^^^^^^^^ not found in this scope - // Python uses: - // NameError: name 'z' is not defined - // JavaScript uses: - // Uncaught ReferenceError: not_defined is not defined - let mut expression_specs: Vec = manifest .expression_specs .as_ref() @@ -450,8 +440,7 @@ fn generate_and_overwrite_cartridge( }, ); - // Re: formatting the generated output, see prior art at `_format_generated_lib_rs` - //write String to file + // Re: formatting the generated Rust code, see prior art at `_format_generated_lib_rs` fs::write(target_dir.join("src/lib.rs"), generated_lib_rs).unwrap(); } @@ -728,22 +717,18 @@ fn recurse_generate_render_nodes_literal( Some(format!("PropertyLiteral::new({})", lv)) } ValueDefinition::Expression(_, id) - | ValueDefinition::Identifier(_, id) => { - Some(format!( - "PropertyExpression::new({})", - id.expect("Tried to use expression but it wasn't compiled") - )) - } - ValueDefinition::Block(block) => { - Some(format!( - "PropertyLiteral::new({})", - recurse_literal_block( - block.clone(), - pd.get_type_definition(&rngc.type_table), - host_crate_info - ) - )) - } + | ValueDefinition::Identifier(_, id) => Some(format!( + "PropertyExpression::new({})", + id.expect("Tried to use expression but it wasn't compiled") + )), + ValueDefinition::Block(block) => Some(format!( + "PropertyLiteral::new({})", + recurse_literal_block( + block.clone(), + pd.get_type_definition(&rngc.type_table), + host_crate_info + ) + )), _ => { panic!("Incorrect value bound to inline setting") } @@ -757,7 +742,6 @@ fn recurse_generate_render_nodes_literal( } }; - if let Some(ril_literal_string) = ril_literal_string { Some((pd.name.clone(), ril_literal_string)) } else { @@ -982,7 +966,7 @@ pub fn run_parser_binary(path: &str, process_child_ids: Arc>>) -> cmd.pre_exec(pre_exec_hook); } - let child = cmd.spawn().expect("failed to spawn child"); + let child = cmd.spawn().expect(ERR_SPAWN); // child.stdin.take().map(drop); let output = wait_with_output(&process_child_ids, child); @@ -994,6 +978,7 @@ use colored::{ColoredString, Colorize}; use crate::parsing::escape_identifier; use serde::Deserialize; +use serde_json::Value; #[derive(Debug, Deserialize)] struct Metadata { @@ -1043,11 +1028,6 @@ fn get_version_of_whitelisted_packages(path: &str) -> Result Result<(), ()> { } else { Some(get_version_of_whitelisted_packages(&ctx.path).unwrap()) }; - clone_all_dependencies_to_tmp(&pax_dir, &pax_version, &ctx); + clone_all_to_pkg_dir(&pax_dir, &pax_version, &ctx); println!("{} 🛠️ Building parser binary with `cargo`...", *PAX_BADGE); // Run parser bin from host project with `--features parser` @@ -1075,11 +1055,11 @@ pub fn perform_build(ctx: &RunContext) -> Result<(), ()> { std::io::stderr() .write_all(output.stderr.as_slice()) .unwrap(); - assert_eq!( - output.status.code().unwrap(), - 0, - "Parsing failed — there is likely a syntax error in the provided pax" - ); + + if !output.status.success() { + println!("Parsing failed — there is likely a syntax error in the provided pax"); + return Err(()); + } let out = String::from_utf8(output.stdout).unwrap(); let mut manifest: PaxManifest = @@ -1098,44 +1078,9 @@ pub fn perform_build(ctx: &RunContext) -> Result<(), ()> { //7. Build the appropriate `chassis` from source, with the patched `Cargo.toml`, Properties Coproduct, and Cartridge from above println!("{} 🧱 Building cartridge with `cargo`", *PAX_BADGE); - build_chassis_with_cartridge(&pax_dir, &ctx, Arc::clone(&ctx.process_child_ids)); - - if ctx.should_also_run { - //8a::run: compile and run `interface`, with freshly built chassis plugged in - println!( - "{} 🐇 Running Pax {}...", - *PAX_BADGE, - <&RunTarget as Into<&str>>::into(&ctx.target) - ); - } else { - //8b::compile: compile and write executable binary / package to disk at specified or implicit path - println!( - "{} 🛠 Compiling executable package for {}...", - *PAX_BADGE, - <&RunTarget as Into<&str>>::into(&ctx.target) - ); - } - build_interface_with_chassis(&pax_dir, &ctx, Arc::clone(&ctx.process_child_ids)); - - Ok(()) -} - -fn copy_dir_to(src_dir: &Path, dst_dir: &Path) -> std::io::Result<()> { - if !dst_dir.exists() { - fs::create_dir_all(dst_dir)?; - } - - for entry_result in fs::read_dir(src_dir)? { - let entry = entry_result?; - let file_type = entry.file_type()?; - let src_path = entry.path(); - let dst_path = dst_dir.join(entry.file_name()); - - if file_type.is_dir() { - copy_dir_to(&src_path, &dst_path)?; - } else { - fs::copy(&src_path, &dst_path)?; - } + let res = build_chassis_with_cartridge(&pax_dir, &ctx, Arc::clone(&ctx.process_child_ids)); + if let Err(()) = res { + return Err(()); } Ok(()) @@ -1183,95 +1128,27 @@ fn start_static_http_server(fs_path: PathBuf) -> std::io::Result<()> { runtime } -fn build_interface_with_chassis( - pax_dir: &PathBuf, - ctx: &RunContext, - process_child_ids: Arc>>, -) { - let target_str: &str = ctx.target.borrow().into(); - let target_str_lower: &str = &target_str.to_lowercase(); - - let interface_path = pax_dir - .join(PAX_DIR_PKG_PATH) - .join(format!("pax-chassis-{}", target_str_lower)) - .join(match ctx.target { - RunTarget::Web => "interface", - RunTarget::MacOS => "pax-dev-harness-macos", - }); - - let is_web = if let RunTarget::Web = ctx.target { - true - } else { - false - }; - - let target_folder: &str = ctx.target.borrow().into(); - - let output_path = pax_dir.join("build").join(target_folder); - let output_path_str = output_path.to_str().unwrap(); - - std::fs::create_dir_all(&output_path).ok(); - - let verbose_val = format!("{}", ctx.verbose); - let exclude_arch_val = if std::env::consts::ARCH == "aarch64" { - "x86_64" - } else { - "arm64" - }; - if is_web { - let asset_src = pax_dir.join("..").join("assets"); - let asset_dest = interface_path.join("public").join("assets"); - - // Create target assets directory - if let Err(e) = fs::create_dir_all(&asset_dest) { - eprintln!("Error creating directory {:?}: {}", asset_dest, e); - } - // Perform recursive copy from userland `assets/` to built `assets/` - if let Err(e) = copy_dir_recursively(&asset_src, &asset_dest) { - eprintln!("Error copying assets: {}", e); - } - - // Start local server if this is a `run` rather than a `build` - if ctx.should_also_run { - let _ = start_static_http_server(interface_path.join("public")); - } - } else { - let script = "./run-debuggable-mac-app.sh"; - let should_also_run = &format!("{}", ctx.should_also_run); - let mut cmd = Command::new(script); - cmd.current_dir(&interface_path) - .arg(verbose_val) - .arg(exclude_arch_val) - .arg(should_also_run) - .arg(output_path_str) - .stdout(std::process::Stdio::inherit()) - .stderr(if ctx.verbose { - std::process::Stdio::inherit() - } else { - std::process::Stdio::piped() - }); - - #[cfg(unix)] - unsafe { - cmd.pre_exec(pre_exec_hook); +/// Helper recursive fs copy method, like fs::copy, but suited for our purposes. +/// Includes ability to ignore directories by name during recursion. +fn copy_dir_recursively( + src: &Path, + dest: &Path, + ignore_list: &[&str], +) -> Result<(), Box> { + if src.is_dir() { + // If the directory name is in the ignore list, we skip this directory + if ignore_list.contains(&src.file_name().unwrap().to_str().unwrap()) { + return Ok(()); } - let child = cmd.spawn().expect("failed to spawn child"); - // child.stdin.take().map(drop); - let _output = wait_with_output(&process_child_ids, child); - } -} - -fn copy_dir_recursively(src: &Path, dest: &Path) -> Result<(), Box> { - if src.is_dir() { - // If source is a directory, create the corresponding directory in the destination, + // Create the corresponding directory in the destination, // and copy its contents recursively fs::create_dir_all(dest)?; for entry in fs::read_dir(src)? { let entry = entry?; let path = entry.path(); let dest_child = dest.join(path.file_name().ok_or("Invalid file name")?); - copy_dir_recursively(&path, &dest_child)?; + copy_dir_recursively(&path, &dest_child, ignore_list)?; } } else { // If source is a file, just copy it to the destination @@ -1280,11 +1157,11 @@ fn copy_dir_recursively(src: &Path, dest: &Path) -> Result<(), Box>>, -) -> Output { +) -> Result<(), ()> { let target: &RunTarget = &ctx.target; let target_str: &str = target.into(); let target_str_lower = &target_str.to_lowercase(); let pax_dir = PathBuf::from(pax_dir.to_str().unwrap()); let chassis_path = pax_dir - .join(PAX_DIR_PKG_PATH) + .join(PKG_DIR_NAME) .join(format!("pax-chassis-{}", target_str_lower)); + let is_release: bool = ctx.is_release; + let is_ios = if let RunTarget::iOS = target { + true + } else { + false + }; + + let build_mode_name: &str = if is_release { "release" } else { "debug" }; + + let interface_path = pax_dir + .join(PKG_DIR_NAME) + .join(format!("pax-chassis-{}", target_str_lower)) + .join("interface"); + //Inject `patch` directive, which allows userland projects to refer to concrete versions like `0.4.0`, while we //swap them for our locally cloned filesystem versions during compilation. let existing_cargo_toml_path = chassis_path.join("Cargo.toml"); @@ -1331,26 +1222,722 @@ pub fn build_chassis_with_cartridge( //string together a shell call to build our chassis, with cartridge inserted via `patch` match target { - RunTarget::MacOS => { - let mut cmd = Command::new("cargo"); - cmd.current_dir(&chassis_path) - .arg("build") - .arg("--color") - .arg("always") - .env("PAX_DIR", &pax_dir) + RunTarget::macOS | RunTarget::iOS => { + //0: Rust arch string, for passing to cargo + //1: Apple arch string, for addressing xcframework + let target_mappings: &[(&str, &str)] = if let RunTarget::macOS = target { + if is_release { + &[ + ("aarch64-apple-darwin", "macos-arm64"), + ("x86_64-apple-darwin", "macos-x86_64"), + ] + } else { + // Build only relevant archs for dev + if std::env::consts::ARCH == "x86_64" { + &[("x86_64-apple-darwin", "macos-x86_64")] + } else { + &[("aarch64-apple-darwin", "macos-arm64")] + } + } + } else { + // Build all archs for iOS builds. We could limit these like we do for macOS + // dev builds, but at time of intial authoring, it was slowing zb down. + &[ + ("aarch64-apple-ios", "ios-arm64"), + ("x86_64-apple-ios", "iossimulator-x86_64"), + ("aarch64-apple-ios-sim", "iossimulator-arm64"), + ] + }; + + let dylib_file_name = if let RunTarget::macOS = target { + RUST_MACOS_DYLIB_FILE_NAME + } else { + RUST_IOS_DYLIB_FILE_NAME + }; + + let mut handles = Vec::new(); + + //(arch id, single-platform .dylib path, stdout/stderr from build) + let build_results: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + + let targets_single_string = target_mappings + .iter() + .map(|tm| tm.1.to_string()) + .collect::>() + .join(", ") + .bold(); + println!( + "{} 🧶 Compiling targets {{{}}} in {} mode using {} threads...\n", + *PAX_BADGE, + &targets_single_string, + &build_mode_name.to_string().bold(), + target_mappings.len() + ); + + let mut index = 0; + for target_mapping in target_mappings { + let chassis_path = chassis_path.clone(); + let pax_dir = pax_dir.clone(); + + let process_child_ids_threadsafe = process_child_ids.clone(); + let build_results_threadsafe = build_results.clone(); + + let handle = thread::spawn(move || { + let mut cmd = Command::new("cargo"); + cmd.current_dir(&chassis_path) + .arg("build") + .arg("--color") + .arg("always") + .arg("--target") + .arg(target_mapping.0) + .env("PAX_DIR", &pax_dir) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + if is_release { + cmd.arg("--release"); + } + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + + let child = cmd.spawn().expect(ERR_SPAWN); + + //Execute `cargo build`, which generates our dylibs + let output = wait_with_output(&process_child_ids_threadsafe, child); + + let dylib_src = chassis_path + .join("target") + .join(target_mapping.0) + .join(build_mode_name) + .join(dylib_file_name); + + let new_val = ( + target_mapping.1.to_string(), + dylib_src.to_str().unwrap().to_string(), + output, + ); + build_results_threadsafe + .lock() + .unwrap() + .insert(index, new_val); + }); + index = index + 1; + handles.push(handle); + } + + let mut index = 0; + // Wait for all threads to complete and print their outputs + for handle in handles { + handle.join().unwrap(); + } + + let results = build_results.lock().unwrap(); + + let mut should_abort = false; + //Print stdout/stderr + for i in 0..target_mappings.len() { + let result = results.get(&(i as u32)).unwrap(); + let target = &result.0; + let output = &result.2; + + let stdout = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + + if stdout != "" || stderr != "" { + println!("{} build finished with output:", &target); + } + if stdout != "" { + println!("{}", &stdout); + } + if stderr != "" { + eprintln!("{}", &stderr); + } + + if !output.status.success() { + should_abort = true; + } + + index = index + 1; + } + + if should_abort { + eprintln!("Failed to build one or more targets with Cargo. Aborting."); + return Err(()); + } + + // Update the `install name` of each Rust-built .dylib, instead of the default-output absolute file paths + // embedded in each .dylib. This allows our .dylibs to be portably embedded into an SPM module. + let result = results.iter().try_for_each(|res| { + let dylib_path = &res.1.1; + let mut cmd = Command::new("install_name_tool"); + cmd + .arg("-id") + .arg(PORTABLE_DYLIB_INSTALL_NAME) + .arg(dylib_path); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().unwrap(); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + println!("Failed to rewrite dynamic library install name with install_name_tool. Aborting."); + return Err(()); + } + + Ok(()) + }); + + match result { + Err(_) => { + return Err(()); + } + _ => {} + }; + + let macos_dylib_dest = pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-common") + .join("pax-swift-cartridge") + .join("PaxCartridge.xcframework") + .join(MACOS_MULTIARCH_PACKAGE_ID) + .join("PaxCartridge.framework") + .join("PaxCartridge"); + + let simulator_dylib_dest = pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-common") + .join("pax-swift-cartridge") + .join("PaxCartridge.xcframework") + .join(IOS_SIMULATOR_MULTIARCH_PACKAGE_ID) + .join("PaxCartridge.framework") + .join("PaxCartridge"); + + let iphone_native_dylib_dest = pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-common") + .join("pax-swift-cartridge") + .join("PaxCartridge.xcframework") + .join(IOS_PACKAGE_ID) + .join("PaxCartridge.framework") + .join("PaxCartridge"); + + if is_release || is_ios { + // Merge architecture-specific binaries with `lipo` (this is an undocumented requirement + // of multi-arch builds + xcframeworks for the Apple toolchain; we cannot bundle two + // macos arch .frameworks in an xcframework; they must lipo'd into a single .framework + dylib. + // Similarly, iOS binaries require a particular bundling for simulator & device builds.) + println!( + "{} 🖇️ Combining architecture-specific binaries with `lipo`...", + *PAX_BADGE + ); + + if let RunTarget::macOS = target { + // For macOS, we want to lipo both our arm64 and x86_64 dylibs into a single binary, + // then bundle that single binary into a single framework within the xcframework. + + let lipo_input_paths = results + .iter() + .map(|res| res.1 .1.clone()) + .collect::>(); + + // Construct the lipo command + let mut lipo_command = Command::new("lipo"); + lipo_command + .arg("-create") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + // Add each input path to the command + for path in &lipo_input_paths { + lipo_command.arg(path); + } + + // Specify the output path + lipo_command.arg("-output").arg(macos_dylib_dest); + + #[cfg(unix)] + unsafe { + lipo_command.pre_exec(pre_exec_hook); + } + let child = lipo_command.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + + if !output.status.success() { + println!("Failed to combine packages with lipo. Aborting."); + return Err(()); + } + } else { + // For iOS, we want to: + // 1. lipo together both simulator build architectures + // 2. copy (a) the lipo'd simulator binary, and (b) the vanilla arm64 iOS binary into the framework + let simulator_builds = results + .iter() + .filter(|res| res.1 .0.starts_with("iossimulator-")) + .collect::>(); + let device_build = results + .iter() + .filter(|res| res.1 .0.starts_with("ios-")) + .collect::>(); + + let lipo_input_paths = simulator_builds + .iter() + .map(|res| res.1 .1.clone()) + .collect::>(); + + // Construct the lipo command + let mut lipo_command = Command::new("lipo"); + lipo_command + .arg("-create") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + // Add each input path to the command + for path in &lipo_input_paths { + lipo_command.arg(path); + } + + // Specify the output path + lipo_command.arg("-output").arg(simulator_dylib_dest); + + #[cfg(unix)] + unsafe { + lipo_command.pre_exec(pre_exec_hook); + } + let child = lipo_command.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Failed to combine dylibs with lipo. Aborting."); + return Err(()); + } + + //Copy singular device build (iOS, not simulator) + let device_dylib_src = &device_build[0].1 .1; + + let _ = fs::copy(device_dylib_src, iphone_native_dylib_dest); + } + } else { + // For macos development builds, instead of lipoing, just drop the singular build into the appropriate output destination + // This measure speeds up development builds substantially. + // Note that we could do something similar for iOS, but it wasn't immediately in reach at time of authoring (build failed when + // providing non-lipo'd binaries in the framework for iOS) + let result = results.iter().next().unwrap(); + let src = &result.1 .1; + let dest = macos_dylib_dest; + let _ = fs::copy(src, dest); + } + + if is_release && is_ios { + unimplemented!("\n\n\ +Release builds for Pax iOS are not yet supported because configuration has not been exposed for development teams or code-signing.\n +You can build a release build manually by configuring the generated xcodeproject in `.pax/pkg/pax-chassis-ios/interface` with your development team and codesigning configuration.\n +The relevant Framework binaries have been built in release mode at `.pax/pkg/pax-chassis-common/pax-swift-cartridge/` and should be loaded via the above xcodeproject.\n +You can also use the SPM package exposed at `.pax/pkg/pax-chassis-common/pax-swift-cartridge/` for manual inclusion in your own SwiftUI app.\n +Note that the temporary directories mentioned above are subject to overwriting.\n\n") + } + + let (xcodeproj_path, scheme) = if let RunTarget::macOS = target { + ( + pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-macos") + .join("interface") + .join("pax-app-macos") + .join("pax-app-macos.xcodeproj"), + if is_release { + XCODE_MACOS_TARGET_RELEASE + } else { + XCODE_MACOS_TARGET_DEBUG + }, + ) + } else { + ( + pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-ios") + .join("interface") + .join("pax-app-ios") + .join("pax-app-ios.xcodeproj"), + if is_release { + XCODE_IOS_TARGET_RELEASE + } else { + XCODE_IOS_TARGET_DEBUG + }, + ) + }; + + let configuration = if is_release { "Release" } else { "Debug" }; + + let build_dest_base = pax_dir + .join(BUILD_DIR_NAME) + .join(build_mode_name) + .join(target_str_lower); + let executable_output_dir_path = build_dest_base.join("app"); + let executable_dot_app_path = + executable_output_dir_path.join(&format!("{}.app", &scheme)); + let _ = fs::create_dir_all(&executable_output_dir_path); + + let sdk = if let RunTarget::iOS = target { + if is_release { + "iphoneos" + } else { + "iphonesimulator" + } + } else { + "macosx" + }; + + println!("{} 💻 Building xcodeproject...", *PAX_BADGE); + let mut cmd = Command::new("xcodebuild"); + cmd.arg("-configuration") + .arg(configuration) + .arg("-project") + .arg(&xcodeproj_path) + .arg("-scheme") + .arg(scheme) + .arg("-sdk") + .arg(sdk) + .arg(&format!( + "CONFIGURATION_BUILD_DIR={}", + executable_output_dir_path.to_str().unwrap() + )) .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::inherit()); + .stderr(std::process::Stdio::piped()); + + if !is_release { + cmd.arg("CODE_SIGNING_REQUIRED=NO") + .arg("CODE_SIGN_IDENTITY="); + } + + if !ctx.verbose { + cmd.arg("-quiet"); + cmd.arg("GCC_WARN_INHIBIT_ALL_WARNINGS=YES"); + } #[cfg(unix)] unsafe { cmd.pre_exec(pre_exec_hook); } - - let child = cmd.spawn().expect("failed to spawn child"); - // child.stdin.take().map(drop); + let child = cmd.spawn().expect(ERR_SPAWN); let output = wait_with_output(&process_child_ids, child); - output + // Crudely prune out noisy xcodebuild warnings due to an apparent xcode-internal bug at time of authoring, spitting out: + // Details: createItemModels creation requirements should not create capability item model for a capability item model that already exists. + // Function: createItemModels(for:itemModelSource:) + // Thread: <_NSMainThread: 0x600000be02c0>{number = 1, name = main} + // Please file a bug at https://feedbackassistant.apple.com with this warning message and any useful information you can provide. + // If we get to a point where xcodebuild isn't spitting these errors, we can drop this block of code and just `.inherit` stderr in + // the command above. + let stderr = String::from_utf8_lossy(&output.stderr).to_string(); + if ctx.verbose { + println!("{}", stderr); + } else { + let mut skip_lines = 0; + for line in stderr.lines() { + // Check if this line starts a blacklisted message + if line.starts_with("Details: createItemModels") { + skip_lines = 5; // There are 5 lines to skip, including this one + } + + // If skip_lines is non-zero, skip printing and decrement the counter + if skip_lines > 0 { + skip_lines -= 1; + continue; + } + + println!("{}", line); + } + } + + if !output.status.success() { + eprintln!("Failed to build project with xcodebuild. Aborting."); + return Err(()); + } + + //Copy build artifacts & packages into `build` + let swift_cart_src = pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-common") + .join("pax-swift-cartridge"); + let swift_common_src = pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-common") + .join("pax-swift-common"); + + let swift_cart_build_dest = build_dest_base + .join("pax-chassis-common") + .join("pax-swift-cartridge"); + let swift_common_build_dest = build_dest_base + .join("pax-chassis-common") + .join("pax-swift-common"); + + let (app_xcodeproj_src, app_xcodeproj_build_dest) = if let RunTarget::macOS = target { + ( + pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-macos") + .join("interface") + .join("pax-app-macos"), + build_dest_base + .join("pax-chassis-macos") + .join("interface") + .join("pax-app-macos"), + ) + } else { + ( + pax_dir + .join(PKG_DIR_NAME) + .join("pax-chassis-ios") + .join("interface") + .join("pax-app-ios"), + build_dest_base + .join("pax-chassis-ios") + .join("interface") + .join("pax-app-ios"), + ) + }; + + let _ = fs::create_dir_all(&swift_cart_build_dest); + let _ = fs::create_dir_all(&swift_common_build_dest); + let _ = fs::create_dir_all(&app_xcodeproj_build_dest); + + let _ = copy_dir_recursively( + &swift_cart_src, + &swift_cart_build_dest, + &DIR_IGNORE_LIST_MACOS, + ); + let _ = copy_dir_recursively( + &swift_common_src, + &swift_common_build_dest, + &DIR_IGNORE_LIST_MACOS, + ); + let _ = copy_dir_recursively( + &app_xcodeproj_src, + &app_xcodeproj_build_dest, + &DIR_IGNORE_LIST_MACOS, + ); + + // Start `run` rather than a `build` + let target_str: &str = target.into(); + if ctx.should_also_run { + println!("{} 🐇 Running Pax {}...", *PAX_BADGE, target_str); + + if let RunTarget::macOS = target { + // + // Handle macOS `run` + // + + let system_binary_path = + executable_dot_app_path.join(&format!("Contents/MacOS/{}", scheme)); + + let status = Command::new(system_binary_path) + .status() // This will wait for the process to complete + .expect("failed to execute the app"); + + println!("App exited with: {:?}", status); + } else { + // + // Handle iOS `run` + // + + // Get list of devices + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("list") + .arg("-j") + .arg("devices") + .arg("available") + .stdout(std::process::Stdio::piped()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + + let output_str = std::str::from_utf8(&output.stdout).map_err(|_| ())?; + let parsed: Value = serde_json::from_str(&output_str).map_err(|_| ())?; + + // Extract devices + let devices = parsed["devices"] + .as_object() + .ok_or_else(|| { + eprintln!("Invalid JSON format for devices."); + () + })?; + + let mut max_iphone_number = 0; + let mut desired_udid = None; + + for (_, device_list) in devices { + if let Some(device_array) = device_list.as_array() { + for device in device_array { + if let Some(device_type) = device["deviceTypeIdentifier"].as_str() { + if device_type.starts_with("com.apple.CoreSimulator.SimDeviceType.iPhone-") { + if let Some(number) = device_type.split('-').last() { + if let Ok(number) = number.parse::() { + if number > max_iphone_number { + max_iphone_number = number; + desired_udid = device["udid"].as_str(); + } + } + } + } + } + } + } + } + + let device_udid = match desired_udid { + Some(udid) => udid, + None => { + eprintln!("No installed iOS simulators found on this system. Install at least one iPhone simulator through xcode and try again."); + return Err(()); + } + }; + + // Open the Simulator app + let mut cmd = Command::new("open"); + cmd.arg("-a") + .arg("Simulator") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Error opening iOS simulator. Aborting."); + return Err(()); + } + + // Boot current device + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("boot") + .arg(device_udid) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let _output = wait_with_output(&process_child_ids, child); + + // Boot the relevant simulator + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("spawn") + .arg(device_udid) + .arg("launchctl") + .arg("print") + .arg("system") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Error spawning iOS simulator. Aborting."); + return Err(()); + } + // ^ Note that we don't handle errors on this particular command; it will return an error by default + // if the simulator isn't running, which isn't an "error" for us. Instead, defer to the following + // polling logic to decide whether the simulator failed to start, which would indeed be an error. + + // After opening the simulator, wait for the simulator to be booted + let max_retries = 5; + let retry_period_secs = 5; + let mut retries = 0; + + while !is_simulator_booted(device_udid, &process_child_ids) + && retries < max_retries + { + println!("{} 💤 Waiting for simulator to boot...", *PAX_BADGE); + std::thread::sleep(std::time::Duration::from_secs(retry_period_secs)); + retries = retries + 1; + } + + if retries == max_retries { + eprintln!( + "Failed to boot the simulator within the expected time. Aborting." + ); + return Err(()); + } + + // Install and run app on simulator + println!( + "{} 📤 Installing and running app from {} on simulator...", + *PAX_BADGE, + executable_output_dir_path.to_str().unwrap() + ); + + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("install") + .arg(device_udid) + .arg(executable_dot_app_path) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Error installing app on iOS simulator. Aborting."); + return Err(()); + } + + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("launch") + .arg(device_udid) + .arg("dev.pax.pax-app-ios") + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Error launching app on iOS simulator. Aborting."); + return Err(()); + } + let status = output.status.code().unwrap(); + + println!( + "{} 🚀 App launched on simulator. Launch command exited with code: {:?}", + *PAX_BADGE, status + ); + } + } else { + let build_path = executable_output_dir_path.to_str().unwrap().bold(); + println!( + "{} 🗂️ Done: {} {} build available at {}", + *PAX_BADGE, target_str, build_mode_name, build_path + ); + } } RunTarget::Web => { let mut cmd = Command::new("wasm-pack"); @@ -1372,11 +1959,10 @@ pub fn build_chassis_with_cartridge( .stdout(std::process::Stdio::inherit()) .stderr(std::process::Stdio::inherit()); - //approximate `should_also_run` as "dev build," `!should_also_run` as prod - if ctx.should_also_run { - cmd.arg("--dev"); - } else { + if is_release { cmd.arg("--release"); + } else { + cmd.arg("--dev"); } #[cfg(unix)] @@ -1384,17 +1970,88 @@ pub fn build_chassis_with_cartridge( cmd.pre_exec(pre_exec_hook); } - let child = cmd.spawn().expect("failed to spawn child"); - // child.stdin.take().map(drop); + let child = cmd.spawn().expect(ERR_SPAWN); + + // Execute wasm-pack build let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + eprintln!("Failed to build project with wasm-pack. Aborting."); + return Err(()); + } + + // Copy assets + let asset_src = pax_dir.join("..").join(ASSETS_DIR_NAME); + let asset_dest = interface_path.join(PUBLIC_DIR_NAME).join(ASSETS_DIR_NAME); + + // Create target assets directory + if let Err(e) = fs::create_dir_all(&asset_dest) { + eprintln!("Error creating directory {:?}: {}", asset_dest, e); + return Err(()); + } + + // Perform recursive copy from userland `assets/` to built `assets/` + if let Err(e) = copy_dir_recursively(&asset_src, &asset_dest, &vec![]) { + eprintln!("Error copying assets: {}", e); + return Err(()); + } - output + //Copy fully built project into .pax/build/web, ready for e.g. publishing + let build_src = interface_path.join(PUBLIC_DIR_NAME); + let build_dest = pax_dir + .join(BUILD_DIR_NAME) + .join(build_mode_name) + .join(target_str_lower); + let res = copy_dir_recursively(&build_src, &build_dest, &DIR_IGNORE_LIST_WEB); + if let Err(e) = res { + eprintln!( + "Failed to copy built files from {} to {}. {:?}", + &build_src.to_str().unwrap(), + &build_dest.to_str().unwrap(), + e + ); + } + + // Start local server if this is a `run` rather than a `build` + if ctx.should_also_run { + println!("{} 🐇 Running Pax Web...", *PAX_BADGE); + let _ = start_static_http_server(interface_path.join(PUBLIC_DIR_NAME)); + } else { + println!( + "{} 🗂️ Done: {} build available at {}", + *PAX_BADGE, + build_mode_name, + build_dest.to_str().unwrap() + ); + } } } + Ok(()) } -static PAX_CREATE_TEMPLATE: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/new-project-template"); -const PAX_CREATE_TEMPLATE_DIR_NAME: &str = "new-project-template"; +// This function checks if the simulator with the given UDID is booted +fn is_simulator_booted(device_udid: &str, process_child_ids: &Arc>>) -> bool { + let mut cmd = Command::new("xcrun"); + cmd.arg("simctl") + .arg("list") + .arg("devices") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()); + + #[cfg(unix)] + unsafe { + cmd.pre_exec(pre_exec_hook); + } + let child = cmd.spawn().expect(ERR_SPAWN); + let output = wait_with_output(&process_child_ids, child); + if !output.status.success() { + panic!("Error checking simulator status. This is an unhandled error and may leave orphaned processes."); + } + + let output_str = String::from_utf8(output.stdout).expect("Failed to convert to string"); + output_str + .lines() + .any(|line| line.contains(device_udid) && line.contains("Booted")) +} pub fn perform_create(ctx: &CreateContext) { let full_path = Path::new(&ctx.path); @@ -1534,18 +2191,23 @@ pub struct RunContext { pub should_also_run: bool, pub is_libdev_mode: bool, pub process_child_ids: Arc>>, + pub is_release: bool, } pub enum RunTarget { - MacOS, + #[allow(non_camel_case_types)] + macOS, Web, + #[allow(non_camel_case_types)] + iOS, } impl From<&str> for RunTarget { fn from(input: &str) -> Self { match input.to_lowercase().as_str() { - "macos" => RunTarget::MacOS, + "macos" => RunTarget::macOS, "web" => RunTarget::Web, + "ios" => RunTarget::iOS, _ => { unreachable!() } @@ -1557,7 +2219,8 @@ impl<'a> Into<&'a str> for &'a RunTarget { fn into(self) -> &'a str { match self { RunTarget::Web => "Web", - RunTarget::MacOS => "MacOS", + RunTarget::macOS => "macOS", + RunTarget::iOS => "iOS", } } } @@ -1592,21 +2255,28 @@ impl Ord for NamespaceTrieNode { } } -const ERR_ASYNC: &str = "Expected synchronous execution; encountered async execution"; +const ERR_LOCK: &str = "Failed to lock process_child_ids mutex"; + pub fn wait_with_output( process_child_ids: &Arc>>, child: std::process::Child, ) -> std::process::Output { let child_id: u64 = child.id().into(); - process_child_ids.lock().expect(ERR_ASYNC).push(child_id); + + // Push the child_id to the shared process_child_ids vector + process_child_ids.lock().expect(ERR_LOCK).push(child_id); + + // Wait for the child process to complete let output = child .wait_with_output() .expect("Failed to wait for child process"); - assert!( - process_child_ids.lock().expect(ERR_ASYNC).pop().unwrap() == child_id, - "{}", - ERR_ASYNC - ); + + // Ensure the child ID is removed after completion + process_child_ids + .lock() + .expect(ERR_LOCK) + .retain(|&id| id != child_id); + output } diff --git a/pax-compiler/src/parsing.rs b/pax-compiler/src/parsing.rs index f1a9657e5..61011e8ac 100644 --- a/pax-compiler/src/parsing.rs +++ b/pax-compiler/src/parsing.rs @@ -847,14 +847,13 @@ impl Default for ParsingContext { } } - #[derive(Debug)] pub struct ParsingError { pub error_name: String, pub error_message: String, pub matched_string: String, pub start: (usize, usize), - pub end: (usize, usize), + pub end: (usize, usize), } /// Extract all errors from a Pax parse result @@ -891,7 +890,7 @@ pub fn extract_errors(pairs: pest::iterators::Pairs) -> Vec format!("{:?}", pair.as_rule()), "Open tag is malformed".to_string(), )), - Rule::tag_error => Some(( + Rule::tag_error => Some(( format!("{:?}", pair.as_rule()), "Tag structure is unexpected.".to_string(), )), @@ -899,7 +898,8 @@ pub fn extract_errors(pairs: pest::iterators::Pairs) -> Vec }; if let Some((error_name, error_message)) = error { let span = pair.as_span(); - let ((line_start, start_col), (line_end, end_col)) = (pair.line_col(),span.end_pos().line_col()); + let ((line_start, start_col), (line_end, end_col)) = + (pair.line_col(), span.end_pos().line_col()); let error = ParsingError { error_name, error_message, @@ -909,7 +909,7 @@ pub fn extract_errors(pairs: pest::iterators::Pairs) -> Vec }; errors.push(error); } - errors.extend(extract_errors(pair.into_inner())); + errors.extend(extract_errors(pair.into_inner())); } errors @@ -929,27 +929,27 @@ pub fn assemble_component_definition( .expect(&format!("unsuccessful parse from {}", &pax)) // unwrap the parse result .next() .unwrap(); // get and unwrap the `pax_component_definition` rule - - let errors = extract_errors(_ast.clone().into_inner()); - if !errors.is_empty() { - let mut error_messages = String::new(); - - for error in &errors { - let msg = format!( - "error: {}\n --> {}:{}\n |\n{} | {}\n |{}\n\n", - error.error_message, - error.start.0, - error.start.1, - error.start.0, - error.matched_string, - "^".repeat(error.matched_string.len()) - ); - error_messages.push_str(&msg); - } - - panic!("{}", error_messages); + + let errors = extract_errors(_ast.clone().into_inner()); + if !errors.is_empty() { + let mut error_messages = String::new(); + + for error in &errors { + let msg = format!( + "error: {}\n --> {}:{}\n |\n{} | {}\n |{}\n\n", + error.error_message, + error.start.0, + error.start.1, + error.start.0, + error.matched_string, + "^".repeat(error.matched_string.len()) + ); + error_messages.push_str(&msg); } - + + panic!("{}", error_messages); + } + if is_main_component { ctx.main_component_type_id = self_type_id.to_string(); } @@ -1279,7 +1279,6 @@ impl Reflectable for pax_runtime_api::Numeric { } } - impl Reflectable for pax_runtime_api::SizePixels { fn get_import_path() -> String { "pax_lang::api::SizePixels".to_string() diff --git a/pax-compiler/src/templating.rs b/pax-compiler/src/templating.rs index f67f73231..70041362f 100644 --- a/pax-compiler/src/templating.rs +++ b/pax-compiler/src/templating.rs @@ -4,8 +4,7 @@ use serde_derive::{Deserialize, Serialize}; #[allow(unused_imports)] use serde_json; use std::collections::HashMap; -use tera::{Tera, Context}; - +use tera::{Context, Tera}; use crate::manifest::{ExpressionSpec, PropertyDefinition}; @@ -137,12 +136,12 @@ pub fn press_template_codegen_cartridge_render_node_literal( .unwrap(); let mut tera = Tera::default(); - tera.add_raw_template("cartridge-render-node-literal", template).unwrap(); + tera.add_raw_template("cartridge-render-node-literal", template) + .unwrap(); tera.render( "cartridge-render-node-literal", &Context::from_serialize(args).unwrap(), ) - .unwrap() + .unwrap() } - diff --git a/pax-core/src/component.rs b/pax-core/src/component.rs index a54aaf475..d92ff4cfb 100644 --- a/pax-core/src/component.rs +++ b/pax-core/src/component.rs @@ -90,9 +90,6 @@ impl RenderNode for ComponentInstance { bounds } fn compute_properties(&mut self, rtc: &mut RenderTreeContext) { - self.common_properties.compute_properties(rtc); - - (*self.compute_properties_fn)(Rc::clone(&self.properties), rtc); //expand adoptees before adding to stack frame. //NOTE: this requires *evaluating properties* for `should_flatten` nodes like Repeat and Conditional, whose @@ -109,10 +106,13 @@ impl RenderNode for ComponentInstance { )); (*rtc.runtime).borrow_mut().push_stack_frame( - flattened_adoptees, + Rc::clone(&flattened_adoptees), Rc::clone(&self.properties), self.timeline.clone(), ); + + self.common_properties.compute_properties(rtc); + (*self.compute_properties_fn)(Rc::clone(&self.properties), rtc); } fn get_layer_type(&mut self) -> Layer { diff --git a/pax-core/src/engine.rs b/pax-core/src/engine.rs index d725a4aa8..312f83a23 100644 --- a/pax-core/src/engine.rs +++ b/pax-core/src/engine.rs @@ -1061,6 +1061,9 @@ impl PaxEngine { } } + //lifecycle: did_render + node.borrow_mut().handle_did_render(rtc, rcs); + //Handle node unmounting if marked_for_unmount { //lifecycle: will_unmount @@ -1070,11 +1073,8 @@ impl PaxEngine { self.instance_registry .borrow_mut() .mounted_set - .remove(&id_chain); //, "Tried to unmount a node, but it was not mounted"); + .remove(&id_chain); } - - //lifecycle: did_render - node.borrow_mut().handle_did_render(rtc, rcs); } /// Simple 2D raycasting: the coordinates of the ray represent a diff --git a/pax-core/src/runtime.rs b/pax-core/src/runtime.rs index b4540ba36..03ec610c6 100644 --- a/pax-core/src/runtime.rs +++ b/pax-core/src/runtime.rs @@ -119,7 +119,7 @@ impl Runtime { /// Handles special-cases like `for`/`Repeat`, where properties for the /// control flow primitive need to be computed out-of-lifecycle, and where nested child elements /// need to be treated as top-level elements. - /// For example, for `for i in (0..3){ }`, + /// For example, given `for i in (0..3){ }`, /// without this special handling `Stacker` will receive only two adoptees: the `Ellipse` and the `Repeat` node /// created by `for`. In other words `for`s children need to be treated as ``s children, /// and this processing allows that to happpen. @@ -131,8 +131,8 @@ impl Runtime { ) -> Vec> { let mut adoptee_borrowed = (**adoptee).borrow_mut(); if adoptee_borrowed.should_flatten() { - //1. this is an `if` or `for` (etc.) — it needs its properties computed - // in order for its children to be correct + // //1. this is an `if` or `for` (etc.) — it needs its properties computed + // // in order for its children to be correct adoptee_borrowed.compute_properties(rtc); //2. recurse into top-level should_flatten() nodes (*adoptee_borrowed.get_rendering_children()) @@ -143,14 +143,13 @@ impl Runtime { }) .flatten() .collect() - //NOTE: probably worth optimizing (pending profiling.) Lots of allocation happening here -- flattening and collecting `Vec`s is probably not - //the most efficient possible approach, and this is fairly hot-running code. } else { vec![Rc::clone(adoptee)] } } } + /// Data structure for a single frame of our runtime stack, including /// a reference to its parent frame, a list of `adoptees` for /// prospective [`Slot`] consumption, and `properties` for diff --git a/pax-example/components/color_grid.pax b/pax-example/components/color_grid.pax new file mode 100644 index 000000000..958ea644f --- /dev/null +++ b/pax-example/components/color_grid.pax @@ -0,0 +1,9 @@ +for i in 0..10 { + + for j in 0..10 { + + + + } + +} \ No newline at end of file diff --git a/pax-example/pax b/pax-example/pax index a29ea90fa..b6606002b 100755 --- a/pax-example/pax +++ b/pax-example/pax @@ -1,12 +1,14 @@ #!/bin/bash -### Helper binary for pax libdev, allowing pax-cli-like ergonomics inside the pax-example directory +### Helper script for pax libdev, allowing pax-cli-like ergonomics inside the pax-example directory +### ### For example, from @/pax root: ### `cd pax-example && ./pax run --target=macos` ### `cd pax-example && ./pax parse` ### `cd pax-example && ./pax libdev build-chassis` + set -e pushd ../pax-cli cargo build ../target/debug/pax-cli "$@" --path="../pax-example" --libdev -popd \ No newline at end of file +popd diff --git a/pax-std/pax-std-primitives/src/text.rs b/pax-std/pax-std-primitives/src/text.rs index c25115b53..fa18c64cc 100644 --- a/pax-std/pax-std-primitives/src/text.rs +++ b/pax-std/pax-std-primitives/src/text.rs @@ -61,6 +61,7 @@ impl RenderNode for TextInstance { Rc::new(RefCell::new(vec![])) } fn compute_properties(&mut self, rtc: &mut RenderTreeContext) { + let properties = &mut *self.properties.as_ref().borrow_mut(); if let Some(text) = rtc.compute_vtable_value(properties.text._get_vtable_id()) { diff --git a/scripts/print-dag.py b/scripts/print-dag.py index 73dda0cfb..fa5438f13 100755 --- a/scripts/print-dag.py +++ b/scripts/print-dag.py @@ -6,7 +6,9 @@ PACKAGES = [ "pax-cartridge", + "pax-chassis-common", "pax-chassis-macos", + "pax-chassis-web", "pax-cli", "pax-compiler", "pax-core", diff --git a/scripts/release.py b/scripts/release.py index 99d038ad0..2ffbb398c 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -24,6 +24,7 @@ PACKAGES = [ "pax-cartridge", + "pax-chassis-common", "pax-chassis-macos", "pax-chassis-web", "pax-cli",