diff --git a/.claude/guides/OBJC2_GUIDE.md b/.claude/guides/OBJC2_GUIDE.md new file mode 100644 index 000000000..11cdb9930 --- /dev/null +++ b/.claude/guides/OBJC2_GUIDE.md @@ -0,0 +1,705 @@ +# objc2 Rust Guide for macOS Framework Bindings + +This guide documents patterns and best practices for using `objc2` and related crates (`objc2-foundation`, `objc2-app-kit`, `objc2-virtualization`, etc.) to interface with Apple's Objective-C frameworks from Rust. + +## Table of Contents +1. [Strategy & Workflow](#strategy--workflow) ⭐ **READ THIS FIRST** +2. [Crate Structure](#crate-structure) +3. [Key Types](#key-types) +4. [Allocation and Initialization](#allocation-and-initialization) +5. [Class Inheritance and Type Coercion](#class-inheritance-and-type-coercion) +6. [Defining Custom Objective-C Classes](#defining-custom-objective-c-classes) +7. [Working with NSArray](#working-with-nsarray) +8. [Delegates and Protocols](#delegates-and-protocols) +9. [Main Thread Safety](#main-thread-safety) +10. [Memory Management](#memory-management) +11. [Common Patterns](#common-patterns) +12. [Troubleshooting](#troubleshooting) + +--- + +## Strategy & Workflow + +**This section captures the optimal workflow for writing objc2 code. Follow this process to minimize iteration cycles with the compiler.** + +### Phase 1: Research Before Coding + +1. **Check crate features FIRST** before adding to Cargo.toml: + ```bash + cargo search objc2-av-foundation + # Then check available features: + curl -s https://crates.io/api/v1/crates/objc2-av-foundation/0.3.2 | \ + python3 -c "import sys,json; [print(k) for k in sorted(json.load(sys.stdin)['version']['features'].keys())]" + ``` + +2. **Feature naming patterns**: + - Features usually match class names: `AVCaptureSession`, `VZVirtualMachine` + - Subclasses are often bundled with parent: `AVCaptureDeviceInput` is part of `AVCaptureInput` + - When in doubt, search for the parent class name + +3. **Use documentation lookup tools** to understand available types and method signatures before writing code. + +### Phase 2: Understand Naming Conventions + +The objc2 crates follow consistent naming transformations from Objective-C: + +| Objective-C | Rust | +|------------|------| +| `kCALayerWidthSizable` | `CAAutoresizingMask::LayerWidthSizable` | +| `NSBackingStoreBuffered` | `NSBackingStoreType::Buffered` | +| `AVMediaTypeVideo` | `AVMediaTypeVideo` (extern static, needs unsafe) | +| `-initWithFoo:bar:` | `initWithFoo_bar(alloc, ...)` | + +**Key pattern**: Constants drop their prefix (`kCA`, `NS`) and become associated constants on a type. + +### Phase 3: Handle Extern Statics Correctly + +Global constants like `AVMediaTypeVideo` are extern statics: +```rust +// These are Option<&'static NSString> and need unsafe access +let media_type = unsafe { AVMediaTypeVideo.expect("not available") }; +``` + +### Phase 4: Build Incrementally + +1. **Run `cargo check` early** - after imports, after each function +2. **Don't write the entire file** before checking if it compiles +3. **Trust compiler errors** for API details you couldn't verify upfront +4. **Check if unsafe is actually needed** - many methods are safe with MainThreadMarker + +### Phase 5: Common Gotchas Checklist + +Before your first build, verify: + +- [ ] Imported `AnyThread` trait if using `Type::alloc()` on non-UI types +- [ ] Using `into_super()` on array *elements*, not the array itself +- [ ] `define_class!` struct has semicolon (no inline fields) +- [ ] Thread-local storage set up for state that must stay alive +- [ ] Checked if methods actually need `unsafe` (many are safe) + +### Anti-Patterns to Avoid + +```rust +// DON'T: Wrap everything in unsafe "just in case" +unsafe { app.run() }; // run() is actually safe! + +// DON'T: Guess constant names from Objective-C +objc2_quartz_core::kCALayerWidthSizable // Wrong! + +// DON'T: Assume extern statics are non-optional +AVMediaTypeVideo // This is Option<&NSString>, not &NSString + +// DON'T: Write the whole file before checking compilation +// DO: Build incrementally, let compiler guide you +``` + +### Debugging Strategy + +When the build fails: +1. Read the error message carefully - rustc is usually very helpful +2. Check the suggested type vs expected type +3. Look for `into_super()` issues with NSArray +4. Check if you need `unsafe` or are using it unnecessarily +5. Verify feature flags are enabled for the types you're using + +--- + +## Crate Structure + +The objc2 ecosystem is organized as follows: + +```toml +[dependencies] +# Core runtime +objc2 = "0.6" +block2 = "0.6" # For Objective-C blocks (callbacks) + +# Foundation types (NSString, NSArray, NSURL, etc.) +objc2-foundation = { version = "0.3", features = ["NSString", "NSArray", ...] } + +# AppKit for macOS GUI +objc2-app-kit = { version = "0.3", features = ["NSApplication", "NSWindow", ...] } + +# Framework-specific crates +objc2-virtualization = { version = "0.3", features = ["VZVirtualMachine", ...] } +``` + +**Important**: Each type/class requires its feature to be enabled explicitly. + +--- + +## Key Types + +### `Retained` +Smart pointer for Objective-C objects with automatic reference counting (ARC). + +```rust +use objc2::rc::Retained; + +let obj: Retained = NSString::from_str("hello"); + +// Dereference to get &T +let reference: &NSString = &*obj; +// Or use Deref coercion +some_method(&obj); // If method takes &NSString +``` + +### `MainThreadMarker` +Proof that code is running on the main thread. Required for UI operations. + +```rust +use objc2_foundation::MainThreadMarker; + +fn main() { + let mtm = MainThreadMarker::new().expect("Must run on main thread"); + + // Use mtm to allocate main-thread-only objects + let window = unsafe { NSWindow::initWith...(mtm.alloc(), ...) }; +} +``` + +### `ProtocolObject` +Type-erased protocol object for delegate patterns. + +```rust +use objc2::runtime::ProtocolObject; + +let delegate = MyDelegate::new(mtm); +app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +``` + +--- + +## Allocation and Initialization + +### For `MainThreadOnly` types (UI classes) +Use `mtm.alloc::()`: + +```rust +let mtm = MainThreadMarker::new().unwrap(); + +// Allocate main-thread-only types +let window = unsafe { + NSWindow::initWithContentRect_styleMask_backing_defer( + mtm.alloc::(), + rect, + style_mask, + backing_store, + false, + ) +}; +``` + +### For `AnyThread` types (non-UI classes) +Use `T::alloc()` with `AnyThread` trait in scope: + +```rust +use objc2::AnyThread; + +let config = unsafe { + VZMacGraphicsDisplayConfiguration::initWithWidthInPixels_heightInPixels_pixelsPerInch( + VZMacGraphicsDisplayConfiguration::alloc(), + 1920, + 1200, + 80, + ) +}; +``` + +### Using `new()` (when available) +Many types provide a `new()` method that combines alloc+init: + +```rust +let boot_loader = unsafe { VZMacOSBootLoader::new() }; +let config = unsafe { VZVirtualMachineConfiguration::new() }; +``` + +--- + +## Class Inheritance and Type Coercion + +### The Problem +Objective-C uses class inheritance, but Rust's type system is strict. When a method expects `&NSArray` but you have `Retained`, you need to coerce. + +### Solution: `into_super()` +Use `.into_super()` to convert a `Retained` into `Retained`: + +```rust +// VZMacGraphicsDeviceConfiguration inherits from VZGraphicsDeviceConfiguration +let graphics: Retained = create_graphics_config(); + +// Convert to parent type for array that expects the base class +let graphics_base: Retained = graphics.into_super(); + +// Now it works with NSArray +let array = NSArray::from_retained_slice(&[graphics_base]); +unsafe { config.setGraphicsDevices(&array) }; +``` + +### Common inheritance chains: +``` +VZMacGraphicsDeviceConfiguration -> VZGraphicsDeviceConfiguration +VZVirtioBlockDeviceConfiguration -> VZStorageDeviceConfiguration +VZVirtioNetworkDeviceConfiguration -> VZNetworkDeviceConfiguration +VZMacKeyboardConfiguration -> VZKeyboardConfiguration +VZMacTrackpadConfiguration -> VZPointingDeviceConfiguration +VZMacPlatformConfiguration -> VZPlatformConfiguration +``` + +--- + +## Defining Custom Objective-C Classes + +Use `define_class!` macro for custom classes (delegates, subclasses): + +```rust +use objc2::{define_class, msg_send, MainThreadOnly}; +use objc2::rc::Retained; +use objc2::runtime::{NSObject, NSObjectProtocol}; + +define_class!( + // Specify parent class + #[unsafe(super = NSObject)] + + // Thread safety (use MainThreadOnly for UI-related classes) + #[thread_kind = MainThreadOnly] + + // Objective-C class name + #[name = "MyDelegate"] + + // Struct definition (NO inline ivars in objc2 0.6.x!) + struct MyDelegate; + + // Implement NSObjectProtocol (required) + unsafe impl NSObjectProtocol for MyDelegate {} + + // Implement any protocols + unsafe impl SomeDelegate for MyDelegate { + #[unsafe(method(someMethod:))] + fn some_method(&self, arg: &SomeType) { + // Implementation + } + } +); + +impl MyDelegate { + fn new(mtm: MainThreadMarker) -> Retained { + unsafe { msg_send![mtm.alloc::(), init] } + } +} +``` + +### Important: No inline ivars in objc2 0.6.x +The `define_class!` macro does NOT support inline instance variables: + +```rust +// WRONG - won't compile in objc2 0.6.x +struct MyClass { + field: SomeType, // NO! +} + +// CORRECT - use semicolon, no fields +struct MyClass; +``` + +**Workaround for state**: Use thread-local storage: + +```rust +use std::cell::RefCell; + +thread_local! { + static STATE: RefCell> = const { RefCell::new(None) }; +} + +struct MyState { + window: Retained, + // other fields... +} + +// Store state when needed +STATE.with(|s| { + *s.borrow_mut() = Some(MyState { window, ... }); +}); +``` + +--- + +## Working with NSArray + +### Creating arrays +```rust +use objc2_foundation::NSArray; + +// From a slice of Retained objects +let items: Vec> = vec![item1, item2]; +let array = NSArray::from_retained_slice(&items); + +// Or inline +let array = NSArray::from_retained_slice(&[item1, item2]); +``` + +### Type coercion for arrays +When the setter expects a different (parent) type: + +```rust +// Method expects &NSArray +// But we have Retained + +let mac_graphics = create_mac_graphics_config(); +let base_graphics = mac_graphics.into_super(); // Coerce to parent type +let array = NSArray::from_retained_slice(&[base_graphics]); +unsafe { config.setGraphicsDevices(&array) }; +``` + +--- + +## Delegates and Protocols + +### Setting a delegate +```rust +use objc2::runtime::ProtocolObject; + +let delegate = MyDelegate::new(mtm); + +// Convert to protocol object for setDelegate +unsafe { + vm.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +} + +// IMPORTANT: Keep delegate alive! Store it somewhere. +``` + +### Implementing delegate methods +```rust +define_class!( + #[unsafe(super = NSObject)] + #[thread_kind = MainThreadOnly] + #[name = "VMDelegate"] + struct VMDelegate; + + unsafe impl NSObjectProtocol for VMDelegate {} + + unsafe impl VZVirtualMachineDelegate for VMDelegate { + #[unsafe(method(guestDidStopVirtualMachine:))] + fn guest_did_stop(&self, _vm: &VZVirtualMachine) { + println!("VM stopped"); + // Note: Can access MainThreadMarker here since we're MainThreadOnly + let app = NSApplication::sharedApplication(MainThreadMarker::new().unwrap()); + app.terminate(None); + } + + #[unsafe(method(virtualMachine:didStopWithError:))] + fn vm_did_stop_with_error(&self, _vm: &VZVirtualMachine, error: &NSError) { + eprintln!("VM error: {:?}", error); + } + } +); +``` + +--- + +## Main Thread Safety + +### MainThreadOnly types +Types marked with `MainThreadOnly` can only be created/used on the main thread: +- `NSApplication`, `NSWindow`, `NSView`, `VZVirtualMachineView` +- Custom classes with `#[thread_kind = MainThreadOnly]` + +### Obtaining MainThreadMarker +```rust +// At program start +let mtm = MainThreadMarker::new().expect("Must be on main thread"); + +// In delegate methods (if class is MainThreadOnly) +let mtm = MainThreadMarker::new().unwrap(); // Safe because we know we're on main thread +``` + +### Methods that don't need unsafe +Many methods on `MainThreadOnly` types are safe when you have proof of main thread: +```rust +// These are safe (no unsafe needed) +let app = NSApplication::sharedApplication(mtm); +app.setActivationPolicy(NSApplicationActivationPolicy::Regular); +app.run(); + +window.setTitle(ns_string!("Title")); +window.center(); +window.makeKeyAndOrderFront(None); +``` + +--- + +## Memory Management + +### Retained keeps objects alive +```rust +let delegate = MyDelegate::new(mtm); +app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); + +// delegate must stay alive! Don't let it drop. +// Store it in thread-local, struct field, or keep in scope. +``` + +### Thread-local storage pattern +```rust +thread_local! { + static APP_STATE: RefCell> = const { RefCell::new(None) }; +} + +struct AppState { + _window: Retained, // Keep window alive + _delegate: Retained, // Keep delegate alive +} + +// Store after creation +APP_STATE.with(|state| { + *state.borrow_mut() = Some(AppState { + _window: window, + _delegate: delegate, + }); +}); +``` + +--- + +## Common Patterns + +### Creating an AppKit application +```rust +fn main() { + let mtm = MainThreadMarker::new().expect("Must run on main thread"); + + let app = NSApplication::sharedApplication(mtm); + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + + let delegate = AppDelegate::new(mtm); + app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); + + app.run(); // Blocks until app terminates +} +``` + +### Completion handlers with blocks +```rust +use block2::RcBlock; + +let completion = RcBlock::new(|error: *mut NSError| { + if !error.is_null() { + let err = unsafe { &*error }; + eprintln!("Error: {:?}", err); + } else { + println!("Success!"); + } +}); + +unsafe { + some_async_method(&completion); +} +``` + +### Working with NSString +```rust +use objc2_foundation::{NSString, ns_string}; + +// From &str +let string = NSString::from_str("hello"); + +// Static string (compile-time) +window.setTitle(ns_string!("Window Title")); +``` + +### Working with NSURL +```rust +use objc2_foundation::NSURL; + +let path = "/path/to/file"; +let url = NSURL::fileURLWithPath(&NSString::from_str(path)); +``` + +### Working with NSData +```rust +use objc2_foundation::NSData; + +let bytes: Vec = fs::read("file.bin")?; +let data = NSData::with_bytes(&bytes); +``` + +--- + +## Critical Checklist (Read Before Writing Code!) + +Before writing objc2 code, verify these common pitfalls: + +### 1. Import `AnyThread` for non-UI allocations +If you're calling `.alloc()` on any non-MainThreadOnly type, you MUST import the trait: +```rust +use objc2::AnyThread; // Required for Type::alloc() to work! +``` +Without this, you'll get "no function or associated item named `alloc` found". + +### 2. `into_super()` goes on ELEMENTS, not arrays +When building an NSArray for a setter that expects a parent type: +```rust +// WRONG - calling into_super() on the array +let array = NSArray::from_retained_slice(&[item]); +config.setDevices(&array.into_super()); // NO! + +// CORRECT - call into_super() on each element BEFORE the array +let array = NSArray::from_retained_slice(&[item.into_super()]); +config.setDevices(&array); // YES! +``` + +### 3. No inline ivars in define_class! (objc2 0.6.x) +```rust +// WRONG - will fail with "no rules expected this token" +define_class!( + #[unsafe(super(NSObject))] + struct MyClass { + field: Type, // NO! + } +); + +// CORRECT - empty struct, use thread-local for state +define_class!( + #[unsafe(super(NSObject))] + struct MyClass; // Semicolon, no body! +); +``` + +### 4. Always use parentheses in #[unsafe(super(...))] +```rust +// CORRECT syntax +#[unsafe(super(NSObject))] + +// NOT this (older syntax) +#[unsafe(super = NSObject)] +``` + +### 5. Retain objects that must stay alive +Delegates, windows, and other objects passed to Objective-C must be kept alive: +```rust +thread_local! { + static STATE: RefCell> = const { RefCell::new(None) }; +} + +// Store BEFORE the function returns +STATE.with(|s| *s.borrow_mut() = Some(AppState { delegate, window, ... })); +``` + +### 6. Only import the types you actually use +Don't import parent classes if you only use subclasses: +```rust +// If you only create VZMacGraphicsDeviceConfiguration, +// you don't need to import VZGraphicsDeviceConfiguration +// (into_super() handles the conversion) +``` + +### 7. Check method safety +Some methods are safe with MainThreadMarker proof, others always need unsafe: +```rust +// Safe (takes mtm as proof) +let app = NSApplication::sharedApplication(mtm); +app.setActivationPolicy(...); // Safe +app.run(); // Safe + +// Unsafe (framework-specific operations) +unsafe { config.setBootLoader(...) }; +unsafe { vm.startWithCompletionHandler(...) }; +``` + +--- + +## Troubleshooting + +### "no rules expected this token" in define_class! +You're trying to use inline ivars. Remove them: +```rust +// Wrong +struct MyClass { field: Type } + +// Correct +struct MyClass; +``` + +### "no function or associated item named `alloc` found" +Import the `AnyThread` trait: +```rust +use objc2::AnyThread; // For non-MainThreadOnly types +// MainThreadOnly types use mtm.alloc::() +``` + +### "mismatched types" with NSArray +Use `.into_super()` to coerce subclass to parent class: +```rust +let array = NSArray::from_retained_slice(&[subclass_item.into_super()]); +``` + +### "expected &T, found &Retained" +Dereference with `&*`: +```rust +// Wrong +method(&retained_obj); + +// Correct (if method takes &T) +method(&*retained_obj); +// Or let Deref coercion work +method(&retained_obj); // Sometimes works +``` + +### Delegate/callback not being called +Make sure the delegate object stays alive: +```rust +// Store in thread-local or struct field +let delegate = MyDelegate::new(mtm); +app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +// delegate MUST NOT be dropped here! +``` + +### NSBackingStoreType variants +Use `NSBackingStoreType::Buffered` (not `NSBackingStoreBuffered`). + +--- + +## Virtualization Framework Specifics + +### Required entitlements +Create `entitlements.plist`: +```xml + + + + + com.apple.security.virtualization + + + +``` + +Sign binary: +```bash +codesign --entitlements entitlements.plist --force -s - target/release/myapp +``` + +### VM Bundle structure +``` +~/VM.bundle/ +├── AuxiliaryStorage # Boot data +├── Disk.img # Virtual disk +├── HardwareModel # Hardware model data +├── MachineIdentifier # Machine ID data +└── SaveFile.vzvmsave # Optional saved state +``` + +### Loading persisted hardware identity +```rust +fn load_hardware_model(path: &Path) -> Retained { + let data = fs::read(path).expect("Failed to read"); + let ns_data = NSData::with_bytes(&data); + unsafe { + VZMacHardwareModel::initWithDataRepresentation( + VZMacHardwareModel::alloc(), + &ns_data, + ).expect("Invalid hardware model") + } +} +``` diff --git a/.claude/guides/OBJC2_MIGRATION_GUIDE.md b/.claude/guides/OBJC2_MIGRATION_GUIDE.md new file mode 100644 index 000000000..3a442aa66 --- /dev/null +++ b/.claude/guides/OBJC2_MIGRATION_GUIDE.md @@ -0,0 +1,595 @@ +# objc2 0.5 → 0.6 Migration Guide + +A comprehensive guide based on practical experience porting Swift/Objective-C code to Rust using objc2 0.6+. + +## Table of Contents +- [Major Changes Overview](#major-changes-overview) +- [Memory Management](#memory-management) +- [Class Declaration](#class-declaration) +- [Method Calls](#method-calls) +- [Collections](#collections) +- [Foundation Types](#foundation-types) +- [Common Patterns](#common-patterns) +- [Troubleshooting](#troubleshooting) + +--- + +## Major Changes Overview + +### What Changed in objc2 0.6 + +1. **`Retained` replaces `Id`** + - All owned Objective-C objects now use `Retained` + - Better semantics around object ownership + +2. **Simplified `unsafe` usage** + - Many APIs that were safe are now exposed without `unsafe` + - Framework-specific safe wrappers (e.g., `NSApplication::sharedApplication()`) + +3. **`ClassType` trait method changes** + - `::alloc()` now requires the trait to be in scope + - Use `MainThreadMarker::alloc::()` for main-thread classes + +4. **`NSArray` API changes** + - `from_vec()` → `from_slice()` + - Requires references now, not owned values + +5. **Auto-generated bindings** + - `objc2-virtualization`, `objc2-app-kit`, etc. are now auto-generated + - Consistent API across all frameworks + +--- + +## Memory Management + +### Retained vs Id + +**objc2 0.5:** +```rust +use objc2::rc::Id; + +let obj: Id = unsafe { NSObject::new() }; +``` + +**objc2 0.6:** +```rust +use objc2::rc::Retained; + +let obj: Retained = unsafe { NSObject::new() }; +``` + +### Object Allocation + +**objc2 0.5:** +```rust +use objc2::ClassType; + +let obj = unsafe { MyClass::alloc() }; +``` + +**objc2 0.6:** +```rust +use objc2::ClassType; // Must import trait for ::alloc() + +let obj = unsafe { MyClass::alloc() }; +``` + +**For Main-Thread Classes:** +```rust +use objc2_foundation::MainThreadMarker; + +let mtm = MainThreadMarker::new().unwrap(); +let obj = mtm.alloc::(); // Preferred for AppKit classes +``` + +### Casting Between Types + +**Downcasting (child → parent):** +```rust +// objc2 0.5 +let parent: Retained = unsafe { Retained::cast(child) }; + +// objc2 0.6 +let parent: Retained = child.downcast().unwrap(); +``` + +**Upcasting (to super type):** +```rust +let parent_ref: &Parent = child.as_super(); +``` + +--- + +## Class Declaration + +### Basic Class Declaration + +**objc2 0.5:** +```rust +objc2::declare_class!( + struct MyClass; + + unsafe impl ClassType for MyClass { + type Super = NSObject; + const NAME: &'static str = "MyClass"; + } +); +``` + +**objc2 0.6:** +```rust +use objc2::declare_class; +use objc2::mutability::MainThreadOnly; // If needed + +declare_class!( + struct MyClass; + + unsafe impl ClassType for MyClass { + type Super = NSObject; + type Mutability = MainThreadOnly; // NEW: Required + const NAME: &'static str = "MyClass"; + } +); +``` + +### Protocol Implementation + +**objc2 0.6 Pattern:** +```rust +// 1. Declare the class +declare_class!( + struct AppDelegate; + + unsafe impl ClassType for AppDelegate { + type Super = NSObject; + type Mutability = MainThreadOnly; + const NAME: &'static str = "AppDelegate"; + } +); + +// 2. Implement protocol OUTSIDE declare_class! +unsafe impl NSApplicationDelegate for AppDelegate {} + +// 3. Regular Rust impl block for Rust-side methods +impl AppDelegate { + fn new(mtm: MainThreadMarker) -> Retained { + unsafe { msg_send_id![mtm.alloc::(), init] } + } +} +``` + +### ⚠️ What Doesn't Work + +**DO NOT put method implementations inside `declare_class!`:** +```rust +// ❌ This will NOT compile in objc2 0.6 +declare_class!( + struct MyClass; + + unsafe impl ClassType for MyClass { ... } + + impl MyClass { // ❌ ERROR: no rules expected `MyClass` + #[method(myMethod)] + fn my_method(&self) { ... } + } +); +``` + +**The macro expects a specific structure and doesn't support custom impl blocks inside.** + +--- + +## Method Calls + +### msg_send! vs msg_send_id! + +**For methods returning primitives or void:** +```rust +use objc2::msg_send; + +let count: usize = unsafe { msg_send![obj, count] }; +let _: () = unsafe { msg_send![obj, doSomething] }; +``` + +**For methods returning objects:** +```rust +use objc2::msg_send_id; + +let obj: Retained = unsafe { + msg_send_id![ + NSString::alloc(), + initWithUTF8String: c"Hello".as_ptr() + ] +}; +``` + +### Window/View Creation Pattern + +**Creating NSWindow (objc2 0.6):** +```rust +use objc2::msg_send; +use objc2_foundation::{MainThreadMarker, NSRect, NSPoint, NSSize}; +use objc2_app_kit::{NSWindow, NSWindowStyleMask, NSBackingStoreType}; + +let mtm = MainThreadMarker::new().unwrap(); + +let frame = NSRect { + origin: NSPoint { x: 0.0, y: 0.0 }, + size: NSSize { width: 800.0, height: 600.0 }, +}; + +let style = NSWindowStyleMask::Titled + | NSWindowStyleMask::Closable + | NSWindowStyleMask::Resizable; + +let window: Retained = unsafe { + msg_send![ + mtm.alloc::(), + initWithContentRect: frame, + styleMask: style, + backing: NSBackingStoreType::Buffered, + defer: false + ] +}; +``` + +--- + +## Collections + +### NSArray + +**objc2 0.5:** +```rust +let array = NSArray::from_vec(vec![obj1, obj2, obj3]); +``` + +**objc2 0.6:** +```rust +// Takes slice of references now, not owned values +let array = NSArray::from_slice(&[&*obj1, &*obj2, &*obj3]); + +// OR use as_super() for protocol types +let array = NSArray::from_slice(&[obj1.as_super(), obj2.as_super()]); +``` + +### Creating Arrays of Different Types + +When you have different types that share a common supertype: + +```rust +let input: Retained = ...; +let output: Retained = ...; + +// Downcast to common parent type +let input_stream: Retained = + input.downcast().unwrap(); +let output_stream: Retained = + output.downcast().unwrap(); + +// Now create array with references +let streams = NSArray::from_slice(&[&*input_stream, &*output_stream]); +``` + +--- + +## Foundation Types + +### Geometry Types + +**objc2 0.5:** Often used `CGRect`, `CGPoint`, `CGSize` + +**objc2 0.6:** Use `NS*` variants for AppKit + +```rust +use objc2_foundation::{NSRect, NSPoint, NSSize}; + +let rect = NSRect { + origin: NSPoint { x: 0.0, y: 0.0 }, + size: NSSize { width: 100.0, height: 100.0 }, +}; +``` + +### String Literals + +**Creating NSString from Rust string:** +```rust +use objc2_foundation::NSString; + +// From &str +let ns_str = NSString::from_str("Hello, World!"); + +// String literal macro +use objc2_foundation::ns_string; +let ns_str = ns_string!("Hello, World!"); +``` + +### URLs + +```rust +use objc2_foundation::{NSString, NSURL}; +use std::path::PathBuf; + +let path = PathBuf::from("/path/to/file"); +let ns_path = NSString::from_str(&path.to_string_lossy()); +let url = NSURL::fileURLWithPath(&ns_path); +``` + +### MainThreadMarker + +**When to use:** +- Required for allocating AppKit/UIKit objects +- Ensures code runs on main thread +- Compile-time thread safety + +```rust +use objc2_foundation::MainThreadMarker; + +fn main() { + let mtm = MainThreadMarker::new() + .expect("Must run on main thread"); + + let app = NSApplication::sharedApplication(mtm); + // ... rest of app setup +} +``` + +**In callbacks:** +```rust +let callback = move || { + let mtm = MainThreadMarker::new().unwrap(); + let app = NSApplication::sharedApplication(mtm); + app.terminate(None); +}; +``` + +--- + +## Common Patterns + +### Pattern: Error Handling with Objective-C Methods + +**Methods that return `Option>`:** +```rust +let obj = unsafe { SomeClass::initWithSomething(...) } + .ok_or_else(|| "Failed to initialize".to_string())?; +``` + +**Methods that return `Result, Retained>`:** +```rust +let obj = unsafe { SomeClass::initWithURL_error(...) } + .map_err(|e| format!("Failed: {:?}", e))?; +``` + +**Methods that just return `Retained` (never fail):** +```rust +let obj = unsafe { SomeClass::new() }; // No error handling needed +``` + +### Pattern: Keeping Objects Alive + +**Problem:** Objects get deallocated when they go out of scope + +**Solution 1: Box::leak (quick & dirty):** +```rust +// Keep alive for the entire program duration +Box::leak(Box::new(window)); +Box::leak(Box::new(vm)); +``` + +**Solution 2: Store in struct (proper):** +```rust +struct AppState { + window: Retained, + vm: Retained, +} + +static APP_STATE: OnceCell = OnceCell::new(); +``` + +### Pattern: Working with Blocks + +**Creating a block for callbacks:** +```rust +use block2::RcBlock; + +let callback = RcBlock::new(|error: *mut NSError| { + if !error.is_null() { + eprintln!("Error: {:?}", unsafe { &*error }); + } +}); + +unsafe { obj.doSomethingWithCompletionHandler(&callback) }; +``` + +### Pattern: Protocol Method Implementation + +**You CANNOT implement protocol methods with bodies in objc2 0.6.** + +The `declare_class!` macro is only for: +1. Declaring the class structure +2. Specifying class metadata (name, superclass, mutability) +3. Implementing protocol conformance (empty `unsafe impl Protocol for Class {}`) + +For custom behavior, use regular Rust methods or function pointers. + +--- + +## Troubleshooting + +### Error: "no rules expected `ClassName`" + +**Problem:** +```rust +declare_class!( + struct MyClass; + + unsafe impl ClassType for MyClass { ... } + + impl MyClass { // ❌ ERROR HERE + ... + } +); +``` + +**Solution:** Move `impl` block outside `declare_class!`: +```rust +declare_class!( + struct MyClass; + + unsafe impl ClassType for MyClass { ... } +); + +impl MyClass { // ✅ Outside the macro + ... +} +``` + +### Error: "function or associated item `alloc` exists for struct X, but its trait bounds were not satisfied" + +**Problem:** `ClassType` trait not in scope + +**Solution:** +```rust +use objc2::ClassType; // Import the trait + +let obj = unsafe { MyClass::alloc() }; +``` + +**OR use MainThreadMarker for AppKit classes:** +```rust +let mtm = MainThreadMarker::new().unwrap(); +let obj = mtm.alloc::(); +``` + +### Error: "no function or associated item named `from_vec`" + +**Problem:** `NSArray::from_vec()` was removed + +**Solution:** +```rust +// Old (0.5) +let array = NSArray::from_vec(vec![obj1, obj2]); + +// New (0.6) +let array = NSArray::from_slice(&[&*obj1, &*obj2]); +``` + +### Error: "cannot find struct, variant or union type `CGRect`" + +**Problem:** Using Core Graphics types instead of Foundation types + +**Solution:** +```rust +// Wrong +use objc2_foundation::{CGRect, CGPoint, CGSize}; + +// Correct for AppKit +use objc2_foundation::{NSRect, NSPoint, NSSize}; +``` + +### Error: "method `map_err` not found for struct `Retained`" + +**Problem:** Method doesn't return `Result`, it returns `Retained` directly + +**Solution:** +```rust +// If method signature is: fn foo() -> Retained +let obj = unsafe { SomeClass::foo() }; // Just use it directly + +// If method signature is: fn foo() -> Option> +let obj = unsafe { SomeClass::foo() } + .ok_or_else(|| "Error".to_string())?; +``` + +### Error: "unexpected end of macro invocation" in declare_class! + +**Problem:** Missing required sections in `declare_class!` + +**Solution:** Ensure you have all required parts: +```rust +declare_class!( + struct MyClass; // Semicolon required + + unsafe impl ClassType for MyClass { // Required + type Super = NSObject; + type Mutability = MainThreadOnly; // Required in 0.6 + const NAME: &'static str = "MyClass"; + } + // That's it! Nothing else goes inside. +); +``` + +--- + +## Quick Reference Card + +| Task | objc2 0.5 | objc2 0.6 | +|------|-----------|-----------| +| Object type | `Id` | `Retained` | +| Allocate object | `T::alloc()` | `T::alloc()` (trait in scope) or `mtm.alloc::()` | +| Create NSArray | `NSArray::from_vec(vec![...])` | `NSArray::from_slice(&[&*obj, ...])` | +| Geometry types | `CGRect/CGPoint/CGSize` | `NSRect/NSPoint/NSSize` (AppKit) | +| Cast to super | `Retained::cast(obj)` | `obj.downcast()` or `obj.as_super()` | +| msg_send returns object | `msg_send_id!` | `msg_send_id!` (unchanged) | +| Class mutability | Not specified | `type Mutability = MainThreadOnly` | + +--- + +## Best Practices + +1. **Always import `ClassType` when using `::alloc()`** + ```rust + use objc2::ClassType; + ``` + +2. **Use `MainThreadMarker` for AppKit/UIKit** + ```rust + let mtm = MainThreadMarker::new().unwrap(); + let obj = mtm.alloc::(); + ``` + +3. **Keep protocol impls outside `declare_class!`** + ```rust + declare_class!( ... ); + unsafe impl SomeProtocol for MyClass {} + ``` + +4. **Prefer safe wrappers when available** + ```rust + // Instead of unsafe msg_send + app.setActivationPolicy(NSApplicationActivationPolicy::Regular); + ``` + +5. **Use `from_slice` with references for NSArray** + ```rust + NSArray::from_slice(&[&*obj1, &*obj2]) + ``` + +6. **Check method signatures in auto-generated bindings** + - Does it return `Retained`, `Option>`, or `Result<...>`? + - Adjust error handling accordingly + +--- + +## Resources + +- **objc2 docs:** https://docs.rs/objc2/ +- **objc2-foundation:** https://docs.rs/objc2-foundation/ +- **objc2-app-kit:** https://docs.rs/objc2-app-kit/ +- **objc2-virtualization:** https://docs.rs/objc2-virtualization/ + +--- + +## Summary + +The migration from objc2 0.5 to 0.6 involves: +- Changing `Id` to `Retained` +- Adding `type Mutability` to class declarations +- Using `from_slice` instead of `from_vec` for NSArray +- Importing `ClassType` trait explicitly +- Keeping protocol implementations outside `declare_class!` +- Using `NS*` geometry types for AppKit +- Leveraging `MainThreadMarker` for thread-safe main-thread allocation + +The auto-generated bindings make the API more consistent and easier to use, but require understanding the new patterns and conventions. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d212e21aa..63f74ce65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,13 +23,13 @@ jobs: # We only run `cargo build` (not `cargo test`) so as to avoid requiring dev-dependencies to build with the MSRV # version. Building is likely sufficient as runtime errors varying between rust versions is very unlikely. build-msrv: - name: "MSRV Build [Rust 1.88]" + name: "MSRV Build [Rust 1.89]" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.88 + toolchain: 1.89 - run: perl -pi.bak -e 's/opt-level = 2/opt-level = 0/g' Cargo.toml - uses: awalsh128/cache-apt-pkgs-action@latest with: @@ -130,6 +130,7 @@ jobs: env: RUST_CARGO_COMMAND: ${{ matrix.platform.cross == true && 'cross' || 'cargo' }} strategy: + fail-fast: false matrix: platform: - { @@ -156,6 +157,22 @@ jobs: command: "test", args: "--all --tests", } + - { + name: ios, + target: aarch64-apple-ios, + os: macos-latest, + cross: false, + command: "build", + args: "--all", + } + - { + name: android, + target: aarch64-linux-android, + os: ubuntu-latest, + cross: true, + command: "build", + args: "--all", + } name: Test (${{ matrix.platform.name }}) @@ -168,17 +185,23 @@ jobs: targets: ${{ matrix.platform.target }} components: rustfmt + # Install cross from source. Because latest release doesn't work with recent Rust + # when targeting Android. See https://github.com/cross-rs/cross/issues/1222 - name: Install cross if: ${{ matrix.platform.cross == true }} - uses: taiki-e/install-action@cross - - - name: Free Disk Space (Ubuntu) - if: ${{ matrix.platform.os == 'ubuntu-latest' }} - uses: jlumbroso/free-disk-space@v1.3.1 - with: # speed things up a bit - large-packages: false - docker-images: false - swap-storage: false + run: cargo install cross --git https://github.com/cross-rs/cross --rev 426e811 + + # This is currently disabled as Blitz's CI doesn't need the extra disk space. + # So this task is just taking up time for no reason. We will reinstate if our + # disk space requirement increase. + # + # - name: Free Disk Space (Ubuntu) + # if: ${{ matrix.platform.os == 'ubuntu-latest' }} + # uses: jlumbroso/free-disk-space@v1.3.1 + # with: # speed things up a bit + # large-packages: false + # docker-images: false + # swap-storage: false - uses: Swatinem/rust-cache@v2 with: @@ -190,7 +213,7 @@ jobs: if: ${{ matrix.platform.os == 'ubuntu-latest' }} uses: awalsh128/cache-apt-pkgs-action@latest with: - packages: libasound2-dev libatk1.0-dev libgtk-3-dev libudev-dev libpango1.0-dev libxdo-dev + packages: libasound2-dev libatk1.0-dev libgtk-3-dev libudev-dev libpango1.0-dev libxdo-dev libssl-dev version: 1.0 - name: Setup diff --git a/Cargo.lock b/Cargo.lock index 1888413d8..ef62db699 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,44 +20,54 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "accesskit" -version = "0.17.1" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eca13c82f9a5cd813120b2e9b6a5d10532c6e4cd140c295cebd1f770095c8a5" + +[[package]] +name = "accesskit_android" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d3b8f9bae46a948369bc4a03e815d4ed6d616bd00de4051133a5019dc31c5a" +checksum = "d28b60a573c7165b1eb346d66c14e85a1f7923fe2e71e396ce936ca6afb519ae" +dependencies = [ + "accesskit", + "accesskit_consumer", + "jni", + "log", +] [[package]] name = "accesskit_atspi_common" -version = "0.10.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5dd55e6e94949498698daf4d48fb5659e824d7abec0d394089656ceaf99d4f" +checksum = "3eb9cc46b7fb6987c4f891f0301b230b29d9e69b4854f060a0cf41fbc407ab77" dependencies = [ "accesskit", "accesskit_consumer", "atspi-common", "serde", - "thiserror 1.0.69", - "zvariant 4.2.0", + "zvariant", ] [[package]] name = "accesskit_consumer" -version = "0.26.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" +checksum = "69d880a613f29621c90e801feec40f5dd61d837d7e20bf9b67676d45e7364a36" dependencies = [ "accesskit", - "hashbrown 0.15.5", - "immutable-chunkmap", + "hashbrown 0.16.1", ] [[package]] name = "accesskit_macos" -version = "0.18.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" +checksum = "5b0ddfc3fe3d457d11cc1c4989105986a03583a1d54d0c25053118944b62e100" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -65,9 +75,9 @@ dependencies = [ [[package]] name = "accesskit_unix" -version = "0.13.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcee751cc20d88678c33edaf9c07e8b693cd02819fe89053776f5313492273f5" +checksum = "d5d552169ef018149966ed139bb0311c6947b3343e9140d1b9f88d69da9528fd" dependencies = [ "accesskit", "accesskit_atspi_common", @@ -78,36 +88,36 @@ dependencies = [ "futures-lite", "futures-util", "serde", - "zbus 4.4.0", + "tokio", + "tokio-stream", + "zbus", ] [[package]] name = "accesskit_windows" -version = "0.24.1" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" +checksum = "d277279d0a3b0c0021dd110b55aa1fe326b09ee2cbc338df28f847c7daf94e25" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown 0.15.5", - "paste", + "hashbrown 0.16.1", "static_assertions", - "windows 0.58.0", - "windows-core 0.58.0", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] -name = "accesskit_winit" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6a48dad5530b6deb9fc7a52cc6c3bf72cdd9eb8157ac9d32d69f2427a5e879" +name = "accesskit_xplat" +version = "0.1.0" dependencies = [ "accesskit", + "accesskit_android", "accesskit_macos", "accesskit_unix", "accesskit_windows", + "android-activity", "raw-window-handle", - "winit", ] [[package]] @@ -264,9 +274,9 @@ dependencies = [ [[package]] name = "anyrender_skia" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd23fede1c9b592693a3d6213eb8a4c3eb3855c5ccf6cd32c2babbc68c70a31" +checksum = "a462fde618a4394fdb9618a5e4733357f9a1c5b99b17c98704234461449e61b3" dependencies = [ "anyrender", "ash 0.38.0+1.3.281", @@ -282,6 +292,7 @@ dependencies = [ "objc2-core-foundation", "objc2-metal 0.3.2", "objc2-quartz-core 0.3.2", + "objc2-ui-kit", "peniko", "pixels_window_renderer", "raw-window-handle", @@ -436,7 +447,7 @@ dependencies = [ "enumflags2", "futures-channel", "futures-util", - "rand 0.9.2", + "rand", "raw-window-handle", "serde", "serde_repr", @@ -445,7 +456,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", - "zbus 5.12.0", + "zbus", ] [[package]] @@ -486,17 +497,6 @@ dependencies = [ "slab", ] -[[package]] -name = "async-fs" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" -dependencies = [ - "async-lock", - "blocking", - "futures-lite", -] - [[package]] name = "async-io" version = "2.6.0" @@ -610,53 +610,39 @@ checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "atspi" -version = "0.22.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be534b16650e35237bb1ed189ba2aab86ce65e88cc84c66f4935ba38575cecbf" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" dependencies = [ "atspi-common", - "atspi-connection", "atspi-proxies", ] [[package]] name = "atspi-common" -version = "0.6.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1909ed2dc01d0a17505d89311d192518507e8a056a48148e3598fef5e7bb6ba7" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" dependencies = [ "enumflags2", "serde", "static_assertions", - "zbus 4.4.0", + "zbus", "zbus-lockstep", "zbus-lockstep-macros", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - -[[package]] -name = "atspi-connection" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430c5960624a4baaa511c9c0fcc2218e3b58f5dbcc47e6190cafee344b873333" -dependencies = [ - "atspi-common", - "atspi-proxies", - "futures-lite", - "zbus 4.4.0", + "zbus_names", + "zvariant", ] [[package]] name = "atspi-proxies" -version = "0.6.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e6c5de3e524cf967569722446bcd458d5032348554d9a17d7d72b041ab7496" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" dependencies = [ "atspi-common", "serde", - "zbus 4.4.0", - "zvariant 4.2.0", + "zbus", ] [[package]] @@ -805,7 +791,7 @@ dependencies = [ "fastrand", "html-escape", "image", - "keyboard-types", + "keyboard-types 0.7.0", "linebender_resource_handle", "markup5ever", "objc2 0.6.3", @@ -867,6 +853,34 @@ dependencies = [ "xml5ever", ] +[[package]] +name = "blitz-ios-uikit" +version = "0.2.0" +dependencies = [ + "blitz-dom", + "blitz-html", + "blitz-net", + "blitz-traits", + "dioxus", + "dioxus-core", + "dioxus-native-dom", + "futures-util", + "keyboard-types 0.7.0", + "markup5ever", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", + "objc2-ui-kit", + "raw-window-handle", + "rustc-hash 1.1.0", + "stylo", + "taffy", + "tokio", + "winit", +] + [[package]] name = "blitz-net" version = "0.2.1" @@ -904,7 +918,7 @@ name = "blitz-shell" version = "0.2.2" dependencies = [ "accesskit", - "accesskit_winit", + "accesskit_xplat", "android-activity", "anyrender", "arboard", @@ -913,7 +927,7 @@ dependencies = [ "blitz-traits", "data-url", "futures-util", - "keyboard-types", + "keyboard-types 0.7.0", "rfd", "tracing", "winit", @@ -927,7 +941,7 @@ dependencies = [ "bytes", "cursor-icon", "http", - "keyboard-types", + "keyboard-types 0.7.0", "serde", "smol_str", "url", @@ -979,6 +993,15 @@ dependencies = [ "piper", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "cfg_aliases 0.2.1", +] + [[package]] name = "brotli-decompressor" version = "5.0.0" @@ -993,6 +1016,7 @@ dependencies = [ name = "browser" version = "0.0.0" dependencies = [ + "android-activity", "blitz-dom", "blitz-html", "blitz-net", @@ -1001,6 +1025,7 @@ dependencies = [ "linebender_resource_handle", "tracing-subscriber", "webbrowser", + "winit", ] [[package]] @@ -1093,26 +1118,25 @@ dependencies = [ [[package]] name = "calloop" -version = "0.13.0" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e" dependencies = [ "bitflags 2.10.0", - "log", "polling", - "rustix 0.38.44", + "rustix 1.1.2", "slab", - "thiserror 1.0.69", + "tracing", ] [[package]] name = "calloop-wayland-source" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" dependencies = [ "calloop", - "rustix 0.38.44", + "rustix 1.1.2", "wayland-backend", "wayland-client", ] @@ -1232,7 +1256,7 @@ dependencies = [ "cocoa-foundation", "core-foundation 0.9.4", "core-graphics", - "foreign-types 0.5.0", + "foreign-types", "libc", "objc", ] @@ -1480,7 +1504,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "foreign-types", "libc", ] @@ -1519,6 +1543,7 @@ dependencies = [ name = "counter" version = "0.1.0" dependencies = [ + "android-activity", "blitz-dom", "blitz-paint", "dioxus-native", @@ -2007,7 +2032,7 @@ dependencies = [ "futures-channel", "futures-util", "generational-box", - "keyboard-types", + "keyboard-types 0.7.0", "lazy-js-bundle", "rustversion", "serde", @@ -2085,7 +2110,7 @@ dependencies = [ "dioxus-signals", "dioxus-stores", "futures-util", - "keyboard-types", + "keyboard-types 0.7.0", "manganis", "rustc-hash 1.1.0", "tokio", @@ -2104,7 +2129,7 @@ dependencies = [ "dioxus-core", "dioxus-html", "futures-util", - "keyboard-types", + "keyboard-types 0.7.0", "rustc-hash 1.1.0", "tracing", ] @@ -2216,12 +2241,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" version = "0.3.0" @@ -2626,15 +2645,6 @@ dependencies = [ "yeslogic-fontconfig-sys", ] -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - [[package]] name = "foreign-types" version = "0.5.0" @@ -2642,7 +2652,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared 0.3.1", + "foreign-types-shared", ] [[package]] @@ -2656,12 +2666,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -2836,7 +2840,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" dependencies = [ "rustix 1.1.2", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2855,8 +2859,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -2866,9 +2872,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -3395,22 +3403,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", + "webpki-roots", ] [[package]] @@ -3432,11 +3425,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] @@ -3584,15 +3575,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" -[[package]] -name = "immutable-chunkmap" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" -dependencies = [ - "arrayvec", -] - [[package]] name = "indexmap" version = "2.12.1" @@ -3761,6 +3743,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags 2.10.0", + "serde", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -3855,7 +3847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -3932,6 +3924,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3bd0dd2cd90571056fdb71f6275fada10131182f84899f4b2a916e565d81d86" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -4083,7 +4081,7 @@ dependencies = [ "bitflags 2.10.0", "block", "core-graphics-types 0.1.3", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -4098,7 +4096,7 @@ dependencies = [ "bitflags 2.10.0", "block", "core-graphics-types 0.2.0", - "foreign-types 0.5.0", + "foreign-types", "log", "objc", "paste", @@ -4231,23 +4229,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "native-tls" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "ndk" version = "0.9.0" @@ -4293,19 +4274,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases 0.2.1", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.30.1" @@ -4526,19 +4494,6 @@ dependencies = [ "objc2-quartz-core 0.3.2", ] -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.10.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-cloud-kit" version = "0.3.2" @@ -4550,17 +4505,6 @@ dependencies = [ "objc2-foundation 0.3.2", ] -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-core-data" version = "0.2.2" @@ -4590,7 +4534,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", "dispatch2", + "libc", "objc2 0.6.3", ] @@ -4601,10 +4547,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", "dispatch2", + "libc", "objc2 0.6.3", "objc2-core-foundation", "objc2-io-surface", + "objc2-metal 0.3.2", ] [[package]] @@ -4631,36 +4580,37 @@ dependencies = [ [[package]] name = "objc2-core-location" -version = "0.2.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", + "objc2 0.6.3", + "objc2-foundation 0.3.2", ] [[package]] -name = "objc2-core-location" +name = "objc2-core-text" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ + "bitflags 2.10.0", "objc2 0.6.3", - "objc2-foundation 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", ] [[package]] -name = "objc2-core-text" +name = "objc2-core-video" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" dependencies = [ "bitflags 2.10.0", "objc2 0.6.3", "objc2-core-foundation", "objc2-core-graphics", + "objc2-io-surface", ] [[package]] @@ -4677,7 +4627,6 @@ checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.10.0", "block2 0.5.1", - "dispatch", "libc", "objc2 0.5.2", ] @@ -4706,18 +4655,6 @@ dependencies = [ "objc2-core-foundation", ] -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - [[package]] name = "objc2-metal" version = "0.2.2" @@ -4761,43 +4698,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.10.0", + "block2 0.6.2", + "libc", "objc2 0.6.3", "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-video", "objc2-foundation 0.3.2", "objc2-metal 0.3.2", ] -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.10.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit 0.2.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core 0.2.2", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications 0.2.2", -] - [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -4807,40 +4717,16 @@ dependencies = [ "bitflags 2.10.0", "block2 0.6.2", "objc2 0.6.3", - "objc2-cloud-kit 0.3.2", + "objc2-cloud-kit", "objc2-core-data 0.3.2", "objc2-core-foundation", "objc2-core-graphics", "objc2-core-image 0.3.2", - "objc2-core-location 0.3.2", + "objc2-core-location", "objc2-core-text", "objc2-foundation 0.3.2", "objc2-quartz-core 0.3.2", - "objc2-user-notifications 0.3.2", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.10.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location 0.2.2", - "objc2-foundation 0.2.2", + "objc2-user-notifications", ] [[package]] @@ -4874,50 +4760,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -4969,10 +4811,10 @@ checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" dependencies = [ "android_system_properties", "log", - "nix 0.30.1", + "nix", "objc2 0.6.3", "objc2-foundation 0.3.2", - "objc2-ui-kit 0.3.2", + "objc2-ui-kit", "serde", "windows-sys 0.61.2", ] @@ -5018,7 +4860,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -5372,9 +5214,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", "serde", @@ -5390,49 +5232,83 @@ dependencies = [ ] [[package]] -name = "quote" -version = "1.0.42" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ - "proc-macro2", + "bytes", + "cfg_aliases 0.2.1", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", ] [[package]] -name = "r-efi" -version = "5.3.0" +name = "quinn-proto" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash 2.1.1", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] [[package]] -name = "rand" -version = "0.8.5" +name = "quinn-udp" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ + "cfg_aliases 0.2.1", "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", ] [[package]] -name = "rand" -version = "0.9.2" +name = "quote" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", + "proc-macro2", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "rand_chacha", + "rand_core", ] [[package]] @@ -5442,16 +5318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", + "rand_core", ] [[package]] @@ -5542,15 +5409,6 @@ dependencies = [ "winit", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.18" @@ -5646,22 +5504,22 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", "mime_guess", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -5671,6 +5529,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", ] [[package]] @@ -5795,6 +5654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", + "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -5807,6 +5667,7 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ + "web-time", "zeroize", ] @@ -5869,15 +5730,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -5892,9 +5744,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "1dd3accc0f3f4bbaf2c9e1957a030dc582028130c67660d44c0a0345a22ca69b" dependencies = [ "ab_glyph", "log", @@ -5903,29 +5755,6 @@ dependencies = [ "tiny-skia", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "selectors" version = "0.33.0" @@ -6242,9 +6071,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" dependencies = [ "bitflags 2.10.0", "calloop", @@ -6253,13 +6082,15 @@ dependencies = [ "libc", "log", "memmap2 0.9.9", - "rustix 0.38.44", - "thiserror 1.0.69", + "rustix 1.1.2", + "thiserror 2.0.17", "wayland-backend", "wayland-client", "wayland-csd-frame", "wayland-cursor", "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", "wayland-protocols-wlr", "wayland-scanner", "xkeysym", @@ -6267,11 +6098,12 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.2.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5" dependencies = [ - "serde", + "borsh", + "serde_core", ] [[package]] @@ -6660,27 +6492,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "system-deps" version = "7.0.7" @@ -6942,6 +6753,7 @@ dependencies = [ name = "todomvc" version = "0.1.0" dependencies = [ + "android-activity", "dioxus-native", "idna_adapter", "tracing-subscriber", @@ -6956,6 +6768,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -6975,16 +6788,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" @@ -7145,6 +6948,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -7237,7 +7041,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand", "sha1", "thiserror 2.0.17", "utf-8", @@ -7463,12 +7267,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vello" version = "0.6.0" @@ -7771,6 +7569,32 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.3.9" @@ -7868,6 +7692,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" @@ -8225,16 +8058,38 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + [[package]] name = "windows" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", + "windows-collections 0.3.2", "windows-core 0.62.2", - "windows-future", - "windows-numerics", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", ] [[package]] @@ -8268,6 +8123,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -8276,11 +8144,22 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement 0.60.2", "windows-interface 0.59.3", - "windows-link", + "windows-link 0.2.1", "windows-result 0.4.1", "windows-strings 0.5.1", ] +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + [[package]] name = "windows-future" version = "0.3.2" @@ -8288,8 +8167,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ "windows-core 0.62.2", - "windows-link", - "windows-threading", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -8336,6 +8215,12 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-link" version = "0.2.1" @@ -8344,23 +8229,22 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.3.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.62.2", - "windows-link", + "windows-core 0.61.2", + "windows-link 0.1.3", ] [[package]] -name = "windows-registry" -version = "0.6.1" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] @@ -8372,13 +8256,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -8391,13 +8284,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -8442,7 +8344,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -8482,7 +8384,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", + "windows-link 0.2.1", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -8493,13 +8395,22 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-threading" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -8642,51 +8553,224 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.30.12" +version = "0.31.0-beta.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "2879d2854d1a43e48f67322d4bd097afcb6eb8f8f775c8de0260a71aea1df1aa" dependencies = [ - "ahash", - "android-activity", - "atomic-waker", "bitflags 2.10.0", - "block2 0.5.1", - "bytemuck", - "calloop", "cfg_aliases 0.2.1", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", "cursor-icon", "dpi", - "js-sys", "libc", - "memmap2 0.9.9", + "raw-window-handle", + "rustix 1.1.2", + "smol_str", + "tracing", + "winit-android", + "winit-appkit", + "winit-common", + "winit-core", + "winit-orbital", + "winit-uikit", + "winit-wayland", + "winit-web", + "winit-win32", + "winit-x11", +] + +[[package]] +name = "winit-android" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d9c0d2cd93efec3a9f9ad819cfaf0834782403af7c0d248c784ec0c61761df" +dependencies = [ + "android-activity", + "bitflags 2.10.0", + "dpi", "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", + "raw-window-handle", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-appkit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21310ca07851a49c348e0c2cc768e36b52ca65afda2c2354d78ed4b90074d8aa" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "dispatch2", + "dpi", + "objc2 0.6.3", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-video", + "objc2-foundation 0.3.2", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-common" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45375fbac4cbb77260d83a30b1f9d8105880dbac99a9ae97f56656694680ff69" +dependencies = [ + "memmap2 0.9.9", + "objc2 0.6.3", + "objc2-core-foundation", + "smol_str", + "tracing", + "winit-core", + "x11-dl", + "xkbcommon-dl", +] + +[[package]] +name = "winit-core" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f0ccd7abb43740e2c6124ac7cae7d865ecec74eec63783e8922577ac232583" +dependencies = [ + "bitflags 2.10.0", + "cursor-icon", + "dpi", + "keyboard-types 0.8.3", + "raw-window-handle", + "smol_str", + "web-time", +] + +[[package]] +name = "winit-orbital" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ea1fb262e7209f265f12bd0cc792c399b14355675e65531e9c8a87db287d46" +dependencies = [ + "bitflags 2.10.0", + "dpi", "orbclient", - "percent-encoding", - "pin-project", "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", + "redox_syscall 0.5.18", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-uikit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680a356e798837d8eb274d4556e83bceaf81698194e31aafc5cfb8a9f2fab643" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "dispatch2", + "dpi", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-wayland" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce5afb2ba07da603f84b722c95f9f9396d2cedae3944fb6c0cda4a6f88de545" +dependencies = [ + "ahash", + "bitflags 2.10.0", + "calloop", + "cursor-icon", + "dpi", + "libc", + "memmap2 0.9.9", + "raw-window-handle", + "rustix 1.1.2", "sctk-adwaita", "smithay-client-toolkit", "smol_str", "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", "wayland-backend", "wayland-client", "wayland-protocols", "wayland-protocols-plasma", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-web" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2490a953fb776fbbd5e295d54f1c3847f4f15b6c3929ec53c09acda6487a92" +dependencies = [ + "atomic-waker", + "bitflags 2.10.0", + "concurrent-queue", + "cursor-icon", + "dpi", + "js-sys", + "pin-project", + "raw-window-handle", + "smol_str", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", "web-time", - "windows-sys 0.52.0", + "winit-core", +] + +[[package]] +name = "winit-win32" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644ea78af0e858aa3b092e5d1c67c41995a98220c81813f1353b28bc8bb91eaa" +dependencies = [ + "bitflags 2.10.0", + "cursor-icon", + "dpi", + "raw-window-handle", + "smol_str", + "tracing", + "unicode-segmentation", + "windows-sys 0.59.0", + "winit-core", +] + +[[package]] +name = "winit-x11" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5b600756534c7041aa93cd0d244d44b09fca1b89e202bd1cd80dd9f3636c46" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "calloop", + "cursor-icon", + "dpi", + "libc", + "percent-encoding", + "raw-window-handle", + "rustix 1.1.2", + "smol_str", + "tracing", + "winit-common", + "winit-core", "x11-dl", "x11rb", "xkbcommon-dl", @@ -8807,6 +8891,7 @@ dependencies = [ "once_cell", "rustix 1.1.2", "x11rb-protocol", + "xcursor", ] [[package]] @@ -8831,16 +8916,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" -[[package]] -name = "xdg-home" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "xkbcommon-dl" version = "0.4.2" @@ -8930,13 +9005,12 @@ dependencies = [ [[package]] name = "zbus" -version = "4.4.0" +version = "5.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" dependencies = [ "async-broadcast", "async-executor", - "async-fs", "async-io", "async-lock", "async-process", @@ -8947,40 +9021,9 @@ dependencies = [ "enumflags2", "event-listener", "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.29.0", - "ordered-stream", - "rand 0.8.5", - "serde", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "windows-sys 0.52.0", - "xdg-home", - "zbus_macros 4.4.0", - "zbus_names 3.0.0", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus" -version = "5.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" -dependencies = [ - "async-broadcast", - "async-recursion", - "async-trait", - "enumflags2", - "event-listener", - "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "nix", "ordered-stream", "serde", "serde_repr", @@ -8990,46 +9033,33 @@ dependencies = [ "uuid", "windows-sys 0.61.2", "winnow", - "zbus_macros 5.12.0", - "zbus_names 4.2.0", - "zvariant 5.8.0", + "zbus_macros", + "zbus_names", + "zvariant", ] [[package]] name = "zbus-lockstep" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca2c5dceb099bddaade154055c926bb8ae507a18756ba1d8963fd7b51d8ed1d" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" dependencies = [ "zbus_xml", - "zvariant 4.2.0", + "zvariant", ] [[package]] name = "zbus-lockstep-macros" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" dependencies = [ "proc-macro2", "quote", "syn 2.0.111", "zbus-lockstep", "zbus_xml", - "zvariant 4.2.0", -] - -[[package]] -name = "zbus_macros" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.111", - "zvariant_utils 2.1.0", + "zvariant", ] [[package]] @@ -9042,20 +9072,9 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.111", - "zbus_names 4.2.0", - "zvariant 5.8.0", - "zvariant_utils 3.2.1", -] - -[[package]] -name = "zbus_names" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" -dependencies = [ - "serde", - "static_assertions", - "zvariant 4.2.0", + "zbus_names", + "zvariant", + "zvariant_utils", ] [[package]] @@ -9067,20 +9086,20 @@ dependencies = [ "serde", "static_assertions", "winnow", - "zvariant 5.8.0", + "zvariant", ] [[package]] name = "zbus_xml" -version = "4.0.0" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f374552b954f6abb4bd6ce979e6c9b38fb9d0cd7cc68a7d796e70c9f3a233" +checksum = "589e9a02bfafb9754bb2340a9e3b38f389772684c63d9637e76b1870377bec29" dependencies = [ - "quick-xml 0.30.0", + "quick-xml 0.36.2", "serde", "static_assertions", - "zbus_names 3.0.0", - "zvariant 4.2.0", + "zbus_names", + "zvariant", ] [[package]] @@ -9183,19 +9202,6 @@ dependencies = [ "zune-core", ] -[[package]] -name = "zvariant" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" -dependencies = [ - "endi", - "enumflags2", - "serde", - "static_assertions", - "zvariant_derive 4.2.0", -] - [[package]] name = "zvariant" version = "5.8.0" @@ -9207,21 +9213,8 @@ dependencies = [ "serde", "url", "winnow", - "zvariant_derive 5.8.0", - "zvariant_utils 3.2.1", -] - -[[package]] -name = "zvariant_derive" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.111", - "zvariant_utils 2.1.0", + "zvariant_derive", + "zvariant_utils", ] [[package]] @@ -9234,18 +9227,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.111", - "zvariant_utils 3.2.1", -] - -[[package]] -name = "zvariant_utils" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", + "zvariant_utils", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ed1a301b3..f12993a39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "packages/accesskit_xplat", "packages/debug_timer", "packages/blitz-traits", "packages/blitz-dom", @@ -17,7 +18,7 @@ members = [ "wpt/runner", "examples/counter", "examples/todomvc", - "examples/wgpu_texture", + "examples/wgpu_texture", "packages/blitz-ios-uikit", ] exclude = ["sites"] resolver = "2" @@ -29,7 +30,7 @@ homepage = "https://github.com/dioxuslabs/blitz" repository = "https://github.com/dioxuslabs/blitz" categories = ["gui"] edition = "2024" -rust-version = "1.88.0" +rust-version = "1.89.0" [workspace.dependencies] # Blitz dependencies(in-repo) @@ -44,6 +45,7 @@ stylo_taffy = { version = "0.2.0", path = "./packages/stylo_taffy", default-feat dioxus-native = { version = "0.7.0", path = "./packages/dioxus-native", default-features = false } dioxus-native-dom = { version = "0.7.0", path = "./packages/dioxus-native-dom", default-features = false } debug_timer = { version = "0.1.2", path = "./packages/debug_timer" } +accesskit_xplat = { version = "0.1", path = "./packages/accesskit_xplat", default-features = false } # Servo dependencies style = { version = "0.9.0", package = "stylo" } @@ -100,7 +102,7 @@ anyrender = { version = "0.6" } anyrender_vello = { version = "0.6" } anyrender_vello_cpu = { version = "0.8" } anyrender_vello_hybrid = { version = "0.1" } -anyrender_skia = { version = "0.3.0" } +anyrender_skia = { version = "0.3.1" } anyrender_svg = { version = "0.6" } wgpu_context = { version = "0.1.2" } @@ -128,9 +130,8 @@ usvg = "0.45.1" # Windowing & Input raw-window-handle = "0.6.0" -winit = { version = "0.30.2", features = ["rwh_06"] } -accesskit_winit = "0.23" -accesskit = "0.17" +winit = { version = "=0.31.0-beta.2" } +accesskit = "0.22" arboard = { version = "3.4.1", default-features = false } rfd = { version = "0.15.3", default-features = false } keyboard-types = "0.7" @@ -141,7 +142,7 @@ url = "2.5.0" http = "1.1.0" data-url = "0.3.1" tokio = "1.42" -reqwest = "0.12" +reqwest = { version = "0.12", default-features = false } reqwest-middleware = { version = "0.4.2", default-features = false } http-cache-reqwest = { version = "=1.0.0-alpha.2", default-features = false } @@ -167,7 +168,7 @@ tracing-subscriber = "0.3" futures-util = "0.3.30" futures-intrusive = "0.5.0" pollster = "0.4" -smol_str = "0.2" +smol_str = "0.3" bitflags = "2.8.0" bytemuck = "1" fastrand = "2.3.0" @@ -214,7 +215,7 @@ edition = "2024" description = "Top level crate for Blitz" license = "MIT OR Apache-2.0" keywords = ["dom", "ui", "gui", "react", "wasm"] -rust-version = "1.86.0" +rust-version = "1.89.0" publish = false [dev-dependencies] @@ -245,6 +246,7 @@ tracing-subscriber = "0.3" # anyrender_skia = { path = "../anyrender/crates/anyrender_skia" } # anyrender_vello = { path = "../anyrender/crates/anyrender_vello" } # anyrender_vello_cpu = { path = "../anyrender/crates/anyrender_vello_cpu" } +# anyrender_vello_hybrid = { path = "../anyrender/crates/anyrender_vello_hybrid" } # anyrender_svg = { path = "../anyrender/crates/anyrender_svg" } # wgpu_context = { path = "../anyrender/crates/wgpu_context" } diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 000000000..8f70d451c --- /dev/null +++ b/Cross.toml @@ -0,0 +1,6 @@ +[build] + # additional commands to run prior to building the package +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && apt-get --assume-yes install python3:$CROSS_DEB_ARCH" +] \ No newline at end of file diff --git a/apps/browser/Cargo.toml b/apps/browser/Cargo.toml index e737a515f..e9d31314c 100644 --- a/apps/browser/Cargo.toml +++ b/apps/browser/Cargo.toml @@ -36,8 +36,13 @@ dioxus-native = { workspace = true, features = [ ] } blitz-traits = { workspace = true } blitz-dom = { workspace = true, features = ["woff-rust", "parallel-construct"] } -blitz-net = { workspace = true } +blitz-net = { workspace = true, features = ["http2"] } blitz-html = { workspace = true } linebender_resource_handle = { workspace = true } tracing-subscriber = { workspace = true, optional = true } webbrowser = { workspace = true } +winit = { workspace = true } + + +[target.'cfg(target_os = "android")'.dependencies] +android-activity = { version = "0.6.0", features = ["native-activity"] } diff --git a/apps/browser/Dioxus.toml b/apps/browser/Dioxus.toml index e389d582f..eaf405d22 100644 --- a/apps/browser/Dioxus.toml +++ b/apps/browser/Dioxus.toml @@ -1,4 +1,5 @@ [application] +android_main_activity = "MainActivity.kt.hbs" [bundle] publisher = "DioxusLabs" diff --git a/apps/browser/MainActivity.kt.hbs b/apps/browser/MainActivity.kt.hbs new file mode 100644 index 000000000..a0ce0e2c3 --- /dev/null +++ b/apps/browser/MainActivity.kt.hbs @@ -0,0 +1,4 @@ +package dev.dioxus.main; + +class MainActivity : android.app.NativeActivity() +//class MainActivity : com.google.androidgamesdk.GameActivity() diff --git a/apps/browser/assets/browser.css b/apps/browser/assets/browser.css index 88c94b743..a3ad7dc09 100644 --- a/apps/browser/assets/browser.css +++ b/apps/browser/assets/browser.css @@ -12,6 +12,7 @@ html, body, #main, #frame { #frame { display: flex; flex-direction: column; + background: black; } .urlbar { diff --git a/apps/browser/src/main.rs b/apps/browser/src/main.rs index ca929f40f..23c15a4e5 100644 --- a/apps/browser/src/main.rs +++ b/apps/browser/src/main.rs @@ -25,10 +25,16 @@ use icons::IconButton; static BROWSER_UI_STYLES: Asset = asset!("../assets/browser.css"); +#[unsafe(no_mangle)] +#[cfg(target_os = "android")] +pub fn android_main(android_app: dioxus_native::AndroidApp) { + dioxus_native::set_android_app(android_app); + main() +} + fn main() { #[cfg(feature = "tracing")] tracing_subscriber::fmt::init(); - dioxus_native::launch(app) } @@ -39,7 +45,7 @@ fn use_sync_store(value: impl FnOnce() -> T) -> SyncSt } fn app() -> Element { - let home_url = use_hook(|| Url::parse("https://html.duckduckgo.com").unwrap()); + let home_url = use_hook(|| Url::parse("https://en.wikipedia.org/wiki/Main_Page").unwrap()); let mut url_input_handle = use_signal(|| None); let mut webview_node_handle: Signal> = use_signal(|| None); @@ -83,8 +89,23 @@ fn app() -> Element { } }); + // HACK: Winit doesn't support "safe area" on Android yet. + // So we just hardcode a fallback safe area. + const TOP_PAD: &str = if cfg!(target_os = "android") { + "30px" + } else { + "" + }; + const BOTTOM_PAD: &str = if cfg!(target_os = "android") { + "44px" + } else { + "" + }; + rsx!( div { id: "frame", + padding_top: TOP_PAD, + padding_bottom: BOTTOM_PAD, title { "Blitz Browser" } document::Link { rel: "stylesheet", href: BROWSER_UI_STYLES } @@ -134,7 +155,12 @@ fn app() -> Element { } }, onkeydown: move |evt| { - if evt.key() == Key::Enter { + let is_enter = match evt.key() { + Key::Enter => true, + Key::Character(s) if s == "\n" => true, + _ => false, + }; + if is_enter { evt.prevent_default(); let req = req_from_string(&url_input_value.read()); if let Some(req) = req { diff --git a/apps/readme/src/main.rs b/apps/readme/src/main.rs index 5ecb72773..b1742621a 100644 --- a/apps/readme/src/main.rs +++ b/apps/readme/src/main.rs @@ -37,24 +37,22 @@ use markdown::{BLITZ_MD_STYLES, GITHUB_MD_STYLES, markdown_to_html}; use notify::{Error as NotifyError, Event as NotifyEvent, RecursiveMode, Watcher as _}; use readme_application::{ReadmeApplication, ReadmeEvent}; -use blitz_shell::{BlitzShellEvent, BlitzShellNetWaker, WindowConfig, create_default_event_loop}; +use blitz_shell::{BlitzShellEvent, BlitzShellProxy, WindowConfig, create_default_event_loop}; use std::env::current_dir; use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::sync::oneshot; use url::Url; -use winit::event_loop::EventLoopProxy; use winit::window::WindowAttributes; struct ReadmeNavigationProvider { - proxy: EventLoopProxy, + proxy: BlitzShellProxy, } impl NavigationProvider for ReadmeNavigationProvider { fn navigate_to(&self, opts: NavigationOptions) { - let _ = self - .proxy + self.proxy .send_event(BlitzShellEvent::Navigate(Box::new(opts))); } } @@ -74,9 +72,10 @@ fn main() { let _guard = rt.enter(); let event_loop = create_default_event_loop(); - let proxy = event_loop.create_proxy(); + let winit_proxy = event_loop.create_proxy(); + let (proxy, event_queue) = BlitzShellProxy::new(winit_proxy); - let net_waker = Some(BlitzShellNetWaker::shared(proxy.clone())); + let net_waker = Some(Arc::new(proxy.clone()) as _); let net_provider = Arc::new(Provider::new(net_waker)); let (base_url, contents, is_md, file_path) = @@ -98,7 +97,6 @@ fn main() { // println!("{html}"); - let proxy = event_loop.create_proxy(); let navigation_provider = ReadmeNavigationProvider { proxy: proxy.clone(), }; @@ -121,6 +119,7 @@ fn main() { // Create application let mut application = ReadmeApplication::new( proxy.clone(), + event_queue, raw_url.clone(), net_provider, navigation_provider, @@ -131,7 +130,7 @@ fn main() { let mut watcher = notify::recommended_watcher(move |_: Result| { let event = BlitzShellEvent::Embedder(Arc::new(ReadmeEvent)); - proxy.send_event(event).unwrap(); + proxy.send_event(event); }) .unwrap(); @@ -145,7 +144,7 @@ fn main() { } // Run event loop - event_loop.run_app(&mut application).unwrap() + event_loop.run_app(application).unwrap() } async fn fetch( diff --git a/apps/readme/src/readme_application.rs b/apps/readme/src/readme_application.rs index ce22dd9ea..3e635d910 100644 --- a/apps/readme/src/readme_application.rs +++ b/apps/readme/src/readme_application.rs @@ -4,13 +4,15 @@ use crate::WindowRenderer; use blitz_dom::DocumentConfig; use blitz_html::HtmlDocument; use blitz_net::Provider; -use blitz_shell::{BlitzApplication, BlitzShellEvent, View, WindowConfig}; +use blitz_shell::{BlitzApplication, BlitzShellEvent, BlitzShellProxy, View, WindowConfig}; use blitz_traits::navigation::{NavigationOptions, NavigationProvider}; use tokio::runtime::Handle; use winit::application::ApplicationHandler; use winit::event::{Modifiers, StartCause, WindowEvent}; -use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; +use winit::event_loop::ActiveEventLoop; use winit::keyboard::{KeyCode, PhysicalKey}; +#[cfg(target_os = "macos")] +use winit::platform::macos::ApplicationHandlerExtMacOS; use winit::window::{Theme, WindowId}; use crate::fetch; @@ -30,14 +32,15 @@ pub struct ReadmeApplication { impl ReadmeApplication { pub fn new( - proxy: EventLoopProxy, + proxy: BlitzShellProxy, + event_queue: std::sync::mpsc::Receiver, raw_url: String, net_provider: Arc, navigation_provider: Arc, ) -> Self { let handle = Handle::current(); Self { - inner: BlitzApplication::new(proxy.clone()), + inner: BlitzApplication::new(proxy, event_queue), handle, raw_url, net_provider, @@ -63,14 +66,12 @@ impl ReadmeApplication { self.handle.spawn(async move { let url = url; let (base_url, contents, is_md, _file_path) = fetch(&url, net_provider).await; - proxy - .send_event(BlitzShellEvent::NavigationLoad { - url: base_url, - contents, - is_md, - retain_scroll_position, - }) - .unwrap(); + proxy.send_event(BlitzShellEvent::NavigationLoad { + url: base_url, + contents, + is_md, + retain_scroll_position, + }); }); } @@ -81,14 +82,12 @@ impl ReadmeApplication { Box::new(move |result| { let (url, bytes) = result.unwrap(); let contents = std::str::from_utf8(&bytes).unwrap().to_string(); - proxy - .send_event(BlitzShellEvent::NavigationLoad { - url, - contents, - is_md: false, - retain_scroll_position: false, - }) - .unwrap(); + proxy.send_event(BlitzShellEvent::NavigationLoad { + url, + contents, + is_md: false, + retain_scroll_position: false, + }); }), ); } @@ -132,22 +131,35 @@ impl ReadmeApplication { } } -impl ApplicationHandler for ReadmeApplication { - fn resumed(&mut self, event_loop: &ActiveEventLoop) { +impl ApplicationHandler for ReadmeApplication { + #[cfg(target_os = "macos")] + fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> { + self.inner.macos_handler() + } + + fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { self.inner.resumed(event_loop); } - fn suspended(&mut self, event_loop: &ActiveEventLoop) { + fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { self.inner.suspended(event_loop); } - fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + self.inner.can_create_surfaces(event_loop); + } + + fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + self.inner.destroy_surfaces(event_loop); + } + + fn new_events(&mut self, event_loop: &dyn ActiveEventLoop, cause: StartCause) { self.inner.new_events(event_loop, cause); } fn window_event( &mut self, - event_loop: &ActiveEventLoop, + event_loop: &dyn ActiveEventLoop, window_id: WindowId, event: WindowEvent, ) { @@ -157,7 +169,7 @@ impl ApplicationHandler for ReadmeApplication { if let WindowEvent::KeyboardInput { event, .. } = &event { let mods = self.keyboard_modifiers.state(); - if !event.state.is_pressed() && (mods.control_key() || mods.super_key()) { + if !event.state.is_pressed() && (mods.control_key() || mods.meta_key()) { match event.physical_key { PhysicalKey::Code(KeyCode::KeyR) => self.reload_document(true), PhysicalKey::Code(KeyCode::KeyT) => self.toggle_theme(), @@ -175,28 +187,30 @@ impl ApplicationHandler for ReadmeApplication { self.inner.window_event(event_loop, window_id, event); } - fn user_event(&mut self, event_loop: &ActiveEventLoop, event: BlitzShellEvent) { - match event { - BlitzShellEvent::Embedder(event) => { - if let Some(_event) = event.downcast_ref::() { - self.reload_document(true); + fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { + while let Ok(event) = self.inner.event_queue.try_recv() { + match event { + BlitzShellEvent::Embedder(event) => { + if let Some(_event) = event.downcast_ref::() { + self.reload_document(true); + } } + BlitzShellEvent::Navigate(options) => { + let old_url = std::mem::replace(&mut self.raw_url, options.url.to_string()); + self.url_history.push(old_url); + self.reload_document(false); + self.navigate(*options); + } + BlitzShellEvent::NavigationLoad { + url, + contents, + retain_scroll_position, + is_md, + } => { + self.load_document(contents, retain_scroll_position, url, is_md); + } + event => self.inner.handle_blitz_shell_event(event_loop, event), } - BlitzShellEvent::Navigate(options) => { - let old_url = std::mem::replace(&mut self.raw_url, options.url.to_string()); - self.url_history.push(old_url); - self.reload_document(false); - self.navigate(*options); - } - BlitzShellEvent::NavigationLoad { - url, - contents, - retain_scroll_position, - is_md, - } => { - self.load_document(contents, retain_scroll_position, url, is_md); - } - event => self.inner.user_event(event_loop, event), } } } diff --git a/examples/counter/Cargo.toml b/examples/counter/Cargo.toml index 111acc388..d4dae0579 100644 --- a/examples/counter/Cargo.toml +++ b/examples/counter/Cargo.toml @@ -5,20 +5,24 @@ edition = "2024" license.workspace = true publish = false +[lib] +crate-type = ["cdylib"] + [features] -default = ["system_fonts", "vello"] +default = ["vello"] system_fonts = ["blitz-dom/system_fonts"] vello = ["dioxus-native/vello"] cpu = ["cpu-pixels"] svg = ["blitz-paint/svg"] cpu-pixels = ["dioxus-native/vello-cpu-pixels"] cpu-softbuffer = ["dioxus-native/vello-cpu-softbuffer"] +skia = ["dioxus-native/skia"] incremental = ["dioxus-native/incremental"] log_frame_times = ["dioxus-native/log-frame-times"] log_phase_times = ["dioxus-native/log-phase-times"] [dependencies] -dioxus-native = { workspace = true, default-features = false, features = ["prelude"]} +dioxus-native = { workspace = true, default-features = false, features = ["prelude", "system-fonts"] } # Control whether system font support is enabled blitz-dom = { workspace = true, default-features = false } @@ -26,4 +30,7 @@ blitz-paint = { workspace = true, default-features = false } # Disable unicode URL support # See https://github.com/hsivonen/idna_adapter -idna_adapter = "=1.0.0" \ No newline at end of file +idna_adapter = "=1.0.0" + +[target.'cfg(target_os = "android")'.dependencies] +android-activity = { version = "0.6.0", features = ["native-activity"] } \ No newline at end of file diff --git a/examples/counter/Dioxus.toml b/examples/counter/Dioxus.toml new file mode 100644 index 000000000..3292650d6 --- /dev/null +++ b/examples/counter/Dioxus.toml @@ -0,0 +1,2 @@ +[application] +android_main_activity = "MainActivity.kt.hbs" \ No newline at end of file diff --git a/examples/counter/MainActivity.kt.hbs b/examples/counter/MainActivity.kt.hbs new file mode 100644 index 000000000..a0ce0e2c3 --- /dev/null +++ b/examples/counter/MainActivity.kt.hbs @@ -0,0 +1,4 @@ +package dev.dioxus.main; + +class MainActivity : android.app.NativeActivity() +//class MainActivity : com.google.androidgamesdk.GameActivity() diff --git a/examples/counter/src/app.rs b/examples/counter/src/app.rs new file mode 100644 index 000000000..1e6613de5 --- /dev/null +++ b/examples/counter/src/app.rs @@ -0,0 +1,114 @@ +//! Drive the renderer from Dioxus +use dioxus_native::prelude::*; + +pub fn app() -> Element { + let mut count = use_signal(|| 0); + + rsx! { + div { class: "container", + style { {CSS} } + h1 { class: "header", "Count: {count}" } + div { class: "buttons", + button { + class: "counter-button btn-green", + onclick: move |_| { count += 1 }, + "Increment" + } + button { + class: "counter-button btn-red", + onclick: move |_| { count -= 1 }, + "Decrement" + } + } + button { + class: "counter-button btn-blue", + onclick: move |_| { count.set(0) }, + "Reset" + } + } + } +} + +const CSS: &str = r#" + +html, body, #main { + padding: 0; + margin: 0; + background-color: green; + height: 100%; +} + +.header { + background-color: pink; + padding: 20px; + line-height: 1; + font-family: sans-serif; +} + +.container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + width: 100vw; + background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), + linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), + linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); +} + +.buttons { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin: 20px 0; +} + +.counter-button { + margin: 0 10px; + padding: 10px 20px; + border-radius: 5px; + font-size: 1.5rem; + cursor: pointer; + line-height: 1; + font-family: sans-serif; + border-width: 2px; + border-style: solid; +} +.counter-button:focus { + outline: 4px solid black; +} + +.btn-green { + background-color: green; + border-color: green; + color: white; +} +.btn-green:hover { + color: green; + background-color: white; +} + +.btn-red { + background-color: red; + border-color: red; + color: white; +} +.btn-red:hover { + color: red; + background-color: white; +} + +.btn-blue { + background-color: blue; + border-color: blue; + color: white; +} +.btn-blue:hover { + color: blue; + background-color: white; +} + + +"#; diff --git a/examples/counter/src/lib.rs b/examples/counter/src/lib.rs new file mode 100644 index 000000000..98df23332 --- /dev/null +++ b/examples/counter/src/lib.rs @@ -0,0 +1,10 @@ +#![cfg(target_os = "android")] + +mod app; + +/// Run with `cargo apk run` +#[unsafe(no_mangle)] +pub fn android_main(android_app: dioxus_native::AndroidApp) { + dioxus_native::set_android_app(android_app); + dioxus_native::launch(app::app) +} diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index 38e7e65a3..0621c4f51 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,117 +1,17 @@ -//! Drive the renderer from Dioxus -use dioxus_native::prelude::*; - -fn main() { - dioxus_native::launch(app); -} - -fn app() -> Element { - let mut count = use_signal(|| 0); - - rsx! { - div { class: "container", - style { {CSS} } - h1 { class: "header", "Count: {count}" } - div { class: "buttons", - button { - class: "counter-button btn-green", - onclick: move |_| { count += 1 }, - "Increment" - } - button { - class: "counter-button btn-red", - onclick: move |_| { count -= 1 }, - "Decrement" - } - } - button { - class: "counter-button btn-blue", - onclick: move |_| { count.set(0) }, - "Reset" - } - } - } -} - -const CSS: &str = r#" - -html, body { - padding: 0; - margin: 0; - background-color: white; -} - -.header { - background-color: pink; - padding: 20px; - line-height: 1; - font-family: sans-serif; -} - -.container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: 100vh; - width: 100vw; - background: linear-gradient(217deg, rgba(255,0,0,.8), rgba(255,0,0,0) 70.71%), - linear-gradient(127deg, rgba(0,255,0,.8), rgba(0,255,0,0) 70.71%), - linear-gradient(336deg, rgba(0,0,255,.8), rgba(0,0,255,0) 70.71%); -} +// On Windows do NOT show a console window when opening the app +#![cfg_attr(all(not(test), target_os = "windows"), windows_subsystem = "windows")] -.buttons { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - margin: 20px 0; -} +//! Drive the renderer from Dioxus -.counter-button { - margin: 0 10px; - padding: 10px 20px; - border-radius: 5px; - font-size: 1.5rem; - cursor: pointer; - line-height: 1; - font-family: sans-serif; - border-width: 2px; - border-style: solid; -} -.counter-button:focus { - outline: 4px solid black; -} +mod app; -.btn-green { - background-color: green; - border-color: green; - color: white; -} -.btn-green:hover { - color: green; - background-color: white; +#[unsafe(no_mangle)] +#[cfg(target_os = "android")] +pub fn android_main(android_app: dioxus_native::AndroidApp) { + dioxus_native::set_android_app(android_app); + dioxus_native::launch(app::app) } -.btn-red { - background-color: red; - border-color: red; - color: white; -} -.btn-red:hover { - color: red; - background-color: white; -} - -.btn-blue { - background-color: blue; - border-color: blue; - color: white; -} -.btn-blue:hover { - color: blue; - background-color: white; +fn main() { + dioxus_native::launch(app::app) } - - -"#; diff --git a/examples/inner_html.rs b/examples/inner_html.rs index 03df3be4b..d2e4d8268 100644 --- a/examples/inner_html.rs +++ b/examples/inner_html.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyrender_vello::VelloWindowRenderer; use blitz_dom::DocumentConfig; use blitz_html::{HtmlDocument, HtmlProvider}; -use blitz_shell::{BlitzApplication, BlitzShellEvent, WindowConfig, create_default_event_loop}; +use blitz_shell::{BlitzApplication, BlitzShellProxy, WindowConfig, create_default_event_loop}; pub fn main() { // Create renderer @@ -22,14 +22,15 @@ pub fn main() { doc.resolve(0.0); // Create the Winit application and window - let event_loop = create_default_event_loop::(); - let mut application = BlitzApplication::new(event_loop.create_proxy()); + let event_loop = create_default_event_loop(); + let (proxy, reciever) = BlitzShellProxy::new(event_loop.create_proxy()); + let mut application = BlitzApplication::new(proxy, reciever); let renderer = VelloWindowRenderer::new(); let window = WindowConfig::new(Box::new(doc), renderer); application.add_window(window); // Run event loop - event_loop.run_app(&mut application).unwrap() + event_loop.run_app(application).unwrap() } static HTML: &str = r#" diff --git a/examples/screenshot.rs b/examples/screenshot.rs index 8a05dc25d..df3de74fd 100644 --- a/examples/screenshot.rs +++ b/examples/screenshot.rs @@ -117,7 +117,15 @@ async fn main() { ); // Render document - paint_scene(scene, document.as_ref(), scale, render_width, render_height); + paint_scene( + scene, + document.as_ref(), + scale, + render_width, + render_height, + 0, + 0, + ); }, render_width, render_height, diff --git a/examples/todomvc/Cargo.toml b/examples/todomvc/Cargo.toml index 6fbaaabbc..4de43cef3 100644 --- a/examples/todomvc/Cargo.toml +++ b/examples/todomvc/Cargo.toml @@ -29,4 +29,7 @@ tracing-subscriber = { workspace = true, optional = true} # Disable unicode URL support # See https://github.com/hsivonen/idna_adapter -idna_adapter = "=1.0.0" \ No newline at end of file +idna_adapter = "=1.0.0" + +[target.'cfg(target_os = "android")'.dependencies] +android-activity = { version = "0.6.0", features = ["native-activity"] } \ No newline at end of file diff --git a/examples/todomvc/Dioxus.toml b/examples/todomvc/Dioxus.toml new file mode 100644 index 000000000..3292650d6 --- /dev/null +++ b/examples/todomvc/Dioxus.toml @@ -0,0 +1,2 @@ +[application] +android_main_activity = "MainActivity.kt.hbs" \ No newline at end of file diff --git a/examples/todomvc/MainActivity.kt.hbs b/examples/todomvc/MainActivity.kt.hbs new file mode 100644 index 000000000..a0ce0e2c3 --- /dev/null +++ b/examples/todomvc/MainActivity.kt.hbs @@ -0,0 +1,4 @@ +package dev.dioxus.main; + +class MainActivity : android.app.NativeActivity() +//class MainActivity : com.google.androidgamesdk.GameActivity() diff --git a/examples/todomvc/src/app.rs b/examples/todomvc/src/app.rs index 300f85a5a..bbbabe6c2 100644 --- a/examples/todomvc/src/app.rs +++ b/examples/todomvc/src/app.rs @@ -113,7 +113,12 @@ fn TodoHeader(mut todos: Signal>) -> Element { let mut todo_id = use_signal(|| 0); let onkeydown = move |evt: KeyboardEvent| { - if evt.key() == Key::Enter && !draft.read().is_empty() { + let is_enter = match evt.key() { + Key::Enter => true, + Key::Character(s) if s == "\n" => true, + _ => false, + }; + if is_enter && !draft.read().is_empty() { let id = todo_id(); let todo = TodoItem { id, diff --git a/examples/todomvc/src/main.rs b/examples/todomvc/src/main.rs index 575086eaa..649a27add 100644 --- a/examples/todomvc/src/main.rs +++ b/examples/todomvc/src/main.rs @@ -5,6 +5,13 @@ mod app; +#[unsafe(no_mangle)] +#[cfg(target_os = "android")] +pub fn android_main(android_app: dioxus_native::AndroidApp) { + dioxus_native::set_android_app(android_app); + dioxus_native::launch(app::app) +} + fn main() { #[cfg(feature = "tracing")] tracing_subscriber::fmt::init(); diff --git a/examples/wgpu_texture/src/html.rs b/examples/wgpu_texture/src/html.rs index 77f10c244..0a28feeba 100644 --- a/examples/wgpu_texture/src/html.rs +++ b/examples/wgpu_texture/src/html.rs @@ -1,7 +1,7 @@ use anyrender_vello::{VelloRendererOptions, VelloWindowRenderer}; use blitz_dom::{qual_name, DocumentConfig}; use blitz_html::HtmlDocument; -use blitz_shell::{create_default_event_loop, BlitzApplication, BlitzShellEvent, WindowConfig}; +use blitz_shell::{create_default_event_loop, BlitzApplication, BlitzShellProxy, WindowConfig}; use crate::{limits, DemoPaintSource, FEATURES, STYLES}; @@ -30,13 +30,14 @@ pub fn launch_html() { .set_attribute(canvas_node_id, src_attr, &src_str); // Create the Winit application and window - let event_loop = create_default_event_loop::(); - let mut application = BlitzApplication::new(event_loop.create_proxy()); + let event_loop = create_default_event_loop(); + let (proxy, reciever) = BlitzShellProxy::new(event_loop.create_proxy()); + let mut application = BlitzApplication::new(proxy, reciever); let window = WindowConfig::new(Box::new(doc), renderer); application.add_window(window); // Run event loop - event_loop.run_app(&mut application).unwrap() + event_loop.run_app(application).unwrap() } static HTML: &str = r#" diff --git a/justfile b/justfile index d1fd010fb..73c539829 100644 --- a/justfile +++ b/justfile @@ -69,6 +69,11 @@ todoandroid *ARGS: export CARGO_APK_RELEASE_KEYSTORE_PASSWORD="android" cargo apk run --lib --no-default-features --features skia -p todomvc +counterandroid *ARGS: + export CARGO_APK_RELEASE_KEYSTORE="$HOME/.android/debug.keystore" + export CARGO_APK_RELEASE_KEYSTORE_PASSWORD="android" + cargo apk run --lib --no-default-features --features skia -p counter + ## Ops bump *ARGS: diff --git a/packages/accesskit_xplat/CHANGELOG.md b/packages/accesskit_xplat/CHANGELOG.md new file mode 100644 index 000000000..3a981878a --- /dev/null +++ b/packages/accesskit_xplat/CHANGELOG.md @@ -0,0 +1,885 @@ +# Changelog + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.1.4 to 0.1.5 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.8.0 to 0.8.1 + * accesskit_windows bumped from 0.10.0 to 0.10.1 + * accesskit_macos bumped from 0.2.0 to 0.2.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.1 to 0.10.2 + * accesskit_macos bumped from 0.2.1 to 0.3.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.3.0 to 0.4.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.3 to 0.10.4 + * accesskit_macos bumped from 0.4.1 to 0.4.2 + * accesskit_unix bumped from 0.1.0 to 0.1.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.3.0 to 0.3.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.10.0 to 0.10.1 + * accesskit_windows bumped from 0.13.0 to 0.13.1 + * accesskit_macos bumped from 0.6.0 to 0.6.1 + * accesskit_unix bumped from 0.3.1 to 0.3.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.13.1 to 0.13.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.6.1 to 0.6.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.10.1 to 0.11.0 + * accesskit_windows bumped from 0.13.2 to 0.13.3 + * accesskit_macos bumped from 0.6.2 to 0.6.3 + * accesskit_unix bumped from 0.3.2 to 0.3.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.7.0 to 0.7.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.0 to 0.11.1 + * accesskit_windows bumped from 0.14.0 to 0.14.1 + * accesskit_macos bumped from 0.7.1 to 0.8.0 + * accesskit_unix bumped from 0.5.0 to 0.5.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.1 to 0.11.2 + * accesskit_windows bumped from 0.14.1 to 0.14.2 + * accesskit_macos bumped from 0.8.0 to 0.9.0 + * accesskit_unix bumped from 0.5.1 to 0.5.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.14.2 to 0.14.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.0 to 0.16.1 + * accesskit_unix bumped from 0.7.1 to 0.7.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.2 to 0.7.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.1 to 0.16.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.2 to 0.12.3 + * accesskit_windows bumped from 0.16.2 to 0.16.3 + * accesskit_macos bumped from 0.11.0 to 0.11.1 + * accesskit_unix bumped from 0.7.3 to 0.7.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.4 to 0.7.5 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.16.3 to 0.16.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.0 to 0.18.1 + * accesskit_macos bumped from 0.13.0 to 0.13.1 + * accesskit_unix bumped from 0.9.0 to 0.9.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.1 to 0.18.2 + * accesskit_macos bumped from 0.13.1 to 0.13.2 + * accesskit_unix bumped from 0.9.1 to 0.9.2 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.18.2 to 0.19.0 + * accesskit_macos bumped from 0.13.2 to 0.14.0 + * accesskit_unix bumped from 0.9.2 to 0.10.0 + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.19.0 to 0.20.0 + * accesskit_macos bumped from 0.14.0 to 0.15.0 + * accesskit_unix bumped from 0.10.0 to 0.10.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.11.0 to 0.11.1 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.2 to 0.16.3 + * accesskit_windows bumped from 0.23.1 to 0.23.2 + * accesskit_macos bumped from 0.17.2 to 0.17.3 + * accesskit_unix bumped from 0.12.2 to 0.12.3 + +* The following workspace dependencies were updated + * dependencies + * accesskit_macos bumped from 0.17.3 to 0.17.4 + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.17.0 to 0.17.1 + * accesskit_windows bumped from 0.24.0 to 0.24.1 + * accesskit_macos bumped from 0.18.0 to 0.18.1 + * accesskit_unix bumped from 0.13.0 to 0.13.1 + +## [0.29.2](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.29.1...accesskit_winit-v0.29.2) (2025-10-20) + + +### Bug Fixes + +* Fix winit examples window not showing up under Wayland ([#625](https://github.com/AccessKit/accesskit/issues/625)) ([87ce769](https://github.com/AccessKit/accesskit/commit/87ce769282b00684f2b2ab6a3410ed6edb894f22)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.29.1 to 0.29.2 + * accesskit_macos bumped from 0.22.1 to 0.22.2 + * accesskit_unix bumped from 0.17.1 to 0.17.2 + * accesskit_android bumped from 0.4.1 to 0.4.2 + +## [0.29.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.29.0...accesskit_winit-v0.29.1) (2025-10-02) + + +### Bug Fixes + +* Impl `Clone` and `PartialEq` on `WindowEvent` ([#618](https://github.com/AccessKit/accesskit/issues/618)) ([3a4771b](https://github.com/AccessKit/accesskit/commit/3a4771b87455cc005c18152935535818a3f9f825)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.21.0 to 0.21.1 + * accesskit_windows bumped from 0.29.0 to 0.29.1 + * accesskit_macos bumped from 0.22.0 to 0.22.1 + * accesskit_unix bumped from 0.17.0 to 0.17.1 + * accesskit_android bumped from 0.4.0 to 0.4.1 + +## [0.29.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.28.0...accesskit_winit-v0.29.0) (2025-07-16) + + +### Features + +* Let parents declare actions supported on their children ([#593](https://github.com/AccessKit/accesskit/issues/593)) ([70b534b](https://github.com/AccessKit/accesskit/commit/70b534bed168a84b84cc35199588aa8ab784fb43)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.20.0 to 0.21.0 + * accesskit_windows bumped from 0.28.0 to 0.29.0 + * accesskit_macos bumped from 0.21.0 to 0.22.0 + * accesskit_unix bumped from 0.16.0 to 0.17.0 + * accesskit_android bumped from 0.3.0 to 0.4.0 + +## [0.28.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.27.0...accesskit_winit-v0.28.0) (2025-06-26) + + +### ⚠ BREAKING CHANGES + +* Force a semver-breaking release ([#589](https://github.com/AccessKit/accesskit/issues/589)) + +### Bug Fixes + +* Force a semver-breaking release ([#589](https://github.com/AccessKit/accesskit/issues/589)) ([2887cdd](https://github.com/AccessKit/accesskit/commit/2887cddde817ba3851688068d8d10de5cef7c624)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.19.0 to 0.20.0 + * accesskit_windows bumped from 0.27.0 to 0.28.0 + * accesskit_macos bumped from 0.20.0 to 0.21.0 + * accesskit_unix bumped from 0.15.0 to 0.16.0 + * accesskit_android bumped from 0.2.0 to 0.3.0 + +## [0.27.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.26.0...accesskit_winit-v0.27.0) (2025-05-06) + + +### ⚠ BREAKING CHANGES + +* Simplify the core Android adapter API ([#558](https://github.com/AccessKit/accesskit/issues/558)) +* Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) + +### Code Refactoring + +* Drop redundant `HasPopup::True` ([#550](https://github.com/AccessKit/accesskit/issues/550)) ([56abf17](https://github.com/AccessKit/accesskit/commit/56abf17356e4c7f13f64aaeaca6a63c8f7ede553)) +* Simplify the core Android adapter API ([#558](https://github.com/AccessKit/accesskit/issues/558)) ([7ac5911](https://github.com/AccessKit/accesskit/commit/7ac5911b11f3d6b8b777b91e6476e7073f6b0e4a)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.18.0 to 0.19.0 + * accesskit_windows bumped from 0.26.0 to 0.27.0 + * accesskit_macos bumped from 0.19.0 to 0.20.0 + * accesskit_unix bumped from 0.14.0 to 0.15.0 + * accesskit_android bumped from 0.1.1 to 0.2.0 + +## [0.26.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.25.0...accesskit_winit-v0.26.0) (2025-03-17) + + +### ⚠ BREAKING CHANGES + +* Panic if the window is visible when the adapter is created, for adapters where this is a problem ([#529](https://github.com/AccessKit/accesskit/issues/529)) + +### Bug Fixes + +* Panic if the window is visible when the adapter is created, for adapters where this is a problem ([#529](https://github.com/AccessKit/accesskit/issues/529)) ([c43c37b](https://github.com/AccessKit/accesskit/commit/c43c37ba2502656fcae4fd726b9b7db0bb520f31)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.25.0 to 0.26.0 + * accesskit_android bumped from 0.1.0 to 0.1.1 + +## [0.25.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.24.0...accesskit_winit-v0.25.0) (2025-03-08) + + +### ⚠ BREAKING CHANGES + +* Make accesskit_android an optional dependency of accesskit_winit ([#524](https://github.com/AccessKit/accesskit/issues/524)) + +### Bug Fixes + +* Make accesskit_android an optional dependency of accesskit_winit ([#524](https://github.com/AccessKit/accesskit/issues/524)) ([bb17d44](https://github.com/AccessKit/accesskit/commit/bb17d449b601eaffad1c7201ec5bf8de241bb8f8)) + +## [0.24.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.23.1...accesskit_winit-v0.24.0) (2025-03-06) + + +### ⚠ BREAKING CHANGES + +* Add event loop parameter to winit adapter constructors ([#517](https://github.com/AccessKit/accesskit/issues/517)) +* Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) + +### Features + +* Android adapter ([#500](https://github.com/AccessKit/accesskit/issues/500)) ([7e65ac7](https://github.com/AccessKit/accesskit/commit/7e65ac77d7e108ac5b9f3722f488a2fdf2e3b3e0)) + + +### Bug Fixes + +* Update winit to 0.30.9 ([#511](https://github.com/AccessKit/accesskit/issues/511)) ([0be21e6](https://github.com/AccessKit/accesskit/commit/0be21e6a2979af483b573b1c9b07c677286b871d)) + + +### Code Refactoring + +* Add event loop parameter to winit adapter constructors ([#517](https://github.com/AccessKit/accesskit/issues/517)) ([0d15f24](https://github.com/AccessKit/accesskit/commit/0d15f246a301a68af4424f7602c2f3be25da9327)) +* Drop `Tree::app_name` ([#492](https://github.com/AccessKit/accesskit/issues/492)) ([089794c](https://github.com/AccessKit/accesskit/commit/089794c8f74957e91a19ae3df508e2a892f39ebc)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.17.1 to 0.18.0 + * accesskit_windows bumped from 0.24.1 to 0.25.0 + * accesskit_macos bumped from 0.18.1 to 0.19.0 + * accesskit_unix bumped from 0.13.1 to 0.14.0 + +## [0.23.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.4...accesskit_winit-v0.23.0) (2024-10-31) + + +### ⚠ BREAKING CHANGES + +* Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) +* Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) +* Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) +* Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) + +### Features + +* Make the core crate no-std ([#468](https://github.com/AccessKit/accesskit/issues/468)) ([2fa0d3f](https://github.com/AccessKit/accesskit/commit/2fa0d3f5b2b7ac11ef1751c133706f29e548bd6d)) + + +### Code Refactoring + +* Drop `DefaultActionVerb` ([#472](https://github.com/AccessKit/accesskit/issues/472)) ([ef3b003](https://github.com/AccessKit/accesskit/commit/ef3b0038224459094f650368412650bc3b69526b)) +* Rename `name` to `label` and use `value` for label content ([#475](https://github.com/AccessKit/accesskit/issues/475)) ([e0053a5](https://github.com/AccessKit/accesskit/commit/e0053a5399929e8e0d4f07aa18de604ed8766ace)) +* Rename `NodeBuilder` to `Node` and the old `Node` to `FrozenNode` ([#476](https://github.com/AccessKit/accesskit/issues/476)) ([7d8910e](https://github.com/AccessKit/accesskit/commit/7d8910e35f7bc0543724cc124941a3bd0304bcc0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.3 to 0.17.0 + * accesskit_windows bumped from 0.23.2 to 0.24.0 + * accesskit_macos bumped from 0.17.4 to 0.18.0 + * accesskit_unix bumped from 0.12.3 to 0.13.0 + +## [0.22.2](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.1...accesskit_winit-v0.22.2) (2024-10-07) + + +### Bug Fixes + +* Update minimum supported Rust version to 1.75 ([#457](https://github.com/AccessKit/accesskit/issues/457)) ([fc622fe](https://github.com/AccessKit/accesskit/commit/fc622fe7657c80a4eedad6f6cded11d2538b54d5)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.1 to 0.16.2 + * accesskit_windows bumped from 0.23.0 to 0.23.1 + * accesskit_macos bumped from 0.17.1 to 0.17.2 + * accesskit_unix bumped from 0.12.1 to 0.12.2 + +## [0.22.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.22.0...accesskit_winit-v0.22.1) (2024-09-24) + + +### Bug Fixes + +* Use the new HWND type on accesskit_winit ([#453](https://github.com/AccessKit/accesskit/issues/453)) ([68a2462](https://github.com/AccessKit/accesskit/commit/68a24629381f0b18f6ed1ee008fe72ce9330092e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.16.0 to 0.16.1 + * accesskit_windows bumped from 0.22.0 to 0.23.0 + * accesskit_macos bumped from 0.17.0 to 0.17.1 + * accesskit_unix bumped from 0.12.0 to 0.12.1 + +## [0.22.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.21.1...accesskit_winit-v0.22.0) (2024-06-29) + + +### ⚠ BREAKING CHANGES + +* Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) + +### Code Refactoring + +* Rename the `StaticText` role to `Label` ([#434](https://github.com/AccessKit/accesskit/issues/434)) ([7086bc0](https://github.com/AccessKit/accesskit/commit/7086bc0fad446d3ed4a0fd5eff641a1e75f6c599)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.15.0 to 0.16.0 + * accesskit_windows bumped from 0.21.0 to 0.22.0 + * accesskit_macos bumped from 0.16.0 to 0.17.0 + * accesskit_unix bumped from 0.11.1 to 0.12.0 + +## [0.21.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.20.4...accesskit_winit-v0.21.0) (2024-06-09) + + +### Features + +* Add `author_id` property ([#424](https://github.com/AccessKit/accesskit/issues/424)) ([0d1c56f](https://github.com/AccessKit/accesskit/commit/0d1c56f0bdde58715e1c69f6015df600cb7cb8c1)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.14.0 to 0.15.0 + * accesskit_windows bumped from 0.20.0 to 0.21.0 + * accesskit_macos bumped from 0.15.0 to 0.16.0 + * accesskit_unix bumped from 0.10.1 to 0.11.0 + +## [0.20.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.19.0...accesskit_winit-v0.20.0) (2024-04-30) + + +### ⚠ BREAKING CHANGES + +* Update winit to 0.30 ([#397](https://github.com/AccessKit/accesskit/issues/397)) +* Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) + +### Bug Fixes + +* Increase minimum supported Rust version to `1.70` ([#396](https://github.com/AccessKit/accesskit/issues/396)) ([a8398b8](https://github.com/AccessKit/accesskit/commit/a8398b847aa003de91042ac45e33126fc2cae053)) +* Update winit to 0.30 ([#397](https://github.com/AccessKit/accesskit/issues/397)) ([de93be3](https://github.com/AccessKit/accesskit/commit/de93be387c03a438fbf598670207e578686e6bcf)) + + +### Code Refactoring + +* Drop `NodeClassSet` ([#389](https://github.com/AccessKit/accesskit/issues/389)) ([1b153ed](https://github.com/AccessKit/accesskit/commit/1b153ed51f8421cdba2dc98beca2e8f5f8c781bc)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.13.0 to 0.14.0 + * accesskit_windows bumped from 0.17.0 to 0.18.0 + * accesskit_macos bumped from 0.12.0 to 0.13.0 + * accesskit_unix bumped from 0.8.0 to 0.9.0 + +## [0.19.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.18.7...accesskit_winit-v0.19.0) (2024-04-14) + + +### ⚠ BREAKING CHANGES + +* New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) + +### Code Refactoring + +* New approach to lazy initialization ([#375](https://github.com/AccessKit/accesskit/issues/375)) ([9baebdc](https://github.com/AccessKit/accesskit/commit/9baebdceed7300389b6768815d7ae48f1ce401e4)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.3 to 0.13.0 + * accesskit_windows bumped from 0.16.4 to 0.17.0 + * accesskit_macos bumped from 0.11.1 to 0.12.0 + * accesskit_unix bumped from 0.7.5 to 0.8.0 + +## [0.18.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.18.0...accesskit_winit-v0.18.1) (2024-01-11) + + +### Bug Fixes + +* Run our own async executor on Unix ([#337](https://github.com/AccessKit/accesskit/issues/337)) ([8f937ba](https://github.com/AccessKit/accesskit/commit/8f937baaa510dd96da196501822b82f75f05b595)) +* Show an error at compile-time if no raw-window-handle feature is enabled for the winit adapter ([#339](https://github.com/AccessKit/accesskit/issues/339)) ([a24f5fd](https://github.com/AccessKit/accesskit/commit/a24f5fd443a683a6194b54244052ff3e1cc05de6)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.7.0 to 0.7.1 + +## [0.18.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.17.0...accesskit_winit-v0.18.0) (2024-01-03) + + +### ⚠ BREAKING CHANGES + +* Lazily activate Unix adapters ([#324](https://github.com/AccessKit/accesskit/issues/324)) +* Remove `accesskit_winit::Adapter::update` ([#325](https://github.com/AccessKit/accesskit/issues/325)) + +### Bug Fixes + +* Lazily activate Unix adapters ([#324](https://github.com/AccessKit/accesskit/issues/324)) ([54ed036](https://github.com/AccessKit/accesskit/commit/54ed036c99d87428a8eb5bb03fd77e9e31562d4c)) +* Remove `accesskit_winit::Adapter::update` ([#325](https://github.com/AccessKit/accesskit/issues/325)) ([f121bff](https://github.com/AccessKit/accesskit/commit/f121bffe9e651fd2ac6deb882f57e1c9b613b7eb)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.1 to 0.12.2 + * accesskit_windows bumped from 0.15.1 to 0.16.0 + * accesskit_macos bumped from 0.10.1 to 0.11.0 + * accesskit_unix bumped from 0.6.2 to 0.7.0 + +## [0.17.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.16.1...accesskit_winit-v0.17.0) (2023-12-14) + + +### ⚠ BREAKING CHANGES + +* Force a semver break for the winit rwh feature additions ([#322](https://github.com/AccessKit/accesskit/issues/322)) + +### Bug Fixes + +* Add a `rwh_05` feature flag to `accesskit_winit` ([#319](https://github.com/AccessKit/accesskit/issues/319)) ([f4d279c](https://github.com/AccessKit/accesskit/commit/f4d279c5ece16df2925c0e31dc82eaf192c40cd0)) +* Force a semver break for the winit rwh feature additions ([#322](https://github.com/AccessKit/accesskit/issues/322)) ([61acdb0](https://github.com/AccessKit/accesskit/commit/61acdb0ea083263c88a00ad4db637b25863852c0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.6.1 to 0.6.2 + +## [0.16.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.16.0...accesskit_winit-v0.16.1) (2023-11-05) + + +### Bug Fixes + +* Account for window decorations when `accesskit_winit::Adapter::process_event` receives a resizing event on Unix ([#312](https://github.com/AccessKit/accesskit/issues/312)) ([e2b264c](https://github.com/AccessKit/accesskit/commit/e2b264c2e5b0fb699576f2ece905509c38ffc9be)) + +## [0.16.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.15.0...accesskit_winit-v0.16.0) (2023-11-04) + + +### ⚠ BREAKING CHANGES + +* Rename `accesskit_winit::Adapter::on_event` to `process_event` ([#307](https://github.com/AccessKit/accesskit/issues/307)) +* Bump winit to 0.29 ([#256](https://github.com/AccessKit/accesskit/issues/256)) + +### deps + +* Bump winit to 0.29 ([#256](https://github.com/AccessKit/accesskit/issues/256)) ([4eb21ff](https://github.com/AccessKit/accesskit/commit/4eb21ff64256fcf0a16ab831554b06b80de9b36e)) + + +### Bug Fixes + +* Add missing semicolons when not returning anything ([#303](https://github.com/AccessKit/accesskit/issues/303)) ([38d4de1](https://github.com/AccessKit/accesskit/commit/38d4de1442247e701047d75122a9638a2ed99b1f)) +* Use raw-window-handle 0.6 ([#310](https://github.com/AccessKit/accesskit/issues/310)) ([3fa69ab](https://github.com/AccessKit/accesskit/commit/3fa69ab4d9216b51b651d3cf2a9c8217a77069f4)) + + +### Code Refactoring + +* Rename `accesskit_winit::Adapter::on_event` to `process_event` ([#307](https://github.com/AccessKit/accesskit/issues/307)) ([6fbebde](https://github.com/AccessKit/accesskit/commit/6fbebdeb9d1e96b1776ed1faf7ad21d9cc0a68df)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.12.0 to 0.12.1 + * accesskit_windows bumped from 0.15.0 to 0.15.1 + * accesskit_macos bumped from 0.10.0 to 0.10.1 + * accesskit_unix bumped from 0.6.0 to 0.6.1 + +## [0.15.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.14.4...accesskit_winit-v0.15.0) (2023-09-27) + + +### ⚠ BREAKING CHANGES + +* Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) +* Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) +* Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) +* Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) + +### Features + +* Allow providing app_name, toolkit_name and toolkit_version in Tree, remove parameters from unix adapter constructor ([#291](https://github.com/AccessKit/accesskit/issues/291)) ([5313860](https://github.com/AccessKit/accesskit/commit/531386023257150f49b5e4be942f359855fb7cb6)) + + +### Bug Fixes + +* Fix doc build for accesskit_winit ([#281](https://github.com/AccessKit/accesskit/issues/281)) ([e3b38b8](https://github.com/AccessKit/accesskit/commit/e3b38b8164d0c5442a5a1904165e2b05847376c2)) + + +### Code Refactoring + +* Decouple in-tree focus from host window/view focus ([#278](https://github.com/AccessKit/accesskit/issues/278)) ([d360d20](https://github.com/AccessKit/accesskit/commit/d360d20cf951e7643b81a5303006c9f7daa5bd56)) +* Make `ActionHandler::do_action` take `&mut self` ([#296](https://github.com/AccessKit/accesskit/issues/296)) ([4fc7846](https://github.com/AccessKit/accesskit/commit/4fc7846d732d61fb45c023060ebab96801a0053e)) +* Switch to simple unsigned 64-bit integer for node IDs ([#276](https://github.com/AccessKit/accesskit/issues/276)) ([3eadd48](https://github.com/AccessKit/accesskit/commit/3eadd48ec47854faa94a94ebf910ec08f514642f)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.11.2 to 0.12.0 + * accesskit_windows bumped from 0.14.3 to 0.15.0 + * accesskit_macos bumped from 0.9.0 to 0.10.0 + * accesskit_unix bumped from 0.5.2 to 0.6.0 + +## [0.14.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.13.0...accesskit_winit-v0.14.0) (2023-05-21) + + +### Features + +* Add features for async runtimes on Unix ([#248](https://github.com/AccessKit/accesskit/issues/248)) ([b56b4ea](https://github.com/AccessKit/accesskit/commit/b56b4ea7c967ee5a1dae21a2fa0dcd385346031e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_unix bumped from 0.4.0 to 0.5.0 + +## [0.13.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.12.5...accesskit_winit-v0.13.0) (2023-03-30) + + +### ⚠ BREAKING CHANGES + +* Force a semver-breaking version bump in downstream crates ([#234](https://github.com/AccessKit/accesskit/issues/234)) + +### Bug Fixes + +* Force a semver-breaking version bump in downstream crates ([#234](https://github.com/AccessKit/accesskit/issues/234)) ([773389b](https://github.com/AccessKit/accesskit/commit/773389bff857fa18edf15de426e029251fc34591)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.13.3 to 0.14.0 + * accesskit_macos bumped from 0.6.3 to 0.7.0 + * accesskit_unix bumped from 0.3.3 to 0.4.0 + +## [0.12.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.11.0...accesskit_winit-v0.12.0) (2023-02-18) + + +### Features + +* Feature-gate the Unix adapter in accesskit_winit ([#214](https://github.com/AccessKit/accesskit/issues/214)) ([be95807](https://github.com/AccessKit/accesskit/commit/be95807dda64f2a49b4d20cc9084b14a7aa2844e)) + +## [0.11.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.10.0...accesskit_winit-v0.11.0) (2023-02-12) + + +### ⚠ BREAKING CHANGES + +* Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) + +### Code Refactoring + +* Move thread synchronization into platform adapters; drop parking_lot ([#212](https://github.com/AccessKit/accesskit/issues/212)) ([5df52e5](https://github.com/AccessKit/accesskit/commit/5df52e5545faddf6a51905409013c2f5be23981e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.9.0 to 0.10.0 + * accesskit_windows bumped from 0.12.0 to 0.13.0 + * accesskit_macos bumped from 0.5.0 to 0.6.0 + * accesskit_unix bumped from 0.2.0 to 0.3.0 + +## [0.10.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.9.1...accesskit_winit-v0.10.0) (2023-02-05) + + +### ⚠ BREAKING CHANGES + +* Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) + +### Code Refactoring + +* Make `Node` opaque and optimize it for size ([#205](https://github.com/AccessKit/accesskit/issues/205)) ([4811152](https://github.com/AccessKit/accesskit/commit/48111521439b76c1a8687418a4b20f9b705eac6d)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.8.1 to 0.9.0 + * accesskit_windows bumped from 0.11.0 to 0.12.0 + * accesskit_macos bumped from 0.4.2 to 0.5.0 + * accesskit_unix bumped from 0.1.1 to 0.2.0 + +## [0.9.1](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.9.0...accesskit_winit-v0.9.1) (2023-02-05) + + +### Bug Fixes + +* Don't force winit's X11 and Wayland features to be enabled ([#209](https://github.com/AccessKit/accesskit/issues/209)) ([a3ed357](https://github.com/AccessKit/accesskit/commit/a3ed35754ad8f69a8ed54adacc30b6d57c19329a)) + +## [0.9.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.8.1...accesskit_winit-v0.9.0) (2023-02-02) + + +### ⚠ BREAKING CHANGES + +* Update winit to 0.28 ([#207](https://github.com/AccessKit/accesskit/issues/207)) + +### Miscellaneous Chores + +* Update winit to 0.28 ([#207](https://github.com/AccessKit/accesskit/issues/207)) ([3ff0cf5](https://github.com/AccessKit/accesskit/commit/3ff0cf59f982af504499142a3804f7aeeb4defe0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.4 to 0.11.0 + +## [0.8.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.7.3...accesskit_winit-v0.8.0) (2023-01-05) + + +### Features + +* Basic Unix platform adapter ([#198](https://github.com/AccessKit/accesskit/issues/198)) ([1cea32e](https://github.com/AccessKit/accesskit/commit/1cea32e44ee743b778ac941ceff9087ae745cb37)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.10.2 to 0.10.3 + * accesskit_macos bumped from 0.4.0 to 0.4.1 + +## [0.7.0](https://github.com/AccessKit/accesskit/compare/accesskit_winit-v0.6.6...accesskit_winit-v0.7.0) (2022-11-29) + + +### ⚠ BREAKING CHANGES + +* Move lazy initialization from the core platform adapter to the caller ([#179](https://github.com/AccessKit/accesskit/issues/179)) + +### Code Refactoring + +* Move lazy initialization from the core platform adapter to the caller ([#179](https://github.com/AccessKit/accesskit/issues/179)) ([f35c941](https://github.com/AccessKit/accesskit/commit/f35c941f395f3162db376a69cfaaaf770d376267)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit_windows bumped from 0.9.3 to 0.10.0 + * accesskit_macos bumped from 0.1.5 to 0.2.0 + +### [0.6.4](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.6.3...accesskit_winit-v0.6.4) (2022-11-25) + + +### Bug Fixes + +* Reduce the winit version requirement to match egui ([#170](https://www.github.com/AccessKit/accesskit/issues/170)) ([1d27482](https://www.github.com/AccessKit/accesskit/commit/1d27482221140c1f3b3e3eaf93e7feaf8105611d)) + +## [0.6.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.5.1...accesskit_winit-v0.6.0) (2022-11-23) + + +### Features + +* **platforms/macos:** Basic macOS platform adapter ([#158](https://www.github.com/AccessKit/accesskit/issues/158)) ([a06725e](https://www.github.com/AccessKit/accesskit/commit/a06725e952e6041dbd366944fa793b746c9f195e)) + + +### Bug Fixes + +* **platforms/macos:** Fix macOS crate version number ([#161](https://www.github.com/AccessKit/accesskit/issues/161)) ([e0a6a40](https://www.github.com/AccessKit/accesskit/commit/e0a6a401050cdcaea4efa870ed77ae94388f1ce0)) +* **platforms/windows:** Re-export the windows-rs HWND type ([#159](https://www.github.com/AccessKit/accesskit/issues/159)) ([389187a](https://www.github.com/AccessKit/accesskit/commit/389187ac5e96895ed1763d14d315d2f8f4256460)) + +### [0.5.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.5.0...accesskit_winit-v0.5.1) (2022-11-17) + + +### Bug Fixes + +* **platforms/winit:** Eliminate some problematic indirect dependencies ([#154](https://www.github.com/AccessKit/accesskit/issues/154)) ([58048ae](https://www.github.com/AccessKit/accesskit/commit/58048aebedc293eda5c5819ea66db9b40b8926b0)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.7.0 to 0.8.0 + +## [0.5.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.4.0...accesskit_winit-v0.5.0) (2022-11-14) + + +### Features + +* **platforms/winit:** Allow a custom action handler ([#149](https://www.github.com/AccessKit/accesskit/issues/149)) ([cdb1a16](https://www.github.com/AccessKit/accesskit/commit/cdb1a164de06f18cad497409a514f270a8336b4c)) + +## [0.4.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.3...accesskit_winit-v0.4.0) (2022-11-12) + + +### ⚠ BREAKING CHANGES + +* **platforms/windows:** Update to windows-rs 0.42.0 (#148) + +### Bug Fixes + +* **consumer, platforms/windows, platforms/winit:** Update to parking_lot 0.12.1 ([#146](https://www.github.com/AccessKit/accesskit/issues/146)) ([6772855](https://www.github.com/AccessKit/accesskit/commit/6772855a7b540fd728faad15d8d208b05c1bbd8a)) +* **platforms/windows:** Update to windows-rs 0.42.0 ([#148](https://www.github.com/AccessKit/accesskit/issues/148)) ([70d1a89](https://www.github.com/AccessKit/accesskit/commit/70d1a89f51fd6c3a32b7192d9d7f3937db09d196)) + +### [0.3.3](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.2...accesskit_winit-v0.3.3) (2022-11-11) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.6.1 to 0.7.0 + +### [0.3.2](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.1...accesskit_winit-v0.3.2) (2022-10-11) + + +### Bug Fixes + +* **platforms/winit:** Derive `Debug` on `ActionRequestEvent` ([#141](https://www.github.com/AccessKit/accesskit/issues/141)) ([8b84c75](https://www.github.com/AccessKit/accesskit/commit/8b84c7547c6fdb52cd6d5c6d79f812dc614f08dd)) + +### [0.3.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.3.0...accesskit_winit-v0.3.1) (2022-10-10) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.6.0 to 0.6.1 + +## [0.3.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.2.1...accesskit_winit-v0.3.0) (2022-10-09) + + +### ⚠ BREAKING CHANGES + +* Wrap `TreeUpdate` nodes in `Arc` (#135) +* Store node ID in `TreeUpdate`, not `accesskit::Node` (#132) + +### Code Refactoring + +* Store node ID in `TreeUpdate`, not `accesskit::Node` ([#132](https://www.github.com/AccessKit/accesskit/issues/132)) ([0bb86dd](https://www.github.com/AccessKit/accesskit/commit/0bb86ddb298cb5a253a91f07be0bad8b84b2fda3)) +* Wrap `TreeUpdate` nodes in `Arc` ([#135](https://www.github.com/AccessKit/accesskit/issues/135)) ([907bc18](https://www.github.com/AccessKit/accesskit/commit/907bc1820b80d95833b6c5c3acaa2a8a4e93a6c2)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.5.1 to 0.6.0 + +### [0.2.1](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.2.0...accesskit_winit-v0.2.1) (2022-10-03) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.5.0 to 0.5.1 + +## [0.2.0](https://www.github.com/AccessKit/accesskit/compare/accesskit_winit-v0.1.0...accesskit_winit-v0.2.0) (2022-09-23) + + +### ⚠ BREAKING CHANGES + +* Basic live regions (#128) +* **platforms/windows:** Bump windows-rs dependency (#126) +* **platforms/winit:** Bump winit dependency (#125) + +### Features + +* Basic live regions ([#128](https://www.github.com/AccessKit/accesskit/issues/128)) ([03d745b](https://www.github.com/AccessKit/accesskit/commit/03d745b891147175bde2693cc10b96a2f6e31f39)) + + +### Miscellaneous Chores + +* **platforms/windows:** Bump windows-rs dependency ([#126](https://www.github.com/AccessKit/accesskit/issues/126)) ([472a75e](https://www.github.com/AccessKit/accesskit/commit/472a75e4214b90396f3282f247df08100ed8362d)) +* **platforms/winit:** Bump winit dependency ([#125](https://www.github.com/AccessKit/accesskit/issues/125)) ([6026c1b](https://www.github.com/AccessKit/accesskit/commit/6026c1b2ecede3ca2f2076075ed158000154b34e)) + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.4.0 to 0.5.0 + +## 0.1.0 (2022-07-22) + + +### Features + +* **platforms/winit:** New winit adapter ([#121](https://www.github.com/AccessKit/accesskit/issues/121)) ([fdc274e](https://www.github.com/AccessKit/accesskit/commit/fdc274e7d3a901873d2ad0c7a4824a19111787ef)) + + + +### Dependencies + +* The following workspace dependencies were updated + * dependencies + * accesskit bumped from 0.3.0 to 0.4.0 diff --git a/packages/accesskit_xplat/Cargo.toml b/packages/accesskit_xplat/Cargo.toml new file mode 100644 index 000000000..60dba5c2f --- /dev/null +++ b/packages/accesskit_xplat/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "accesskit_xplat" +version = "0.1.0" +license = "Apache-2.0" +description = "AccessKit UI accessibility infrastructure: cross-platform adapter" +keywords = ["gui", "ui", "accessibility"] +repository.workspace = true +edition.workspace = true +rust-version.workspace = true + +[features] +default = ["accesskit_unix", "async-io"] +async-io = ["accesskit_unix/async-io"] +tokio = ["accesskit_unix/tokio"] + +[dependencies] +accesskit = { version = "0.22" } +raw-window-handle = { version = "0.6.2", features = ["std"] } + +[target.'cfg(target_os = "windows")'.dependencies] +accesskit_windows = { version = "0.30.0" } + +[target.'cfg(target_os = "macos")'.dependencies] +accesskit_macos = { version = "0.23.0" } + +[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies] +accesskit_unix = { version = "0.18.0", optional = true, default-features = false } + +[target.'cfg(target_os = "android")'.dependencies] +accesskit_android = { version = "0.5.0", optional = true, features = ["embedded-dex"] } +android-activity = { version = "0.6.0" } + diff --git a/packages/accesskit_xplat/src/lib.rs b/packages/accesskit_xplat/src/lib.rs new file mode 100644 index 000000000..3ac7ee768 --- /dev/null +++ b/packages/accesskit_xplat/src/lib.rs @@ -0,0 +1,168 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +/// ## Compatibility with async runtimes +/// +/// The following only applies on Linux/Unix: +/// +/// While this crate's API is purely blocking, it internally spawns asynchronous tasks on an executor. +/// +/// - If you use tokio, make sure to enable the `tokio` feature of this crate. +/// - If you use another async runtime or if you don't use one at all, the default feature will suit your needs. + +#[cfg(all( + feature = "accesskit_unix", + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + not(feature = "async-io"), + not(feature = "tokio") +))] +compile_error!("Either \"async-io\" (default) or \"tokio\" feature must be enabled."); + +#[cfg(all( + feature = "accesskit_unix", + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ), + feature = "async-io", + feature = "tokio" +))] +compile_error!( + "Both \"async-io\" (default) and \"tokio\" features cannot be enabled at the same time." +); + +use std::sync::Arc; + +use accesskit::{ + ActionHandler, ActionRequest, ActivationHandler, DeactivationHandler, Rect, TreeUpdate, +}; + +#[cfg(target_os = "android")] +use android_activity::AndroidApp; +#[cfg(not(target_os = "android"))] +use raw_window_handle::RawWindowHandle; + +mod platform_impl; + +#[derive(Clone, Debug, PartialEq)] +pub enum WindowEvent { + InitialTreeRequested, + ActionRequested(ActionRequest), + AccessibilityDeactivated, +} + +pub trait EventHandler: Send + Sync + 'static { + fn handle_accesskit_event(&self, event: WindowEvent); +} + +#[derive(Clone)] +struct CombinedHandler(Arc); + +impl ActivationHandler for CombinedHandler { + fn request_initial_tree(&mut self) -> Option { + self.0 + .handle_accesskit_event(WindowEvent::InitialTreeRequested); + None + } +} +impl DeactivationHandler for CombinedHandler { + fn deactivate_accessibility(&mut self) { + self.0 + .handle_accesskit_event(WindowEvent::AccessibilityDeactivated); + } +} +impl ActionHandler for CombinedHandler { + fn do_action(&mut self, request: ActionRequest) { + self.0 + .handle_accesskit_event(WindowEvent::ActionRequested(request)); + } +} + +pub struct Adapter { + /// A user-supplied ID that we pass back to + inner: platform_impl::Adapter, +} + +impl Adapter { + /// Creates a new AccessKit adapter for a winit window. This must be done + /// before the window is shown for the first time. This means that you must + /// use [`winit::window::WindowAttributes::with_visible`] to make the window + /// initially invisible, then create the adapter, then show the window. + /// + /// Use this if you want to provide your own AccessKit handler callbacks + /// rather than dispatching requests through the winit event loop. This is + /// especially useful for the activation handler, because depending on + /// your application's architecture, implementing the handler directly may + /// allow you to return an initial tree synchronously, rather than requiring + /// some platform adapters to use a placeholder tree until you send + /// the first update. However, remember that each of these handlers may be + /// called on any thread, depending on the underlying platform adapter. + /// + /// # Panics + /// + /// Panics if the window is already visible. + pub fn with_split_handlers( + #[cfg(target_os = "android")] android_app: &AndroidApp, + #[cfg(not(target_os = "android"))] window_handle: RawWindowHandle, + activation_handler: impl 'static + ActivationHandler + Send, + action_handler: impl 'static + ActionHandler + Send, + deactivation_handler: impl 'static + DeactivationHandler + Send, + ) -> Self { + let inner = platform_impl::Adapter::new( + #[cfg(target_os = "android")] + android_app, + #[cfg(not(target_os = "android"))] + window_handle, + activation_handler, + action_handler, + deactivation_handler, + ); + Self { inner } + } + + pub fn with_combined_handler( + #[cfg(target_os = "android")] android_app: &AndroidApp, + #[cfg(not(target_os = "android"))] window_handle: RawWindowHandle, + handler: Arc, + ) -> Self { + let handler = CombinedHandler(handler); + let inner = platform_impl::Adapter::new( + #[cfg(target_os = "android")] + android_app, + #[cfg(not(target_os = "android"))] + window_handle, + handler.clone(), + handler.clone(), + handler, + ); + Self { inner } + } + + /// If and only if the tree has been initialized, call the provided function + /// and apply the resulting update. Note: If the caller's implementation of + /// [`ActivationHandler::request_initial_tree`] initially returned `None`, + /// or if the caller created the adapter using [`EventLoopProxy`], then + /// the [`TreeUpdate`] returned by the provided function must contain + /// a full tree. + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + self.inner.update_if_active(updater); + } + + pub fn set_focus(&mut self, is_focused: bool) { + self.inner.set_focus(is_focused); + } + + pub fn set_window_bounds(&mut self, outer_bounds: Rect, inner_bounds: Rect) { + self.inner.set_window_bounds(outer_bounds, inner_bounds); + } +} diff --git a/packages/accesskit_xplat/src/platform_impl/android.rs b/packages/accesskit_xplat/src/platform_impl/android.rs new file mode 100644 index 000000000..89f2dfd7f --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/android.rs @@ -0,0 +1,46 @@ +// Copyright 2025 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, TreeUpdate}; +use accesskit_android::{ + InjectingAdapter, + jni::{JavaVM, objects::JObject}, +}; +use android_activity::AndroidApp; + +pub struct Adapter { + adapter: InjectingAdapter, +} + +impl Adapter { + pub fn new( + android_app: &AndroidApp, + activation_handler: impl 'static + ActivationHandler + Send, + action_handler: impl 'static + ActionHandler + Send, + _deactivation_handler: impl 'static + DeactivationHandler, + ) -> Self { + let vm = unsafe { JavaVM::from_raw(android_app.vm_as_ptr() as *mut _) }.unwrap(); + let mut env = vm.get_env().unwrap(); + let activity = unsafe { JObject::from_raw(android_app.activity_as_ptr() as *mut _) }; + let view = env + .get_field( + &activity, + "mSurfaceView", + "Lcom/google/androidgamesdk/GameActivity$InputEnabledSurfaceView;", + ) + .unwrap() + .l() + .unwrap(); + let adapter = InjectingAdapter::new(&mut env, &view, activation_handler, action_handler); + Self { adapter } + } + + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + self.adapter.update_if_active(updater); + } + + pub fn set_focus(&mut self, is_focused: bool) {} + + pub fn set_window_bounds(&mut self, outer_bounds: Rect, inner_bounds: Rect) {} +} diff --git a/packages/accesskit_xplat/src/platform_impl/macos.rs b/packages/accesskit_xplat/src/platform_impl/macos.rs new file mode 100644 index 000000000..6a1ddb37f --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/macos.rs @@ -0,0 +1,43 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, Rect, TreeUpdate}; +use accesskit_macos::SubclassingAdapter; +use raw_window_handle::RawWindowHandle; + +pub struct Adapter { + adapter: SubclassingAdapter, +} + +impl Adapter { + pub fn new( + window_handle: RawWindowHandle, + activation_handler: impl 'static + ActivationHandler, + action_handler: impl 'static + ActionHandler, + _deactivation_handler: impl 'static + DeactivationHandler, + ) -> Self { + let view = match window_handle { + RawWindowHandle::AppKit(handle) => handle.ns_view.as_ptr(), + RawWindowHandle::UiKit(_) => unimplemented!(), + _ => unreachable!(), + }; + + let adapter = unsafe { SubclassingAdapter::new(view, activation_handler, action_handler) }; + Self { adapter } + } + + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + if let Some(events) = self.adapter.update_if_active(updater) { + events.raise(); + } + } + + pub fn set_focus(&mut self, is_focused: bool) { + if let Some(events) = self.adapter.update_view_focus_state(is_focused) { + events.raise(); + } + } + + pub fn set_window_bounds(&mut self, _outer_bounds: Rect, _inner_bounds: Rect) {} +} diff --git a/packages/accesskit_xplat/src/platform_impl/mod.rs b/packages/accesskit_xplat/src/platform_impl/mod.rs new file mode 100644 index 000000000..27f7df0c5 --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/mod.rs @@ -0,0 +1,50 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +// Based loosely on winit's src/platform_impl/mod.rs. + +pub use self::platform::*; + +#[cfg(target_os = "windows")] +#[path = "windows.rs"] +mod platform; + +#[cfg(target_os = "macos")] +#[path = "macos.rs"] +mod platform; + +#[cfg(all( + feature = "accesskit_unix", + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ) +))] +#[path = "unix.rs"] +mod platform; + +#[cfg(all(feature = "accesskit_android", target_os = "android"))] +#[path = "android.rs"] +mod platform; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + all( + feature = "accesskit_unix", + any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" + ) + ), + all(feature = "accesskit_android", target_os = "android") +)))] +#[path = "null.rs"] +mod platform; diff --git a/packages/accesskit_xplat/src/platform_impl/null.rs b/packages/accesskit_xplat/src/platform_impl/null.rs new file mode 100644 index 000000000..b4ad34058 --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/null.rs @@ -0,0 +1,30 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, Rect, TreeUpdate}; + +#[cfg(target_os = "android")] +use android_activity::AndroidApp; +#[cfg(not(target_os = "android"))] +use raw_window_handle::RawWindowHandle; + +pub struct Adapter; + +impl Adapter { + pub fn new( + #[cfg(target_os = "android")] _android_app: &AndroidApp, + #[cfg(not(target_os = "android"))] _window_handle: RawWindowHandle, + _activation_handler: impl 'static + ActivationHandler, + _action_handler: impl 'static + ActionHandler, + _deactivation_handler: impl 'static + DeactivationHandler, + ) -> Self { + Self {} + } + + pub fn update_if_active(&mut self, _updater: impl FnOnce() -> TreeUpdate) {} + + pub fn set_focus(&mut self, _is_focused: bool) {} + + pub fn set_window_bounds(&mut self, _outer_bounds: Rect, _inner_bounds: Rect) {} +} diff --git a/packages/accesskit_xplat/src/platform_impl/unix.rs b/packages/accesskit_xplat/src/platform_impl/unix.rs new file mode 100644 index 000000000..542461e95 --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/unix.rs @@ -0,0 +1,43 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, Rect, TreeUpdate}; +use accesskit_unix::Adapter as UnixAdapter; +use raw_window_handle::RawWindowHandle; + +pub struct Adapter { + adapter: UnixAdapter, +} + +impl Adapter { + pub fn new( + _window_handle: RawWindowHandle, + activation_handler: impl 'static + ActivationHandler + Send, + action_handler: impl 'static + ActionHandler + Send, + deactivation_handler: impl 'static + DeactivationHandler + Send, + ) -> Self { + let adapter = UnixAdapter::new(activation_handler, action_handler, deactivation_handler); + Self { adapter } + } + + fn set_root_window_bounds(&mut self, outer: Rect, inner: Rect) { + self.adapter.set_root_window_bounds(outer, inner); + } + + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + self.adapter.update_if_active(updater); + } + + fn update_window_focus_state(&mut self, is_focused: bool) { + self.adapter.update_window_focus_state(is_focused); + } + + pub fn set_focus(&mut self, is_focused: bool) { + self.update_window_focus_state(is_focused); + } + + pub fn set_window_bounds(&mut self, outer_bounds: Rect, inner_bounds: Rect) { + self.set_root_window_bounds(outer_bounds, inner_bounds) + } +} diff --git a/packages/accesskit_xplat/src/platform_impl/windows.rs b/packages/accesskit_xplat/src/platform_impl/windows.rs new file mode 100644 index 000000000..ada159bac --- /dev/null +++ b/packages/accesskit_xplat/src/platform_impl/windows.rs @@ -0,0 +1,39 @@ +// Copyright 2022 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file). + +use accesskit::{ActionHandler, ActivationHandler, DeactivationHandler, Rect, TreeUpdate}; +use accesskit_windows::{HWND, SubclassingAdapter}; +use raw_window_handle::RawWindowHandle; + +pub struct Adapter { + adapter: SubclassingAdapter, +} + +impl Adapter { + pub fn new( + window_handle: RawWindowHandle, + activation_handler: impl 'static + ActivationHandler, + action_handler: impl 'static + ActionHandler + Send, + _deactivation_handler: impl 'static + DeactivationHandler, + ) -> Self { + let hwnd = match window_handle { + RawWindowHandle::Win32(handle) => handle.hwnd.get() as *mut _, + RawWindowHandle::WinRt(_) => unimplemented!(), + _ => unreachable!(), + }; + + let adapter = SubclassingAdapter::new(HWND(hwnd), activation_handler, action_handler); + Self { adapter } + } + + pub fn update_if_active(&mut self, updater: impl FnOnce() -> TreeUpdate) { + if let Some(events) = self.adapter.update_if_active(updater) { + events.raise(); + } + } + + pub fn set_focus(&mut self, _is_focused: bool) {} + + pub fn set_window_bounds(&mut self, _outer_bounds: Rect, _inner_bounds: Rect) {} +} diff --git a/packages/blitz-dom/Cargo.toml b/packages/blitz-dom/Cargo.toml index 55fcac695..22dfcb17a 100644 --- a/packages/blitz-dom/Cargo.toml +++ b/packages/blitz-dom/Cargo.toml @@ -70,7 +70,8 @@ fastrand = { workspace = true } rayon = { workspace = true } # Media & Decoding -image = { workspace = true } +# Note: image crate needs format features enabled for web image support +image = { workspace = true, features = ["png", "jpeg", "gif", "webp"] } usvg = { workspace = true, optional = true } woff = { workspace = true, optional = true, features = ["version1", "version2"] } wuff = { workspace = true, optional = true } diff --git a/packages/blitz-dom/src/document.rs b/packages/blitz-dom/src/document.rs index 2ada36754..ae34610cf 100644 --- a/packages/blitz-dom/src/document.rs +++ b/packages/blitz-dom/src/document.rs @@ -29,7 +29,7 @@ use selectors::{Element, matching::QuirksMode}; use slab::Slab; use std::any::Any; use std::cell::RefCell; -use std::collections::{BTreeMap, Bound, HashMap, HashSet}; +use std::collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::str::FromStr; @@ -174,6 +174,51 @@ pub enum DocumentEvent { ResourceLoad(ResourceLoadResponse), } +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct FlingState { + pub(crate) target: usize, + pub(crate) last_seen_time: f64, + pub(crate) x_velocity: f64, + pub(crate) y_velocity: f64, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum ScrollAnimationState { + None, + Fling(FlingState), +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PanState { + pub(crate) target: usize, + pub(crate) last_x: f32, + pub(crate) last_y: f32, + pub(crate) samples: VecDeque, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct PanSample { + pub(crate) time: u64, + pub(crate) dx: f32, + pub(crate) dy: f32, +} + +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum DragMode { + /// We are not currently dragging + None, + /// We are currently dragging a selection (probably mouse) + Selecting, + /// We are currently panning the document with a drag (probably touch) + Panning(PanState), +} + +impl DragMode { + pub(crate) fn take(&mut self) -> DragMode { + std::mem::replace(self, DragMode::None) + } +} + pub struct BaseDocument { /// ID of the document id: usize, @@ -234,7 +279,9 @@ pub struct BaseDocument { /// How many clicks have been made in quick succession pub(crate) click_count: u16, /// Whether we're currently in a text selection drag (moved 2px+ from mousedown) - pub(crate) is_selecting: bool, + pub(crate) drag_mode: DragMode, + /// Whether and what kind of scroll animation is currently in progress + pub(crate) scroll_animation: ScrollAnimationState, /// Text selection state (for non-input text) pub(crate) text_selection: TextSelection, @@ -401,7 +448,8 @@ impl BaseDocument { last_mousedown_time: None, mousedown_position: taffy::Point::ZERO, click_count: 0, - is_selecting: false, + drag_mode: DragMode::None, + scroll_animation: ScrollAnimationState::None, text_selection: TextSelection::default(), }; @@ -839,7 +887,6 @@ impl BaseDocument { pub fn load_resource(&mut self, res: ResourceLoadResponse) { let Ok(resource) = res.result else { - // TODO: handle error return; }; @@ -1171,7 +1218,10 @@ impl BaseDocument { } pub fn is_animating(&self) -> bool { - self.has_canvas | self.has_active_animations | self.subdoc_is_animating + self.has_canvas + | self.has_active_animations + | self.subdoc_is_animating + | (self.scroll_animation != ScrollAnimationState::None) } /// Update the device and reset the stylist to process the new size @@ -1372,6 +1422,20 @@ impl BaseDocument { self.viewport_scroll != initial } + pub fn scroll_by( + &mut self, + anchor_node_id: Option, + scroll_x: f64, + scroll_y: f64, + dispatch_event: &mut dyn FnMut(DomEvent), + ) -> bool { + if let Some(anchor_node_id) = anchor_node_id { + self.scroll_node_by_has_changed(anchor_node_id, scroll_x, scroll_y, dispatch_event) + } else { + self.scroll_viewport_by_has_changed(scroll_x, scroll_y) + } + } + pub fn viewport_scroll(&self) -> crate::Point { self.viewport_scroll } diff --git a/packages/blitz-dom/src/events/driver.rs b/packages/blitz-dom/src/events/driver.rs index 4daa2e378..859914c2b 100644 --- a/packages/blitz-dom/src/events/driver.rs +++ b/packages/blitz-dom/src/events/driver.rs @@ -1,5 +1,5 @@ use crate::Document; -use blitz_traits::events::{BlitzMouseButtonEvent, DomEvent, DomEventData, EventState, UiEvent}; +use blitz_traits::events::{DomEvent, DomEventData, EventState, UiEvent}; use std::collections::VecDeque; pub trait EventHandler { @@ -37,8 +37,6 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { pub fn handle_ui_event(&mut self, event: UiEvent) { let doc = self.doc.inner(); - let viewport_scroll = doc.viewport_scroll(); - let zoom = doc.viewport.zoom(); let mut hover_node_id = doc.hover_node_id; let focussed_node_id = doc.focus_node_id; @@ -48,8 +46,8 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { match &event { UiEvent::MouseMove(event) => { let mut doc = self.doc.inner_mut(); - let dom_x = event.x + viewport_scroll.x as f32 / zoom; - let dom_y = event.y + viewport_scroll.y as f32 / zoom; + let dom_x = event.x; + let dom_y = event.y; let changed = doc.set_hover_to(dom_x, dom_y); let prev_hover_node_id = hover_node_id; @@ -142,21 +140,9 @@ impl<'doc, Handler: EventHandler> EventDriver<'doc, Handler> { }; let data = match event { - UiEvent::MouseMove(data) => DomEventData::MouseMove(BlitzMouseButtonEvent { - x: data.x + viewport_scroll.x as f32 / zoom, - y: data.y + viewport_scroll.y as f32 / zoom, - ..data - }), - UiEvent::MouseUp(data) => DomEventData::MouseUp(BlitzMouseButtonEvent { - x: data.x + viewport_scroll.x as f32 / zoom, - y: data.y + viewport_scroll.y as f32 / zoom, - ..data - }), - UiEvent::MouseDown(data) => DomEventData::MouseDown(BlitzMouseButtonEvent { - x: data.x + viewport_scroll.x as f32 / zoom, - y: data.y + viewport_scroll.y as f32 / zoom, - ..data - }), + UiEvent::MouseMove(data) => DomEventData::MouseMove(data), + UiEvent::MouseUp(data) => DomEventData::MouseUp(data), + UiEvent::MouseDown(data) => DomEventData::MouseDown(data), UiEvent::Wheel(data) => DomEventData::Wheel(data), UiEvent::KeyUp(data) => DomEventData::KeyUp(data), UiEvent::KeyDown(data) => DomEventData::KeyDown(data), diff --git a/packages/blitz-dom/src/events/ime.rs b/packages/blitz-dom/src/events/ime.rs index 635015681..ad629915a 100644 --- a/packages/blitz-dom/src/events/ime.rs +++ b/packages/blitz-dom/src/events/ime.rs @@ -41,6 +41,14 @@ pub(crate) fn handle_ime_event( } doc.shell_provider.request_redraw(); } + BlitzImeEvent::DeleteSurrounding { + before_bytes, + after_bytes, + } => { + let _ = before_bytes; + let _ = after_bytes; + // TODO + } } println!("Sent ime event to {node_id}"); } diff --git a/packages/blitz-dom/src/events/keyboard.rs b/packages/blitz-dom/src/events/keyboard.rs index ce8145001..d3611384a 100644 --- a/packages/blitz-dom/src/events/keyboard.rs +++ b/packages/blitz-dom/src/events/keyboard.rs @@ -237,6 +237,14 @@ fn apply_keypress_event( } return Some(GeneratedEvent::Input); } + Key::Character(c) if c == "\n" => { + if is_multiline { + driver.insert_or_replace_selection("\n"); + return Some(GeneratedEvent::Input); + } else { + return Some(GeneratedEvent::Submit); + } + } Key::Enter => { if is_multiline { driver.insert_or_replace_selection("\n"); diff --git a/packages/blitz-dom/src/events/mod.rs b/packages/blitz-dom/src/events/mod.rs index 45e4960a8..f4d05a982 100644 --- a/packages/blitz-dom/src/events/mod.rs +++ b/packages/blitz-dom/src/events/mod.rs @@ -26,22 +26,29 @@ pub(crate) fn handle_dom_event( let pos = node.absolute_position(0.0, 0.0); let mut set_focus = false; if let Some(sub_doc) = node.subdoc_mut() { + let viewport_scroll = sub_doc.inner().viewport_scroll(); // TODO: eliminate clone let ui_event = match event.data.clone() { DomEventData::MouseMove(mut mouse_event) => { - mouse_event.x -= pos.x; - mouse_event.y -= pos.y; + mouse_event.x -= pos.x - viewport_scroll.x as f32; + mouse_event.y -= pos.y - viewport_scroll.y as f32; + mouse_event.client_x -= pos.x; + mouse_event.client_y -= pos.y; Some(UiEvent::MouseMove(mouse_event)) } DomEventData::MouseDown(mut mouse_event) => { - mouse_event.x -= pos.x; - mouse_event.y -= pos.y; + mouse_event.x -= pos.x - viewport_scroll.x as f32; + mouse_event.y -= pos.y - viewport_scroll.y as f32; + mouse_event.client_x -= pos.x; + mouse_event.client_y -= pos.y; set_focus = true; Some(UiEvent::MouseDown(mouse_event)) } DomEventData::MouseUp(mut mouse_event) => { - mouse_event.x -= pos.x; - mouse_event.y -= pos.y; + mouse_event.x -= pos.x - viewport_scroll.x as f32; + mouse_event.y -= pos.y - viewport_scroll.y as f32; + mouse_event.client_x -= pos.x; + mouse_event.client_y -= pos.y; set_focus = true; Some(UiEvent::MouseUp(mouse_event)) } @@ -85,15 +92,7 @@ pub(crate) fn handle_dom_event( match &event.data { DomEventData::MouseMove(mouse_event) => { - let changed = handle_mousemove( - doc, - target_node_id, - mouse_event.x, - mouse_event.y, - mouse_event.buttons, - mouse_event, - dispatch_event, - ); + let changed = handle_mousemove(doc, target_node_id, mouse_event, dispatch_event); if changed { doc.shell_provider.request_redraw(); } diff --git a/packages/blitz-dom/src/events/mouse.rs b/packages/blitz-dom/src/events/mouse.rs index b66441bf4..e3feba3a4 100644 --- a/packages/blitz-dom/src/events/mouse.rs +++ b/packages/blitz-dom/src/events/mouse.rs @@ -1,37 +1,91 @@ -use std::time::{Duration, Instant}; +use std::{ + collections::VecDeque, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, +}; use blitz_traits::{ events::{ - BlitzInputEvent, BlitzMouseButtonEvent, BlitzWheelDelta, BlitzWheelEvent, DomEvent, - DomEventData, MouseEventButton, MouseEventButtons, + BlitzInputEvent, BlitzPointerEvent, BlitzPointerId, BlitzWheelDelta, BlitzWheelEvent, + DomEvent, DomEventData, MouseEventButton, MouseEventButtons, }, navigation::NavigationOptions, }; use keyboard_types::Modifiers; use markup5ever::local_name; -use crate::{BaseDocument, node::SpecialElementData}; +use crate::{ + BaseDocument, + document::{DragMode, FlingState, PanSample, PanState, ScrollAnimationState}, + node::SpecialElementData, +}; use super::focus::generate_focus_events; pub(crate) fn handle_mousemove( doc: &mut BaseDocument, target: usize, - x: f32, - y: f32, - buttons: MouseEventButtons, - event: &BlitzMouseButtonEvent, + event: &BlitzPointerEvent, mut dispatch_event: F, ) -> bool { + let x = event.x; + let y = event.y; + let buttons = event.buttons; + let mut changed = doc.set_hover_to(x, y); // Check if we've moved enough to be considered a selection drag (2px threshold) - if buttons != MouseEventButtons::None && !doc.is_selecting { - let dx = (x - doc.mousedown_position.x).abs(); - let dy = (y - doc.mousedown_position.y).abs(); - if dx > 2.0 || dy > 2.0 { - doc.is_selecting = true; + if buttons != MouseEventButtons::None && doc.drag_mode == DragMode::None { + let dx = x - doc.mousedown_position.x; + let dy = y - doc.mousedown_position.y; + if dx.abs() > 2.0 || dy.abs() > 2.0 { + match event.id { + BlitzPointerId::Mouse => { + doc.drag_mode = DragMode::Selecting; + } + BlitzPointerId::Finger(_) => { + doc.drag_mode = DragMode::Panning(PanState { + target, + last_x: event.screen_x, + last_y: event.screen_y, + samples: VecDeque::with_capacity(200), + }); + } + } + } + } + + if let DragMode::Panning(state) = &mut doc.drag_mode { + let dx = (event.screen_x - state.last_x) as f64; + let dy = (event.screen_y - state.last_y) as f64; + let time_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + let target = state.target; + state.last_x = event.screen_x; + state.last_y = event.screen_y; + + state.samples.push_back(PanSample { + time: time_ms, + // TODO: account for scroll delta not applied due to clamping + dx: dx as f32, + dy: dy as f32, + }); + + // Remove samples older than 100ms + if state.samples.len() > 50 && time_ms - state.samples.front().unwrap().time > 100 { + let idx = state + .samples + .partition_point(|sample| time_ms - sample.time > 100); + // FIXME: use truncate_front once stable + for _ in 0..idx { + state.samples.pop_front(); + } } + + let has_changed = doc.scroll_by(Some(target), dx, dy, &mut dispatch_event); + return has_changed; } let Some(hit) = doc.hit(x, y) else { @@ -123,7 +177,8 @@ pub(crate) fn handle_mousedown( // Update mousedown tracking for next click and selection drag detection doc.last_mousedown_time = Some(Instant::now()); doc.mousedown_position = taffy::Point { x, y }; - doc.is_selecting = false; + doc.drag_mode = DragMode::None; + doc.scroll_animation = ScrollAnimationState::None; let Some(hit) = doc.hit(x, y) else { // Clear text selection when clicking outside any element @@ -228,7 +283,7 @@ pub(crate) fn handle_mousedown( pub(crate) fn handle_mouseup( doc: &mut BaseDocument, target: usize, - event: &BlitzMouseButtonEvent, + event: &BlitzPointerEvent, mut dispatch_event: F, ) { if doc.devtools().highlight_hover { @@ -243,11 +298,66 @@ pub(crate) fn handle_mouseup( return; } - // Don't dispatch click if we were doing a text selection drag - let do_click = !doc.is_selecting; + // Reset Document's drag state to DragMode::None, storing the state + // locally for use within this function + let drag_mode = doc.drag_mode.take(); + + // Don't dispatch click if we were doing a text selection drag or panning + // the document with a touch + let do_click = drag_mode == DragMode::None; + + let time_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + + if let DragMode::Panning(state) = &drag_mode { + // Generate "fling" + if let Some(last_sample) = state.samples.back() + && time_ms - last_sample.time < 100 + { + let idx = state + .samples + .partition_point(|sample| time_ms - sample.time > 100); + + // Compute pan_time. Will always be <= 100ms as we ignore samples older than that. + let pan_start_time = state.samples[idx].time; + let pan_time = (time_ms - pan_start_time) as f32; + + // Avoid division by 0 + if pan_time > 0.0 { + let (pan_x, pan_y) = state + .samples + .iter() + .skip(idx) + .fold((0.0, 0.0), |(dx, dy), sample| { + (dx + sample.dx, dy + sample.dy) + }); + + let x_velocity = if pan_x.abs() > pan_y.abs() { + pan_x / pan_time + } else { + 0.0 + }; + + let y_velocity = if pan_y.abs() > pan_x.abs() { + pan_y / pan_time + } else { + 0.0 + }; - // Reset selection state - doc.is_selecting = false; + let fling = FlingState { + target: state.target, + last_seen_time: time_ms as f64, + x_velocity: x_velocity as f64 * 2.0, + y_velocity: y_velocity as f64 * 2.0, + }; + + doc.scroll_animation = ScrollAnimationState::Fling(fling); + doc.shell_provider.request_redraw(); + } + } + } // Dispatch a click event if do_click && event.button == MouseEventButton::Main { @@ -266,7 +376,7 @@ pub(crate) fn handle_mouseup( pub(crate) fn handle_click( doc: &mut BaseDocument, target: usize, - event: &BlitzMouseButtonEvent, + event: &BlitzPointerEvent, dispatch_event: &mut dyn FnMut(DomEvent), ) { let double_click_event = event.clone(); @@ -431,19 +541,19 @@ pub(crate) fn handle_wheel( doc: &mut BaseDocument, _: usize, event: BlitzWheelEvent, - dispatch_event: F, + mut dispatch_event: F, ) { let (scroll_x, scroll_y) = match event.delta { BlitzWheelDelta::Lines(x, y) => (x * 20.0, y * 20.0), BlitzWheelDelta::Pixels(x, y) => (x, y), }; - let has_changed = if let Some(hover_node_id) = doc.get_hover_node_id() { - doc.scroll_node_by_has_changed(hover_node_id, scroll_x, scroll_y, dispatch_event) - } else { - doc.scroll_viewport_by_has_changed(scroll_x, scroll_y) - }; - + let has_changed = doc.scroll_by( + doc.get_hover_node_id(), + scroll_x, + scroll_y, + &mut dispatch_event, + ); if has_changed { doc.shell_provider.request_redraw(); } diff --git a/packages/blitz-dom/src/node/node.rs b/packages/blitz-dom/src/node/node.rs index 0fdf07d98..d6fb66bef 100644 --- a/packages/blitz-dom/src/node/node.rs +++ b/packages/blitz-dom/src/node/node.rs @@ -1,6 +1,6 @@ use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; use bitflags::bitflags; -use blitz_traits::events::{BlitzMouseButtonEvent, DomEventData, HitResult}; +use blitz_traits::events::{BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult}; use blitz_traits::shell::ShellProvider; use html_escape::encode_quoted_attribute_to_string; use keyboard_types::Modifiers; @@ -196,7 +196,7 @@ impl Node { } } - pub(crate) fn display_style(&self) -> Option { + pub fn display_style(&self) -> Option { Some(self.primary_styles().as_ref()?.clone_display()) } @@ -1047,14 +1047,23 @@ impl Node { DomEventData::Click(self.synthetic_click_event_data(mods)) } - pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzMouseButtonEvent { + pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent { let absolute_position = self.absolute_position(0.0, 0.0); let x = absolute_position.x + (self.final_layout.size.width / 2.0); let y = absolute_position.y + (self.final_layout.size.height / 2.0); - BlitzMouseButtonEvent { + BlitzPointerEvent { + id: BlitzPointerId::Mouse, + is_primary: true, x, y, + + // TODO: should these be different? + screen_x: x, + screen_y: y, + client_x: x, + client_y: y, + mods, button: Default::default(), buttons: Default::default(), diff --git a/packages/blitz-dom/src/resolve.rs b/packages/blitz-dom/src/resolve.rs index 37fa16972..ff001ff7e 100644 --- a/packages/blitz-dom/src/resolve.rs +++ b/packages/blitz-dom/src/resolve.rs @@ -1,6 +1,9 @@ //! Resolve style and layout -use std::cell::RefCell; +use std::{ + cell::RefCell, + time::{SystemTime, UNIX_EPOCH}, +}; use debug_timer::debug_timer; use parley::LayoutContext; @@ -22,6 +25,7 @@ use taffy::AvailableSpace; use crate::{ BaseDocument, NON_INCREMENTAL, + document::ScrollAnimationState, layout::{ construct::{ ConstructionTask, ConstructionTaskData, ConstructionTaskResult, @@ -46,6 +50,8 @@ impl BaseDocument { // Process messages that have been sent to our message channel (e.g. loaded resource) self.handle_messages(); + self.resolve_scroll_animation(); + let root_node_id = self.root_element().id; debug_timer!(timer, feature = "log_phase_times"); @@ -110,6 +116,38 @@ impl BaseDocument { timer.print_times(&format!("Resolve({}): ", self.id())); } + pub fn resolve_scroll_animation(&mut self) { + match &mut self.scroll_animation { + ScrollAnimationState::Fling(fling_state) => { + let time_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64 as f64; + + let time_diff_ms = time_ms - fling_state.last_seen_time; + + // 0.95 @ 60fps normalized to actual frame times + let deceleration = 1.0 - ((0.05 / 16.66666) * time_diff_ms); + + fling_state.x_velocity *= deceleration; + fling_state.y_velocity *= deceleration; + fling_state.last_seen_time = time_ms; + let fling_state = fling_state.clone(); + + let dx = fling_state.x_velocity * time_diff_ms; + let dy = fling_state.y_velocity * time_diff_ms; + + self.scroll_by(Some(fling_state.target), dx, dy, &mut |_| {}); + if fling_state.x_velocity.abs() < 0.1 && fling_state.y_velocity.abs() < 0.1 { + self.scroll_animation = ScrollAnimationState::None; + } + } + ScrollAnimationState::None => { + // Do nothing + } + } + } + /// Ensure that the layout_children field is populated for all nodes pub fn resolve_layout_children(&mut self) { resolve_layout_children_recursive(self, self.root_node().id); diff --git a/packages/blitz-ios-uikit/Cargo.toml b/packages/blitz-ios-uikit/Cargo.toml new file mode 100644 index 000000000..81211e61a --- /dev/null +++ b/packages/blitz-ios-uikit/Cargo.toml @@ -0,0 +1,101 @@ +[package] +name = "blitz-ios-uikit" +version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +categories.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "UIKit renderer for blitz-dom - maps DOM nodes to native iOS views" + +[lib] +name = "blitz_ios_uikit" +path = "src/lib.rs" + +# Note: inspo.rs is kept as reference code, not a runnable example +# It demonstrates UIKit patterns with objc2 but requires external Swift FFI + +[features] +default = ["dioxus", "hot-reload"] +dioxus = ["dep:dioxus-native-dom", "dep:dioxus-core", "dep:blitz-net", "dep:tokio"] +hot-reload = ["dep:dioxus-devtools"] + +[dependencies] +# Note: system_fonts enables CoreText font backend on iOS/macOS +# woff-rust enables pure-Rust WOFF font decoding for web fonts +# floats enables CSS float layout support +blitz-dom = { workspace = true, features = ["svg", "system_fonts", "woff-rust", "floats"]} +blitz-traits = { workspace = true } +rustc-hash = { workspace = true } +taffy = { workspace = true } +keyboard-types = { workspace = true } +markup5ever = { workspace = true } + +# Dioxus integration (optional) +dioxus-native-dom = { workspace = true, optional = true } +dioxus-core = { workspace = true, optional = true } + +# Networking for image loading (optional, enabled with dioxus feature) +blitz-net = { workspace = true, optional = true } +tokio = { version = "1", features = ["rt", "macros"], optional = true } + +# Hot reload support (optional) +dioxus-devtools = { workspace = true, optional = true } + +# Stylo (for computed styles) +style = { workspace = true } + +# Async support for event loop integration +futures-util = { workspace = true } + +# Window management +winit = { workspace = true } +raw-window-handle = "0.6" + +[dev-dependencies] +blitz-html = { workspace = true } +blitz-net = { workspace = true } +winit = { workspace = true } +raw-window-handle = "0.6" +tokio = { version = "1", features = ["rt", "macros", "time", "full"] } +dioxus = { workspace = true } + +# objc2 bindings for iOS (UIKit requires iOS or Mac Catalyst) +# For Mac Catalyst, build with: --target aarch64-apple-ios-macabi +[target.'cfg(target_os = "ios")'.dependencies] +objc2 = "0.6" +objc2-foundation = { version = "0.3", features = ["NSThread", "NSString", "NSDictionary", "NSSet", "NSEnumerator", "NSGeometry"] } +objc2-ui-kit = { version = "0.3", features = [ + "UIResponder", + "UIView", + "UIViewController", + "UIWindow", + "UIScreen", + "UIApplication", + "UIColor", + "UILabel", + "UIButton", + "UIButtonConfiguration", + "UIControl", + "UITextField", + "UITextInput", + "UISwitch", + "UIImageView", + "UIScrollView", + "UIImage", + "UITouch", + "UIEvent", + "UIGestureRecognizer", + "UIFont", + "NSText", + "NSAttributedString", +] } +objc2-quartz-core = { version = "0.3", features = ["CALayer"] } +objc2-core-foundation = { version = "0.3", features = ["CFData"] } +objc2-core-graphics = { version = "0.3", features = [ + "CGImage", + "CGDataProvider", + "CGColorSpace", + "CGBitmapContext", # for CGImageAlphaInfo +] } diff --git a/packages/blitz-ios-uikit/examples/counter.html b/packages/blitz-ios-uikit/examples/counter.html new file mode 100644 index 000000000..a66740ab2 --- /dev/null +++ b/packages/blitz-ios-uikit/examples/counter.html @@ -0,0 +1,81 @@ + + + + + + +
+

Counter

+
0
+
+ + +
+ +
+ + diff --git a/packages/blitz-ios-uikit/examples/counter.rs b/packages/blitz-ios-uikit/examples/counter.rs new file mode 100644 index 000000000..65cc2a641 --- /dev/null +++ b/packages/blitz-ios-uikit/examples/counter.rs @@ -0,0 +1,215 @@ +//! iOS Counter Example - demonstrates blitz-ios-uikit with Dioxus +//! +//! This example creates a simple counter app using the UIKit renderer with +//! Dioxus for reactive UI. It also includes various input types for testing +//! text input and IME interaction. +//! +//! # Running +//! +//! Via Dioxus CLI: +//! ```sh +//! dx serve --example counter --platform ios +//! ``` +//! +//! Direct build for iOS Simulator: +//! ```sh +//! cargo build --example counter --target aarch64-apple-ios-sim +//! ``` + +#![cfg(target_os = "ios")] + +use blitz_ios_uikit::launch; +use dioxus::prelude::*; + +fn main() { + launch(app); +} + +fn app() -> Element { + let mut count = use_signal(|| 0); + let mut text_value = use_signal(|| String::new()); + let mut number_value = use_signal(|| String::new()); + let mut email_value = use_signal(|| String::new()); + let mut password_value = use_signal(|| String::new()); + let mut search_value = use_signal(|| String::new()); + + rsx! { + style { {CSS} } + div { class: "container", + img { + class: "logo", + src: "https://avatars.githubusercontent.com/u/79236386?s=200&v=4", + alt: "Dioxus Logo", + } + h1 { "Counter" } + div { class: "count", "{count}" } + div { class: "buttons", + button { class: "btn-decrement", onclick: move |_| count -= 1, "-" } + button { class: "btn-increment", onclick: move |_| count += 1, "+" } + } + button { class: "btn-reset", onclick: move |_| count.set(0), "Reset" } + + // Input testing section + h2 { "Input Tests" } + + div { class: "input-group", + label { "Text Input:" } + input { + r#type: "text", + placeholder: "Enter text...", + value: "{text_value}", + oninput: move |e| text_value.set(e.value()), + } + span { class: "input-value", "Value: {text_value}" } + } + div { class: "input-group", + label { "Text Input:" } + input { + r#type: "text", + placeholder: "Enter text...", + value: "{text_value}", + oninput: move |e| text_value.set(e.value()), + } + span { class: "input-value", "Value: {text_value}" } + } + + // Checkbox test + div { class: "input-group", + label { "Checkbox:" } + input { + r#type: "checkbox", + onchange: move |e| { + println!("Checkbox changed: {:?}", e.checked()); + }, + } + } + } + } +} + +const CSS: &str = r#" + html, body { + margin: 0; + padding: 0; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, sans-serif; + } + + body { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 20px; + } + + .container { + background: white; + border-radius: 20px; + padding: 30px; + text-align: center; + min-width: 300px; + max-width: 400px; + } + + h1 { + color: #333; + margin: 0 0 16px 0; + font-size: 32px; + } + + h2 { + color: #333; + margin: 24px 0 16px 0; + font-size: 20px; + border-top: 1px solid #eee; + padding-top: 16px; + } + + .count { + font-size: 48px; + font-weight: bold; + color: #667eea; + margin: 16px 0; + } + + .buttons { + display: flex; + gap: 10px; + justify-content: center; + } + + button { + padding: 15px 30px; + font-size: 24px; + border: none; + border-radius: 10px; + } + + .btn-increment { + background: #4CAF50; + color: white; + } + + .btn-decrement { + background: #f44336; + color: white; + } + + .btn-reset { + background: #2196F3; + color: white; + margin-top: 10px; + } + + .logo { + width: 80px; + height: 80px; + margin-bottom: 16px; + border-radius: 16px; + } + + .input-group { + display: flex; + flex-direction: column; + align-items: flex-start; + margin: 12px 0; + width: 100%; + } + + .input-group label { + font-size: 14px; + color: #666; + margin-bottom: 4px; + } + + .input-group input[type="text"], + .input-group input[type="number"], + .input-group input[type="email"], + .input-group input[type="password"], + .input-group input[type="search"] { + width: 100%; + padding: 12px; + font-size: 16px; + border: 1px solid #ddd; + border-radius: 8px; + box-sizing: border-box; + } + + .input-group input:focus { + border-color: #667eea; + outline: none; + } + + .input-value { + font-size: 12px; + color: #999; + margin-top: 4px; + } + + .input-group input[type="checkbox"] { + width: 24px; + height: 24px; + } +"#; diff --git a/packages/blitz-ios-uikit/src/application.rs b/packages/blitz-ios-uikit/src/application.rs new file mode 100644 index 000000000..f18d6c12d --- /dev/null +++ b/packages/blitz-ios-uikit/src/application.rs @@ -0,0 +1,228 @@ +//! UIKit Application - manages the winit event loop and views +//! +//! This provides proper integration with the winit event loop, using custom wakers +//! to ensure the UI only updates when the DOM actually changes. + +use crate::{UIKitRenderer, UIKitView, ViewConfig}; +use std::cell::Cell; +use std::collections::HashMap; +use std::sync::mpsc::{Receiver, Sender, channel}; +use std::sync::Arc; + +use blitz_traits::net::NetWaker; +use futures_util::task::ArcWake; +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoopProxy}; +use winit::window::WindowId; + +// ============================================================================= +// Events +// ============================================================================= + +/// Events that can be sent to wake up the event loop +#[derive(Debug, Clone)] +pub enum UIKitEvent { + /// Poll a specific window for updates + Poll { window_id: WindowId }, + /// Request a redraw for a document + RequestRedraw { doc_id: usize }, +} + +// ============================================================================= +// Proxy +// ============================================================================= + +/// Proxy for sending events to the winit event loop. +/// +/// This implements `NetWaker` so it can be used with blitz-net to wake up +/// the event loop when network requests complete. +#[derive(Clone)] +pub struct UIKitProxy(Arc); + +struct UIKitProxyInner { + winit_proxy: EventLoopProxy, + sender: Sender, +} + +impl UIKitProxy { + /// Create a new proxy and event receiver. + pub fn new(winit_proxy: EventLoopProxy) -> (Self, Receiver) { + let (sender, receiver) = channel(); + let proxy = Self(Arc::new(UIKitProxyInner { + winit_proxy, + sender, + })); + (proxy, receiver) + } + + /// Wake up the event loop. + pub fn wake_up(&self) { + self.0.winit_proxy.wake_up(); + } + + /// Send an event to the application. + pub fn send_event(&self, event: UIKitEvent) { + let _ = self.0.sender.send(event); + self.wake_up(); + } +} + +impl NetWaker for UIKitProxy { + fn wake(&self, doc_id: usize) { + self.send_event(UIKitEvent::RequestRedraw { doc_id }); + } +} + +/// Create a waker that sends Poll events to the event loop. +/// +/// This allows async tasks in the VirtualDom to wake up the event loop +/// when they complete. +pub fn create_waker(proxy: &UIKitProxy, window_id: WindowId) -> std::task::Waker { + struct WakerHandle { + proxy: UIKitProxy, + window_id: WindowId, + } + + impl ArcWake for WakerHandle { + fn wake_by_ref(arc_self: &Arc) { + arc_self.proxy.send_event(UIKitEvent::Poll { + window_id: arc_self.window_id, + }); + } + } + + futures_util::task::waker(Arc::new(WakerHandle { + proxy: proxy.clone(), + window_id, + })) +} + +// ============================================================================= +// Application +// ============================================================================= + +/// The main application handler for UIKit-based Blitz apps. +/// +/// This integrates with the winit event loop and manages one or more UIKitViews. +pub struct UIKitApplication { + /// Active views by window ID + pub views: HashMap, + /// Pending view configurations to create on resume + pub pending_views: Vec, + /// Proxy for sending events + pub proxy: UIKitProxy, + /// Receiver for application events + pub event_queue: Receiver, +} + +impl UIKitApplication { + /// Create a new application with the given proxy and event queue. + pub fn new(proxy: UIKitProxy, event_queue: Receiver) -> Self { + Self { + views: HashMap::new(), + pending_views: Vec::new(), + proxy, + event_queue, + } + } + + /// Add a view configuration to be created when the event loop starts. + pub fn add_view(&mut self, config: ViewConfig) { + self.pending_views.push(config); + } + + /// Find a view by document ID. + fn view_by_doc_id(&mut self, doc_id: usize) -> Option<&mut UIKitView> { + self.views.values_mut().find(|v| v.doc_id() == doc_id) + } + + /// Handle a UIKit application event. + pub fn handle_event(&mut self, _event_loop: &dyn ActiveEventLoop, event: UIKitEvent) { + match event { + UIKitEvent::Poll { window_id } => { + if let Some(view) = self.views.get_mut(&window_id) { + view.poll(); + } + } + UIKitEvent::RequestRedraw { doc_id } => { + if let Some(view) = self.view_by_doc_id(doc_id) { + view.request_redraw(); + } + } + } + } +} + +impl ApplicationHandler for UIKitApplication { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + // Resume existing views + for view in self.views.values_mut() { + view.resume(); + } + + // Create pending views + for config in self.pending_views.drain(..) { + let view = UIKitView::init(config, event_loop, &self.proxy); + self.views.insert(view.window_id(), view); + } + } + + fn destroy_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) { + for view in self.views.values_mut() { + view.suspend(); + } + } + + fn resumed(&mut self, _event_loop: &dyn ActiveEventLoop) { + // Called when app comes to foreground on iOS + for view in self.views.values_mut() { + view.request_redraw(); + } + } + + fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) { + // Called when app goes to background on iOS + } + + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + // Handle close requests + if matches!(event, WindowEvent::CloseRequested) { + self.views.remove(&window_id); + if self.views.is_empty() { + event_loop.exit(); + } + return; + } + + // Forward event to the appropriate view + if let Some(view) = self.views.get_mut(&window_id) { + view.handle_window_event(event); + } + + // Queue a poll for this window + self.proxy.send_event(UIKitEvent::Poll { window_id }); + } + + fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { + // Process all queued events + while let Ok(event) = self.event_queue.try_recv() { + self.handle_event(event_loop, event); + } + } + + fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { + // On iOS, we need to call request_redraw here due to winit limitations + // But we only do it if the view has pending updates + for view in self.views.values() { + if view.needs_redraw() { + view.request_redraw(); + } + } + } +} diff --git a/packages/blitz-ios-uikit/src/dioxus.rs b/packages/blitz-ios-uikit/src/dioxus.rs new file mode 100644 index 000000000..9494da9f7 --- /dev/null +++ b/packages/blitz-ios-uikit/src/dioxus.rs @@ -0,0 +1,702 @@ +//! Dioxus integration for UIKit renderer +//! +//! This module provides a `launch` function similar to `dioxus-native` that: +//! - Creates a VirtualDom from a Dioxus component +//! - Wraps it in a DioxusDocument +//! - Renders to native UIKit views +//! - Handles events and async tasks +//! - Supports hot-reloading when the `hot-reload` feature is enabled + +use crate::events::{drain_input_events, has_pending_input_events, InputEvent}; +use crate::UIKitRenderer; + +use std::cell::Cell; +use std::collections::HashMap; +use std::sync::mpsc::Receiver; +use std::sync::Arc; +use std::task::{Context as TaskContext, Waker}; + +use blitz_dom::Document; +use blitz_traits::events::{ + BlitzFocusEvent, BlitzInputEvent, BlitzPointerId, BlitzPointerEvent, DomEvent, DomEventData, + MouseEventButton, MouseEventButtons, UiEvent, +}; +use blitz_traits::shell::{ColorScheme, ShellProvider, Viewport}; +use dioxus_core::{ComponentFunction, Element, VirtualDom}; +use dioxus_native_dom::{DioxusDocument, DocumentConfig}; +use keyboard_types::Modifiers; +use objc2::rc::Retained; +use objc2_foundation::{MainThreadMarker, NSPoint, NSRect, NSSize}; +use objc2_ui_kit::UIView; +use raw_window_handle::{HasWindowHandle, RawWindowHandle}; +use winit::application::ApplicationHandler; +use winit::event::{ElementState, WindowEvent}; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowAttributes, WindowId}; + +// ============================================================================= +// UIKit Dioxus Events +// ============================================================================= + +/// Events for Dioxus UIKit application +#[derive(Debug, Clone)] +pub enum UIKitDioxusEvent { + /// Poll a specific window for updates + Poll { window_id: WindowId }, + /// Request a redraw for a document + RequestRedraw { doc_id: usize }, + /// Hot reload event from devserver + #[cfg(all(feature = "hot-reload", debug_assertions))] + HotReload(dioxus_devtools::DevserverMsg), +} + +/// Proxy for sending Dioxus UIKit events to the event loop. +#[derive(Clone)] +pub struct DioxusUIKitProxy { + inner: Arc, +} + +struct DioxusUIKitProxyInner { + winit_proxy: winit::event_loop::EventLoopProxy, + sender: std::sync::mpsc::Sender, +} + +impl DioxusUIKitProxy { + /// Create a new proxy and event receiver. + pub fn new(winit_proxy: winit::event_loop::EventLoopProxy) -> (Self, Receiver) { + let (sender, receiver) = std::sync::mpsc::channel(); + let proxy = Self { + inner: Arc::new(DioxusUIKitProxyInner { + winit_proxy, + sender, + }), + }; + (proxy, receiver) + } + + /// Wake up the event loop. + pub fn wake_up(&self) { + self.inner.winit_proxy.wake_up(); + } + + /// Send an event to the application. + pub fn send_event(&self, event: UIKitDioxusEvent) { + let _ = self.inner.sender.send(event); + self.wake_up(); + } +} + +impl blitz_traits::net::NetWaker for DioxusUIKitProxy { + fn wake(&self, doc_id: usize) { + self.send_event(UIKitDioxusEvent::RequestRedraw { doc_id }); + } +} + +/// Create a waker that sends Poll events to the event loop. +/// +/// This allows async tasks in the VirtualDom to wake up the event loop +/// when they complete. +fn create_dioxus_waker(proxy: &DioxusUIKitProxy, window_id: WindowId) -> Waker { + use futures_util::task::ArcWake; + + struct WakerHandle { + proxy: DioxusUIKitProxy, + window_id: WindowId, + } + + impl ArcWake for WakerHandle { + fn wake_by_ref(arc_self: &Arc) { + arc_self.proxy.send_event(UIKitDioxusEvent::Poll { + window_id: arc_self.window_id, + }); + } + } + + futures_util::task::waker(Arc::new(WakerHandle { + proxy: proxy.clone(), + window_id, + })) +} + +// ============================================================================= +// UIKitShellProvider - for triggering redraws when resources load +// ============================================================================= + +/// Shell provider that triggers window redraws when resources (images, fonts) load. +pub struct UIKitShellProvider { + window: Arc, +} + +impl UIKitShellProvider { + pub fn new(window: Arc) -> Self { + Self { window } + } +} + +impl ShellProvider for UIKitShellProvider { + fn request_redraw(&self) { + self.window.request_redraw(); + } +} + +// ============================================================================= +// DioxusUIKitView +// ============================================================================= + +/// A view that renders a DioxusDocument using UIKit. +/// +/// This is the Dioxus-aware version of UIKitView. It polls the VirtualDom +/// and handles events through the Dioxus event system. +pub struct DioxusUIKitView { + /// The winit window + window: Arc, + /// The Dioxus document (wraps VirtualDom + BaseDocument) + doc: DioxusDocument, + /// The UIKit renderer + renderer: UIKitRenderer, + /// Waker for async integration + waker: Option, + /// Proxy for event loop communication + proxy: DioxusUIKitProxy, + /// MainThreadMarker for UIKit operations + mtm: MainThreadMarker, + /// Whether a redraw is needed + needs_redraw: Cell, + /// Whether the view has been initialized + initialized: Cell, +} + +impl DioxusUIKitView { + /// Initialize a new Dioxus view. + pub fn init( + mut doc: DioxusDocument, + attributes: WindowAttributes, + event_loop: &dyn ActiveEventLoop, + proxy: &DioxusUIKitProxy, + ) -> Self { + let mtm = MainThreadMarker::new().expect("DioxusUIKitView must be created on main thread"); + + // Create window + let window: Arc = Arc::from( + event_loop.create_window(attributes).unwrap() + ); + + // Get window metrics + let size = window.surface_size(); + let scale = window.scale_factor() as f32; + + // Set up shell provider for resource loading callbacks (images, fonts) + let shell_provider = Arc::new(UIKitShellProvider::new(window.clone())); + doc.inner.borrow_mut().set_shell_provider(shell_provider); + + // Set viewport on document + let viewport = Viewport::new(size.width, size.height, scale, ColorScheme::Light); + doc.inner.borrow_mut().set_viewport(viewport); + + // Get root UIView from window + let root_view = get_uiview_from_window(&*window, mtm); + + // Create renderer using the inner BaseDocument + let mut renderer = UIKitRenderer::new(doc.inner.clone(), root_view, mtm); + renderer.set_scale(scale as f64); + + // Create waker for async tasks + let waker = create_dioxus_waker(proxy, window.id()); + + // Run initial build of the VirtualDom + doc.initial_build(); + + Self { + window, + doc, + renderer, + waker: Some(waker), + proxy: proxy.clone(), + mtm, + needs_redraw: Cell::new(true), + initialized: Cell::new(false), + } + } + + /// Apply hot-reload changes to the document. + #[cfg(all(feature = "hot-reload", debug_assertions))] + pub fn apply_hot_reload(&mut self, msg: &dioxus_devtools::HotReloadMsg) { + // Apply changes to the vdom + dioxus_devtools::apply_changes(&self.doc.vdom, msg); + + // Reload changed assets + for asset_path in &msg.assets { + if let Some(url) = asset_path.to_str() { + self.doc.inner.borrow_mut().reload_resource_by_href(url); + } + } + + // Request redraw + self.request_redraw(); + } + + /// Get the window ID. + pub fn window_id(&self) -> WindowId { + self.window.id() + } + + /// Get the document ID. + pub fn doc_id(&self) -> usize { + self.doc.id() + } + + /// Check if the view needs a redraw. + pub fn needs_redraw(&self) -> bool { + self.needs_redraw.get() + } + + /// Request a redraw of this view. + pub fn request_redraw(&self) { + self.needs_redraw.set(true); + self.window.request_redraw(); + } + + /// Resume rendering (called when surface is available). + pub fn resume(&mut self) { + if !self.initialized.get() { + self.doc.inner.borrow_mut().resolve(0.0); + self.renderer.sync(); + self.initialized.set(true); + self.needs_redraw.set(false); + } + } + + /// Suspend rendering (called when surface is lost). + pub fn suspend(&mut self) { + self.waker = None; + } + + /// Poll the VirtualDom for updates. + /// + /// Returns true if there were changes that require a redraw. + pub fn poll(&mut self) -> bool { + // Create task context from waker + let cx = self.waker.as_ref().map(|w| TaskContext::from_waker(w)); + + // Poll the DioxusDocument (this drives the VirtualDom) + let has_changes = self.doc.poll(cx); + + if has_changes { + self.request_redraw(); + } + + has_changes + } + + /// Process queued input events from UIKit native controls. + /// + /// This converts InputEvents from native UIKit controls (UITextField, etc.) + /// to DomEvents and dispatches them through the Dioxus event system. + /// + /// Returns true if any events were processed. + pub fn process_input_events(&mut self) -> bool { + let events = drain_input_events(); + let had_events = !events.is_empty(); + + for event in events { + let dom_event = match event { + InputEvent::Click { node_id } => { + DomEvent::new( + node_id, + DomEventData::Click(BlitzPointerEvent { + id: BlitzPointerId::Finger(0), + is_primary: true, + x: 0.0, + y: 0.0, + screen_x: 0.0, + screen_y: 0.0, + client_x: 0.0, + client_y: 0.0, + button: MouseEventButton::Main, + buttons: MouseEventButtons::None, + mods: Modifiers::empty(), + }), + ) + } + InputEvent::TextChanged { node_id, value } => { + DomEvent::new(node_id, DomEventData::Input(BlitzInputEvent { value })) + } + InputEvent::FocusGained { node_id } => { + DomEvent::new(node_id, DomEventData::Focus(BlitzFocusEvent)) + } + InputEvent::FocusLost { node_id } => { + DomEvent::new(node_id, DomEventData::Blur(BlitzFocusEvent)) + } + }; + // Dispatch through Dioxus event handler + self.doc.handle_dom_event(dom_event); + } + + had_events + } + + /// Perform a redraw if needed. + pub fn redraw(&mut self) { + if !self.needs_redraw.get() { + return; + } + + self.needs_redraw.set(false); + + // Process any queued input events + let had_events = self.process_input_events(); + + // If we had events, poll the VirtualDom to process them and get mutations + if had_events { + // Poll with no waker since we're already in redraw + let _ = self.doc.poll(None); + } + + // Resolve layout + self.doc.inner.borrow_mut().resolve(0.0); + + // Sync UIKit views with DOM + self.renderer.sync(); + } + + /// Handle a winit window event. + pub fn handle_window_event(&mut self, event: WindowEvent) { + match event { + WindowEvent::RedrawRequested => { + self.redraw(); + } + WindowEvent::SurfaceResized(size) => { + let scale = self.window.scale_factor() as f32; + let viewport = Viewport::new(size.width, size.height, scale, ColorScheme::Light); + self.doc.inner.borrow_mut().set_viewport(viewport); + self.request_redraw(); + } + WindowEvent::ScaleFactorChanged { scale_factor, .. } => { + self.renderer.set_scale(scale_factor); + let mut inner = self.doc.inner.borrow_mut(); + let (width, height) = inner.viewport().window_size; + let viewport = Viewport::new(width, height, scale_factor as f32, ColorScheme::Light); + inner.set_viewport(viewport); + drop(inner); + self.request_redraw(); + } + WindowEvent::PointerMoved { position, primary, .. } => { + let scale = self.window.scale_factor(); + let x = (position.x / scale) as f32; + let y = (position.y / scale) as f32; + + let event = UiEvent::MouseMove(BlitzPointerEvent { + id: BlitzPointerId::Finger(0), + is_primary: primary, + x, + y, + screen_x: x, + screen_y: y, + client_x: x, + client_y: y, + button: MouseEventButton::Main, + buttons: MouseEventButtons::Primary, + mods: Modifiers::empty(), + }); + + self.doc.handle_ui_event(event); + } + WindowEvent::PointerButton { state, position, primary, .. } => { + let scale = self.window.scale_factor(); + let x = (position.x / scale) as f32; + let y = (position.y / scale) as f32; + + let (event_type, buttons) = match state { + ElementState::Pressed => (true, MouseEventButtons::Primary), + ElementState::Released => (false, MouseEventButtons::None), + }; + + let pointer_event = BlitzPointerEvent { + id: BlitzPointerId::Finger(0), + is_primary: primary, + x, + y, + screen_x: x, + screen_y: y, + client_x: x, + client_y: y, + button: MouseEventButton::Main, + buttons, + mods: Modifiers::empty(), + }; + + let event = if event_type { + println!("[DioxusUIKitView] Pointer down at ({}, {})", x, y); + UiEvent::MouseDown(pointer_event) + } else { + println!("[DioxusUIKitView] Pointer up at ({}, {})", x, y); + UiEvent::MouseUp(pointer_event) + }; + + self.doc.handle_ui_event(event); + self.request_redraw(); + } + _ => {} + } + } +} + +/// Extract the root UIView from a winit window. +fn get_uiview_from_window(window: &dyn Window, mtm: MainThreadMarker) -> Retained { + let handle = window.window_handle().expect("Failed to get window handle"); + let raw_handle = handle.as_raw(); + + match raw_handle { + RawWindowHandle::UiKit(uikit_handle) => { + let ui_view_ptr = uikit_handle.ui_view.as_ptr(); + unsafe { + let ui_view: *mut UIView = ui_view_ptr.cast(); + Retained::retain(ui_view).expect("UIView pointer should be valid") + } + } + _ => { + eprintln!("[WARNING] Not a UIKit window handle, creating detached UIView"); + let frame = NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(390.0, 844.0)); + UIView::initWithFrame(mtm.alloc::(), frame) + } + } +} + +// ============================================================================= +// DioxusUIKitApplication +// ============================================================================= + +/// Application handler for Dioxus UIKit apps. +pub struct DioxusUIKitApplication { + /// Active views by window ID + views: HashMap, + /// Pending document to create on resume + pending_doc: Option<(DioxusDocument, WindowAttributes)>, + /// Proxy for sending events + proxy: DioxusUIKitProxy, + /// Receiver for application events + event_queue: Receiver, +} + +impl DioxusUIKitApplication { + /// Create a new application. + pub fn new( + doc: DioxusDocument, + attributes: WindowAttributes, + proxy: DioxusUIKitProxy, + event_queue: Receiver, + ) -> Self { + Self { + views: HashMap::new(), + pending_doc: Some((doc, attributes)), + proxy, + event_queue, + } + } + + fn view_by_doc_id(&mut self, doc_id: usize) -> Option<&mut DioxusUIKitView> { + self.views.values_mut().find(|v| v.doc_id() == doc_id) + } + + fn handle_event(&mut self, event_loop: &dyn ActiveEventLoop, event: UIKitDioxusEvent) { + match event { + UIKitDioxusEvent::Poll { window_id } => { + if let Some(view) = self.views.get_mut(&window_id) { + view.poll(); + } + } + UIKitDioxusEvent::RequestRedraw { doc_id } => { + if let Some(view) = self.view_by_doc_id(doc_id) { + view.request_redraw(); + } + } + #[cfg(all(feature = "hot-reload", debug_assertions))] + UIKitDioxusEvent::HotReload(msg) => { + self.handle_hot_reload(event_loop, msg); + } + } + } + + /// Handle hot-reload messages from the devserver. + #[cfg(all(feature = "hot-reload", debug_assertions))] + fn handle_hot_reload(&mut self, event_loop: &dyn ActiveEventLoop, msg: dioxus_devtools::DevserverMsg) { + match msg { + dioxus_devtools::DevserverMsg::HotReload(hotreload_msg) => { + // Apply hot-reload to all views + for view in self.views.values_mut() { + view.apply_hot_reload(&hotreload_msg); + view.poll(); + } + } + dioxus_devtools::DevserverMsg::Shutdown => { + println!("[HotReload] Devserver shutdown, exiting..."); + event_loop.exit(); + } + dioxus_devtools::DevserverMsg::FullReloadStart => { + println!("[HotReload] Full reload starting..."); + } + dioxus_devtools::DevserverMsg::FullReloadFailed => { + println!("[HotReload] Full reload failed"); + } + dioxus_devtools::DevserverMsg::FullReloadCommand => { + println!("[HotReload] Full reload command received"); + } + _ => {} + } + } +} + +impl ApplicationHandler for DioxusUIKitApplication { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + // Resume existing views + for view in self.views.values_mut() { + view.resume(); + } + + // Create pending view + if let Some((doc, attributes)) = self.pending_doc.take() { + let view = DioxusUIKitView::init(doc, attributes, event_loop, &self.proxy); + self.views.insert(view.window_id(), view); + } + } + + fn destroy_surfaces(&mut self, _event_loop: &dyn ActiveEventLoop) { + for view in self.views.values_mut() { + view.suspend(); + } + } + + fn resumed(&mut self, _event_loop: &dyn ActiveEventLoop) { + for view in self.views.values_mut() { + view.request_redraw(); + } + } + + fn suspended(&mut self, _event_loop: &dyn ActiveEventLoop) {} + + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + window_id: WindowId, + event: WindowEvent, + ) { + if matches!(event, WindowEvent::CloseRequested) { + self.views.remove(&window_id); + if self.views.is_empty() { + event_loop.exit(); + } + return; + } + + if let Some(view) = self.views.get_mut(&window_id) { + view.handle_window_event(event); + } + + self.proxy.send_event(UIKitDioxusEvent::Poll { window_id }); + } + + fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) { + while let Ok(event) = self.event_queue.try_recv() { + self.handle_event(event_loop, event); + } + } + + fn about_to_wait(&mut self, _event_loop: &dyn ActiveEventLoop) { + // Check if there are pending input events from native controls (buttons, text fields) + let has_pending = has_pending_input_events(); + + for view in self.views.values() { + // Request redraw if view needs it OR if there are pending input events + if view.needs_redraw() || has_pending { + view.request_redraw(); + } + } + } +} + +// ============================================================================= +// Launch function +// ============================================================================= + +/// Launch a Dioxus app with UIKit rendering. +/// +/// This is the main entry point for Dioxus apps on iOS using native UIKit views. +/// +/// # Example +/// +/// ```ignore +/// use blitz_ios_uikit::launch; +/// use dioxus::prelude::*; +/// +/// fn app() -> Element { +/// rsx! { +/// div { +/// h1 { "Hello, iOS!" } +/// button { +/// onclick: |_| println!("Clicked!"), +/// "Click me" +/// } +/// } +/// } +/// } +/// +/// fn main() { +/// launch(app); +/// } +/// ``` +pub fn launch(app: fn() -> Element) { + launch_cfg(app, WindowAttributes::default()) +} + +/// Launch a Dioxus app with custom window configuration. +pub fn launch_cfg(app: fn() -> Element, attributes: WindowAttributes) { + launch_cfg_with_props(app, (), attributes) +} + +/// Launch a Dioxus app with props and custom window configuration. +pub fn launch_cfg_with_props( + app: impl ComponentFunction, + props: P, + attributes: WindowAttributes, +) { + // Create tokio runtime for async networking + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .expect("Failed to create tokio runtime"); + let _guard = rt.enter(); + + // Create net provider for image/font loading + let net_provider = blitz_net::Provider::shared(None); + + // Create event loop + let event_loop = EventLoop::new().expect("Failed to create event loop"); + let winit_proxy = event_loop.create_proxy(); + let (proxy, event_queue) = DioxusUIKitProxy::new(winit_proxy); + + // Setup hot-reloading if enabled + #[cfg(all(feature = "hot-reload", debug_assertions))] + { + let proxy = proxy.clone(); + dioxus_devtools::connect(move |event| { + proxy.send_event(UIKitDioxusEvent::HotReload(event)); + }); + println!("[HotReload] Connected to devtools server"); + } + + // Create VirtualDom + let vdom = VirtualDom::new_with_props(app, props); + + // Create DioxusDocument with net provider + let config = DocumentConfig { + net_provider: Some(net_provider), + ..Default::default() + }; + let doc = DioxusDocument::new(vdom, config); + + // Create application + let application = DioxusUIKitApplication::new(doc, attributes, proxy, event_queue); + + // Run event loop + event_loop.run_app(application).expect("Event loop failed"); +} diff --git a/packages/blitz-ios-uikit/src/elements/button.rs b/packages/blitz-ios-uikit/src/elements/button.rs new file mode 100644 index 000000000..d0c463338 --- /dev/null +++ b/packages/blitz-ios-uikit/src/elements/button.rs @@ -0,0 +1,144 @@ +//! Button element (UIButton) implementation +//! +//! Maps `