pub fn back_fallback<V>(
fallback: &BackFallback,
child: V,
) -> BackFallbackView<V>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.
Wrap child so fallback takes part in back arbitration while this view is
mounted — the view-scoped half of BackFallback.
Call from every Component::build, exactly like
navigator: at call time it consumes any new back press
(the tracked read that subscribes the rebuild, routed under R44-back, so the
fallback receives it only if no navigator claims it) and then registers or
refreshes the fallback at the fallback rank. The returned
BackFallbackView is a pass-through: it renders child unchanged and
counts its own mounts, which is the fallback’s liveness. It is a View
for every State its child is one for, so the function itself names no
State and a state-agnostic child (a text) needs no annotation.
The facade’s re-export docs carry a back-to-home-tab example.
§Panics
Runs during a rebuild’s build pass; the first wire registers the live
handles_back provider, which panics off the UI thread (always the UI
thread here).