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
impl BackFallback
Sourcepub fn new(
interest: impl Fn() -> bool + 'static,
on_back: impl Fn() + 'static,
) -> Self
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§
Auto Trait Implementations§
impl !RefUnwindSafe for BackFallback
impl !Send for BackFallback
impl !Sync for BackFallback
impl !UnwindSafe for BackFallback
impl Freeze for BackFallback
impl Unpin for BackFallback
impl UnsafeUnpin for BackFallback
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.