use fission::prelude::*;
use fission_onboarding::{OnboardingAnchor, OnboardingFlow, OnboardingHost, OnboardingStep};
#[derive(Clone, Debug, Default)]
struct State {
name: String,
created: bool,
step: usize,
done: bool,
}
impl GlobalState for State {}
#[fission_reducer(ChangeName)]
fn change_name(state: &mut State, ctx: &mut ReducerContext<State>) {
if let Some(change) = ctx.input.text_change() {
state.name = change.new_text.clone();
}
}
#[fission_reducer(CreateItem)]
fn create_item(state: &mut State) {
if !state.name.trim().is_empty() {
state.created = true;
state.step = 2;
}
}
#[fission_reducer(NextStep)]
fn next_step(state: &mut State) {
if state.step == 0 && !state.name.trim().is_empty() {
state.step = 1;
} else if state.step == 1 && state.created {
state.step = 2;
}
}
#[fission_reducer(BackStep)]
fn back_step(state: &mut State) {
state.step = state.step.saturating_sub(1);
}
#[fission_reducer(FinishTour)]
fn finish_tour(state: &mut State) {
state.done = true;
}
#[derive(Clone)]
struct App;
impl From<App> for Widget {
fn from(_: App) -> Widget {
let (ctx, view) = fission::build::current::<State>();
let state = view.state();
let field = WidgetId::explicit("demo.item-name");
let create = WidgetId::explicit("demo.create-item");
let result = WidgetId::explicit("demo.created-item");
let flow = OnboardingFlow::new(
"first-item",
1,
vec![
OnboardingStep {
id: "name".into(),
anchor: field,
route: None,
title: "Name your item".into(),
body: "Type a name into the highlighted field.".into(),
},
OnboardingStep {
id: "create".into(),
anchor: create,
route: None,
title: "Create it".into(),
body: "Press the real Create button to finish this task.".into(),
},
OnboardingStep {
id: "result".into(),
anchor: result,
route: None,
title: "Here it is".into(),
body: "Your result appears in the app, not in a tour mockup.".into(),
},
],
)
.expect("valid tour");
let mut children = widgets![
Text::new("Your items"),
OnboardingAnchor {
id: field,
child: TextInput {
label: Some("Item name".into()),
value: state.name.clone(),
on_input: Some(ctx.bind(ChangeName, reduce_with!(change_name))),
..Default::default()
}
.into(),
},
OnboardingAnchor {
id: create,
child: Button {
child: Some(Text::new("Create item").into()),
on_press: Some(ctx.bind(CreateItem, reduce_with!(create_item))),
disabled: state.name.trim().is_empty(),
..Default::default()
}
.into(),
},
OnboardingAnchor {
id: result,
child: Text::new(if state.created {
format!("Created: {}", state.name)
} else {
"No item yet".into()
})
.into(),
},
];
if !state.done {
let finish = ctx.bind(FinishTour, reduce_with!(finish_tour));
let mut tour = OnboardingHost::new(
"first-item",
flow,
state.step,
ctx.bind(NextStep, reduce_with!(next_step)),
finish.clone(),
);
tour.labels.progress = format!("Step {} of 3", state.step + 1).into();
tour.continue_enabled = match state.step {
0 => !state.name.trim().is_empty(),
1 => state.created,
_ => true,
};
tour.on_back = (state.step > 0).then(|| ctx.bind(BackStep, reduce_with!(back_step)));
tour.on_skip = Some(finish);
children.push(tour.into());
}
Container::new(Column {
gap: Some(20.0),
children,
..Default::default()
})
.padding_all(32.0)
.into()
}
}
fn main() {
DesktopApp::<State, _>::new(App).run().expect("start app");
}