use fission::prelude::*;
use crate::{
OnboardingFlow, OnboardingStyle, card::OnboardingCard, focus_ring::SpotlightFocusRing,
};
#[derive(Clone, Debug, PartialEq)]
pub struct OnboardingLabels {
pub progress: TextContent,
pub back: TextContent,
pub continue_label: TextContent,
pub finish: TextContent,
pub skip: TextContent,
pub dialog_label: String,
}
impl Default for OnboardingLabels {
fn default() -> Self {
Self {
progress: "Getting started".into(),
back: "Back".into(),
continue_label: "Continue".into(),
finish: "Finish".into(),
skip: "Skip tour".into(),
dialog_label: "Guided tour".into(),
}
}
}
#[derive(Clone)]
pub struct OnboardingHost {
pub id: String,
pub flow: OnboardingFlow,
pub current_step: usize,
pub labels: OnboardingLabels,
pub style: OnboardingStyle,
pub busy: bool,
pub continue_enabled: bool,
pub contextual_action: Option<(TextContent, ActionEnvelope)>,
pub contextual_status: Option<TextContent>,
pub on_back: Option<ActionEnvelope>,
pub on_continue: ActionEnvelope,
pub on_skip: Option<ActionEnvelope>,
pub on_complete: ActionEnvelope,
}
impl OnboardingHost {
pub fn new(
id: impl Into<String>,
flow: OnboardingFlow,
current_step: usize,
on_continue: ActionEnvelope,
on_complete: ActionEnvelope,
) -> Self {
Self {
id: id.into(),
flow,
current_step,
labels: OnboardingLabels::default(),
style: OnboardingStyle::default(),
busy: false,
continue_enabled: true,
contextual_action: None,
contextual_status: None,
on_back: None,
on_continue,
on_skip: None,
on_complete,
}
}
}
impl From<OnboardingHost> for Widget {
fn from(host: OnboardingHost) -> Self {
let (ctx, _) = fission::build::current::<()>();
let Some(step) = host.flow.step(host.current_step).cloned() else {
return Spacer::default().into();
};
let dim_panel = |suffix: &str| {
GestureDetector {
id: Some(WidgetId::explicit(&format!(
"{}-spotlight-{suffix}",
host.id
))),
child: Container::new(Spacer::default())
.bg(host.style.backdrop)
.into(),
on_tap: None,
..Default::default()
}
.into()
};
let focus_ring: Widget = SpotlightFocusRing {
color: host.style.focus_ring,
width: host.style.focus_ring_width,
}
.into();
let overlay = Positioned {
left: Some(0.0),
top: Some(0.0),
right: Some(0.0),
bottom: Some(0.0),
child: Some(
Spotlight {
anchor: step.anchor,
padding: host.style.spotlight_padding,
children: [
dim_panel("top"),
dim_panel("bottom"),
dim_panel("left"),
dim_panel("right"),
focus_ring,
],
}
.into(),
),
..Default::default()
};
ctx.register_portal_with_layer(
fission::core::PortalLayer::Modal,
Some(WidgetId::explicit(&format!("{}-spotlight", host.id))),
overlay.into(),
);
let card: Widget = OnboardingCard {
id: format!("{}-card", host.id),
step: step.clone(),
labels: host.labels,
style: host.style.clone(),
first_step: host.current_step == 0,
last_step: host.flow.is_last_step(host.current_step),
busy: host.busy,
continue_enabled: host.continue_enabled,
contextual_action: host.contextual_action,
contextual_status: host.contextual_status,
on_back: host.on_back,
on_continue: host.on_continue,
on_skip: host.on_skip,
on_complete: host.on_complete,
}
.into();
let callout: Widget = Column {
gap: Some(0.0),
children: vec![
Spacer {
height: Some(host.style.callout_gap),
..Default::default()
}
.into(),
card,
Spacer {
height: Some(host.style.callout_gap),
..Default::default()
}
.into(),
],
..Default::default()
}
.into();
ctx.register_portal_with_layer(
fission::core::PortalLayer::Flyout,
Some(WidgetId::explicit(&format!("{}-callout", host.id))),
fission::widgets::flyout(step.anchor, callout),
);
Spacer::default().into()
}
}