use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph},
Frame, Terminal,
};
use std::io;
use tui_math::MathWidget;
const EXAMPLES: &[(&str, &str)] = &[
("Quadratic Formula", r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}"),
("Euler's Identity", r"e^{i\pi} + 1 = 0"),
("Integral", r"\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}"),
("Sum", r"\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}"),
("Greek Letters", r"\alpha + \beta = \gamma"),
("Matrix-like", r"a_{11} + a_{22} + a_{33}"),
("Fraction", r"\frac{a + b}{c + d}"),
("Square Root", r"\sqrt{x^2 + y^2}"),
("Limits", r"\lim_{x \to \infty} \frac{1}{x} = 0"),
("Derivative", r"\frac{d}{dx} x^n = nx^{n-1}"),
("Product", r"\prod_{i=1}^{n} i = n!"),
("Binomial", r"\binom{n}{k} = \frac{n!}{k!(n-k)!}"),
];
struct App {
current_example: usize,
custom_latex: String,
editing: bool,
}
impl App {
fn new() -> Self {
Self {
current_example: 0,
custom_latex: String::new(),
editing: false,
}
}
fn current_latex(&self) -> &str {
if self.editing {
&self.custom_latex
} else {
EXAMPLES[self.current_example].1
}
}
fn current_title(&self) -> &str {
if self.editing {
"Custom Input"
} else {
EXAMPLES[self.current_example].0
}
}
}
fn main() -> io::Result<()> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let app = App::new();
let res = run_app(&mut terminal, app);
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
eprintln!("Error: {err:?}");
}
Ok(())
}
fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, &app))?;
if let Event::Key(key) = event::read()? {
if key.kind != KeyEventKind::Press {
continue;
}
match key.code {
KeyCode::Char('q') if !app.editing => return Ok(()),
KeyCode::Esc => {
if app.editing {
app.editing = false;
} else {
return Ok(());
}
}
KeyCode::Right | KeyCode::Char('l') if !app.editing => {
app.current_example = (app.current_example + 1) % EXAMPLES.len();
}
KeyCode::Left | KeyCode::Char('h') if !app.editing => {
app.current_example = app.current_example.checked_sub(1).unwrap_or(EXAMPLES.len() - 1);
}
KeyCode::Char('e') if !app.editing => {
app.editing = true;
app.custom_latex = EXAMPLES[app.current_example].1.to_string();
}
KeyCode::Enter if app.editing => {
app.editing = false;
}
KeyCode::Char(c) if app.editing => {
app.custom_latex.push(c);
}
KeyCode::Backspace if app.editing => {
app.custom_latex.pop();
}
_ => {}
}
}
}
}
fn ui(f: &mut Frame, app: &App) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints([
Constraint::Length(3), Constraint::Length(3), Constraint::Min(10), Constraint::Length(3), ])
.split(f.area());
let title = Paragraph::new(Line::from(vec![
Span::styled("tui-math ", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
Span::raw("- "),
Span::styled(app.current_title(), Style::default().fg(Color::Yellow)),
Span::raw(format!(" ({}/{})", app.current_example + 1, EXAMPLES.len())),
]))
.block(Block::default().borders(Borders::ALL).title("Demo"));
f.render_widget(title, chunks[0]);
let source_style = if app.editing {
Style::default().fg(Color::Green)
} else {
Style::default().fg(Color::Gray)
};
let source = Paragraph::new(app.current_latex())
.style(source_style)
.block(Block::default().borders(Borders::ALL).title(if app.editing { "LaTeX (editing)" } else { "LaTeX" }));
f.render_widget(source, chunks[1]);
let math_widget = MathWidget::new(app.current_latex())
.style(Style::default().fg(Color::White))
.block(Block::default().borders(Borders::ALL).title("Rendered"));
f.render_widget(math_widget, chunks[2]);
let help_text = if app.editing {
"Enter: finish editing | Esc: cancel | Type to edit"
} else {
"←/→ or h/l: navigate | e: edit | q/Esc: quit"
};
let help = Paragraph::new(help_text)
.style(Style::default().fg(Color::DarkGray))
.block(Block::default().borders(Borders::ALL).title("Help"));
f.render_widget(help, chunks[3]);
}