Add initial implementation of TUI-WM with README, build tasks, and configuration files

This commit is contained in:
Björn Blomberg
2026-03-27 14:52:50 +01:00
parent f50ffa04de
commit d7c70baf35
7 changed files with 932 additions and 1 deletions

174
src/main.rs Normal file
View 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(())
}