Add initial implementation of TUI-WM with README, build tasks, and configuration files
This commit is contained in:
174
src/main.rs
Normal file
174
src/main.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, MouseButton, MouseEventKind},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Paragraph},
|
||||
Terminal,
|
||||
};
|
||||
use std::io;
|
||||
|
||||
struct App {
|
||||
should_quit: bool,
|
||||
button_hovered: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
should_quit: false,
|
||||
button_hovered: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event, button_rect: Rect) {
|
||||
match event {
|
||||
Event::Key(key) => {
|
||||
if key.code == KeyCode::Char('q') || key.code == KeyCode::Esc {
|
||||
self.should_quit = true;
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse) => {
|
||||
let mx = mouse.column;
|
||||
let my = mouse.row;
|
||||
let in_button = mx >= button_rect.x
|
||||
&& mx < button_rect.x + button_rect.width
|
||||
&& my >= button_rect.y
|
||||
&& my < button_rect.y + button_rect.height;
|
||||
|
||||
match mouse.kind {
|
||||
MouseEventKind::Moved | MouseEventKind::Drag(_) => {
|
||||
self.button_hovered = in_button;
|
||||
}
|
||||
MouseEventKind::Down(MouseButton::Left) => {
|
||||
if in_button {
|
||||
self.should_quit = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {
|
||||
let x = area.x + area.width.saturating_sub(width) / 2;
|
||||
let y = area.y + area.height.saturating_sub(height) / 2;
|
||||
Rect::new(x, y, width.min(area.width), height.min(area.height))
|
||||
}
|
||||
|
||||
fn button_rect_inside_dialog(dialog: Rect) -> Rect {
|
||||
// Centered button, 16 wide, 3 tall, 2 rows from bottom of dialog
|
||||
let btn_w: u16 = 16;
|
||||
let btn_h: u16 = 3;
|
||||
let x = dialog.x + dialog.width.saturating_sub(btn_w) / 2;
|
||||
let y = dialog.y + dialog.height.saturating_sub(btn_h + 2);
|
||||
Rect::new(x, y, btn_w, btn_h)
|
||||
}
|
||||
|
||||
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 mut app = App::new();
|
||||
// Initialize button_rect so the first event has something to compare against.
|
||||
let mut last_button_rect = Rect::default();
|
||||
|
||||
loop {
|
||||
terminal.draw(|frame| {
|
||||
let area = frame.area();
|
||||
|
||||
// Dialog box: 40 wide, 10 tall, centered
|
||||
let dialog = centered_rect(40, 10, area);
|
||||
let button = button_rect_inside_dialog(dialog);
|
||||
last_button_rect = button;
|
||||
|
||||
// Draw dialog
|
||||
let block = Block::default()
|
||||
.title(" TUI-WM ")
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Cyan));
|
||||
|
||||
frame.render_widget(Clear, dialog);
|
||||
frame.render_widget(block, dialog);
|
||||
|
||||
// Welcome text
|
||||
let text = Paragraph::new(vec![
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled(
|
||||
"Välkommen till TUI-WM",
|
||||
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![Span::styled(
|
||||
"Tryck Q eller klicka Exit",
|
||||
Style::default().fg(Color::DarkGray),
|
||||
)]),
|
||||
])
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
|
||||
// Inner area for text (leave room for button)
|
||||
let text_area = Rect::new(
|
||||
dialog.x + 1,
|
||||
dialog.y + 1,
|
||||
dialog.width.saturating_sub(2),
|
||||
dialog.height.saturating_sub(5),
|
||||
);
|
||||
frame.render_widget(text, text_area);
|
||||
|
||||
// Exit button with hover animation
|
||||
let (btn_style, btn_border_style, label) = if app.button_hovered {
|
||||
(
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD),
|
||||
Style::default().fg(Color::White),
|
||||
" [ EXIT ] ",
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(Color::Cyan),
|
||||
Style::default().fg(Color::DarkGray),
|
||||
" Exit ",
|
||||
)
|
||||
};
|
||||
|
||||
let btn_widget = Paragraph::new(Line::from(vec![Span::styled(label, btn_style)]))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(btn_border_style),
|
||||
)
|
||||
.alignment(ratatui::layout::Alignment::Center);
|
||||
|
||||
frame.render_widget(btn_widget, button);
|
||||
})?;
|
||||
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
let ev = event::read()?;
|
||||
app.handle_event(ev, last_button_rect);
|
||||
}
|
||||
|
||||
if app.should_quit {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user