use std::io::{stdout, Write, stdin};
use crate::{TerminalMenu, TerminalMenuStruct, TMIKind, utils, back_button, PrintState};
use crossterm::*;
use strip_ansi_escapes::strip_str;
pub fn run(menu: TerminalMenu) {
{
let mut menu_wr = menu.write().unwrap();
menu_wr.active = true;
menu_wr.exited = false;
menu_wr.canceled = false;
menu_wr.longest_name = menu_wr.items.iter().map(|a| a.name.len()).max().unwrap();
print(&mut menu_wr);
}
terminal::enable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Hide
).unwrap();
while menu.read().unwrap().active {
handle_input(&menu);
}
terminal::disable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Show,
).unwrap();
{
let mut menu_wr = menu.write().unwrap();
execute!(
stdout(),
terminal::LeaveAlternateScreen
).unwrap();
menu_wr.printed = PrintState::None;
menu_wr.exited = true;
}
}
fn print(menu_wr: &mut TerminalMenuStruct) {
print_big(menu_wr);
}
fn print_big(menu: &mut TerminalMenuStruct) {
let term_height = utils::term_height();
if term_height <= 3 {
return;
}
if let PrintState::None = menu.printed {
queue!(
stdout(),
terminal::EnterAlternateScreen
).unwrap();
}
queue!(
stdout(),
cursor::MoveTo(0, 0),
terminal::Clear(terminal::ClearType::All),
style::Print("..."),
).unwrap();
println!("\r");
let item_count = menu.items.len().min(term_height - 3);
let mut top = 0;
if menu.selected > item_count / 2 {
top = menu.selected - item_count / 2;
if top + item_count > menu.items.len() {
top = menu.items.len() - item_count;
}
}
for i in top..(top + item_count) {
print_item(menu, i);
println!("\r");
}
println!("...");
menu.printed = PrintState::Big;
}
fn print_item(menu: &TerminalMenuStruct, index: usize) {
if menu.selected == index {
queue!(
stdout(),
crossterm::style::SetForegroundColor(crossterm::style::Color::Cyan),
crossterm::style::Print("> "),
crossterm::style::Print(&menu.items[index].name),
).unwrap();
} else {
queue!(
stdout(),
crossterm::style::Print(" "),
crossterm::style::SetForegroundColor(menu.items[index].color),
crossterm::style::Print(&menu.items[index].name)
).unwrap();
}
for _ in menu.items[index].name.len()..menu.longest_name + 5 {
queue!(
stdout(),
crossterm::style::Print(" ")
).unwrap();
}
match &menu.items[index].kind {
TMIKind::Label |
TMIKind::Button |
TMIKind::BackButton |
TMIKind::Submenu(_) => {}
TMIKind::List { values, selected } => {
for (i, item) in values.iter().enumerate() {
if i == *selected {
queue!(
stdout(),
style::Print("["),
style::Print(&item),
style::Print("]")
).unwrap();
} else {
queue!(
stdout(),
style::Print(" "),
style::Print(&item),
style::Print(" ")
).unwrap();
}
}
}
TMIKind::Scroll { values, selected } => {
queue!(
stdout(),
style::Print(" "),
style::Print(values.get(*selected).unwrap()),
).unwrap();
}
TMIKind::String { value, .. } => {
queue!(
stdout(),
style::Print(" "),
style::Print(value)
).unwrap();
}
TMIKind::Password { value, .. } => {
queue!(
stdout(),
style::Print(" "),
style::Print("*".repeat(value.len()))
).unwrap();
}
TMIKind::Numeric { value, .. } => {
queue!(
stdout(),
style::Print(" "),
style::Print(value)
).unwrap()
}
}
queue!(
stdout(),
style::ResetColor
).unwrap();
}
fn handle_input(menu: &TerminalMenu) {
while crossterm::event::poll(*utils::INTERVAL).unwrap() {
match crossterm::event::read().unwrap() {
crossterm::event::Event::Key(crossterm::event::KeyEvent { code, kind: crossterm::event::KeyEventKind::Press, .. }) => {
let mut menu_wr = menu.write().unwrap();
let selected = menu_wr.selected;
use crossterm::event::KeyCode::*;
match code {
Up | Char('w') | Char('k') => {
let new = dec(&menu_wr, selected);
select(&mut menu_wr, new);
},
Down | Char('s') | Char('j') => {
let new = inc(&menu_wr, selected);
select(&mut menu_wr, new);
},
Left | Char('a') | Char('h') => dec_value(&mut menu_wr),
Right | Char('d') | Char('l') => inc_value(&mut menu_wr),
Enter | Char(' ') => handle_enter(&mut menu_wr),
Esc | Char('q') => {
menu_wr.active = false;
menu_wr.exit = menu_wr.name.clone();
menu_wr.canceled = true;
return;
},
_ => {}
}
}
event::Event::Resize(_, _) => {
print(&mut menu.write().unwrap());
}
_ => {}
}
}
}
fn select(menu: &mut TerminalMenuStruct, index: usize) {
menu.selected = index;
print_big(menu);
stdout().flush().unwrap();
}
fn inc(menu: &TerminalMenuStruct, mut index: usize) -> usize {
index += 1;
if index == menu.items.len() {
index = 0;
}
if let TMIKind::Label = menu.items[index].kind {
inc(menu, index)
} else {
index
}
}
fn dec(menu: &TerminalMenuStruct, mut index: usize) -> usize {
if index == 0 {
index = menu.items.len() - 1;
} else {
index -= 1
}
if let TMIKind::Label = menu.items[index].kind {
dec(menu, index)
} else {
index
}
}
fn handle_enter(menu: &mut TerminalMenuStruct) {
let _item_count = menu.items.len();
match &mut menu.items[menu.selected].kind {
TMIKind::Button => {
menu.exit = menu.name.clone();
menu.active = false;
}
TMIKind::BackButton => {
menu.active = false;
}
TMIKind::Scroll { selected, values } |
TMIKind::List { selected, values } => {
let temp_menu =
crate::menu(values.iter().enumerate().map(|(i, s)|
if i == *selected {
back_button(s).colorize(style::Color::Green)
} else {
back_button(s)
}
).collect());
temp_menu.write().unwrap().selected = *selected;
crate::run(&temp_menu);
*selected = temp_menu.read().unwrap().selected;
menu.printed = PrintState::None;
print(menu);
terminal::enable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Hide
).unwrap();
}
TMIKind::String { value, allow_empty } | TMIKind::Password { value, allow_empty } => {
queue!(
stdout(),
cursor::MoveToNextLine(1000)
).unwrap();
print!(": ");
stdout().flush().unwrap();
terminal::disable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Show,
).unwrap();
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
input = strip_str(input.trim());
terminal::enable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Hide,
).unwrap();
utils::unprint(1);
if *allow_empty || !input.is_empty() {
*value = input;
}
print(menu);
}
TMIKind::Numeric { value, step, min, max } => {
queue!(
stdout(),
cursor::MoveToNextLine(1000)
).unwrap();
utils::number_range_indicator(*step, *min, *max);
stdout().flush().unwrap();
terminal::disable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Show,
).unwrap();
let mut input = String::new();
stdin().read_line(&mut input).unwrap();
terminal::enable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Hide,
).unwrap();
utils::unprint(1);
if let Ok(input) = input.trim().parse() {
if utils::value_valid(input, *step, *min, *max) {
*value = input;
}
}
print(menu);
}
TMIKind::Submenu(submenu) => {
crate::run(submenu);
if let Some(exit_menu) = &submenu.clone().read().unwrap().exit {
menu.exit = Some(exit_menu.clone());
menu.canceled = submenu.read().unwrap().canceled;
menu.active = false;
} else {
menu.printed = PrintState::None;
print(menu);
terminal::enable_raw_mode().unwrap();
execute!(
stdout(),
cursor::Hide
).unwrap();
}
}
_ => {}
}
}
fn inc_value(menu: &mut TerminalMenuStruct) {
match &mut menu.items[menu.selected].kind {
TMIKind::Scroll { values, selected } |
TMIKind::List { values, selected }=> {
*selected += 1;
if *selected == values.len() {
*selected = 0;
}
}
TMIKind::Numeric { value, step, max, .. } => {
if let Some(step) = step {
*value += *step;
if let Some(max) = max {
if *value > *max {
*value = *max;
}
}
}
}
_ => return
}
print(menu);
}
fn dec_value(menu: &mut TerminalMenuStruct) {
match &mut menu.items[menu.selected].kind {
TMIKind::Scroll { values, selected } |
TMIKind::List { values, selected }=> {
if *selected == 0 {
*selected = values.len() - 1;
} else {
*selected -= 1;
}
}
TMIKind::Numeric { value, step, min, .. } => {
if let Some(step) = step {
*value -= *step;
if let Some(min) = min {
if *value < *min {
*value = *min;
}
}
}
}
_ => return
}
print(menu);
}