use envision::prelude::*;
struct NumberInputApp;
#[derive(Clone)]
struct State {
quantity: NumberInputState,
price: NumberInputState,
temperature: NumberInputState,
focus_index: usize,
}
#[derive(Clone, Debug)]
enum Msg {
Quantity(NumberInputMessage),
Price(NumberInputMessage),
Temperature(NumberInputMessage),
FocusNext,
FocusPrev,
Quit,
}
impl State {
fn focused_input_mut(&mut self) -> &mut NumberInputState {
match self.focus_index {
0 => &mut self.quantity,
1 => &mut self.price,
_ => &mut self.temperature,
}
}
fn set_all_unfocused(&mut self) {
self.quantity.set_focused(false);
self.price.set_focused(false);
self.temperature.set_focused(false);
}
}
const INPUT_COUNT: usize = 3;
impl App for NumberInputApp {
type State = State;
type Message = Msg;
fn init() -> (State, Command<Msg>) {
let mut quantity = NumberInputState::integer(1)
.with_min(0.0)
.with_max(100.0)
.with_label("Quantity");
quantity.set_focused(true);
let price = NumberInputState::new(9.99)
.with_min(0.0)
.with_step(0.01)
.with_precision(2)
.with_label("Price");
let temperature = NumberInputState::new(22.0)
.with_range(-40.0, 50.0)
.with_step(0.5)
.with_precision(1)
.with_label("Temp");
let state = State {
quantity,
price,
temperature,
focus_index: 0,
};
(state, Command::none())
}
fn update(state: &mut State, msg: Msg) -> Command<Msg> {
match msg {
Msg::Quantity(m) => {
NumberInput::update(&mut state.quantity, m);
}
Msg::Price(m) => {
NumberInput::update(&mut state.price, m);
}
Msg::Temperature(m) => {
NumberInput::update(&mut state.temperature, m);
}
Msg::FocusNext => {
state.set_all_unfocused();
state.focus_index = (state.focus_index + 1) % INPUT_COUNT;
state.focused_input_mut().set_focused(true);
}
Msg::FocusPrev => {
state.set_all_unfocused();
state.focus_index = (state.focus_index + INPUT_COUNT - 1) % INPUT_COUNT;
state.focused_input_mut().set_focused(true);
}
Msg::Quit => return Command::quit(),
}
Command::none()
}
fn view(state: &State, frame: &mut Frame) {
let theme = Theme::default();
let area = frame.area();
let chunks = Layout::vertical([
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Min(0),
Constraint::Length(1),
])
.split(area);
NumberInput::view(
&state.quantity,
frame,
chunks[0],
&theme,
&ViewContext::default(),
);
NumberInput::view(
&state.price,
frame,
chunks[1],
&theme,
&ViewContext::default(),
);
NumberInput::view(
&state.temperature,
frame,
chunks[2],
&theme,
&ViewContext::default(),
);
let status = format!(
" Qty: {} | Price: {} | Temp: {} | Tab: navigate, Enter: edit, q: quit",
state.quantity.format_value(),
state.price.format_value(),
state.temperature.format_value(),
);
frame.render_widget(
ratatui::widgets::Paragraph::new(status).style(Style::default().fg(Color::DarkGray)),
chunks[4],
);
}
fn handle_event_with_state(state: &State, event: &Event) -> Option<Msg> {
let any_editing = state.quantity.is_editing()
|| state.price.is_editing()
|| state.temperature.is_editing();
if let Some(key) = event.as_key() {
if !any_editing {
match key.code {
KeyCode::Char('q') | KeyCode::Esc => return Some(Msg::Quit),
KeyCode::Tab => return Some(Msg::FocusNext),
KeyCode::BackTab => return Some(Msg::FocusPrev),
_ => {}
}
}
}
match state.focus_index {
0 => state.quantity.handle_event(event).map(Msg::Quantity),
1 => state.price.handle_event(event).map(Msg::Price),
_ => state.temperature.handle_event(event).map(Msg::Temperature),
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut vt = Runtime::<NumberInputApp, _>::virtual_terminal(60, 16)?;
println!("=== Number Input Example ===\n");
vt.tick()?;
println!("Initial state:");
println!("{}\n", vt.display());
vt.dispatch(Msg::Quantity(NumberInputMessage::Increment));
vt.dispatch(Msg::Quantity(NumberInputMessage::Increment));
vt.tick()?;
println!("After incrementing quantity twice:");
println!("{}\n", vt.display());
vt.dispatch(Msg::FocusNext);
vt.dispatch(Msg::Price(NumberInputMessage::Increment));
vt.tick()?;
println!("After incrementing price:");
println!("{}\n", vt.display());
vt.dispatch(Msg::FocusNext);
vt.dispatch(Msg::Temperature(NumberInputMessage::SetValue(37.5)));
vt.tick()?;
println!("After setting temperature to 37.5:");
println!("{}\n", vt.display());
Ok(())
}