diff options
Diffstat (limited to 'src/client/ui.rs')
| -rw-r--r-- | src/client/ui.rs | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/src/client/ui.rs b/src/client/ui.rs new file mode 100644 index 0000000..036c36a --- /dev/null +++ b/src/client/ui.rs | |||
| @@ -0,0 +1,143 @@ | |||
| 1 | //! Pure text/history helpers for the client TUI. | ||
| 2 | //! | ||
| 3 | //! Nothing here touches the terminal, so it is unit-tested from `tests/` | ||
| 4 | //! without a PTY. | ||
| 5 | |||
| 6 | struct CommandHelp { | ||
| 7 | name: &'static str, | ||
| 8 | usage: &'static str, | ||
| 9 | description: &'static str, | ||
| 10 | } | ||
| 11 | |||
| 12 | const COMMANDS: &[CommandHelp] = &[ | ||
| 13 | CommandHelp { | ||
| 14 | name: "connect", | ||
| 15 | usage: "connect <ip>:<port>", | ||
| 16 | description: "connect to the server", | ||
| 17 | }, | ||
| 18 | CommandHelp { | ||
| 19 | name: "send", | ||
| 20 | usage: "send <message>", | ||
| 21 | description: "send a message to the server", | ||
| 22 | }, | ||
| 23 | CommandHelp { | ||
| 24 | name: "disconnect", | ||
| 25 | usage: "disconnect", | ||
| 26 | description: "disconnect from the server", | ||
| 27 | }, | ||
| 28 | CommandHelp { | ||
| 29 | name: "ping", | ||
| 30 | usage: "ping", | ||
| 31 | description: "ping the server", | ||
| 32 | }, | ||
| 33 | CommandHelp { | ||
| 34 | name: "login", | ||
| 35 | usage: "login", | ||
| 36 | description: "login to the server", | ||
| 37 | }, | ||
| 38 | CommandHelp { | ||
| 39 | name: "register", | ||
| 40 | usage: "register", | ||
| 41 | description: "register a new user", | ||
| 42 | }, | ||
| 43 | CommandHelp { | ||
| 44 | name: "setcred", | ||
| 45 | usage: "setcred", | ||
| 46 | description: "set credentials for a host", | ||
| 47 | }, | ||
| 48 | CommandHelp { | ||
| 49 | name: "logout", | ||
| 50 | usage: "logout", | ||
| 51 | description: "logout from the server", | ||
| 52 | }, | ||
| 53 | CommandHelp { | ||
| 54 | name: "info", | ||
| 55 | usage: "info", | ||
| 56 | description: "show account and connection info", | ||
| 57 | }, | ||
| 58 | CommandHelp { | ||
| 59 | name: "play", | ||
| 60 | usage: "play <game> [bet]", | ||
| 61 | description: "start a game (e.g. play slots 50)", | ||
| 62 | }, | ||
| 63 | CommandHelp { | ||
| 64 | name: "games", | ||
| 65 | usage: "games", | ||
| 66 | description: "list available games", | ||
| 67 | }, | ||
| 68 | CommandHelp { | ||
| 69 | name: "help", | ||
| 70 | usage: "help", | ||
| 71 | description: "show this message", | ||
| 72 | }, | ||
| 73 | CommandHelp { | ||
| 74 | name: "forget-host", | ||
| 75 | usage: "forget-host [ip:port]", | ||
| 76 | description: "clear saved trust fingerprint and credentials", | ||
| 77 | }, | ||
| 78 | CommandHelp { | ||
| 79 | name: "hosts", | ||
| 80 | usage: "hosts", | ||
| 81 | description: "list remembered hosts", | ||
| 82 | }, | ||
| 83 | CommandHelp { | ||
| 84 | name: "quit", | ||
| 85 | usage: "quit", | ||
| 86 | description: "quit the program", | ||
| 87 | }, | ||
| 88 | ]; | ||
| 89 | |||
| 90 | pub fn command_names() -> Vec<&'static str> { | ||
| 91 | COMMANDS.iter().map(|command| command.name).collect() | ||
| 92 | } | ||
| 93 | |||
| 94 | pub fn help_lines() -> Vec<String> { | ||
| 95 | let mut lines = vec!["Available commands:".to_string()]; | ||
| 96 | for command in COMMANDS { | ||
| 97 | lines.push(format!( | ||
| 98 | " {:<28} - {}", | ||
| 99 | command.usage, command.description | ||
| 100 | )); | ||
| 101 | } | ||
| 102 | lines | ||
| 103 | } | ||
| 104 | |||
| 105 | /// Add a command to the history, skipping blanks and consecutive duplicates. | ||
| 106 | pub fn record_history(history: &mut Vec<String>, line: &str) { | ||
| 107 | let line = line.trim(); | ||
| 108 | if line.is_empty() { | ||
| 109 | return; | ||
| 110 | } | ||
| 111 | if history.last().is_some_and(|last| last == line) { | ||
| 112 | return; | ||
| 113 | } | ||
| 114 | history.push(line.to_string()); | ||
| 115 | } | ||
| 116 | |||
| 117 | /// Parse the optional bet argument of `play <game> [bet]`. | ||
| 118 | pub fn parse_bet_arg(raw: &str) -> Result<u64, String> { | ||
| 119 | let bet = raw | ||
| 120 | .trim() | ||
| 121 | .parse::<u64>() | ||
| 122 | .map_err(|_| format!("Invalid bet: {raw}"))?; | ||
| 123 | if bet < 10 { | ||
| 124 | return Err("Bet must be at least 10 PLN".to_string()); | ||
| 125 | } | ||
| 126 | Ok(bet) | ||
| 127 | } | ||
| 128 | |||
| 129 | /// Lines to move the cursor up to reach the first line of a region that ends | ||
| 130 | /// on the current line. `render()` has no trailing newline, so a 7-line board | ||
| 131 | /// needs `MoveUp(6)`. | ||
| 132 | pub fn lines_to_move_up(previous_lines: usize) -> u16 { | ||
| 133 | previous_lines | ||
| 134 | .saturating_sub(1) | ||
| 135 | .min(u16::MAX as usize) as u16 | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Column (0-based) to move to after the given prompt and input text. | ||
| 139 | pub fn input_cursor_column(prompt_len: usize, input_len: usize) -> u16 { | ||
| 140 | prompt_len | ||
| 141 | .saturating_add(input_len) | ||
| 142 | .min(u16::MAX as usize) as u16 | ||
| 143 | } | ||
