use std::cell::RefCell;
use std::rc::Rc;
use teksilo_core::signal::Signal;
use super::step::StepStatus;
struct StepperState {
step_count: usize,
statuses: Vec<StepStatus>,
initial_statuses: Vec<StepStatus>,
visible: Vec<bool>,
visit_history: Vec<usize>,
visited: Vec<bool>,
skipped: Vec<bool>,
seeded: bool,
}
impl StepperState {
fn reachable(&self, idx: usize) -> bool {
self.visible.get(idx).copied().unwrap_or(false)
&& !matches!(self.statuses.get(idx), Some(StepStatus::Disabled))
}
fn next_reachable(&self, from: usize) -> Option<usize> {
((from + 1)..self.step_count).find(|&i| self.reachable(i))
}
fn first_reachable(&self) -> Option<usize> {
(0..self.step_count).find(|&i| self.reachable(i))
}
}
#[derive(Clone)]
pub struct StepperController {
inner: Rc<RefCell<StepperState>>,
current: Signal<usize>,
version: Signal<u64>,
}
impl StepperController {
pub fn new(step_count: usize) -> Self {
let mut visited = vec![false; step_count];
if step_count > 0 {
visited[0] = true;
}
Self {
inner: Rc::new(RefCell::new(StepperState {
step_count,
statuses: vec![StepStatus::Upcoming; step_count],
initial_statuses: vec![StepStatus::Upcoming; step_count],
visible: vec![true; step_count],
visit_history: Vec::new(),
visited,
skipped: vec![false; step_count],
seeded: false,
})),
current: Signal::new(0),
version: Signal::new(0),
}
}
pub(crate) fn seed_statuses(&self, statuses: Vec<StepStatus>) {
{
let mut st = self.inner.borrow_mut();
if st.seeded {
return;
}
if statuses.len() == st.step_count {
st.initial_statuses = statuses.clone();
st.statuses = statuses;
}
st.seeded = true;
}
let cur = self.current.get();
self.mark_active(cur);
}
fn bump(&self) {
self.version.set(self.version.get().wrapping_add(1));
}
fn mark_active(&self, idx: usize) {
{
let mut st = self.inner.borrow_mut();
for (i, s) in st.statuses.iter_mut().enumerate() {
if *s == StepStatus::Active && i != idx {
*s = StepStatus::Complete;
}
}
if let Some(s) = st.statuses.get_mut(idx) {
if !matches!(*s, StepStatus::Error | StepStatus::Disabled) {
*s = StepStatus::Active;
}
}
}
self.bump();
}
pub fn next(&self) {
let cur = self.current.get();
let dest = {
let mut st = self.inner.borrow_mut();
let Some(dest) = st.next_reachable(cur) else {
return;
};
st.visit_history.push(cur);
if let Some(v) = st.visited.get_mut(dest) {
*v = true;
}
dest
};
self.current.set(dest);
self.mark_active(dest);
}
pub fn skip(&self) {
let cur = self.current.get();
{
let mut st = self.inner.borrow_mut();
if let Some(sk) = st.skipped.get_mut(cur) {
*sk = true;
}
if let Some(s) = st.statuses.get_mut(cur) {
*s = StepStatus::Skipped;
}
}
self.next();
}
pub fn back(&self) {
let dest = {
let mut st = self.inner.borrow_mut();
loop {
match st.visit_history.pop() {
Some(i) if st.reachable(i) => break Some(i),
Some(_) => continue,
None => break None,
}
}
};
if let Some(dest) = dest {
self.current.set(dest);
self.mark_active(dest);
}
}
pub fn go_to(&self, idx: usize) {
let cur = self.current.get();
{
let mut st = self.inner.borrow_mut();
if idx == cur || !st.reachable(idx) {
return;
}
st.visit_history.push(cur);
if let Some(v) = st.visited.get_mut(idx) {
*v = true;
}
}
self.current.set(idx);
self.mark_active(idx);
}
pub fn reset(&self) {
let dest = {
let mut st = self.inner.borrow_mut();
let n = st.step_count;
st.statuses = st.initial_statuses.clone();
st.visit_history.clear();
st.visited = vec![false; n];
st.skipped = vec![false; n];
let dest = st.first_reachable().unwrap_or(0);
if let Some(v) = st.visited.get_mut(dest) {
*v = true;
}
dest
};
self.current.set(dest);
self.mark_active(dest);
}
pub fn set_status(&self, idx: usize, status: StepStatus) {
{
let mut st = self.inner.borrow_mut();
if let Some(s) = st.statuses.get_mut(idx) {
*s = status;
}
}
self.bump();
}
pub fn set_visible(&self, idx: usize, visible: bool) {
{
let mut st = self.inner.borrow_mut();
match st.visible.get_mut(idx) {
Some(v) if *v == visible => return,
Some(v) => *v = visible,
None => return,
}
}
self.bump();
}
pub fn current(&self) -> usize {
self.current.get()
}
pub fn status(&self, idx: usize) -> StepStatus {
self.inner
.borrow()
.statuses
.get(idx)
.copied()
.unwrap_or_default()
}
pub fn visited(&self, idx: usize) -> bool {
self.inner
.borrow()
.visited
.get(idx)
.copied()
.unwrap_or(false)
}
pub fn skipped(&self, idx: usize) -> bool {
self.inner
.borrow()
.skipped
.get(idx)
.copied()
.unwrap_or(false)
}
pub fn is_visible(&self, idx: usize) -> bool {
self.inner
.borrow()
.visible
.get(idx)
.copied()
.unwrap_or(false)
}
pub fn is_reachable(&self, idx: usize) -> bool {
self.inner.borrow().reachable(idx)
}
pub fn next_reachable(&self, from: usize) -> Option<usize> {
self.inner.borrow().next_reachable(from)
}
pub fn has_next(&self) -> bool {
let cur = self.current.get();
self.inner.borrow().next_reachable(cur).is_some()
}
pub fn step_count(&self) -> usize {
self.inner.borrow().step_count
}
pub fn can_back(&self) -> bool {
let st = self.inner.borrow();
st.visit_history.iter().any(|&i| st.reachable(i))
}
pub fn current_step_signal(&self) -> Signal<usize> {
self.current.clone()
}
pub fn version_signal(&self) -> Signal<u64> {
self.version.clone()
}
}
impl std::fmt::Debug for StepperController {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StepperController")
.field("current", &self.current.get())
.field("step_count", &self.step_count())
.finish()
}
}