use std::cell::RefCell;
use std::rc::Rc;
use teksilo_core::signal::{Prop, Signal};
use teksilo_core::widget::EventContext;
use teksilo_core::widget_id::WidgetId;
use super::controller::StepperController;
use super::step::{StepStatus, StepValidator};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FinishOutcome {
Finished,
Rejected,
}
pub trait IntoFinishOutcome {
fn into_finish_outcome(self) -> FinishOutcome;
}
impl IntoFinishOutcome for () {
fn into_finish_outcome(self) -> FinishOutcome {
FinishOutcome::Finished
}
}
impl IntoFinishOutcome for bool {
fn into_finish_outcome(self) -> FinishOutcome {
if self {
FinishOutcome::Finished
} else {
FinishOutcome::Rejected
}
}
}
impl IntoFinishOutcome for FinishOutcome {
fn into_finish_outcome(self) -> FinishOutcome {
self
}
}
impl<T, E> IntoFinishOutcome for Result<T, E> {
fn into_finish_outcome(self) -> FinishOutcome {
match self {
Ok(_) => FinishOutcome::Finished,
Err(_) => FinishOutcome::Rejected,
}
}
}
pub(crate) type FinishAction = Rc<dyn Fn(&mut EventContext, &StepperController) -> FinishOutcome>;
pub(crate) struct StepNav {
controller: StepperController,
validators: Vec<Option<StepValidator>>,
completion: Vec<Option<Prop<bool>>>,
finish_action: Option<FinishAction>,
next_focus: RefCell<Option<WidgetId>>,
finish_focus: RefCell<Option<WidgetId>>,
}
impl StepNav {
pub(crate) fn new(
controller: StepperController,
validators: Vec<Option<StepValidator>>,
completion: Vec<Option<Prop<bool>>>,
finish_action: Option<FinishAction>,
) -> Self {
Self {
controller,
validators,
completion,
finish_action,
next_focus: RefCell::new(None),
finish_focus: RefCell::new(None),
}
}
pub(crate) fn controller(&self) -> &StepperController {
&self.controller
}
pub(crate) fn set_focus_targets(&self, next: WidgetId, finish: WidgetId) {
*self.next_focus.borrow_mut() = Some(next);
*self.finish_focus.borrow_mut() = Some(finish);
}
pub(crate) fn gate_open(&self, idx: usize) -> bool {
self.completion
.get(idx)
.and_then(|c| c.as_ref())
.map(|p| p.get())
.unwrap_or(true)
}
pub(crate) fn completion_signals(&self) -> Vec<Signal<bool>> {
self.completion
.iter()
.map(|c| {
c.as_ref()
.map(|p| p.as_signal())
.unwrap_or_else(|| Signal::new(true))
})
.collect()
}
fn validates(&self, idx: usize) -> bool {
match self.validators.get(idx) {
Some(Some(v)) => v(),
_ => true,
}
}
pub(crate) fn focus_primary(&self, ctx: &mut EventContext) {
let target = if self.controller.has_next() {
*self.next_focus.borrow()
} else {
*self.finish_focus.borrow()
};
if let Some(t) = target {
ctx.request_focus(t);
}
}
pub(crate) fn advance(&self, ctx: &mut EventContext) -> bool {
let i = self.controller.current();
if !self.gate_open(i) {
return false;
}
if !self.validates(i) {
self.controller.set_status(i, StepStatus::Error);
return false;
}
self.controller.set_status(i, StepStatus::Complete);
self.controller.next();
self.focus_primary(ctx);
true
}
pub(crate) fn finish(&self, ctx: &mut EventContext) -> FinishOutcome {
let i = self.controller.current();
if !self.gate_open(i) {
return FinishOutcome::Rejected;
}
if !self.validates(i) {
self.controller.set_status(i, StepStatus::Error);
return FinishOutcome::Rejected;
}
let outcome = match &self.finish_action {
Some(action) => action(ctx, &self.controller),
None => FinishOutcome::Finished,
};
match outcome {
FinishOutcome::Finished => self.controller.set_status(i, StepStatus::Complete),
FinishOutcome::Rejected => self.controller.set_status(i, StepStatus::Error),
}
outcome
}
pub(crate) fn activate_primary(&self, ctx: &mut EventContext) -> bool {
if self.controller.has_next() {
self.advance(ctx)
} else {
self.finish(ctx) == FinishOutcome::Finished
}
}
}
impl std::fmt::Debug for StepNav {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StepNav")
.field("steps", &self.validators.len())
.finish()
}
}