diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..7ea2672 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,391 @@ +# Architecture + +Felt UI is a high-performance, GPU-accelerated UI engine. It combines the ergonomics of a declarative, fluent API (inspired by GPUI) with the performance of a retained-mode render tree. + +## High-Level API: Declarative Composition + +Developers build UIs using a fluent, builder-pattern API. Elements are composed into a tree which the engine diffs and renders. + +### Example: The View +Views are the building blocks of the application. They implement a `render` method. + +```rust +app.mount_ui(move || { + div() + .size(Size::FULL) + .bg(Colors::WHITE) + .child( + div() + .w_half() + .child(render_sidebar()) + ) + .child( + div() + .w_half() + .child(render_main_content()) + ) +}); +``` + +## Reactivity & State Management + +Felt UI adopts a hybrid reactivity model inspired by **SolidJS** for fine-grained UI updates and **Zustand** for global state management. + +### Fine-Grained Reactivity (SolidJS-inspired) +UI components react to state changes using Signals and Effects. This allows for precise updates without re-rendering the entire component tree. + +#### Signals +Signals are the basic units of state. They hold a value and notify subscribers when that value changes. The type must implement `Hash` for efficient change detection via hash comparison. + +```rust +// Creating a signal (T must implement Hash + Clone) +let (count, set_count) = create_signal::(0); + +// Reading a signal (subscribes the current context) +let current_count = count.get(); + +// Updating a signal (only notifies if hash changes) +set_count.set(current_count + 1); + +// For complex types +#[derive(Clone, Hash, PartialEq)] +struct User { + id: u64, + name: String, +} + +let (user, set_user) = create_signal(User { id: 1, name: "Alice".into() }); +``` + +**Signal Implementation Algorithm:** + +The Signal internally maintains both the value and its hash for efficient change detection: + +```rust +pub struct Signal { + value: Arc>, + hash: Arc>, + subscribers: Arc>>, +} + +impl Signal { + pub fn new(initial: T) -> Self { + let mut hasher = DefaultHasher::new(); + initial.hash(&mut hasher); + let initial_hash = hasher.finish(); + + Self { + value: Arc::new(RwLock::new(initial)), + hash: Arc::new(RwLock::new(initial_hash)), + subscribers: Arc::new(RwLock::new(Vec::new())), + } + } + + pub fn get(&self) -> T { + // Track this signal as a dependency of the current reactive context + track_dependency(self.id()); + self.value.read().unwrap().clone() + } + + pub fn set(&self, new_value: T) { + // 1. Compute hash of new value + let mut hasher = DefaultHasher::new(); + new_value.hash(&mut hasher); + let new_hash = hasher.finish(); + + // 2. Compare with stored hash (fast O(1) comparison) + let old_hash = *self.hash.read().unwrap(); + + if new_hash != old_hash { + // 3. Only update if hash changed + *self.value.write().unwrap() = new_value; + *self.hash.write().unwrap() = new_hash; + + // 4. Notify subscribers + let subscribers = self.subscribers.read().unwrap(); + for subscriber_id in subscribers.iter() { + schedule_effect(*subscriber_id); + } + } + // If hash is the same, do nothing (no notifications, no updates) + } +} +``` + +**Performance Benefits:** +- **O(1) hash comparison** vs O(n) deep equality for complex types +- Prevents cascade re-renders when setting the same value +- Works seamlessly with derived types (enums, structs with many fields) + +#### Effects +Effects are side effects that run when their dependencies change. They automatically track which signals are accessed. + +```rust +create_effect(move || { + // This closure re-runs whenever `count` changes + println!("The count is now: {}", count.get()); +}); + +// Effects can depend on multiple signals +create_effect(move || { + let u = user.get(); + let c = count.get(); + println!("User {} has count {}", u.name, c); +}); +``` + +#### Derived State +Computed values update automatically when their dependencies change. + +```rust +let double_count = move || count.get() * 2; +``` + +#### Binding to UI +Signals can be directly bound to UI properties. + +```rust +div() + .bg(move || if count.get() > 5 { Colors::RED } else { Colors::BLUE }) + .on_click(move |_| set_count.update(|c| c + 1)) +``` + +### Global State Store (Zustand-inspired) +For application-wide state, Felt provides a centralized store with a reducer-like pattern, but simplified. + +#### Store Definition +Define your state struct and the initial state. All state types must implement `Hash` for efficient change detection. + +```rust +#[derive(Clone, Debug, Hash, PartialEq)] +struct AppState { + user: Option, + theme: Theme, + notifications: Vec, +} + +#[derive(Clone, Debug, Hash, PartialEq)] +struct User { + id: u64, + name: String, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq)] +enum Theme { + Light, + Dark, +} + +impl Default for AppState { + fn default() -> Self { + Self { + user: None, + theme: Theme::Light, + notifications: Vec::new(), + } + } +} +``` + +#### Actions +Actions are functions that mutate the store. They can be methods on a `Store` wrapper or standalone functions. + +```rust +impl AppState { + fn login(&mut self, user: User) { + self.user = Some(user); + } + + fn toggle_theme(&mut self) { + self.theme = match self.theme { + Theme::Light => Theme::Dark, + Theme::Dark => Theme::Light, + }; + } +} +``` + +#### Usage in Components +Components subscribe to specific slices of the store using selectors. The selected value must implement `Hash` for efficient change detection. This ensures they only re-render when that specific slice changes. + +```rust +// Subscribe to the user slice (Option must implement Hash) +let user = use_store(|state: &AppState| state.user.clone()); + +// Subscribe to the theme slice (Theme must implement Hash) +let theme = use_store(|state: &AppState| state.theme); + +// For derived/computed values, ensure the result type implements Hash +#[derive(Clone, Hash, PartialEq)] +enum UserStatus { + LoggedIn(String), + LoggedOut, +} + +let user_status = use_store(|state: &AppState| { + match &state.user { + Some(u) => UserStatus::LoggedIn(u.name.clone()), + None => UserStatus::LoggedOut, + } +}); + +div() + .bg(move || match theme.get() { + Theme::Light => Colors::WHITE, + Theme::Dark => Colors::BLACK, + }) + .child( + text(move || match user.get() { + Some(u) => format!("Welcome, {}", u.name), + None => "Please log in".to_string(), + }) + ) +``` + +### Reactivity Execution Model + +The reactivity system drives the UI update loop, ensuring that only the necessary parts of the UI are re-computed and re-rendered. + +#### 1. Dependency Tracking +When a Signal is accessed (e.g., via `.get()`) within an **Effect** or a **UI binding closure**, the running effect is automatically registered as a *subscriber* to that Signal. This creates a dynamic dependency graph. + +#### 2. Hash-Based Change Detection +All reactive values must implement `Hash` for efficient change detection: +- When a Signal's value is updated via `.set()`, the framework computes the hash of the new value. +- This hash is compared with the hash of the previous value. +- **Only if the hashes differ** are subscribers notified, preventing unnecessary re-renders. +- This is significantly faster than deep equality checks for complex types. + +```rust +// Example: Only notifies if the hash changes +let (user, set_user) = create_signal(User { id: 1, name: "Alice".into() }); + +// This will notify subscribers (hash changes) +set_user.set(User { id: 1, name: "Bob".into() }); + +// This will NOT notify subscribers (same hash as previous) +set_user.set(User { id: 1, name: "Bob".into() }); +``` + +#### 3. Change Notification +When a hash mismatch is detected, the Signal notifies all its subscribers. + +#### 4. Effect Scheduling +Notified Effects are scheduled to run. +- **Synchronous**: Simple effects may run immediately. +- **Batching**: To avoid unnecessary work, multiple signal updates within a single event loop tick can be batched, running effects only once. + +#### 5. UI Updates & Rendering +The reactivity system integrates tightly with the rendering pipeline: +- **Property Updates**: If a Signal is bound to a property (e.g., `bg`, `width`), the element's internal state is updated directly. +- **Dirty Marking**: If the property affects layout or visual appearance, the element marks itself as "dirty" (e.g., `needs_layout` or `needs_paint`). +- **Frame Request**: The engine is notified to schedule a new frame. +- **Render Phase**: In the next frame, the engine traverses the tree, re-calculating layout and re-painting only the dirty nodes and their affected ancestors/descendants. + +## Core System: The Engine + +Under the hood, these declarative elements produce a persistent tree of `Widget`s. + +### The Widget Trait +The `Widget` trait is the low-level interface that powers the elements. + +```rust +pub trait Widget { + fn on_event(&mut self, ctx: &mut EventCtx, event: &Event); + fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size; + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene); + fn children(&self) -> SmallVec<[EntityId; 4]>; +} +``` + +## The Pipeline + +```mermaid +graph TD + A[Input Event] --> B(Event Phase) + B --> C{State Changed?} + C -- Yes --> D(Update Phase) + C -- No --> E + D --> E(Layout Phase) + E --> F(Paint Phase) + F --> G(Render Phase) + G --> H[GPU Frame] +``` + +## Rendering Model: Canvas, Clipping, and Scrolling + +Felt leverages Vello's powerful 2D rendering capabilities. + +### Clipping & Layers (ScrollView Implementation) +The `ScrollView` element works by pushing a clip layer onto the Vello scene. + +```rust +// The low-level implementation of the ScrollView widget +impl Widget for ScrollView { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + let viewport = Rect::from_origin_size(Point::ORIGIN, ctx.size()); + + // 1. Push Clip Layer + scene.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, &viewport); + + // 2. Push Scroll Transform + scene.push_layer(Mix::Normal, 1.0, Affine::translate(self.offset), &viewport); + + // 3. Paint Children + for child in self.children() { + ctx.paint_child(child, scene); + } + + // 4. Pop Layers + scene.pop_layer(); + scene.pop_layer(); + } +} +``` + +### Custom Drawing (Canvas Implementation) +For custom visualization, you can use the `canvas` element to drop down to low-level drawing with a closure. + +```rust +canvas(move |ctx, scene| { + // We are already in the correct coordinate space (transformed and clipped) + // thanks to the parent widgets (like ScrollView). + + scene.fill( + Fill::NonZero, + ctx.transform, + &Brush::Solid(Color::RED), + None, + &rect + ); +}) +.size(Size::FULL) // The canvas element acts like a normal box for layout +``` + +## Animation & Real-time Updates + +Felt is designed for high-frequency updates (60-120Hz). + +### The Threading Model +To achieve smooth performance, Felt separates the **recording** of drawing commands from their **execution**. + +1. **Main Thread (Recording)**: + - Runs the application logic, event handling, and layout. + - Executes the `canvas` closures. + - **Crucial**: This thread only *records* lightweight commands into the Vello `Scene`. It does **not** do rasterization or heavy GPU work. This ensures that even complex scenes can be recorded in a fraction of a frame. + +2. **Render Thread (Execution)**: + - Takes the immutable `Scene` produced by the Main Thread. + - Performs Vello's fine rasterization (on GPU via compute shaders) and command encoding. + - Submits work to the GPU. + +## Subsystems + +### Text Engine (Parley) +Text is a first-class citizen. +- **Declarative**: `text("Hello World").font_size(16.0)` +- **Implementation**: The `Text` widget holds a `parley::Layout` cache and issues glyph draw commands to Vello. + +### Accessibility (AccessKit) +- **Tree Sync**: The declarative tree is diffed to update the AccessKit tree. +- **Semantics**: Elements can be annotated: `div().role(Role::Button).aria_label("Submit")`. diff --git a/Cargo.lock b/Cargo.lock index 0d87de6..a21f158 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -31,11 +37,26 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-activity" -version = "0.6.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", "bitflags 2.10.0", @@ -58,6 +79,71 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "arrayref" version = "0.3.9" @@ -76,12 +162,66 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading 0.8.9", +] + [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + +[[package]] +name = "bit-set" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" +dependencies = [ + "bit-vec 0.7.0", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bit-vec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" + [[package]] name = "bitflags" version = "1.3.2" @@ -94,12 +234,28 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + [[package]] name = "block2" -version = "0.5.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" dependencies = [ + "block-sys", "objc2", ] @@ -114,6 +270,20 @@ name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] name = "bytes" @@ -123,9 +293,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" -version = "0.13.0" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ "bitflags 2.10.0", "log", @@ -137,9 +307,9 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.3.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ "calloop", "rustix 0.38.44", @@ -173,9 +343,56 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "com" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e17887fd17353b65b1b2ef1c526c83e26cd72e74f598a8dc1bee13a48f3d9f6" +dependencies = [ + "com_macros", +] + +[[package]] +name = "com_macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d375883580a668c7481ea6631fc1a8863e33cc335bf56bfad8d7e6d4b04b13a5" +dependencies = [ + "com_macros_support", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "com_macros_support" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "ad899a1087a9296d5644792d7cb72b8e34c1bec8e7d4fbc002230169a6e8710c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "combine" @@ -236,6 +453,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -248,6 +474,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.10.0", + "libloading 0.8.9", + "winapi", +] + +[[package]] +name = "d3d12" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" +dependencies = [ + "bitflags 2.10.0", + "libloading 0.8.9", + "winapi", +] + +[[package]] +name = "demo" +version = "0.1.0" +dependencies = [ + "env_logger 0.10.2", + "felt-platform", + "felt-ui", + "log", + "vello", + "winit", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -260,7 +520,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.9", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", ] [[package]] @@ -270,10 +539,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] -name = "dpi" -version = "0.1.2" +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] [[package]] name = "equivalent" @@ -291,10 +590,35 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "euclid" +version = "0.22.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +dependencies = [ + "num-traits", +] + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "felt-platform" version = "0.1.0" dependencies = [ + "anyhow", + "env_logger 0.11.8", + "futures-intrusive", + "log", + "pollster", + "vello", + "wgpu 0.19.4", "winit", ] @@ -302,7 +626,13 @@ dependencies = [ name = "felt-ui" version = "0.1.2" dependencies = [ + "env_logger 0.10.2", "felt-platform", + "glam", + "log", + "smallvec", + "taffy", + "vello", ] [[package]] @@ -311,6 +641,31 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -329,7 +684,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -338,6 +693,23 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "gethostname" version = "1.1.0" @@ -361,382 +733,618 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.16.0" +name = "gl_generator" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] [[package]] -name = "hermit-abi" -version = "0.5.2" +name = "glam" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] -name = "indexmap" -version = "2.12.0" +name = "glow" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "bd348e04c43b32574f2de31c8bb397d96c9fcfa1371bd4ca6d8bdc464ab121b1" dependencies = [ - "equivalent", - "hashbrown", + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "jni" -version = "0.21.1" +name = "glutin_wgl_sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", - "windows-sys 0.45.0", + "gl_generator", ] [[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.34" +name = "glutin_wgl_sys" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" dependencies = [ - "getrandom", - "libc", + "gl_generator", ] [[package]] -name = "js-sys" -version = "0.3.82" +name = "gpu-alloc" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "once_cell", - "wasm-bindgen", + "bitflags 2.10.0", + "gpu-alloc-types", ] [[package]] -name = "libc" -version = "0.2.177" +name = "gpu-alloc-types" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" +dependencies = [ + "bitflags 2.10.0", +] [[package]] -name = "libloading" -version = "0.8.9" +name = "gpu-allocator" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" dependencies = [ - "cfg-if", - "windows-link", + "log", + "presser", + "thiserror", + "winapi", + "windows", ] [[package]] -name = "libredox" -version = "0.1.10" +name = "gpu-allocator" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" +dependencies = [ + "log", + "presser", + "thiserror", + "winapi", + "windows", +] + +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" dependencies = [ "bitflags 2.10.0", - "libc", - "redox_syscall 0.5.18", + "gpu-descriptor-types 0.1.2", + "hashbrown 0.14.5", ] [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "gpu-descriptor" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.10.0", + "gpu-descriptor-types 0.2.0", + "hashbrown 0.15.5", +] [[package]] -name = "linux-raw-sys" +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "grid" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36119f3a540b086b4e436bb2b588cf98a68863470e0e880f4d0842f112a3183a" + +[[package]] +name = "guillotiere" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hassle-rs" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" +dependencies = [ + "bitflags 2.10.0", + "com", + "libc", + "libloading 0.8.9", + "thiserror", + "widestring", + "winapi", +] [[package]] -name = "log" -version = "0.4.28" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] -name = "memchr" -version = "2.7.6" +name = "hexf-parse" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "memmap2" -version = "0.9.9" +name = "humantime" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + +[[package]] +name = "icrate" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" dependencies = [ + "block2", + "dispatch", + "objc2", +] + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", "libc", + "windows-sys 0.61.2", ] [[package]] -name = "ndk" -version = "0.9.0" +name = "is_terminal_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" dependencies = [ - "bitflags 2.10.0", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", "jni-sys", "log", - "ndk-sys", - "num_enum", - "raw-window-handle", "thiserror", + "walkdir", + "windows-sys 0.45.0", ] [[package]] -name = "ndk-context" -version = "0.1.1" +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] -name = "ndk-sys" -version = "0.6.0+11769913" +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "jni-sys", + "getrandom", + "libc", ] [[package]] -name = "num_enum" -version = "0.7.5" +name = "js-sys" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ - "num_enum_derive", - "rustversion", + "once_cell", + "wasm-bindgen", ] [[package]] -name = "num_enum_derive" -version = "0.7.5" +name = "khronos-egl" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", + "libc", + "libloading 0.8.9", + "pkg-config", ] [[package]] -name = "objc-sys" -version = "0.3.5" +name = "khronos_api" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] -name = "objc2" -version = "0.5.2" +name = "kurbo" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ - "objc-sys", - "objc2-encode", + "arrayvec", + "euclid", + "smallvec", ] [[package]] -name = "objc2-app-kit" -version = "0.2.2" +name = "libc" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", - "block2", "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", + "redox_syscall 0.5.18", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memmap2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +dependencies = [ + "libc", +] + +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.10.0", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", ] [[package]] -name = "objc2-cloud-kit" -version = "0.2.2" +name = "metal" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ "bitflags 2.10.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", + "block", + "core-graphics-types", + "foreign-types", + "log", + "objc", + "paste", ] [[package]] -name = "objc2-contacts" -version = "0.2.2" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "adler2", + "simd-adler32", ] [[package]] -name = "objc2-core-data" -version = "0.2.2" +name = "naga" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" dependencies = [ + "bit-set 0.5.3", "bitflags 2.10.0", - "block2", - "objc2", - "objc2-foundation", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "num-traits", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", ] [[package]] -name = "objc2-core-image" -version = "0.2.2" +name = "naga" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "arrayvec", + "bit-set 0.6.0", + "bitflags 2.10.0", + "cfg_aliases", + "codespan-reporting", + "hexf-parse", + "indexmap", + "log", + "rustc-hash", + "spirv", + "termcolor", + "thiserror", + "unicode-xid", ] [[package]] -name = "objc2-core-location" -version = "0.2.2" +name = "ndk" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "block2", - "objc2", - "objc2-contacts", - "objc2-foundation", + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", ] [[package]] -name = "objc2-encode" -version = "4.1.0" +name = "ndk-context" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" [[package]] -name = "objc2-foundation" -version = "0.2.2" +name = "ndk-sys" +version = "0.5.0+25.2.9519653" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" dependencies = [ - "bitflags 2.10.0", - "block2", - "dispatch", - "libc", - "objc2", + "jni-sys", ] [[package]] -name = "objc2-link-presentation" -version = "0.2.2" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", + "autocfg", ] [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "num_enum" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ - "bitflags 2.10.0", - "block2", - "objc2", - "objc2-foundation", + "num_enum_derive", + "rustversion", ] [[package]] -name = "objc2-quartz-core" -version = "0.2.2" +name = "num_enum_derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "bitflags 2.10.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.108", ] [[package]] -name = "objc2-symbols" -version = "0.2.2" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "objc2", - "objc2-foundation", + "malloc_buf", + "objc_exception", ] [[package]] -name = "objc2-ui-kit" -version = "0.2.2" +name = "objc-sys" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.10.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" +name = "objc2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" dependencies = [ - "block2", - "objc2", - "objc2-foundation", + "objc-sys", + "objc2-encode", ] [[package]] -name = "objc2-user-notifications" -version = "0.2.2" +name = "objc2-encode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" + +[[package]] +name = "objc_exception" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" dependencies = [ - "bitflags 2.10.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", + "cc", ] [[package]] @@ -745,6 +1353,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "orbclient" version = "0.3.48" @@ -764,31 +1378,50 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.3.2" +name = "parking_lot" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] [[package]] -name = "pin-project" -version = "1.1.10" +name = "parking_lot_core" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "pin-project-internal", + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", ] [[package]] -name = "pin-project-internal" -version = "1.1.10" +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peniko" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +checksum = "0a648c93f502a0bef0a9cb47fa1335994958a2744667d3f82defe513f276741a" dependencies = [ - "proc-macro2", - "quote", - "syn", + "kurbo", + "smallvec", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -801,6 +1434,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.11.0" @@ -815,6 +1461,33 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -833,6 +1506,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" + [[package]] name = "quick-xml" version = "0.37.5" @@ -857,17 +1536,33 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "range-alloc" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" + [[package]] name = "raw-window-handle" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] @@ -881,6 +1576,47 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.38.44" @@ -928,11 +1664,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sctk-adwaita" -version = "0.10.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" dependencies = [ "ab_glyph", "log", @@ -948,6 +1690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -967,7 +1710,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -976,12 +1719,37 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -990,9 +1758,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" -version = "0.19.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ "bitflags 2.10.0", "calloop", @@ -1014,29 +1782,83 @@ dependencies = [ ] [[package]] -name = "smol_str" -version = "0.2.2" +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spirv" +version = "0.3.0+sdk-1.3.268.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "taffy" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "136661daffcdfb128afb0d45a8f7e88078739196d64d7ffa47f8513f4a00a6d7" dependencies = [ + "arrayvec", + "grid", + "num-traits", "serde", + "slotmap", ] [[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" - -[[package]] -name = "syn" -version = "2.0.108" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "winapi-util", ] [[package]] @@ -1056,7 +1878,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] [[package]] @@ -1114,22 +1936,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" - [[package]] name = "ttf-parser" version = "0.25.1" @@ -1148,6 +1954,69 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vello" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc44dd4eb9af6a41551b9a82c93d068bd832693d6f78ab118ad19780d8e1202e" +dependencies = [ + "bytemuck", + "futures-intrusive", + "log", + "peniko", + "png", + "raw-window-handle", + "skrifa", + "static_assertions", + "thiserror", + "vello_encoding", + "vello_shaders", + "wgpu 22.1.0", +] + +[[package]] +name = "vello_encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8110c14702a4e17f9200f6e3c4fe05dda5a22bf031ae4feafed4a61429f66fb2" +dependencies = [ + "bytemuck", + "guillotiere", + "peniko", + "skrifa", + "smallvec", +] + +[[package]] +name = "vello_shaders" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cad02d6f29f2212a6ee382a8fec6f9977d0cceefacf07f8e361607ffe3988d" +dependencies = [ + "bytemuck", + "naga 22.1.0", + "thiserror", + "vello_encoding", +] + [[package]] name = "version_check" version = "0.9.5" @@ -1218,7 +2087,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.108", "wasm-bindgen-shared", ] @@ -1281,9 +2150,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.9" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -1293,9 +2162,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.3.9" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" +checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -1306,9 +2175,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.9" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -1352,14 +2221,249 @@ dependencies = [ [[package]] name = "web-time" -version = "1.1.0" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases", + "js-sys", + "log", + "naga 0.19.2", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 0.19.4", + "wgpu-hal 0.19.5", + "wgpu-types 0.19.2", +] + +[[package]] +name = "wgpu" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" +dependencies = [ + "arrayvec", + "cfg_aliases", + "document-features", + "js-sys", + "log", + "naga 22.1.0", + "parking_lot", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 22.1.0", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec 0.6.3", + "bitflags 2.10.0", + "cfg_aliases", + "codespan-reporting", + "indexmap", + "log", + "naga 0.19.2", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "web-sys", + "wgpu-hal 0.19.5", + "wgpu-types 0.19.2", +] + +[[package]] +name = "wgpu-core" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" +dependencies = [ + "arrayvec", + "bit-vec 0.7.0", + "bitflags 2.10.0", + "cfg_aliases", + "document-features", + "indexmap", + "log", + "naga 22.1.0", + "once_cell", + "parking_lot", + "profiling", + "raw-window-handle", + "rustc-hash", + "smallvec", + "thiserror", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash 0.37.3+1.3.251", + "bit-set 0.5.3", + "bitflags 2.10.0", + "block", + "cfg_aliases", + "core-graphics-types", + "d3d12 0.19.0", + "glow", + "glutin_wgl_sys 0.5.0", + "gpu-alloc", + "gpu-allocator 0.25.0", + "gpu-descriptor 0.2.4", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "metal 0.27.0", + "naga 0.19.2", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", + "wasm-bindgen", + "web-sys", + "wgpu-types 0.19.2", + "winapi", +] + +[[package]] +name = "wgpu-hal" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ + "android_system_properties", + "arrayvec", + "ash 0.38.0+1.3.281", + "bit-set 0.6.0", + "bitflags 2.10.0", + "block", + "cfg_aliases", + "core-graphics-types", + "d3d12 22.0.0", + "glow", + "glutin_wgl_sys 0.6.1", + "gpu-alloc", + "gpu-allocator 0.26.0", + "gpu-descriptor 0.3.2", + "hassle-rs", "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.9", + "log", + "metal 0.29.0", + "naga 22.1.0", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash", + "smallvec", + "thiserror", "wasm-bindgen", + "web-sys", + "wgpu-types 22.0.0", + "winapi", +] + +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.10.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "wgpu-types" +version = "22.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" +dependencies = [ + "bitflags 2.10.0", + "js-sys", + "web-sys", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -1369,6 +2473,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -1386,11 +2515,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1426,6 +2555,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1448,6 +2592,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1460,6 +2610,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1472,6 +2628,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1490,6 +2652,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1502,6 +2670,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1514,6 +2688,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1526,6 +2706,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1534,41 +2720,37 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.12" +version = "0.29.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.10.0", - "block2", "bytemuck", "calloop", "cfg_aliases", - "concurrent-queue", "core-foundation", "core-graphics", "cursor-icon", - "dpi", + "icrate", "js-sys", "libc", + "log", "memmap2", "ndk", + "ndk-sys", "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", + "once_cell", "orbclient", "percent-encoding", - "pin-project", "raw-window-handle", - "redox_syscall 0.4.1", + "redox_syscall 0.3.5", "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", - "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", @@ -1578,7 +2760,7 @@ dependencies = [ "wayland-protocols-plasma", "web-sys", "web-time", - "windows-sys 0.52.0", + "windows-sys 0.48.0", "x11-dl", "x11rb", "xkbcommon-dl", @@ -1619,7 +2801,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", + "libloading 0.8.9", "once_cell", "rustix 1.1.2", "x11rb-protocol", @@ -1656,6 +2838,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + [[package]] name = "zerocopy" version = "0.8.27" @@ -1673,5 +2861,5 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.108", ] diff --git a/Cargo.toml b/Cargo.toml index cdf5eab..4f54a7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/felt-ui", "crates/felt-platform", + "demo", ] resolver = "2" diff --git a/crates/felt-platform/Cargo.toml b/crates/felt-platform/Cargo.toml index 3c669db..b7f57a4 100644 --- a/crates/felt-platform/Cargo.toml +++ b/crates/felt-platform/Cargo.toml @@ -6,4 +6,11 @@ description = "Platform abstraction for Felt UI (winit glue, IME, clipboard, dia license = "MIT OR Apache-2.0" [dependencies] -winit = "0.30" +winit = { version = "0.29", features = ["rwh_06"] } +wgpu = "0.19" +vello = { version = "0.3.0", features = ["wgpu"] } +pollster = "0.3" +env_logger = "0.11" +log = "0.4" +anyhow = "1.0" +futures-intrusive = "0.5" diff --git a/crates/felt-platform/src/lib.rs b/crates/felt-platform/src/lib.rs index 79a5962..02a8e83 100644 --- a/crates/felt-platform/src/lib.rs +++ b/crates/felt-platform/src/lib.rs @@ -1,2 +1,137 @@ -pub mod application; -pub mod size; +use std::sync::Arc; +use vello::util::{RenderContext, RenderSurface}; +use vello::{Renderer, Scene}; +use winit::event::{Event, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::Window; + +pub struct App { + context: RenderContext, + renderers: Vec>, + surface: Option>, + window: Option>, + paint_callback: Option>, +} + +impl App { + pub fn new() -> Self { + Self { + context: RenderContext::new(), + renderers: vec![], + surface: None, + window: None, + paint_callback: None, + } + } + + pub fn mount(&mut self, callback: impl FnMut(&mut vello::Scene, u32, u32) + 'static) { + self.paint_callback = Some(Box::new(callback)); + } + + pub fn run(mut self) { + let event_loop = EventLoop::new().unwrap(); + let window = Arc::new( + winit::window::WindowBuilder::new() + .build(&event_loop) + .unwrap(), + ); + self.window = Some(window.clone()); + + let surface = pollster::block_on(self.context.create_surface( + window.clone(), + window.inner_size().width, + window.inner_size().height, + vello::wgpu::PresentMode::AutoVsync, + )) + .unwrap(); + + self.surface = Some(surface); + self.renderers.resize_with(1, || None); + + event_loop + .run(move |event, elwt| { + elwt.set_control_flow(ControlFlow::Poll); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => elwt.exit(), + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + if let Some(surface) = &mut self.surface { + self.context + .resize_surface(surface, size.width, size.height); + } + self.window.as_ref().unwrap().request_redraw(); + } + Event::WindowEvent { + event: WindowEvent::RedrawRequested, + .. + } => { + if let (Some(surface), Some(window)) = (&mut self.surface, &self.window) { + let width = surface.config.width; + let height = surface.config.height; + let device_handle = &self.context.devices[surface.dev_id]; + + let surface_texture = surface.surface.get_current_texture().unwrap(); + + let renderer = + self.renderers[surface.dev_id].get_or_insert_with(|| { + Renderer::new( + &device_handle.device, + vello::RendererOptions { + surface_format: Some(surface.format), + use_cpu: false, + antialiasing_support: vello::AaSupport::all(), + num_init_threads: None, + }, + ) + .unwrap() + }); + + let mut scene = vello::Scene::new(); + + // Call the paint callback + if let Some(callback) = &mut self.paint_callback { + callback(&mut scene, width, height); + } else { + // Default clear + scene.fill( + vello::peniko::Fill::NonZero, + vello::kurbo::Affine::IDENTITY, + &vello::peniko::Brush::Solid(vello::peniko::Color::BLACK), + None, + &vello::kurbo::Rect::new(0.0, 0.0, width as f64, height as f64), + ); + } + + renderer + .render_to_surface( + &device_handle.device, + &device_handle.queue, + &scene, + &surface_texture, + &vello::RenderParams { + base_color: vello::peniko::Color::BLACK, + width, + height, + antialiasing_method: vello::AaConfig::Area, + }, + ) + .unwrap(); + + surface_texture.present(); + + // Request next frame for animation + window.request_redraw(); + } + } + _ => {} + } + }) + .unwrap(); + } +} diff --git a/crates/felt-ui/Cargo.toml b/crates/felt-ui/Cargo.toml index b0737ca..70ebef3 100644 --- a/crates/felt-ui/Cargo.toml +++ b/crates/felt-ui/Cargo.toml @@ -7,3 +7,9 @@ license = "MIT OR Apache-2.0" [dependencies] felt-platform = { version = "0.1.0", path = "../felt-platform" } +taffy = "0.6" +vello = "0.3.0" +glam = "0.29" +smallvec = "1.13" +env_logger = "0.10" +log = "0.4" diff --git a/crates/felt-ui/src/draw.rs b/crates/felt-ui/src/draw.rs new file mode 100644 index 0000000..809ef0d --- /dev/null +++ b/crates/felt-ui/src/draw.rs @@ -0,0 +1,164 @@ +// Drawing primitives module - abstracts Vello types +use vello::kurbo; +use vello::peniko; + +// Re-export core geometric types +pub use kurbo::{ + Affine, Arc, BezPath, Circle, Ellipse, Line, Point, Rect, RoundedRect, Size, Vec2, +}; + +// Color abstraction +#[derive(Clone, Copy, Debug)] +pub struct Color { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl Color { + pub const fn rgb(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b, a: 255 } + } + + pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a } + } + + pub(crate) fn to_vello(&self) -> peniko::Color { + peniko::Color::rgba8(self.r, self.g, self.b, self.a) + } +} + +// Brush abstraction +#[derive(Clone, Debug)] +pub enum Brush { + Solid(Color), + Gradient(Gradient), +} + +impl Brush { + pub(crate) fn to_vello(&self) -> peniko::Brush { + match self { + Brush::Solid(color) => peniko::Brush::Solid(color.to_vello()), + Brush::Gradient(gradient) => gradient.to_vello(), + } + } +} + +// Gradient abstraction +#[derive(Clone, Debug)] +pub struct Gradient { + // Simplified for now - can be expanded + pub(crate) inner: peniko::Gradient, +} + +impl Gradient { + pub(crate) fn to_vello(&self) -> peniko::Brush { + peniko::Brush::Gradient(self.inner.clone()) + } +} + +// Fill style +#[derive(Clone, Copy, Debug)] +pub enum FillRule { + NonZero, + EvenOdd, +} + +impl FillRule { + pub(crate) fn to_vello(&self) -> peniko::Fill { + match self { + FillRule::NonZero => peniko::Fill::NonZero, + FillRule::EvenOdd => peniko::Fill::EvenOdd, + } + } +} + +// Stroke style +#[derive(Clone, Debug)] +pub struct StrokeStyle { + pub width: f64, +} + +impl StrokeStyle { + pub fn new(width: f64) -> Self { + Self { width } + } + + pub(crate) fn to_vello(&self) -> kurbo::Stroke { + kurbo::Stroke::new(self.width) + } +} + +// Blend mode +#[derive(Clone, Copy, Debug)] +pub enum BlendMode { + Normal, + Multiply, + Screen, + Overlay, + Darken, + Lighten, + ColorDodge, + ColorBurn, + HardLight, + SoftLight, + Difference, + Exclusion, + Hue, + Saturation, + Color, + Luminosity, + Clip, +} + +impl BlendMode { + pub(crate) fn to_vello(&self) -> peniko::Mix { + match self { + BlendMode::Normal => peniko::Mix::Normal, + BlendMode::Multiply => peniko::Mix::Multiply, + BlendMode::Screen => peniko::Mix::Screen, + BlendMode::Overlay => peniko::Mix::Overlay, + BlendMode::Darken => peniko::Mix::Darken, + BlendMode::Lighten => peniko::Mix::Lighten, + BlendMode::ColorDodge => peniko::Mix::ColorDodge, + BlendMode::ColorBurn => peniko::Mix::ColorBurn, + BlendMode::HardLight => peniko::Mix::HardLight, + BlendMode::SoftLight => peniko::Mix::SoftLight, + BlendMode::Difference => peniko::Mix::Difference, + BlendMode::Exclusion => peniko::Mix::Exclusion, + BlendMode::Hue => peniko::Mix::Hue, + BlendMode::Saturation => peniko::Mix::Saturation, + BlendMode::Color => peniko::Mix::Color, + BlendMode::Luminosity => peniko::Mix::Luminosity, + BlendMode::Clip => peniko::Mix::Clip, + } + } +} + +// Image abstraction +#[derive(Clone)] +pub struct Image { + pub(crate) inner: peniko::Image, +} + +impl Image { + pub fn new(data: Vec, width: u32, height: u32) -> Self { + let inner = peniko::Image::new( + peniko::Blob::new(std::sync::Arc::new(data)), + peniko::Format::Rgba8, + width, + height, + ); + Self { inner } + } + + pub fn from_vello(inner: peniko::Image) -> Self { + Self { inner } + } + + pub(crate) fn to_vello(&self) -> &peniko::Image { + &self.inner + } +} diff --git a/crates/felt-ui/src/element.rs b/crates/felt-ui/src/element.rs new file mode 100644 index 0000000..d5b472c --- /dev/null +++ b/crates/felt-ui/src/element.rs @@ -0,0 +1,18 @@ +use crate::Widget; + +/// An element is a declarative description of a widget. +/// It is a builder that can construct a concrete Widget. +pub trait Element { + fn build(self: Box) -> Box; +} + +/// A trait for types that can be converted into an Element. +pub trait IntoElement { + fn into_element(self) -> Box; +} + +impl IntoElement for T { + fn into_element(self) -> Box { + Box::new(self) + } +} diff --git a/crates/felt-ui/src/elements.rs b/crates/felt-ui/src/elements.rs new file mode 100644 index 0000000..de12c29 --- /dev/null +++ b/crates/felt-ui/src/elements.rs @@ -0,0 +1,66 @@ +use crate::Widget; +use crate::draw::Color; +use crate::element::{Element, IntoElement}; +use crate::widget::container::Container; +use vello::kurbo::{Size, Vec2}; + +pub struct Div { + child: Option>, + size: Option, + bg: Option, + border: Option<(Color, f64)>, + offset: Vec2, +} + +impl Div { + pub fn new() -> Self { + Self { + child: None, + size: None, + bg: None, + border: None, + offset: Vec2::ZERO, + } + } + + pub fn child(mut self, child: impl IntoElement) -> Self { + self.child = Some(child.into_element().build()); + self + } + + pub fn size(mut self, size: Size) -> Self { + self.size = Some(size); + self + } + + pub fn bg(mut self, color: Color) -> Self { + self.bg = Some(color); + self + } + + pub fn border(mut self, color: Color, width: f64) -> Self { + self.border = Some((color, width)); + self + } + + pub fn offset(mut self, offset: Vec2) -> Self { + self.offset = offset; + self + } +} + +impl Element for Div { + fn build(self: Box) -> Box { + Box::new(Container { + child: self.child, + background: self.bg, + border: self.border, + offset: self.offset, + size: self.size, + }) + } +} + +pub fn div() -> Div { + Div::new() +} diff --git a/crates/felt-ui/src/lib.rs b/crates/felt-ui/src/lib.rs index c332a45..2d3b2cd 100644 --- a/crates/felt-ui/src/lib.rs +++ b/crates/felt-ui/src/lib.rs @@ -1,5 +1,83 @@ -pub use felt_platform as platform; +use smallvec::SmallVec; +use vello::Scene; -pub mod prelude { - pub use felt_platform::*; +pub mod draw; +pub mod element; +pub mod elements; +pub mod widget; + +pub use draw::{ + Affine, BlendMode, Brush, Circle, Color, FillRule, Image, Line, Point, Rect, RoundedRect, Size, + StrokeStyle, Vec2, +}; +pub use element::{Element, IntoElement}; +pub use elements::div; +pub use widget::canvas::{DrawContext, canvas}; +pub use widget::scroll::scroll_view; + +pub type EntityId = u64; + +pub struct EventCtx; +pub struct LayoutCtx; + +pub struct PaintCtx { + pub transform: Affine, + pub clip: Rect, +} + +impl PaintCtx { + pub fn paint_child(&mut self, child: &mut dyn Widget, scene: &mut Scene) { + // In a real system, we would adjust transform/clip here based on layout + child.paint(self, scene); + } + + pub fn is_visible(&self, _rect: &Rect) -> bool { + true + } +} + +pub enum Event { + // Stub +} + +pub struct BoxConstraints { + pub min: Size, + pub max: Size, +} + +pub trait Widget { + fn on_event(&mut self, _ctx: &mut EventCtx, _event: &Event) {} + fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size { + Size::ZERO + } + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene); + fn children(&self) -> SmallVec<[EntityId; 4]> { + SmallVec::new() + } +} + +pub trait AppExtension { + fn mount_ui(&mut self, builder: F) + where + F: FnMut() -> E + 'static, + E: IntoElement; +} + +impl AppExtension for felt_platform::App { + fn mount_ui(&mut self, mut builder: F) + where + F: FnMut() -> E + 'static, + E: IntoElement, + { + self.mount(move |scene, width, height| { + let mut root_widget = builder().into_element().build(); + + let mut ctx = PaintCtx { + transform: Affine::IDENTITY, + clip: Rect::new(0.0, 0.0, width as f64, height as f64), + }; + + root_widget.paint(&mut ctx, scene); + }); + } } diff --git a/crates/felt-ui/src/widget/canvas.rs b/crates/felt-ui/src/widget/canvas.rs new file mode 100644 index 0000000..cfeb17d --- /dev/null +++ b/crates/felt-ui/src/widget/canvas.rs @@ -0,0 +1,147 @@ +use crate::draw::{Affine, BlendMode, Brush, FillRule, Point, Rect, Size, StrokeStyle}; +use crate::{PaintCtx, Widget}; +use smallvec::SmallVec; +use vello::Scene; +use vello::kurbo::Shape; +use vello::peniko::Mix; + +pub struct DrawContext<'a> { + ctx: &'a mut PaintCtx, + scene: &'a mut Scene, + pub size: Size, +} + +impl<'a> DrawContext<'a> { + pub fn new(ctx: &'a mut PaintCtx, scene: &'a mut Scene, size: Size) -> Self { + Self { ctx, scene, size } + } + + pub fn fill(&mut self, fill_rule: FillRule, brush: &Brush, shape: &impl Shape) { + let vello_brush = brush.to_vello(); + self.scene.fill( + fill_rule.to_vello(), + self.ctx.transform, + &vello_brush, + None, + shape, + ); + } + + pub fn stroke(&mut self, style: &StrokeStyle, brush: &Brush, shape: &impl Shape) { + let vello_brush = brush.to_vello(); + let vello_stroke = style.to_vello(); + self.scene + .stroke(&vello_stroke, self.ctx.transform, &vello_brush, None, shape); + } + + pub fn push_layer( + &mut self, + blend: BlendMode, + alpha: f32, + transform: Affine, + shape: &impl Shape, + ) { + // Combine the context transform with the pushed transform + let combined_transform = self.ctx.transform * transform; + self.scene + .push_layer(blend.to_vello(), alpha, combined_transform, shape); + } + + pub fn pop_layer(&mut self) { + self.scene.pop_layer(); + } + + pub fn draw_image(&mut self, image: &crate::draw::Image, transform: Affine) { + let combined_transform = self.ctx.transform * transform; + self.scene.draw_image(image.to_vello(), combined_transform); + } + + pub fn push_clip_layer(&mut self, shape: &impl Shape) { + // For clip layers, we need to transform the shape to global coordinates + let global_clip = self.ctx.transform.transform_rect_bbox(shape.bounding_box()); + self.scene.push_layer( + vello::peniko::Mix::Clip, + 1.0, + vello::kurbo::Affine::IDENTITY, + &global_clip, + ); + } + + pub fn append(&mut self, canvas: &Canvas, transform: Affine) { + let combined_transform = self.ctx.transform * transform; + self.scene.append(&canvas.scene, Some(combined_transform)); + } +} + +pub struct Canvas { + pub size: Size, + pub painter: Box, + scene: Scene, // Internal scene for recording +} + +impl Canvas { + pub fn new(size: Size, painter: impl FnMut(&mut DrawContext) + 'static) -> Self { + Self { + size, + painter: Box::new(painter), + scene: Scene::new(), + } + } + + pub fn get_scene(&self) -> &Scene { + &self.scene + } +} + +impl Widget for Canvas { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + let rect = Rect::from_origin_size(Point::ORIGIN, self.size); + + // Clip to the canvas size + // We must transform the local rect to global coordinates for the clip to work correctly + let global_clip = ctx.transform.transform_rect_bbox(rect); + scene.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, &global_clip); + + let mut draw_ctx = DrawContext::new(ctx, scene, self.size); + (self.painter)(&mut draw_ctx); + + scene.pop_layer(); + } + + fn children(&self) -> SmallVec<[crate::EntityId; 4]> { + SmallVec::new() + } +} + +// Declarative API +use crate::element::Element; + +pub struct CanvasElement { + size: Size, + painter: Option>, +} + +impl CanvasElement { + pub fn new(painter: impl FnMut(&mut DrawContext) + 'static) -> Self { + Self { + size: Size::ZERO, + painter: Some(Box::new(painter)), + } + } + + pub fn size(mut self, size: Size) -> Self { + self.size = size; + self + } +} + +impl Element for CanvasElement { + fn build(mut self: Box) -> Box { + let painter = self.painter.take().unwrap(); + Box::new(Canvas::new(self.size, painter)) + } +} + +pub fn canvas(painter: impl FnMut(&mut DrawContext) + 'static) -> CanvasElement { + CanvasElement::new(painter) +} diff --git a/crates/felt-ui/src/widget/container.rs b/crates/felt-ui/src/widget/container.rs new file mode 100644 index 0000000..f56a7fb --- /dev/null +++ b/crates/felt-ui/src/widget/container.rs @@ -0,0 +1,85 @@ +use crate::draw::Color; +use crate::{EntityId, PaintCtx, Widget}; +use smallvec::SmallVec; +use vello::Scene; +use vello::kurbo::{Affine, Rect, Size, Stroke, Vec2}; +use vello::peniko::{Brush, Fill}; + +pub struct Container { + pub child: Option>, + pub background: Option, + pub border: Option<(Color, f64)>, // Color, width + pub offset: Vec2, + pub size: Option, +} + +impl Container { + pub fn new() -> Self { + Self { + child: None, + background: None, + border: None, + offset: Vec2::ZERO, + size: None, + } + } +} + +impl Widget for Container { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + // Apply offset to transform + let transform = ctx.transform.then_translate(self.offset); + + let mut my_ctx = PaintCtx { + transform, + clip: ctx.clip, + }; + + // Draw background if size is known + if let Some(size) = self.size { + let rect = Rect::from_origin_size(vello::kurbo::Point::ORIGIN, size); + + if let Some(color) = self.background { + // Convert Felt UI Color to Vello Color + let vello_color = vello::peniko::Color::rgba8(color.r, color.g, color.b, color.a); + scene.fill( + Fill::NonZero, + transform, + &Brush::Solid(vello_color), + None, + &rect, + ); + } + + if let Some((color, width)) = self.border { + // Convert Felt UI Color to Vello Color + let vello_color = vello::peniko::Color::rgba8(color.r, color.g, color.b, color.a); + scene.stroke( + &Stroke::new(width), + transform, + &Brush::Solid(vello_color), + None, + &rect, + ); + } + } else if let Some(color) = self.background { + // If no size but background, fill the whole clip (Window background case) + let vello_color = vello::peniko::Color::rgba8(color.r, color.g, color.b, color.a); + scene.fill( + Fill::NonZero, + Affine::IDENTITY, + &Brush::Solid(vello_color), + None, + &ctx.clip, + ); + } + + if let Some(child) = &mut self.child { + child.paint(&mut my_ctx, scene); + } + } + + fn children(&self) -> SmallVec<[EntityId; 4]> { + SmallVec::new() + } +} diff --git a/crates/felt-ui/src/widget/mod.rs b/crates/felt-ui/src/widget/mod.rs new file mode 100644 index 0000000..00994e7 --- /dev/null +++ b/crates/felt-ui/src/widget/mod.rs @@ -0,0 +1,5 @@ +pub mod canvas; +pub mod container; +pub mod scroll; +pub use canvas::Canvas; +pub use scroll::ScrollView; diff --git a/crates/felt-ui/src/widget/scroll.rs b/crates/felt-ui/src/widget/scroll.rs new file mode 100644 index 0000000..d6262f7 --- /dev/null +++ b/crates/felt-ui/src/widget/scroll.rs @@ -0,0 +1,111 @@ +use crate::{EntityId, PaintCtx, Widget}; +use smallvec::SmallVec; +use vello::Scene; +use vello::kurbo::{Affine, Point, Rect, Vec2}; +use vello::peniko::Mix; + +pub struct ScrollView { + pub offset: Vec2, + pub size: Vec2, + pub child: Option>, +} + +impl ScrollView { + pub fn new(size: Vec2) -> Self { + Self { + offset: Vec2::ZERO, + size, + child: None, + } + } + + pub fn with_child(mut self, child: impl Widget + 'static) -> Self { + self.child = Some(Box::new(child)); + self + } +} + +impl Widget for ScrollView { + fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut Scene) { + let viewport = Rect::from_origin_size(Point::ORIGIN, (self.size.x, self.size.y)); + + // 1. Calculate Global Clip Rect + // We transform the viewport rect by the current context transform to get the clip in scene coordinates. + // This avoids relying on push_layer's transform behavior for clipping, which might be subtle. + // Note: This assumes ctx.transform is only translation/scale, which it is. + // For rotation, we'd need a Shape transform, but Rect transform is fine here. + let global_clip = ctx.transform.transform_rect_bbox(viewport); + + // 2. Push Clip Layer + // We use Identity transform for the layer, but provide the transformed clip rect. + scene.push_layer(Mix::Normal, 1.0, Affine::IDENTITY, &global_clip); + + // 3. Paint Child with Manual Transform + // We pass the full transform (Parent * ScrollOffset) to the child. + if let Some(child) = &mut self.child { + let mut child_ctx = PaintCtx { + transform: ctx + .transform + .then_translate(Vec2::new(-self.offset.x, -self.offset.y)), + clip: global_clip, + }; + child.paint(&mut child_ctx, scene); + } + + // 4. Pop Clip Layer + scene.pop_layer(); + } + + fn children(&self) -> SmallVec<[EntityId; 4]> { + SmallVec::new() // Stub for PoC + } +} + +// Declarative API +use crate::element::{Element, IntoElement}; + +pub struct ScrollViewElement { + size: Vec2, + offset: Vec2, + child: Option>, +} + +impl ScrollViewElement { + pub fn new() -> Self { + Self { + size: Vec2::ZERO, + offset: Vec2::ZERO, + child: None, + } + } + + pub fn size(mut self, size: Vec2) -> Self { + self.size = size; + self + } + + pub fn offset(mut self, offset: Vec2) -> Self { + self.offset = offset; + self + } + + pub fn child(mut self, child: impl IntoElement) -> Self { + self.child = Some(child.into_element().build()); + self + } +} + +impl Element for ScrollViewElement { + fn build(self: Box) -> Box { + let mut sv = ScrollView::new(self.size); + sv.offset = self.offset; + if let Some(child) = self.child { + sv.child = Some(child); + } + Box::new(sv) + } +} + +pub fn scroll_view() -> ScrollViewElement { + ScrollViewElement::new() +} diff --git a/demo/Cargo.toml b/demo/Cargo.toml new file mode 100644 index 0000000..95a9877 --- /dev/null +++ b/demo/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "demo" +version = "0.1.0" +edition = "2021" + +[dependencies] +felt-platform = { path = "../crates/felt-platform" } +felt-ui = { path = "../crates/felt-ui" } +vello = "0.3.0" +winit = "0.29" +log = "0.4" +env_logger = "0.10" diff --git a/demo/src/main.rs b/demo/src/main.rs new file mode 100644 index 0000000..048e994 --- /dev/null +++ b/demo/src/main.rs @@ -0,0 +1,92 @@ +use felt_platform::App; +use felt_ui::{canvas, div, scroll_view, AppExtension}; +use felt_ui::{Brush, Circle, Color, FillRule, Point, Rect, Size, Vec2}; +use std::time::Instant; + +fn main() { + env_logger::init(); + + let mut app = App::new(); + + let start_time = Instant::now(); + + println!("Running PoC V4 - 3 Distinct Layers"); + + app.mount_ui(move || { + // For every frame, transfirm scroll offset automatically + // In the future we will use gesture events to control this + let t = start_time.elapsed().as_secs_f64(); + let scroll_offset_y = (t * 1.0).sin() * 400.0 + 400.0; + + // Rebuild the widget tree every frame (Declarative Style!) + // Now the entire scene is described as a widget tree. + div() + .bg(Color::rgb(10, 10, 10)) // Window Background + .child( + div() // Container + .offset(Vec2::new(100.0, 100.0)) + .size(Size::new(600.0, 400.0)) + .bg(Color::rgb(40, 40, 40)) // Container Background + .border(Color::rgb(150, 150, 150), 4.0) // Container Border + .child( + scroll_view() + .size(Vec2::new(600.0, 400.0)) + .offset(Vec2::new(0.0, scroll_offset_y)) + .child( + div() // LAYER 2: SCROLL PANEL (The moving surface) + .size(Size::new(500.0, 1200.0)) + .offset(Vec2::new(50.0, 0.0)) // Centered in 600px width + .bg(Color::rgb(80, 80, 80)) // Medium Gray Panel + .child( + div() // Wrapper for positioning the canvas + .offset(Vec2::new(50.0, 50.0)) + .bg(Color::rgb(60, 60, 100)) // Blue-ish Canvas Background + .child( + canvas(move |cx| { + // Draw diagonal stripes + for i in 0..22 { + let y = i as f64 * 50.0; + cx.fill( + FillRule::NonZero, + &Brush::Solid(Color::rgb(70, 70, 110)), + &Rect::new(0.0, y, 400.0, y + 25.0), + ); + } + + // Header + cx.fill( + FillRule::NonZero, + &Brush::Solid(Color::rgb(200, 50, 50)), + &Rect::new(0.0, 0.0, 400.0, 50.0), + ); + + // Footer + cx.fill( + FillRule::NonZero, + &Brush::Solid(Color::rgb(50, 200, 50)), + &Rect::new(0.0, 1050.0, 400.0, 1100.0), + ); + + // Circles + for i in 0..50 { + let y = 50.0 + i as f64 * 40.0; + let x = 200.0 + (t * 2.0 + i as f64 * 0.2).sin() * 250.0; + cx.fill( + FillRule::NonZero, + &Brush::Solid(Color::rgb(200, 200, 255)), + &Circle::new(Point::new(x, y), 15.0), + ); + } + }) + .size(Size::new(400.0, 1100.0)) + ) + ) + // We need to wrap the canvas in a div to offset it to (50, 50) + // But `div().child(canvas)` works. + ) + ) + ) + }); + + app.run(); +}