Conversation
Animations now run inside `StyleTree::compute_style` via a new `StyleSink::apply_animations` hook. The tree applies them on top of the resolved `combined_style` — after the cache write (so the cache holds a pre-animation baseline) and before the inherited-context derivation (so animated inherited props propagate to descendants on the same pass). The cached `computed_style` the tree stores is now post-animation, which matches what Phase 7 / Phase 8 / `style_pass` callbacks expect. This also lets `style_view` drop its explicit `apply_animations` call and the companion `schedule_style` for `has_active_animation`; both now happen inside `compute_one` via the sink. Also fix a subtle `RefCell` issue in `run_style_cascade`: the `root_view.state().borrow().style_node` read was holding the borrow across `tree.compute_style`, which now tries to `borrow_mut` the root ViewState through `apply_animations`. Scope the borrow. Fixes the caveat from 1aba279 where animated inherited props (e.g. animated font-size on a parent) did not propagate to children. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tree already derives the final `computed_style` per node, so it can decide whether the node is position:fixed and call `sink.register_fixed_element` / `unregister_fixed_element` directly. This removes the last piece of post-cascade work from `style_view`'s Phase 5 that required reading `computed_style` back from storage for a simple is_fixed check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion field Two related slims after fixed-element registration moved into the tree: 1. The old-vs-new interaction diff (hidden flip → dirty children + layout; selected/disabled flip → dirty descendants via their selectors) now runs inside `compute_one` using the node's stored previous `style_interaction_cx` and the accumulated `dirty.selectors`. `style_view` stops reading `old_interact_state`, writing `style_interaction_cx` back, and emitting the diff-driven side-effects. 2. `post_compute_combined_interaction` is dropped. Since the per-pass diff now runs in the tree, `style_interaction_cx` no longer holds the "previous pass" value for style_view — both fields always held the same full-cascade value, so one of them is enough. Pass 3 of `run_style_cascade` populates `style_interaction_cx` from the tree, and `style_view` / public queries read from there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tree already computes which selectors each node's style tree uses, so it's the natural place to keep the host's window-level selector-interest registry in sync. Removes one read of `storage.has_style_selectors` from `style_view`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StyleTree::compute_style` already emits `sink.inspector_capture_style` with the same post-animation computed_style, so the call in `style_view` was a duplicate. Removing it also lets the tiny `CaptureState::capture_style` helper go — `record_computed_style` is the only method on the type now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase 7 cursor block was doing the same work `run_style_cascade` could do when it copies `computed_style` from the tree to storage: compare old vs new cursor, update `style_cursor`, flip `needs_cursor_resolution`. Pulling it into Pass 3 keeps `style_view` focused on host-side layout/taffy/box-tree work and avoids a redundant `computed_style.builtin().cursor()` read. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`StyleCx::new` runs after `run_style_cascade` Pass 3 has already mirrored the tree's `combined_style`, inherited context, and class context into `storage`, so it can read from *this* view's storage directly rather than from the parent's. That collapses Phase 5's three-field plumbing block into the constructor and drops the last bit of non-trivial work Phase 5 was doing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
floem's style model follows CSS where layout *is* style — display, flex-*, grid-*, width, padding resolve through the cascade and feed a layout solver. The engine already embedded taffy types deeply (Style::to_taffy_style, builtin_props re-exports, cascade matches against taffy::Display), but without acknowledging the contract. This commit makes it explicit: - Crate-level docs state that floem-style owns the style→layout-input bridge for taffy; reactive runtime and view tree remain out of scope. - `pub use taffy;` at the crate root lets consumers reach taffy types through `floem_style::taffy::...` without a separate dependency. - `tree.rs` and `visibility.rs` use the local `Display` re-export instead of `taffy::style::Display` in comparison sites; taffy is still taffy, but the cascade code reads as floem-style's own. - Dropped the stale "Phase 1a/1b" extraction-history comment. Behavior unchanged; all 1210 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The reactive-scope machinery existed so `ContextValue` closures could re-enter the reactive effect that produced a Style — if such a closure happened to read external signals during cascade, its reads would register as dependencies of the original effect. An audit of every `.def(...)` and `.defer(...)` call in src/, tests/, and floem-style/ itself shows no closure actually does this. Every use is theming (`t.def(|t| t.primary())`) or font-size math (`fs.def(|fs| fs * 0.8)`) — pure functions of the prop value they receive. The test `test_signal_outside_with_context_is_tracked` explicitly asserts the signal read happens in the *enclosing* style closure, not inside `.def()`. So the reactive hook was insurance against a use case nothing exercises. Removed: - `Style::effect_context` field. - `Runtime::get_current_effect()` call in `Style::with_capacity`. - `Runtime::with_effect(...)` wrap in `Style::resolve_context`. - effect_context propagation arg threaded through `apply_iter` / `apply_mut`. - `floem_reactive` from `floem-style/Cargo.toml`. Behavior unchanged — all 1210 tests pass. If a future host genuinely needs signal reads inside cascade-time closures, they can wrap the closure on their side (capture-and-reenter via their own reactive runtime); floem-style no longer prescribes one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
floem_renderer was pulled in for three type names:
- `FontWeight` / `FontStyle` — re-exports of `fontique`, also re-exported by
`parley` which floem-style already depends on.
- `LineHeightValue` — actually defined in floem_renderer, but semantically a
CSS-style value (multiplier vs absolute points), not a renderer concept.
Both legs resolve:
- Font types: switch the four imports in floem-style to `parley::{FontStyle,
FontWeight}`. No new dep; fewer indirections.
- `LineHeightValue`: move the enum + resolver + `From` impls from
`renderer/src/text/attrs.rs` into `floem-style/src/unit.rs` and re-export
at the crate root. `floem_renderer` now imports it from floem_style and
re-exports it under `floem_renderer::text::LineHeightValue` for backward
compat with downstream users.
This flips the layering edge: `floem_renderer` now depends on
`floem_style` (style primitives flow up into the renderer), not the
other way around. Which matches the conceptual model — style resolves
first, renderer consumes resolved values.
floem-style's runtime deps are now `taffy`, `parley`, `peniko`, and
`understory_box_tree`. No floem-specific crates other than
`understory_box_tree` (which itself is generic).
All 1210 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expand the doc on `StyleTree::compute_style` to enumerate the side-effects it emits through the sink (fixed-element registry, selector-interest, dirty propagation for inherited/class/visibility flips, layout, animations, inspector capture), and add an `ignore`'d code example showing a non-floem host driving the cascade end-to-end (new_node → set_parent → set_direct_style → compute_style → computed_style). Point at `tests/mock_sink.rs` and `tests/style_tree_cascade.rs` for complete executable examples. Also drop two redundant explicit link targets introduced by the previous crate-level doc rewrite (`[Display](taffy::style::Display)` → `[Display]`, etc.) so rustdoc resolves through the `pub use taffy;` re-export. Closes the last item of the engine-audit priority list. All 1210 tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `prop_extractor!` macro's expansion referenced floem's `StyleCx`
in four places (now, direct_style, current_view().get_element_id(),
request_transition_for). That coupling was the sole reason the macro
lived in floem rather than floem-style — so every `prop_extractor!`
call site transitively required floem's view context type to be in
scope.
Introduce `floem_style::PropExtractorCx` as the narrow contract the
macro actually needs:
fn now(&self) -> Instant;
fn direct_style(&self) -> &Style;
fn current_element(&self) -> ElementId;
fn request_transition_for(&mut self, target: ElementId);
Move the macro into `floem-style/src/style_macros.rs`, expanding
against `dyn PropExtractorCx` instead of a concrete type. Implement
the trait on floem's `StyleCx` (4 trivial lines). Re-export
`prop_extractor` from floem so `use crate::{prop, prop_extractor, …}`
keeps working.
No behavior change — all 1210 tests pass. Second hosts now get the
full `prop_extractor!` macro by implementing a 4-method trait, no
floem import needed. Unblocks moving engine extractors
(LayoutProps, ViewStyleProps, TransformProps) into floem-style next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With the `prop_extractor!` macro relocated (previous commit) and the
`PropExtractorCx` trait decoupling it from floem's `StyleCx`, the
three engine-facing extractors no longer have any reason to live in
floem. They read properties that floem-style defines (sizes,
padding, flex metrics, overflow, border radii, outlines, shadows —
all built-in props), and their impls (`apply_to_taffy_style`,
`affine`, `clip_rect`, `border_radius`, `border`, `font_size_cx`,
`border_color`) reach for peniko/taffy/FontSizeCx types that
floem-style already owns.
New file `floem-style/src/extractors.rs` holds all three structs and
their inherent impls; floem re-exports them at their previous paths
(`floem::style::{LayoutProps, TransformProps}`,
`floem::view::state::ViewStyleProps`) so no caller changes are
needed. `ViewStyleProps`'s visibility loosens from `pub(crate)` to
`pub` (required to live in another crate) but it only surfaces
through the same re-export paths.
Net effect: floem-style owns the full style→prop-extraction side of
the cascade. A second host picks up layout / transform / view-style
extraction for free — the tree's computed_style → prop-extraction
path no longer imports anything from floem.
All 1210 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Integration test that exercises the style engine the way a second host (floem-native, a headless renderer, whatever) would consume it. Uses only `floem_style::*` plus generic support crates (peniko, taffy, understory_box_tree) — no floem imports anywhere. If this compiles and passes, a downstream host can drive the engine through the documented public surface. Seven scenarios: - `tree_cascade_produces_computed_style_and_inheritance` — build a tree, push direct styles, run `compute_style`, verify inheritance works through the engine edges. - `layout_extractor_fills_taffy_style` — drive `LayoutProps` via `read_explicit`, confirm `apply_to_taffy_style` populates the taffy style the host would hand to a taffy layout solver. - `transform_extractor_through_prop_extractor_cx` — exercise the `PropExtractorCx` trait path by calling `TransformProps::read_style` with the mock host as `&mut dyn PropExtractorCx`. Verifies `affine` and `border_radius` helpers produce sensible outputs. - `view_style_extractor_reads_visual_props` — `ViewStyleProps` against a visual style (background, outline), checks the background aggregator. - `class_context_propagates_through_tree` — parent declares a class, child applies it, cascade picks it up. - `hover_selector_switches_between_passes` — flip `is_hovered` on the sink, re-cascade, verify `:hover` branch activates. - `sink_apply_animations_hook_is_invoked` — override the default `StyleSink::apply_animations` impl, confirm the tree invokes it. The mock host is a plain struct implementing both `StyleSink` (engine callbacks) and `PropExtractorCx` (extractor context). No view tree, no reactive runtime, no window state. It's the blueprint for what a second host's style-engine glue layer looks like. All 1217 tests pass (1210 existing + 7 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last of the engine-shaped extractors living on the floem side. `FontProps` bundles four built-in props (FontSize, FontFamily, FontWeight, FontStyle), all of which already live in floem-style; nothing in its declaration references a floem-side type. Sits naturally alongside the other three extractors in `floem-style/src/extractors.rs`. Floem re-exports it at its previous path (`floem::style::FontProps`), so `views/label.rs` and `views/text_input.rs` keep compiling unchanged. All 1217 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both traits had a single impl and served no real extensibility. Collapsing them removes two public traits from floem-style's API surface without affecting behavior: - StyleSelectorKey had one impl for StyleSelector, an enum closed inside the crate. The doc claim of host-extensibility was never realizable. Replaced with an inherent `to_key` method on StyleSelector. - StylePropReader had a single blanket impl over StyleProp, so every ExtractorField was parameterised over P::Type anyway. Inlined the read/get/new logic directly into ExtractorField<P: StyleProp> and switched prop_extractor! to take `$prop:ty` so the getter resolves via `<$prop as StyleProp>::Type`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The style cache is pure cascade bookkeeping — validating parent-inherited state on lookup and storing combined_style / selectors / post-interact flags. Nothing about it is host-specific. Routing access through `StyleSink::style_cache_mut()` was a round-trip through the host for data the engine already owned. Moves the cache field into `StyleTree` and exposes two narrow hooks for hosts that need cache-level side-effects: - `StyleTree::clear_cache()` — floem calls this on theme flip and responsive-breakpoint changes. - `StyleTree::cache_stats()` — read-only for tests/debug. Drops `style_cache_mut` from `StyleSink`, shrinking the trait by one method and eliminating a cache-reachability test that only existed to prove the sink pathway worked. First of several internalizations that aim to keep the engine owning its own state, so `StyleSink` is left covering only genuine host facts and policy hooks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Before: floem's `WindowState` owned three `FxHashMap<ViewId, ()>` registries (responsive / disabled / selected) plus ancestry-filtered descendant-dirty walks; `StyleSink` carried the trait plumbing so the cascade could keep them in sync. After: `StyleTree` owns the interest registries as `FxHashSet<StyleNodeId>` fields. The cascade updates them inline via a private `update_selector_interest`. `mark_descendants_with_selector_dirty` and its responsive counterpart live on `StyleTree`, walk the engine's own parent-edge graph, and return `(ElementId, StyleReason)` pairs for the host to funnel into its per-frame scheduling. Changes: - `update_selector_interest`, `mark_descendants_with_selector_dirty`, and `mark_descendants_with_responsive_selector_dirty` deleted from `StyleSink`. - `WindowState` drops its three interest maps and the inherent walk bodies; the two remaining inherent entry points (called from `src/style/cx.rs` during dirty-reason propagation) delegate to the tree. - `StyleCx::window_state` changes from `&mut dyn StyleSink` to `&mut WindowState`. `StyleCx` is floem-specific and already needed the concrete type for every other cx in the crate; the trait object was vestigial. - `request_paint` also leaves `StyleSink` — never called by the engine, and keeping both inherent (`impl Into<ElementId>`) and trait (`ElementId`) overloads on the concrete type triggered `.into()` inference ambiguities at every view-crate caller. - Call-site churn: drop `self.id.into()` → `self.id` at the ~8 view impls that were paying the now-redundant conversion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two sink methods were pure pass-through: engine computed "animation still in flight" or "transition still interpolating," called `schedule_style` / `schedule_style_with_target` on the sink, and the host routed them into its frame queue. The engine already owned the state that triggered the schedule. `StyleTree` now keeps a `scheduled: FxHashMap<ElementId, StyleReason>` populated by: - the cascade itself when `apply_animations` reports in-flight animation, - floem's `PropExtractorCx::request_transition_for` impl, which writes through `self.window_state.style_tree.schedule(...)` instead of the removed sink method. After each `compute_style`, `WindowState` drains `tree.take_scheduled()` once and funnels entries into its per-frame update queue. No behavioral change — just a shorter round-trip for data the engine already owned. `schedule_style` and `schedule_style_with_target` deleted from `StyleSink`. `mark_style_dirty_with` stays — it's tangled with floem's downstream taffy/animation triggers and merits its own focused move. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage A of the animation relocation: the declarative data model and
tick machinery land in `floem_style::animation`. Floem's existing
`Animation` shrinks to a thin reactive wrapper.
In floem-style:
- `KeyFrame`, `KeyFrameStyle`, `KeyFrameProp`, `PropCache`
- `ReverseOnce`, `RepeatMode`, `AnimState{Kind,Command}`
- `Animation` struct with the state machine, keyframe storage, folded
style, ext-mode props, cache, and debug description.
- All ~30 builder methods (`duration`, `delay`, `keyframe`,
`keyframe_override`, `auto_reverse`, `reverse_on_exit`, `repeat`,
`repeat_times`, `max_key_frame`, `run_on_create`, `run_on_remove`,
`apply_when_finished`, `initial_state`, `debug_name`,
`view_transition*`, `scale*_effect`, etc.).
- Engine methods: `advance`, `transition`, `animate_into`,
`apply_folded`, `total_time_percent`, `get_local_percent`,
`get_current_kf_props`, `state_kind`, `elapsed`, `is_idle`,
`is_in_progress`, `is_completed`, `is_stopped`, `is_reversing`,
`is_auto_reverse`, `can_advance`, `should_apply_folded`,
`runs_on_create`, `runs_on_remove`, `get_duration`,
`get_repeat_mode`, `debug_description`, `touched_props`.
- `AnimationEvents { started, visual_completed, completed }` is the
return type of `advance`. Engine stays pure data — no Trigger/
signal dependency.
In floem, `Animation` becomes `{ engine: floem_style::Animation,
effect_states, on_start, on_visual_complete, on_complete }`:
- Config builders forward to the engine.
- `.state()/.start()/.pause()/.resume()/.reverse()/.stop()`,
`.on_create()/.on_complete()/.on_visual_complete()` stay here —
they need `RwSignal`, `UpdaterEffect`, and `ViewId::update_animation_state`.
- `advance()` calls `engine.advance()` and maps the returned
`AnimationEvents` to `Trigger.notify()` calls.
Floem types re-export from floem-style (`KeyFrame`, `AnimStateCommand`,
`RepeatMode`, `Easing` variants, etc.) so downstream callers keep
compiling unchanged.
Floem-native or other non-reactive hosts can now take a dependency on
floem-style alone and drive animations through `AnimationEvents` +
their platform-native notification mechanism, without pulling in
floem-reactive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each `StyleNode` now carries a `SmallVec<[Animation; 1]>` registry plus a per-node event buffer. The cascade ticks these animations inline in `compute_one`, folds their interpolated values into `combined_style`, queues lifecycle events (`started`, `visual_completed`, `completed`) for the host to drain, and schedules another cascade pass while anything is active. This runs alongside the existing `StyleSink::apply_animations` hook so both paths coexist: - Hosts with their own per-element animation storage (floem today) keep overriding `apply_animations`; the tree-side registry stays empty and contributes nothing. - Standalone consumers (floem-style tests, future floem-native, any host that doesn't want to reimplement a Stack<Animation>) push engine animations directly via `StyleTree::push_animation`. Public API added to `StyleTree`: - `push_animation(node, anim) -> usize` — append, return slot index. - `set_animation(node, slot, anim)` — replace at slot. - `update_animation_with(node, slot, f)` — mutate in place. - `animations(node)` / `animations_mut(node)` — slice accessors. - `take_animation_events(node)` — drain `(slot, AnimationEvents)` pairs from the latest cascade. A new `tree_stored_animations_tick_during_cascade` integration test in `host_integration.rs` drives a two-keyframe animation end-to-end through `compute_style` using only floem-style — no reactive runtime, no per-view stack — and verifies that `started` fires, events route to the correct slot, and the node lands on the tree's schedule while interpolation is in progress. This is Stage B-data from the animation extraction plan. Stage B-tick (baking the ticker into the cascade unconditionally) stayed off the table because it would block the native-offload story: a future floem-native host needs to *replace* the tick with a CoreAnimation / ViewPropertyAnimator delegation, not run alongside one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`set_cursor`, `clear_cursor`, `mark_needs_cursor_resolution`, and `invalidate_focus_nav_cache` were on the trait but the engine never called any of them. They're purely host policy: cursor overrides and focus-nav caches are data only the host consults during input dispatch and paint. Keeping them on the trait was vestigial — a leftover from an earlier "make everything host-facing a trait method just in case" pass. After this change, every `StyleSink` method corresponds to an actual `sink.*` call in the cascade. Module docs rewritten to describe what the trait is for now (reads + engine-detected writes) rather than the aspirational "second host plumbing" framing that no longer fits. Mocks get `set_cursor` / `clear_cursor` / `mark_needs_cursor_resolution` as inherent methods for the tests that exercised them directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`register_fixed_element` / `unregister_fixed_element` were two more engine-detects-a-fact-host-bookkeeps-it round trips. The cascade computes `builtin().is_fixed()` per node and the host maintained a `FxHashSet<ViewId>` it read back for layout positioning and root-size relayout broadcasts. Nothing external wrote to that set. `StyleTree` now owns the set authoritatively as `FxHashSet<ElementId>`. The cascade calls internal `self.register_fixed_element` / `self.unregister_fixed_element` each node, which dedupe (only the actual set-membership transitions get recorded) and push onto per-pass `added` / `removed` small-vecs. After `compute_style` returns, `WindowState::drain_fixed_element_changes` reads those transitions and fires floem's post-side-effects — marks layout dirty on additions, resets box-tree world positions and marks box-tree commit + layout dirty on removals. Both sink methods are gone. Floem's inherent `register_fixed_element` / `unregister_fixed_element` are gone too — they existed only as the sink destinations, and the drain now does their work post-cascade. Read-side call sites (`set_root_size`, `apply_fixed_element_styles`, `apply_fixed_positioning_transforms`) query `tree.fixed_elements()` and map `ElementId::owning_id()` where a `ViewId` is needed. Membership cleans up automatically in `StyleTree::remove_node` — when a view is destroyed, its companion style node's removal records a `fixed_elements_removed` entry, so `drain_fixed_element_changes` on the next cascade also hits floem's box-tree-position-reset path for removed views. Matches the old `self.fixed_elements.remove(&id)` in `remove_view` without the extra line. `StyleSink` is now: 9 reads, `mark_style_dirty_with`, `mark_needs_layout`, `inspector_capture_style`, and `apply_animations`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three sink methods collapse into tree-owned state the host drains after `compute_style`: - **`mark_needs_layout`** → `tree.needs_layout: bool`. The cascade flips it on visibility transitions and on fixed-element membership changes; `tree.take_needs_layout()` drains it post-cascade. Floem plumbs into its existing `needs_layout` field. One shared sentinel now covers every engine-detected reason layout should re-run. - **`mark_style_dirty_with`** → `tree.dirtied_this_pass: FxHashMap<ElementId, StyleReason>`. Cascade writes to it wherever it used to call `sink.mark_style_dirty_with` — inherited/class- context descendants, visibility-flipped descendants, and selector- interest walk results. Host drains via `tree.take_dirtied_this_pass()` and funnels each entry into its own `style_dirty` map so the next frame's traversal still picks them up. - **`inspector_capture_style`** → gone from the trait. Floem's inspector capture inlines into Pass 3 of `run_style_cascade`, which already iterates every dirty view and reads `tree.computed_style`. No extra work, no extra buffer. Two cascade tests in `style_tree_cascade.rs` that observed `inspector_capture_style` calls to verify traversal order / incremental behavior rewrite to check `tree.is_dirty` + `tree.computed_style` + `tree.take_scheduled` directly. The `inspector_capture_routes_through_default_impl` test in `mock_sink.rs` is deleted — with no default impl left to route through, it had nothing to test. `StyleSink` is now exactly nine read methods plus `apply_animations`: - frame_start, screen_size_bp, keyboard_navigation, root_size_width, is_dark_mode, default_theme_classes, default_theme_inherited, is_hovered, is_focused, is_focus_within, is_active, is_file_hover - apply_animations (policy hook for native-offload backends) Every remaining method corresponds to a read the cascade actually performs or the one mid-cascade callback the native-offload story requires keeping. The sink is at its minimum viable shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`ElementId` leaves `StyleTree`. Every engine-level API — sink reads, scheduling, fixed-element registry, dirtied-this-pass buffer, animation hooks — keys on [`StyleNodeId`], the slotmap handle the tree already owns. This matches taffy's model: the engine's node identity is the engine's own, opaque to the host, and hosts keep a sidecar mapping to relate it back to their view type. Concretely: - `StyleNode` no longer carries `element_id`. Host identity is a host concern. - `StyleTree::new_node()` takes no arguments. - `tree.fixed_elements() -> &FxHashSet<StyleNodeId>`, `tree.take_scheduled()`, `tree.take_dirtied_this_pass()`, and `tree.take_fixed_element_changes()` yield `StyleNodeId`s. - `StyleSink` reads (`is_hovered`, `is_focused`, `is_focus_within`, `is_active`, `is_file_hover`) and `apply_animations` all take `StyleNodeId`. Floem side: `WindowState.style_node_to_view: FxHashMap<StyleNodeId, ViewId>` is the reverse of `ViewState.style_node`, populated on `ensure_style_node` and cleaned up on `remove_view`. The `StyleSink` impl translates `StyleNodeId → ViewId → ElementId` at every read, and every post-cascade drain (schedule, dirtied, fixed-element transitions, descendant-dirty walks) translates before feeding floem's view-keyed maps. `StyleCx::get_interact_state` takes `&WindowState` concretely instead of `&dyn StyleSink` — it was only called by floem code and needs direct field access now. `StyleCx::request_transition_for` translates its target `ElementId` to the owning view's `StyleNodeId` before calling `tree.schedule`. `ElementId` lives on in the workspace — floem uses it internally for box-tree lookups and `PropExtractorCx::current_element` (transition targeting that hosts and extractors both care about). It just no longer leaks into `StyleTree` as the engine's primary identity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port 2 of the cascade-boundary refactor: delete the `StyleSink` trait.
[`StyleTree::compute_style`] now takes `&CascadeInputs`, matching
taffy's shape — reads flow in through plain values plus one closure
for per-node interactions, and the only cascade-time callback is
[`AnimationBackend`], the policy hook a future native host overrides
to delegate animation ticking to its compositor.
`CascadeInputs`:
- frame_start, screen_size_bp, keyboard_navigation, root_size_width,
is_dark_mode: `Copy` values
- default_theme_classes, default_theme_inherited: `&Style`
- interactions: `&dyn Fn(StyleNodeId) -> PerNodeInteraction` — the
five pseudo-class bits, read per dirty node
- animations: `&dyn AnimationBackend` — the policy hook
`AnimationBackend::apply(&self, ...)` takes `&self` rather than
`&mut self`; floem's implementor mutates through `RefCell`-backed
view state, and the `&self` signature lets the closure in the
`interactions` field coexist with the backend reference under the
borrow checker. A `NoAnimationBackend` ZST covers standalone hosts
that don't animate anything.
Floem side:
- `StyleCx::get_interact_state` already took `&WindowState`
concretely; its reads now go straight to `WindowState` fields
(`frame_start`, `screen_size_bp`) instead of the removed sink
trait methods.
- `run_style_cascade` builds a `CascadeInputs` inline per pass: the
interactions closure captures `&WindowState` and translates
`StyleNodeId → ViewId → ElementId` via the reverse map; the
animation backend is a small stack struct holding only a borrow
of the reverse map.
- `FloemAnimationBackend` (in `src/style/sink.rs`) is the sole
remaining `AnimationBackend` impl — finds the owning view for a
`StyleNodeId` and forwards the tick into `ViewState.animations`.
Tests rewritten:
- `style_tree_cascade.rs`: per-test `cascade()` + `inputs()` helpers
build the `CascadeInputs` from a `MockHost`.
- `mock_sink.rs`: no more trait impl; exercises cache + hover-aware
`resolve_nested_maps` directly, plus a standalone demo of the
`interactions` closure shape.
- `host_integration.rs`: `with_inputs(host, |cx| ...)` helper closes
over the host, and `animation_backend_hook_is_invoked` uses a
minimal standalone `AnimationBackend` impl.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folder rename matches the convention used by the other floem_X crates (renderer, reactive, etc.): short topical folder name, floem_X package. ElementId is now defined in floem core (src/box_tree.rs) and no longer named by floem_style. The engine's sole identity is StyleNodeId: PropExtractorCx, StyleReason::targets, and the style_macros all take StyleNodeId. understory_box_tree dropped from the style crate. Sub-element ElementIds (scroll handles/tracks, resizable handles) each lazy-allocate a dedicated StyleNodeId via the new WindowState::ensure_style_node_for_element. These are orphan nodes in the style tree — no parent/child edges, never cascaded; they exist only as identity tokens so StyleReason::targets and the sub-handler dispatch loops route to the right sub-element. ElementIdExt goes away — owning_id() is inherent on ElementId now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A `MiniHost` reference driver exercises the extracted style engine the way a non-floem consumer would: build a tree, run multiple frames, drain scheduled/dirtied outputs between passes, mutate host state, and assert across frames. Six scenarios cover steady-state settling, hover-driven restyle, tree-native animation scheduling, mid-loop structural changes (add/remove), and responsive width-breakpoint flips. Uses only `floem_style::*` plus generic support crates — proof the engine can be driven by a second host (floem-native, etc.) through the public surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`PropExtractorCx` goes from four methods to two: only `now` and `direct_style` remain. `current_element` and `request_transition_for` are gone — the callback during property extraction is replaced by a `&mut transitioning: &mut bool` out-param that the generated `read` / `read_style` methods set when a field is still animating. The caller schedules the re-cascade explicitly. Generated methods drop their `_for` variants. Targets were only needed for the removed transition callback; now the caller already knows which `StyleNodeId` to schedule against. Brings transitions in line with animations: both surface through data (events / flags) at the engine↔host boundary, not through callbacks fired during the pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.