Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,67 @@ tagged release.
time.
- `GridLine::Area` uses `String` for area names. Spec used `SmolStr`;
Phase 3 uses `String` to avoid a new direct supply-chain dep.
- **Layout writing modes (Phase 4 of the layout migration).**
- `WritingMode` component (joins `Style`'s Bundle): `mode`,
`direction`, `text_orientation`, `unicode_bidi`. CSS-faithful surface
for writing-mode + direction + text-orientation + unicode-bidi.
- `WritingModeResolved` private cache component, populated by the new
`BuiyLayoutStep::WritingModeInherit` pipeline step. Memoized
multi-level ancestor walk; idempotent insert preserves Phase 1's
O(0) steady-state contract.
- 4 supporting enums: `WritingModeKind` (HorizontalTb / VerticalRl /
VerticalLr / SidewaysRl / SidewaysLr), `Direction` (Ltr / Rtl),
`TextOrientation` (Mixed / Upright / Sideways), `UnicodeBidi`
(Normal / Embed / Isolate / BidiOverride / IsolateOverride / Plaintext).
- `LogicalEdges` value type with `to_edges(WritingModeKind, Direction)`.
- `LogicalBoxModel` and `LogicalInset` author-ergonomic builder
structs (non-component, non-Bundle) with `.to_box_model(&WritingMode)`
and `.to_inset(&WritingMode)` methods. Vertical modes swap inline ↔
block onto width ↔ height.
- 7 fluent setters on `Style`: `.writing_mode(_)`,
`.writing_mode_kind(_)`, `.direction(_)`, `.ltr()`, `.rtl()`,
`.text_orientation(_)`, `.unicode_bidi(_)`.
- `WritingMode` + `WritingModeResolved` + 5 value types registered
for reflection in `LayoutPlugin`.
- New `BuiyLayoutStep::WritingModeInherit` pipeline step inserted
between `RemovedNodesGc` and `SyncStyles`. The 8-step layout chain
becomes 9. `tests/layout_pipeline_order.rs` widens to assert all 9.
- `Direction::Rtl` flows through `taffy::Style.direction`, mirroring
flex children under RTL.
- `WritingModeKind::Sideways{Rl,Lr}` emit one `warn!` per session and
fall back to their non-sideways vertical equivalent for layout
purposes. Glyph rotation is `buiy-text-rendering-design`'s concern.

### Changed (Phase 4)
- `inherit_writing_mode`'s ancestor walk treats `WritingMode::default()`
as "unset" (CSS `initial`-like). Without this, every `Style`-spawned
entity's bundled `WritingMode::default()` would short-circuit the
walk so descendants of a non-default ancestor would resolve to
default instead of inheriting. Trade-off: an author cannot explicitly
pin `WritingMode::default()` as an override against a non-default
ancestor; the result is observationally identical to inheriting the
root default.
- `sync_styles`'s `Or<>` trigger filter widens with `Changed<WritingMode>`
and `Changed<WritingModeResolved>`. **Phase 2 invariant intact:**
`Changed<ScrollOffset>` / `Changed<ScrollSnapItem>` remain excluded.
- Pipeline gains a 9th step (`WritingModeInherit`); the layout chain
is now 0 → wmi → 1 → ... → 7 in declared order.

### Deferred (Phase 4)
- `ContainingBlock` cache — deferred to Phase 6 (anchor positioning)
where it has a real consumer.
- Dynamic writing-mode switches (CSS `Changed<WritingMode>` →
re-translation pass) — Phase 4 v1 ships construct-time-only
`LogicalBoxModel` / `LogicalInset` translation; runtime flips
require re-spawn / re-insert. Re-translation pass is a v1.x feature
contingent on resolving spec § 4.1 vs § 4.2 internal inconsistency.
- Vertical-mode Taffy axis-swap — Taffy 0.10 has no writing-mode
awareness. Vertical modes are honored only by the logical builders;
Taffy still flows the main axis horizontally. Authors who want
top-to-bottom flow under vertical-rl use `Display::Flex(Column)`.
- Sideways glyph rotation — owned by `buiy-text-rendering-design`.
- BiDi resolution algorithm for `unicode_bidi` — owned by
`buiy-i18n-design`. Phase 4 stores the value only.

### Removed
- `buiy_core::components::Style` (the Phase 0 mega-component) and
Expand Down
14 changes: 8 additions & 6 deletions crates/buiy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ pub use buiy_core::{
components::{Node, ResolvedLayout, Visual},
focus::{FocusVisible, Focusable, FocusedEntity},
layout::{
AlignContent, AlignItems, AspectRatio, BoxModel, BoxSizing, BuiyLayoutStep, Display, Edges,
FlexAxis, FlexGap, FlexItem, FlexParams, FlexWrap, GridAreas, GridAutoFlow, GridItem,
GridLine, GridParams, Inset, JustifyContent, JustifyItems, LayoutPlugin, Length, NamedArea,
Overflow, OverflowMode, OverscrollBehavior, Position, PositionKind, RepeatCount, Scroll,
ScrollBehavior, ScrollOffset, ScrollSnapItem, ScrollbarColor, ScrollbarGutter,
ScrollbarWidth, Sizing, SnapAlign, SnapStop, SnapType, Style, TrackSize,
AlignContent, AlignItems, AspectRatio, BoxModel, BoxSizing, BuiyLayoutStep, Direction,
Display, Edges, FlexAxis, FlexGap, FlexItem, FlexParams, FlexWrap, GridAreas, GridAutoFlow,
GridItem, GridLine, GridParams, Inset, JustifyContent, JustifyItems, LayoutPlugin, Length,
LogicalBoxModel, LogicalEdges, LogicalInset, NamedArea, Overflow, OverflowMode,
OverscrollBehavior, Position, PositionKind, RepeatCount, Scroll, ScrollBehavior,
ScrollOffset, ScrollSnapItem, ScrollbarColor, ScrollbarGutter, ScrollbarWidth, Sizing,
SnapAlign, SnapStop, SnapType, Style, TextOrientation, TrackSize, UnicodeBidi, WritingMode,
WritingModeKind, WritingModeResolved,
},
picking::{BuiyPickingBackendPlugin, Hovered},
theme::{Theme, UserPreferences, default_light_theme},
Expand Down
81 changes: 78 additions & 3 deletions crates/buiy_core/src/layout/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
//! respective phase plans (see foundation plan §"Phasing strategy").

use super::types::{
AlignContent, AlignItems, AspectRatio, BoxSizing, Edges, FlexAxis, FlexGap, FlexWrap,
GridAreas, GridAutoFlow, GridLine, Inset, JustifyContent, JustifyItems, OverflowMode,
AlignContent, AlignItems, AspectRatio, BoxSizing, Direction, Edges, FlexAxis, FlexGap,
FlexWrap, GridAreas, GridAutoFlow, GridLine, Inset, JustifyContent, JustifyItems, OverflowMode,
OverscrollBehavior, PositionKind, ScrollBehavior, ScrollbarColor, ScrollbarGutter,
ScrollbarWidth, Sizing, SnapAlign, SnapStop, SnapType, TrackSize,
ScrollbarWidth, Sizing, SnapAlign, SnapStop, SnapType, TextOrientation, TrackSize, UnicodeBidi,
WritingModeKind,
};
use bevy::prelude::*;

Expand Down Expand Up @@ -233,6 +234,62 @@ pub struct GridItem {
pub align_self: Option<AlignItems>,
}

/// CSS writing-mode + direction + text-orientation + unicode-bidi, all on
/// one component because they're authored together. Joins `Style`'s
/// Bundle. The inherited effective value is computed by the
/// `inherit_writing_mode` system and stored in `WritingModeResolved`.
///
/// Spec: docs/specs/2026-05-08-buiy-layout-design/container-queries-and-writing-modes.md § 2.1.
///
/// Note: vertical writing-modes (`VerticalRl` / `VerticalLr`) do *not*
/// reorient the Taffy main axis — Taffy 0.10 has no writing-mode
/// awareness at the layout-engine level. Vertical modes are honored only
/// by the `LogicalBoxModel` / `LogicalInset` ergonomic builders. Authors
/// who want top-to-bottom flow under vertical-rl use
/// `Display::Flex(Column)` explicitly. See plan § Decisions made #5.
#[derive(Component, Reflect, Default, Clone, Copy, Debug, PartialEq, Eq)]
#[reflect(Component, Default)]
pub struct WritingMode {
pub mode: WritingModeKind,
pub direction: Direction,
pub text_orientation: TextOrientation,
pub unicode_bidi: UnicodeBidi,
}

/// Inherited effective writing-mode for an entity. Synced by the
/// `inherit_writing_mode` system in `BuiyLayoutStep::WritingModeInherit`,
/// run before `SyncStyles`. **Private cache — not author-set, not in
/// `Style`'s Bundle.**
///
/// The translation layer (`style_to_taffy`) reads this value to wire
/// `Direction::Rtl` to `taffy::Style.direction` and to gate `Sideways{Rl,Lr}`
/// through the warn-once fallback. The `LogicalBoxModel` and `LogicalInset`
/// builders take a `&WritingMode` directly (not the Resolved cache),
/// because they translate at construct time on the author's side.
///
/// Spec: docs/specs/2026-05-08-buiy-layout-design/container-queries-and-writing-modes.md § 2.2.
#[derive(Component, Reflect, Default, Clone, Copy, Debug, PartialEq, Eq)]
#[reflect(Component, Default)]
pub struct WritingModeResolved {
pub mode: WritingModeKind,
pub direction: Direction,
pub text_orientation: TextOrientation,
pub unicode_bidi: UnicodeBidi,
}

impl WritingModeResolved {
/// Construct from a parent `WritingMode`. Used by the inheritance
/// system to copy fields one-to-one.
pub(crate) fn from_writing_mode(wm: &WritingMode) -> Self {
Self {
mode: wm.mode,
direction: wm.direction,
text_orientation: wm.text_orientation,
unicode_bidi: wm.unicode_bidi,
}
}
}

/// Runtime scroll position of a scroll container. Mutated by the
/// scroll-input handler in `buiy-input-events-design`. Read by render
/// (drawing) and picking (hit-testing) at consume time, and by Phase 7
Expand Down Expand Up @@ -414,4 +471,22 @@ mod tests {
assert_eq!(g.justify_self, None);
assert_eq!(g.align_self, None);
}

#[test]
fn writing_mode_default_is_horizontal_tb_ltr_mixed_normal() {
let wm = WritingMode::default();
assert_eq!(wm.mode, WritingModeKind::HorizontalTb);
assert_eq!(wm.direction, Direction::Ltr);
assert_eq!(wm.text_orientation, TextOrientation::Mixed);
assert_eq!(wm.unicode_bidi, UnicodeBidi::Normal);
}

#[test]
fn writing_mode_resolved_default_is_horizontal_tb_ltr_mixed_normal() {
let wm = WritingModeResolved::default();
assert_eq!(wm.mode, WritingModeKind::HorizontalTb);
assert_eq!(wm.direction, Direction::Ltr);
assert_eq!(wm.text_orientation, TextOrientation::Mixed);
assert_eq!(wm.unicode_bidi, UnicodeBidi::Normal);
}
}
23 changes: 16 additions & 7 deletions crates/buiy_core/src/layout/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ mod types;

pub use components::{
BoxModel, Display, FlexItem, FlexParams, GridItem, GridParams, Overflow, Position, Scroll,
ScrollOffset, ScrollSnapItem,
ScrollOffset, ScrollSnapItem, WritingMode, WritingModeResolved,
};
pub use pipeline::BuiyLayoutStep;
pub use style::Style;
pub use style::{LogicalBoxModel, LogicalInset, Style};
pub use tree::LayoutTree;
pub use types::{
AlignContent, AlignItems, AspectRatio, BoxSizing, Edges, FlexAxis, FlexGap, FlexWrap,
GridAreas, GridAutoFlow, GridLine, Inset, JustifyContent, JustifyItems, Length, NamedArea,
OverflowMode, OverscrollBehavior, PositionKind, RepeatCount, ScrollBehavior, ScrollbarColor,
ScrollbarGutter, ScrollbarWidth, Sizing, SnapAlign, SnapStop, SnapType, TrackSize,
AlignContent, AlignItems, AspectRatio, BoxSizing, Direction, Edges, FlexAxis, FlexGap,
FlexWrap, GridAreas, GridAutoFlow, GridLine, Inset, JustifyContent, JustifyItems, Length,
LogicalEdges, NamedArea, OverflowMode, OverscrollBehavior, PositionKind, RepeatCount,
ScrollBehavior, ScrollbarColor, ScrollbarGutter, ScrollbarWidth, Sizing, SnapAlign, SnapStop,
SnapType, TextOrientation, TrackSize, UnicodeBidi, WritingModeKind,
};

use bevy::prelude::*;
Expand All @@ -44,6 +45,8 @@ impl Plugin for LayoutPlugin {
.register_type::<ScrollSnapItem>()
.register_type::<GridParams>()
.register_type::<GridItem>()
.register_type::<WritingMode>()
.register_type::<WritingModeResolved>()
.register_type::<Edges>()
.register_type::<Sizing>()
.register_type::<Length>()
Expand All @@ -55,14 +58,20 @@ impl Plugin for LayoutPlugin {
.register_type::<GridAreas>()
.register_type::<NamedArea>()
.register_type::<GridAutoFlow>()
.register_type::<JustifyItems>();
.register_type::<JustifyItems>()
.register_type::<WritingModeKind>()
.register_type::<Direction>()
.register_type::<TextOrientation>()
.register_type::<UnicodeBidi>()
.register_type::<LogicalEdges>();

pipeline::configure_pipeline(app);

app.add_systems(
Update,
(
systems::gc_removed_nodes.in_set(BuiyLayoutStep::RemovedNodesGc),
systems::inherit_writing_mode.in_set(BuiyLayoutStep::WritingModeInherit),
systems::sync_styles.in_set(BuiyLayoutStep::SyncStyles),
systems::taffy_compute.in_set(BuiyLayoutStep::TaffyCompute),
systems::write_resolved_layout.in_set(BuiyLayoutStep::WriteResolvedLayout),
Expand Down
18 changes: 13 additions & 5 deletions crates/buiy_core/src/layout/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
//!
//! Spec: docs/specs/2026-05-08-buiy-layout-design/architecture.md § 3.
//!
//! Eight ordered sub-sets of `BuiySet::Layout`. Phase 1 wires all eight;
//! steps 2 (`CqActivate`), 4 (`CqFlipCheck`), 5 (`CqFlipReRun`), and 6
//! (`PostTaffyOverrides`) are no-ops in Phase 1. Later phases attach
//! systems to those sub-sets without reordering.
//! Nine ordered sub-sets of `BuiySet::Layout`. Phase 1 wires the original
//! eight; Phase 4 inserts `WritingModeInherit` between `RemovedNodesGc`
//! and `SyncStyles` so step 1 sees the effective inherited writing-mode
//! for every entity. Steps 2 (`CqActivate`), 4 (`CqFlipCheck`), 5
//! (`CqFlipReRun`), and 6 (`PostTaffyOverrides`) remain no-ops in Phase 1.
//! Later phases attach systems to those sub-sets without reordering.

use bevy::prelude::*;

Expand All @@ -15,6 +17,11 @@ use bevy::prelude::*;
pub enum BuiyLayoutStep {
/// Step 0 — drop despawned entities from `LayoutTree`.
RemovedNodesGc,
/// Pre-step-1 — populate `WritingModeResolved` by walking the
/// hierarchy. Runs before `SyncStyles` so step 1 sees the effective
/// inherited writing-mode for every entity.
/// **Phase 4.**
WritingModeInherit,
/// Step 1 — translate changed Buiy components → `taffy::Style` and
/// sync hierarchy.
SyncStyles,
Expand All @@ -36,12 +43,13 @@ pub enum BuiyLayoutStep {
WriteResolvedLayout,
}

/// Configure the 8-step chain inside `BuiySet::Layout`.
/// Configure the 9-step chain inside `BuiySet::Layout`.
pub fn configure_pipeline(app: &mut App) {
app.configure_sets(
Update,
(
BuiyLayoutStep::RemovedNodesGc,
BuiyLayoutStep::WritingModeInherit,
BuiyLayoutStep::SyncStyles,
BuiyLayoutStep::CqActivate,
BuiyLayoutStep::TaffyCompute,
Expand Down
Loading
Loading