use crossterm::event::{KeyCode, KeyEvent};
use tty_interface::{pos, Interface, Position};
use crate::{
control::Control,
dependency::{Action, DependencyState},
style::{error_style, muted_style},
text::{
get_segment_length, set_segment_style, set_segment_subset_style, DrawerContents, Segment,
Text,
},
utility::render_segment,
Form,
};
use super::{InputResult, Step};
pub struct CompoundStep {
index: Option<usize>,
controls: Vec<Box<dyn Control>>,
max_line_length: Option<u16>,
active_control: usize,
max_control: usize,
}
impl CompoundStep {
pub fn new() -> Self {
Self {
index: None,
controls: Vec::new(),
max_line_length: None,
active_control: 0,
max_control: 0,
}
}
pub fn add_control(&mut self, control: Box<dyn Control>) {
self.controls.push(control);
}
pub fn set_max_line_length(&mut self, max_length: u16) {
self.max_line_length = Some(max_length);
}
fn advance_control(&mut self) -> bool {
let mut reached_last_control = false;
loop {
if self.active_control + 1 >= self.controls.len() {
reached_last_control = true;
break;
}
self.active_control += 1;
if self.controls[self.active_control].focusable() {
break;
}
}
if self.active_control > self.max_control {
self.max_control = self.active_control;
loop {
if self.max_control + 1 >= self.controls.len() {
break;
}
if !self.controls[self.max_control + 1].focusable() {
self.max_control += 1;
} else {
break;
}
}
}
reached_last_control
}
fn retreat_control(&mut self) -> bool {
loop {
if self.active_control == 0 {
return true;
}
self.active_control -= 1;
if self.controls[self.active_control].focusable() {
break;
}
}
false
}
}
impl Step for CompoundStep {
fn initialize(&mut self, dependency_state: &mut DependencyState, index: usize) {
self.index = Some(index);
if !self.controls[0].focusable() {
self.advance_control();
}
for (control_index, control) in self.controls.iter().enumerate() {
for (id, evaluation) in control.evaluation() {
dependency_state.register_evaluation(&id, index, control_index);
let value = control.evaluate(&evaluation);
dependency_state.update_evaluation(&id, value);
}
}
}
fn render(
&self,
interface: &mut Interface,
dependency_state: &DependencyState,
mut position: Position,
is_focused: bool,
) -> u16 {
interface.clear_line(position.y());
let mut cursor_position = None;
for (control_index, control) in self.controls.iter().enumerate() {
let (mut segment, cursor_offset) = control.text();
if control_index == self.active_control {
if let Some(offset) = cursor_offset {
cursor_position = Some(pos!(position.x() + offset, position.y()));
}
}
let mut should_hide = false;
if let Some((id, action)) = control.dependency() {
let control_touched = control_index <= self.max_control;
let evaluation_result = dependency_state.get_evaluation(&id);
match action {
Action::Hide => {
if control_touched && evaluation_result {
let (step_index, control_index) = dependency_state.get_source(&id);
let source_is_focused = step_index == self.index.unwrap()
&& control_index == self.active_control;
if source_is_focused {
set_segment_style(&mut segment, muted_style());
} else {
should_hide = true;
}
}
}
Action::Show => should_hide = !evaluation_result,
}
}
if let Some(max_length) = self.max_line_length {
let segment_length = get_segment_length(&segment) as u16;
if position.x() + segment_length > max_length {
let error_starts_at = max_length - position.x();
set_segment_subset_style(
&mut segment,
error_starts_at.into(),
segment_length.into(),
error_style(),
);
}
}
if !should_hide {
position = render_segment(interface, position, segment);
}
}
if is_focused {
interface.set_cursor(cursor_position);
}
1
}
fn update(
&mut self,
dependency_state: &mut DependencyState,
input: KeyEvent,
) -> Option<InputResult> {
match input.code {
KeyCode::Enter | KeyCode::Tab => {
if self.advance_control() {
return Some(InputResult::AdvanceForm);
}
}
KeyCode::Esc | KeyCode::BackTab => {
if self.retreat_control() {
return Some(InputResult::RetreatForm);
}
}
_ => {
let control = &mut self.controls[self.active_control];
control.update(input);
if let Some((id, evaluation)) = control.evaluation() {
let value = control.evaluate(&evaluation);
dependency_state.update_evaluation(&id, value);
}
}
}
None
}
fn help(&self) -> Segment {
self.controls[self.active_control]
.help()
.unwrap_or(Text::new(String::new()).as_segment())
}
fn drawer(&self) -> Option<DrawerContents> {
self.controls[self.active_control].drawer()
}
fn result(&self, dependency_state: &DependencyState) -> String {
let mut result = String::new();
for control in &self.controls {
if let Some((id, action)) = control.dependency() {
let evaluation_result = dependency_state.get_evaluation(&id);
match action {
Action::Hide => {
if evaluation_result {
continue;
}
}
Action::Show => {
if !evaluation_result {
continue;
}
}
}
}
let (segments, _) = control.text();
segments
.iter()
.for_each(|text| result.push_str(text.content()));
}
result.push('\n');
result
}
fn add_to(self, form: &mut Form) {
form.add_step(Box::new(self));
}
}