Skip to main content

BackFallback

Struct BackFallback 

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

An app-level back rule for per-tab navigator stacks: BackFallback claims a back press only when no navigator does (it ranks after every navigator, whatever the wire order), and back_fallback wraps the view it belongs to. The Android/Material tab convention — back at a tab root returns to the home tab, back at the home tab root exits — is one fallback:

use frust::{
    BackFallback, Component, Get, GetUntracked, IndexedStack, NavigatorController, RwSignal,
    Set, View, back_fallback, navigator, text,
};

const HOME: usize = 0;

#[derive(Default)]
struct App;

struct AppState {
    selected: RwSignal<usize>,
    tabs: [NavigatorController<AppState>; 3],
    home_fallback: BackFallback,
}

impl Component for App {
    type State = AppState;

    fn init(&self) -> AppState {
        let selected = RwSignal::new(HOME);
        let tabs = [
            NavigatorController::new(),
            NavigatorController::new(),
            NavigatorController::new(),
        ];
        let probe = tabs.clone();
        // Constructed ONCE. It claims a press only off the home tab with the
        // selected tab at its root; any poppable tab claims first anyway.
        let home_fallback = BackFallback::new(
            move || {
                let sel = selected.get_untracked();
                sel != HOME && probe[sel].depth() <= 1
            },
            move || selected.set(HOME),
        );
        AppState { selected, tabs, home_fallback }
    }

    fn build(&self, state: &mut AppState) -> impl View<AppState> {
        let [home, b, c] = &state.tabs;
        // A tracked read: a selection change rebuilds.
        let sel = state.selected.get();
        back_fallback(
            &state.home_fallback,
            IndexedStack(
                Some(sel),
                (
                    navigator(home, || text("home")),
                    navigator(b, || text("search")),
                    navigator(c, || text("profile")),
                ),
            ),
        )
    }
}

frust::app!(App);

Liveness contract. A fallback claims only while a back_fallback wrapper over it is mounted: leaving the screen unmounts the wrapper and the fallback stops claiming, though the handle lives on in app state. Wrap the view every Component::build that should honor it, like navigator.

Accepted residual. A wrapper mounted inside a branch input does not reach — a non-selected IndexedStack child, or the page under a covering page whose own navigator does not claim — still counts as live (it is mounted). Gate the interest closure there on that branch being current. An app-level back rule for per-tab navigator stacks: BackFallback claims a back press only when no navigator does (it ranks after every navigator, whatever the wire order), and back_fallback wraps the view it belongs to. The Android/Material tab convention — back at a tab root returns to the home tab, back at the home tab root exits — is one fallback:

use frust::{
    BackFallback, Component, Get, GetUntracked, IndexedStack, NavigatorController, RwSignal,
    Set, View, back_fallback, navigator, text,
};

const HOME: usize = 0;

#[derive(Default)]
struct App;

struct AppState {
    selected: RwSignal<usize>,
    tabs: [NavigatorController<AppState>; 3],
    home_fallback: BackFallback,
}

impl Component for App {
    type State = AppState;

    fn init(&self) -> AppState {
        let selected = RwSignal::new(HOME);
        let tabs = [
            NavigatorController::new(),
            NavigatorController::new(),
            NavigatorController::new(),
        ];
        let probe = tabs.clone();
        // Constructed ONCE. It claims a press only off the home tab with the
        // selected tab at its root; any poppable tab claims first anyway.
        let home_fallback = BackFallback::new(
            move || {
                let sel = selected.get_untracked();
                sel != HOME && probe[sel].depth() <= 1
            },
            move || selected.set(HOME),
        );
        AppState { selected, tabs, home_fallback }
    }

    fn build(&self, state: &mut AppState) -> impl View<AppState> {
        let [home, b, c] = &state.tabs;
        // A tracked read: a selection change rebuilds.
        let sel = state.selected.get();
        back_fallback(
            &state.home_fallback,
            IndexedStack(
                Some(sel),
                (
                    navigator(home, || text("home")),
                    navigator(b, || text("search")),
                    navigator(c, || text("profile")),
                ),
            ),
        )
    }
}

frust::app!(App);

Liveness contract. A fallback claims only while a back_fallback wrapper over it is mounted: leaving the screen unmounts the wrapper and the fallback stops claiming, though the handle lives on in app state. Wrap the view every Component::build that should honor it, like navigator.

Accepted residual. A wrapper mounted inside a branch input does not reach — a non-selected IndexedStack child, or the page under a covering page whose own navigator does not claim — still counts as live (it is mounted). Gate the interest closure there on that branch being current. An app-level back rule for per-tab navigator stacks: BackFallback claims a back press only when no navigator does (it ranks after every navigator, whatever the wire order), and back_fallback wraps the view it belongs to. The Android/Material tab convention — back at a tab root returns to the home tab, back at the home tab root exits — is one fallback:

use frust::{
    BackFallback, Component, Get, GetUntracked, IndexedStack, NavigatorController, RwSignal,
    Set, View, back_fallback, navigator, text,
};

const HOME: usize = 0;

#[derive(Default)]
struct App;

struct AppState {
    selected: RwSignal<usize>,
    tabs: [NavigatorController<AppState>; 3],
    home_fallback: BackFallback,
}

impl Component for App {
    type State = AppState;

    fn init(&self) -> AppState {
        let selected = RwSignal::new(HOME);
        let tabs = [
            NavigatorController::new(),
            NavigatorController::new(),
            NavigatorController::new(),
        ];
        let probe = tabs.clone();
        // Constructed ONCE. It claims a press only off the home tab with the
        // selected tab at its root; any poppable tab claims first anyway.
        let home_fallback = BackFallback::new(
            move || {
                let sel = selected.get_untracked();
                sel != HOME && probe[sel].depth() <= 1
            },
            move || selected.set(HOME),
        );
        AppState { selected, tabs, home_fallback }
    }

    fn build(&self, state: &mut AppState) -> impl View<AppState> {
        let [home, b, c] = &state.tabs;
        // A tracked read: a selection change rebuilds.
        let sel = state.selected.get();
        back_fallback(
            &state.home_fallback,
            IndexedStack(
                Some(sel),
                (
                    navigator(home, || text("home")),
                    navigator(b, || text("search")),
                    navigator(c, || text("profile")),
                ),
            ),
        )
    }
}

frust::app!(App);

Liveness contract. A fallback claims only while a back_fallback wrapper over it is mounted: leaving the screen unmounts the wrapper and the fallback stops claiming, though the handle lives on in app state. Wrap the view every Component::build that should honor it, like navigator.

Accepted residual. A wrapper mounted inside a branch input does not reach — a non-selected IndexedStack child, or the page under a covering page whose own navigator does not claim — still counts as live (it is mounted). Gate the interest closure there on that branch being current. An app-level back rule that claims a press only when no navigator does — the Android/Material “back at a tab root returns to the home tab; back at the home tab root exits” convention for per-tab navigator stacks.

Not a navigator: it ranks after every navigator in the back arbitration (R44-back’s fallback rank), whatever the wire order, so a selected tab that can still pop (or a root overlay) always takes the press first.

Construct once — in Component::init or app state — and wrap the subtree it belongs to with back_fallback from every Component::build. Cheaply Clone (Rc-backed); every clone is the same fallback.

Liveness is view-scoped. The fallback claims presses only while at least one back_fallback wrapper over it is mounted: leaving the screen unmounts the wrapper, the fallback stops claiming at once, and the arbitration list releases it (see the module docs’ release rules). Merely holding the handle claims nothing.

The facade’s re-export docs carry a back-to-home-tab example and the accepted residual (a wrapper inside a hidden branch still counts as live).

Implementations§

Source§

impl BackFallback

Source

pub fn new( interest: impl Fn() -> bool + 'static, on_back: impl Fn() + 'static, ) -> Self

Create a fallback that claims a back press while interest() is true (and a back_fallback wrapper over it is mounted), running on_back for each press it receives.

Call once — from Component::init or wherever app state is built, on the UI thread. Seeds the shared consumption marker to the current back-press count, like BackHandler::new, so a press delivered before this fallback existed does not fire it. Registers nothing yet: a fallback joins the arbitration when back_fallback first wraps a view with it.

interest is polled whenever the shell asks whether the app handles back, and before each press is routed, with no back-arbitration borrow outstanding; keep it a cheap, untracked read (get_untracked, NavigatorController::depth). on_back runs during a rebuild’s build pass, like a navigator’s request_back.

Trait Implementations§

Source§

impl Clone for BackFallback

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
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> 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> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, S> SimdFrom<T, S> for T
where S: Simd,

Source§

fn simd_from(_simd: S, value: T) -> T

Source§

impl<F, T, S> SimdInto<T, S> for F
where T: SimdFrom<F, S>, S: Simd,

Source§

fn simd_into(self, simd: S) -> T

Source§

impl<T> StorageAccess<T> for T

Source§

fn as_borrowed(&self) -> &T

Borrows the value.
Source§

fn into_taken(self) -> T

Takes the value.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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, !>

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> 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