Skip to main content

telar_layout_reactive/
direction.rs

1//! The active writing-direction signal — the reactive source that drives live LTR/RTL switching.
2//!
3//! Mirrors `theme-core`'s active-mode store and `i18n-core`'s active-locale store: a thread-local `RwSignal`,
4//! a setter, and a reactive getter. Unlike those two, the value is not read by the widgets themselves —
5//! [`compute_layout`](crate::compute_layout) reconciles each surface's layout engine with it before
6//! laying out, so a flip re-resolves the existing nodes rather than rebuilding any part of the tree. That is
7//! also what makes it reach every surface on the thread, not just whichever one was active at the call.
8
9use std::mem::ManuallyDrop;
10
11use layout_core::Direction;
12use reactive_core::{RwSignal, signal};
13
14thread_local! {
15    // ManuallyDrop mirrors theme-core's signals: no TLS destructor is registered, so unmapping the dylib on dlclose stays safe.
16    static DIRECTION: ManuallyDrop<RwSignal<Direction>> =
17        ManuallyDrop::new(signal(Direction::Ltr));
18}
19
20/// Sets the writing direction every surface lays out against, taking effect on the next layout pass.
21pub fn set_direction(direction: Direction) {
22    DIRECTION.with(|s| {
23        if s.peek() != direction {
24            s.set(direction);
25        }
26    });
27}
28
29/// Reactive read of the active direction — subscribes the caller, for the rare widget that has to mirror
30/// something layout cannot flip on its own (a chevron glyph, a directional icon).
31pub fn use_direction() -> Direction {
32    DIRECTION.with(|s| s.get())
33}
34
35/// Non-reactive read of the active direction, for the layout pass and event handlers.
36pub fn current_direction() -> Direction {
37    DIRECTION.with(|s| s.peek())
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn direction_is_reactive_and_starts_left_to_right() {
46        set_direction(Direction::Ltr);
47        let seen = std::rc::Rc::new(std::cell::RefCell::new(Vec::new()));
48        let s = seen.clone();
49        let _e = reactive_core::effect(move || s.borrow_mut().push(use_direction()));
50        set_direction(Direction::Rtl);
51        set_direction(Direction::Rtl);
52        assert_eq!(
53            *seen.borrow(),
54            vec![Direction::Ltr, Direction::Rtl],
55            "the effect re-ran once, not twice: setting the same direction is not a change"
56        );
57        set_direction(Direction::Ltr);
58    }
59}