use altar::*; use tokio::sync::mpsc; struct BinaryTutorialApp { count: i16, shift: i16, show_alphabet: bool, show_colors: bool, show_two_complement: bool, } impl Default for BinaryTutorialApp { fn default() -> Self { Self { count: 0, shift: 0, show_alphabet: false, show_colors: false, show_two_complement: false, } } } enum Message {} impl BinaryTutorialApp { fn handle_key_event( &mut self, key_event: KeyEvent, _tx: &mpsc::UnboundedSender, ) -> bool { match key_event.code { KeyCode::Down => { if self.count == 0 { self.count = 15; } else { self.count = self.count.saturating_sub(1); } } KeyCode::Up => { self.count = self.count.saturating_add(1); } KeyCode::Left => { self.shift = self.shift.saturating_sub(1); } KeyCode::Right => { self.shift = self.shift.saturating_add(1); } KeyCode::Char('q') => return false, KeyCode::Char('a') => self.show_alphabet = !self.show_alphabet, KeyCode::Char('c') => self.show_colors = !self.show_colors, KeyCode::Char('t') => self.show_two_complement = !self.show_two_complement, KeyCode::Char(digit) if digit.is_digit(10) => { let digit = digit.to_digit(10).unwrap(); self.count = digit as i16; self.shift = 0; } _ => {} } // wrap count around if greater than 16 self.count = self.count % 16; true } } // 0000 0000 fn render_binary(n: i16) -> impl View { let bits = (0..4) .map(|i| { if n & (1 << (3 - i)) != 0 { "1".to_string() } else { "0".to_string() } }) .collect::>(); render_helper(bits) } fn render_numbers(n: i16, shift: i16) -> impl View { let numbers = (0..16) .enumerate() .map(|(idx, i)| { let number = i as i16 + shift; text(number.to_string()) .bold_when(idx as i16 == n) .dim_when(idx as i16 != n) .id(idx as i16) }) .collect::>(); hstack(numbers).spacing(1) } fn render_two_complement_numbers(n: i16) -> impl View { let numbers = (0..16) .enumerate() .map(|(idx, i)| { let number = if i >= 8 { i as i16 - 16 } else { i as i16 }; text(number.to_string()) .bold_when(idx as i16 == n) .dim_when(idx as i16 != n) .id(idx as i16) }) .collect::>(); hstack(numbers).spacing(1) } fn render_alphabet(n: i16, shift: i16) -> impl View { let alphabet = (0..16) .enumerate() .map(|(idx, i)| { let letter = (((i as i16 + shift) % 26 + 26) % 26 + 65) as u8 as char; text(letter.to_string()) .bold_when(idx as i16 == n) .dim_when(idx as i16 != n) .id(idx as i16) }) .collect::>(); hstack(alphabet).spacing(1) } fn render_colors(selected: i16, shift: i16) -> impl View { let colors = [ Color::Red, Color::Green, Color::Yellow, Color::Blue, Color::Magenta, Color::Cyan, Color::Red, Color::Green, Color::Yellow, Color::Blue, Color::Magenta, Color::Cyan, Color::Red, Color::Green, Color::Yellow, Color::Blue, ]; let blocks = (0..16) .enumerate() .map(|(idx, i)| { let is_selected = idx as i16 == selected; let color = colors[((i + shift + 16) % 16) as usize]; // Ensure wrapping both ways let char = if is_selected { '█' } else { '░' }; text(char.to_string()).color(color).id(idx as i16) }) .collect::>(); hstack(blocks).spacing(1) } fn render_helper(parts: Vec) -> impl View { let mut leading_zero = true; let parts = parts .into_iter() .enumerate() .map(|(idx, s)| { let is_leading_zero = leading_zero && s == "0"; if !is_leading_zero { leading_zero = false; } text(s).dim_when(is_leading_zero).id(idx as i16) }) .collect::>(); hstack(parts) } impl AsyncTerminalApp for BinaryTutorialApp { type Message = Message; fn render(&self) -> impl View { vstack(( render_numbers(self.count, self.shift) .border() .title(" Numbers ") .border_style(BorderStyle::Rounded), render_two_complement_numbers(self.count) .border() .title(" Two's Complement ") .border_style(BorderStyle::Rounded) .visible(self.show_two_complement), render_alphabet(self.count, self.shift) .border() .title(" Alphabet ") .border_style(BorderStyle::Rounded) .visible(self.show_alphabet), render_colors(self.count, self.shift) .border() .title(" Colors ") .border_style(BorderStyle::Rounded) .visible(self.show_colors), render_binary(self.count) .border() .title(" Binary ") .border_style(BorderStyle::Rounded), )) .fill_horizontally() .fill_vertically() .padding_h(8) .padding_v(4) // .alignment(HorizontalAlignment::Center) // .center() } fn update( &mut self, event: Event, tx: &mpsc::UnboundedSender, ) -> bool { match event { Event::Key(key_event) => self.handle_key_event(key_event, tx), Event::Message(_) => true, } } } #[tokio::main] async fn main() { let mut app = BinaryTutorialApp::default(); app.run(true).await; }