Skip to main content

WindowManager

Struct WindowManager 

Source
pub struct WindowManager { /* private fields */ }
Expand description

Manages multiple application windows.

Each window owns its own WidgetTree and PlatformWindow. The manager routes events by winit WindowId, broadcasts environment changes, and handles modal dialog blocking.

Implementations§

Source§

impl WindowManager

Source

pub fn new(theme: Theme) -> Self

Source

pub fn set_theme_mode(&mut self, mode: ThemeMode)

Set the theme mode (called by TeksiloAppHandler during initialization).

Source

pub fn set_event_proxy(&mut self, proxy: AppEventProxy)

Install the event-loop proxy (called by TeksiloAppHandler once the proxy is available). Enables TitleBarHostCallbacks::request_close to post CloseWindowRequest back through the event loop.

Source

pub fn set_app_context_template(&mut self, template: Rc<TreeAppContext>)

Install the per-tree app context template that every newly created window’s WidgetTree should adopt. Called by TeksiloAppHandler when the application registered an event source on the builder.

Source

pub fn theme_mode(&self) -> ThemeMode

Get the current theme mode.

Source

pub fn apply_os_theme(&mut self, os_dark_hint: Option<bool>)

Recompute and broadcast the theme from the current OS appearance, per the active theme mode. A no-op under Manual. Under Native it adopts the OS’s actual colours (GNOME/KDE/Cinnamon on Linux); under FollowSystem it picks the built-in light/dark preset. Both results carry the id "system".

os_dark_hint is the OS light/dark state as reported by winit (e.g. from a WindowEvent::ThemeChanged), used as the authoritative source on platforms where Teksilo’s own OS-colour query is unimplemented (macOS / Windows, where query_os_theme_colors() returns NoPreference). On Linux the query reports a real scheme and the hint is unused. Pass None when no winit signal is available (e.g. a runtime “follow system” request) — the current window’s reported theme is used where possible, otherwise it resolves to light.

Source

pub fn set_typesetter(&mut self, typesetter: SharedTypesetter)

Source

pub fn create_window( &mut self, config: WindowConfig, target: &ActiveEventLoop, ) -> TeksiloWindowId

Create a new window synchronously. Allocates an id, constructs the winit surface, builds the widget tree, and registers everything in the windows map before returning.

The returned id is immediately usable — it can be passed to find_window, focus_window, or close_window_by_id, and state writes through WindowState are applied at the next drain_window_commands tick.

Source

pub fn close_window(&mut self, teksilo_id: TeksiloWindowId)

Close a window by its TeksiloWindowId.

The single choke point every close funnels through: process_pending calls this both for a forced close (queue_close / close_window_by_id) and for a guarded one (request_close) once its guard has passed. That makes this the one place to fire WindowConfig::on_removed so both paths are covered by a single call site instead of two.

Source

pub fn queue_close(&mut self, teksilo_id: TeksiloWindowId)

Queue an unconditional window closure (processed in the next event loop tick). The window’s close guard is not consulted — use this for explicit programmatic closes and framework-internal teardown (modal dismissals, close_window_by_id). For a guarded close that a window’s on_close_requested can veto, use request_close.

Source

pub fn request_close(&mut self, teksilo_id: TeksiloWindowId)

Queue a guarded window closure (processed in the next event loop tick). Before tearing the window down, process_pending consults the window’s close guard (from WindowConfig::on_close_requested / can_close); a CloseResponse::Veto keeps the window open. Used for the interactive close gestures: the OS close button, a custom-chrome close button, and EventContext::close_window.

Source

pub fn route_title_bar_synthetic_tap( &mut self, teksilo_id: TeksiloWindowId, target: ControlTarget, )

Route a Windows-side synthetic title-bar tap. The wndproc posts a TitleBarSyntheticEvent when WM_NCLBUTTONUP fires on a button rect that the OS treated as non-client; we resolve the matching WidgetId via the host and synthesise a primary-button tap so the widget’s normal on_tap handler runs. No-op on platforms that never produce these events.

Source

pub fn route_title_bar_synthetic_hover( &mut self, teksilo_id: TeksiloWindowId, target: ControlTarget, entered: bool, )

Route a Windows-side synthetic title-bar hover entered/leave. Delegates to the host’s set_button_hover, which writes the signal WindowControls registered for the matching button. No-op on platforms that don’t intercept non-client hover.

Source

pub fn drain_window_commands(&mut self)

Drain the app→OS command queue on every window and translate each WindowCommand into the appropriate winit call. Called once per event-loop tick after event dispatch.

Observers on WindowState signals emit commands when app code writes through them. OS-originated writes go through the *_from_os setters on the state, which flip the re-entrancy guard so the same observers do not fire an echo back out — so the queue only contains genuine app→OS directives.

Source

pub fn process_pending(&mut self, target: &ActiveEventLoop)

Process pending window closures. Called from the event loop each tick. Creation does not need a drain path — open_window from handler code goes through WindowOpsImpl and calls create_window synchronously inside the same dispatch.

A forced close (queue_close) tears the window down immediately. A guarded close (request_close) first runs the window’s close guard via evaluate_close_guard; a CloseResponse::Veto keeps the window open. Guards are strictly per-window, so this is correct for multi-window apps: each pending close consults only its own window’s guard.

Source

pub fn teksilo_id_for_winit(&self, id: WindowId) -> Option<TeksiloWindowId>

Get the TeksiloWindowId for a winit WindowId.

Source

pub fn find_window(&self, string_id: &str) -> Option<TeksiloWindowId>

Find a window by its string ID.

Source

pub fn is_blocked(&self, teksilo_id: TeksiloWindowId) -> bool

Whether a window is blocked by a modal child.

Source

pub fn blocking_modal_child( &self, teksilo_id: TeksiloWindowId, ) -> Option<TeksiloWindowId>

Source

pub fn refocus_modal_child(&self, blocked_parent: TeksiloWindowId)

Source

pub fn set_theme(&mut self, theme: Theme)

Broadcast a theme change to all windows.

ThemeMode is App-level state, so a user-driven theme set under FollowSystem/Native is last-writer-wins against the next OS theme event (handle_theme_changed). The default Manual mode ignores OS events, so an app that wants user theme choices to stick should stay on Manual (the default).

Source

pub fn set_text_scale(&mut self, factor: f32)

Broadcast a user text-scale change to all windows. Stores the factor so windows created later inherit it, then re-scales every existing tree’s text without rebuilding.

Source

pub fn set_initial_text_scale(&mut self, factor: f32)

Seed the user text-scale factor before the first window opens. Called by TeksiloAppHandler after reading teksilo_settings::TEXT_SCALE_KEY, so every initially-created tree starts at the persisted scale.

Source

pub fn refresh_accessibility_preferences(&mut self) -> bool

Re-query the OS accessibility preferences (“increase contrast”, “reduce motion”, text scale) and, if they changed since startup / the last refresh, apply them to every open window’s tree. Lets a runtime toggle of these settings take effect without restarting the app (WCAG / EN 301 549 §11.7). Driven event-first — from WindowEvent::Focused when a window gains focus — so there is no idle polling wakeup. Returns true if anything changed (so the caller can request a redraw).

Source

pub fn set_locale(&mut self, locale: LanguageIdentifier)

Broadcast a locale switch to all windows. Updates the i18n manager (incrementing the version signal) and seeds each tree with the new locale and layout direction. No-op if no I18nConfig was registered.

Source

pub fn theme(&self) -> &Theme

Get the current shared theme.

Source

pub fn accessibility_preferences(&self) -> &AccessibilityPreferences

Get the OS-level accessibility preferences (queried at startup).

Source

pub fn primary_window_id(&self) -> TeksiloWindowId

Get the TeksiloWindowId of the first (primary) window. Falls back to a synthetic ID when no windows are open yet.

Source

pub fn window_count(&self) -> usize

Number of active windows.

Source

pub fn is_empty(&self) -> bool

Whether no windows remain (app should exit).

Source

pub fn winit_id_for_teksilo( &self, teksilo_id: TeksiloWindowId, ) -> Option<WindowId>

Get the winit WindowId for a TeksiloWindowId.

Source

pub fn title_bar_host( &self, teksilo_id: TeksiloWindowId, ) -> Option<Rc<dyn PlatformTitleBarHost>>

Get the platform title bar host for a window, if the window opted into custom chrome via WindowConfig::custom_chrome(true) and the platform supports it. Returns None for windows that use native decorations or run on a window system without custom chrome support (currently X11).

Source

pub fn request_redraw_all(&self)

Request redraw on all windows.

Source

pub fn request_redraw_due(&self, now: Instant)

Request redraw only on windows whose next frame deadline has been reached (<= now). Used at the animation ResumeTimeReached wake so that a single animating window (e.g. a blinking caret) does NOT force a redraw of every other window.

The blanket request_redraw_all() here was a cross-window over-redraw: a wasted-power bug on every platform (an inactive, non-animating window repainted at 60 Hz), and on Windows a correctness bug — winit services only one window’s RedrawRequested per event-loop iteration, so an inactive window flooded with redraws it never wins is starved of its own pending repaint and freezes on its last active frame (caret stuck, colours not desaturated). Targeting only due windows removes both.

Source

pub fn request_redraw_needing_render(&mut self) -> usize

Reconcile every window’s reactive (Signal-bound) state and request redraw ONLY on the ones that come out of that with pending layout or paint work (WidgetTree::needs_render). Returns how many windows were poked, purely so a caller can log/trace it.

§The problem

A Signal mutation made by a handler in one window’s dispatch (e.g. writing to an app-level Signal a sibling window’s widget also reads) is supposed to make that sibling dirty too — that’s the whole point of sharing a Signal across windows. But only the dispatching window’s own event-handling path calls request_redraw() on itself (see the WindowEvent::CursorMoved / MouseInput / KeyboardInput arms); nothing tells winit to schedule a RedrawRequested for the sibling, so it shows a stale frame until the user focuses it (which finally earns it a paint).

§Why this can’t just check needs_render()

A Signal write only advances a change generation on the signal itself (a deliberately lazy design — a signal has no reference back into any WidgetTree’s arena to mark node-level dirty bits synchronously). That generation is only compared against what each window’s BindingRegistry last acted on, and walked into arena.needs_layout / needs_paint — i.e. into what needs_render() actually reads — by WidgetTree’s internal process_state_changes step, which today runs only at the top of that tree’s own layout(). A window’s own layout() runs only as part of handling its own RedrawRequested (see handle_redraw_requested in app.rs). So a sibling window that never redraws never reconciles its bindings either — its needs_render() reads false forever, not just “until it happens to repaint”, because nothing ever performed the walk that would make it true. Checking needs_render() without reconciling first would make this method a permanent no-op for exactly the case it exists to fix.

That’s why a window’s tree.layout() is called here first, at its OWN current size (proposal_changed stays false), whenever WidgetTree::needs_reconcile says reconciling could change the answer. This is the only place in the framework doing this specific reconciliation for a plain app-level Signal (the theme/locale/text-scale/follow-system broadcasts sidestep the whole problem by mutating each tree directly through &mut self.windows, not through a shared Signal, so they always know synchronously that every window needs a redraw).

§Why the reconcile is gated rather than unconditional

It was unconditional at first, on the grounds that layout() short-circuits before the per-node geometry recursion when nothing is dirty. That undersells the cost: layout_with_ops runs a dozen per-frame passes before it gets near that short-circuit — pending animations, the frame tick, the animation scheduler tick, drag ticks, the whole of process_state_changes, tooltips, delayed / pointer-leave / auto-dismiss overlays and overlay fades — and it ran all of them for every open window on every dispatched event, including a fast mouse-move stream.

needs_reconcile() is cheap enough to ask instead (u64 comparisons over unique bound sources, no arena walk) and answers precisely the question this sweep is for. It is safe to skip the rest because everything in that list has its own scheduling path — the timing-driven passes all feed WidgetTree::next_timer_deadline, which request_redraw_due polls — see needs_reconcile’s own doc for the case-by-case argument.

The needs_render() check stays OUTSIDE the gate on purpose: a window can need a repaint for reasons that never involved a binding (a handler called request_rebuild, an event dirtied a node directly), and skipping the reconcile must not also skip poking it.

§Reconciling here does not rob the window of its own reconcile

Load-bearing, and it was not always true. Dirty tracking used to be a bool living on the Signal, which the first registry to flush both read AND cleared — so this very sweep was the thing that broke shared-Signal fan-out: whichever window it happened to visit first consumed the change and every later window’s reconcile found nothing, permanently. Signal now exposes a monotone generation and each BindingRegistry remembers what it last acted on (see teksilo_core::binding::BindingGroup), so visiting windows in any order — this method iterates a HashMap, so the order is arbitrary and varies run to run — delivers the change to all of them.

One known limitation: this uses WidgetTree::layout (a NoopWindowOps sink), not layout_with_ops, so a handler that this reconcile pass happens to run (a data-driven rebuild reacting to the very state change being reconciled) cannot synchronously open a window from here. That is acceptable for a background reconciliation pass — the window still opens correctly the next time this sibling performs its own real redraw with a real WindowOps sink, exactly the way an app that never called this method at all would have behaved.

§Why needs_render(), not needs_redraw()

Deliberately narrower than the broader needs_redraw(), which also reports true while a per-frame shader animation is merely running, with no dirty paint or layout at all. Treating “an animation is running” as a reason to force an extra redraw here — on top of whatever other window’s event just got dispatched — would effectively re-couple that animating window’s frame rate to the dispatch rate of every OTHER window’s input (a fast mouse-move stream can exceed 60 Hz), defeating the 60 Hz WaitUntil pacing those animations already get from next_timer_deadline / request_redraw_due and reintroducing the exact uncapped free-running redraw behaviour that pacing was written to remove.

Source

pub fn drain_pending_modal_requests( &mut self, ) -> Vec<(TeksiloWindowId, Vec<QueuedModalRequest>)>

Drain pending modal requests from all windows.

Source

pub fn drain_pending_modal_dismissals(&mut self) -> Vec<TeksiloWindowId>

Drain native modal-window dismiss requests from all windows.

Source

pub fn drain_close_window_requests(&mut self) -> bool

Drain per-tree close-window requests raised by handlers via EventContext::close_window (a guarded close) and EventContext::close_window_forced (a forced close that bypasses the window’s close guard). Returns true when at least one window was queued for closing.

A forced request wins over a guarded one for the same window in the same drain — if a handler called both, the window closes unconditionally.

Source

pub fn drain_pending_locale_requests(&mut self) -> bool

Drain per-tree locale-switch requests raised by handlers via EventContext::set_locale, parse each one to a LanguageIdentifier, and route it through WindowManager::set_locale so the I18nManager (active locale, version signal, RTL direction) and every tree stay in sync. Invalid or unsupported locale strings are logged and dropped.

Returns true if any request was drained, so the caller can repaint every window — the fan-out marks non-originating windows dirty but only the window that received the triggering event gets its own request_redraw.

Source

pub fn drain_pending_theme_requests(&mut self) -> bool

Drain per-tree theme-switch requests raised by handlers via EventContext::set_theme and route each through WindowManager::set_theme so the new theme is applied to every window, not just the one whose handler requested it.

Returns true if any request was drained (same repaint rationale as WindowManager::drain_pending_locale_requests). If several windows raised a request in the same tick, each is applied in turn — the last wins, matching the locale path.

Source

pub fn drain_pending_follow_system_requests(&mut self) -> bool

Drain per-tree “follow OS theme” requests raised by handlers via EventContext::follow_system_theme. Switches the app to ThemeMode::Native and recomputes the theme from the current OS colours, fanning it to every window. Returns true if any window requested it (so the caller schedules a repaint).

Source

pub fn drain_pending_text_scale_requests(&mut self) -> bool

Drain per-tree text-scale requests raised by handlers via EventContext::set_text_scale and route each through WindowManager::set_text_scale so the new factor is applied to every window. Returns true if any request was drained (so the caller schedules a repaint). Last writer wins if several windows requested in the same tick.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more