Introduce central theme module; rounded borders and consistent styling

All colors/styles for windows, panel, dropdown and desktop now live in
src/theme.rs — restyle the whole WM from one file. Windows and menus
use rounded borders; hover/focus states share one accent style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:39:39 +02:00
parent 6aee7ce00a
commit 9cebeae716
3 changed files with 95 additions and 32 deletions

View File

@@ -7,6 +7,7 @@ mod log;
mod pty; mod pty;
mod render; mod render;
mod server; mod server;
mod theme;
use app::{App, AppIpcOut, WindowContent}; use app::{App, AppIpcOut, WindowContent};
use config::Config; use config::Config;

View File

@@ -1,10 +1,11 @@
use crate::app::{App, DropdownState, FloatingWindow, ResizeEdge, WindowContent}; use crate::app::{App, DropdownState, FloatingWindow, ResizeEdge, WindowContent};
use crate::theme;
use crate::config::StatusAlign; use crate::config::StatusAlign;
use ratatui::{ use ratatui::{
layout::Rect, layout::Rect,
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Clear, Paragraph, Wrap}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
Frame, Frame,
}; };
@@ -15,7 +16,7 @@ pub fn render(frame: &mut Frame, app: &App) {
let bg_color = if app.config.background_image.is_some() { let bg_color = if app.config.background_image.is_some() {
Color::Reset Color::Reset
} else { } else {
Color::Indexed(235) theme::DESKTOP_BG
}; };
frame.render_widget( frame.render_widget(
Block::default().style(Style::default().bg(bg_color)), Block::default().style(Style::default().bg(bg_color)),
@@ -76,15 +77,15 @@ fn render_panel(
) { ) {
// Panelens bakgrund // Panelens bakgrund
frame.render_widget( frame.render_widget(
Block::default().style(Style::default().bg(Color::Indexed(238))), Block::default().style(Style::default().bg(theme::PANEL_BG)),
rect, rect,
); );
// Logotyp klickbar meny-knapp med hover-highlight // Logotyp klickbar meny-knapp med hover-highlight
let logo_style = if hovered_tui { let logo_style = if hovered_tui {
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD) theme::hover_highlight()
} else { } else {
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD) theme::logo()
}; };
frame.render_widget( frame.render_widget(
Paragraph::new(Span::styled(" TUI-WM ", logo_style)), Paragraph::new(Span::styled(" TUI-WM ", logo_style)),
@@ -92,9 +93,9 @@ fn render_panel(
); );
// Fokus-indikator: grön ● = skrivbord fokuserat, grå ● = fönster fokuserat // Fokus-indikator: grön ● = skrivbord fokuserat, grå ● = fönster fokuserat
let dot_color = if desktop_focused { Color::Green } else { Color::Indexed(240) }; let dot_color = if desktop_focused { theme::OK } else { theme::HINT };
frame.render_widget( frame.render_widget(
Paragraph::new(Span::styled("", Style::default().fg(dot_color).bg(Color::Indexed(238)))), Paragraph::new(Span::styled("", Style::default().fg(dot_color).bg(theme::PANEL_BG))),
Rect::new(rect.x + 10, rect.y, 1, 1), Rect::new(rect.x + 10, rect.y, 1, 1),
); );
@@ -103,9 +104,9 @@ fn render_panel(
let Some(&ir) = item_rects.get(ii) else { continue }; let Some(&ir) = item_rects.get(ii) else { continue };
let is_hovered = hovered == Some((panel_idx, ii)); let is_hovered = hovered == Some((panel_idx, ii));
let style = if is_hovered { let style = if is_hovered {
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD) theme::hover_highlight()
} else { } else {
Style::default().fg(Color::White).bg(Color::Indexed(238)) Style::default().fg(theme::TEXT).bg(theme::PANEL_BG)
}; };
frame.render_widget( frame.render_widget(
Paragraph::new(Span::styled(format!(" {} ", item.label), style)), Paragraph::new(Span::styled(format!(" {} ", item.label), style)),
@@ -131,7 +132,7 @@ fn render_panel(
frame.render_widget( frame.render_widget(
Paragraph::new(Span::styled( Paragraph::new(Span::styled(
padded, padded,
Style::default().fg(Color::Indexed(252)).bg(Color::Indexed(238)), theme::panel_item(),
)), )),
draw_rect, draw_rect,
); );
@@ -150,7 +151,7 @@ fn render_panel(
frame.render_widget( frame.render_widget(
Paragraph::new(Span::styled( Paragraph::new(Span::styled(
padded, padded,
Style::default().fg(Color::Indexed(252)).bg(Color::Indexed(238)), theme::panel_item(),
)), )),
draw_rect, draw_rect,
); );
@@ -164,16 +165,17 @@ fn render_dropdown(frame: &mut Frame, dd: &DropdownState) {
frame.render_widget( frame.render_widget(
Block::default() Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
.border_style(Style::default().fg(Color::Cyan)) .border_type(BorderType::Rounded)
.style(Style::default().bg(Color::Indexed(236))), .border_style(Style::default().fg(theme::ACCENT))
.style(Style::default().bg(theme::MENU_BG)),
border_rect, border_rect,
); );
for (i, (item, &ir)) in dd.items.iter().zip(dd.item_rects.iter()).enumerate() { for (i, (item, &ir)) in dd.items.iter().zip(dd.item_rects.iter()).enumerate() {
let is_hovered = dd.hovered == Some(i); let is_hovered = dd.hovered == Some(i);
let style = if is_hovered { let style = if is_hovered {
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD) theme::hover_highlight()
} else { } else {
Style::default().fg(Color::White).bg(Color::Indexed(236)) Style::default().fg(theme::TEXT).bg(theme::MENU_BG)
}; };
// ir pekar redan på den faktiska raden innanför kanten // ir pekar redan på den faktiska raden innanför kanten
let draw_rect = Rect::new(ir.x + 1, ir.y, ir.width.saturating_sub(2), 1); let draw_rect = Rect::new(ir.x + 1, ir.y, ir.width.saturating_sub(2), 1);
@@ -200,15 +202,7 @@ fn render_window(
let is_focused = focused_id == Some(window.id); let is_focused = focused_id == Some(window.id);
let is_resizing = window.resizing.is_some(); let is_resizing = window.resizing.is_some();
let border_style = if is_resizing { let border_style = theme::window_border(is_focused, is_resizing, resize_hover.is_some());
Style::default().fg(Color::Yellow)
} else if resize_hover.is_some() {
Style::default().fg(Color::Indexed(220)) // ljusgul vid hover på kant
} else if is_focused {
Style::default().fg(Color::Cyan)
} else {
Style::default().fg(Color::Indexed(242))
};
let title_text = match &window.content { let title_text = match &window.content {
WindowContent::Terminal { title, .. } => format!(" {} ", title), WindowContent::Terminal { title, .. } => format!(" {} ", title),
@@ -219,11 +213,9 @@ fn render_window(
frame.render_widget(Clear, full_rect); frame.render_widget(Clear, full_rect);
frame.render_widget( frame.render_widget(
Block::default() Block::default()
.title(Span::styled( .title(Span::styled(&title_text, theme::window_title()))
&title_text,
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
))
.borders(Borders::ALL) .borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(border_style) .border_style(border_style)
.style(Style::default().bg(match &window.content { .style(Style::default().bg(match &window.content {
WindowContent::Terminal { .. } => terminal_bg, WindowContent::Terminal { .. } => terminal_bg,
@@ -237,9 +229,9 @@ fn render_window(
if cb.x + 3 <= full_rect.x + full_rect.width { if cb.x + 3 <= full_rect.x + full_rect.width {
let close_hov = hovered_close == Some(window.id); let close_hov = hovered_close == Some(window.id);
let close_style = if close_hov { let close_style = if close_hov {
Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD) Style::default().fg(theme::TEXT).bg(theme::DANGER).add_modifier(Modifier::BOLD)
} else { } else {
Style::default().fg(Color::Red) Style::default().fg(theme::DANGER)
}; };
frame.render_widget(Paragraph::new(Span::styled("[X]", close_style)), cb); frame.render_widget(Paragraph::new(Span::styled("[X]", close_style)), cb);
} }
@@ -326,9 +318,9 @@ fn render_popup_dialog(
for (i, label) in labels.iter().enumerate() { for (i, label) in labels.iter().enumerate() {
let w = label.len() as u16; let w = label.len() as u16;
let style = if i == *selected { let style = if i == *selected {
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD) theme::hover_highlight()
} else { } else {
Style::default().fg(Color::White).bg(Color::Indexed(238)) Style::default().fg(theme::TEXT).bg(theme::PANEL_BG)
}; };
if btn_x + w <= rect.x + rect.width { if btn_x + w <= rect.x + rect.width {
frame.render_widget( frame.render_widget(

70
src/theme.rs Normal file
View File

@@ -0,0 +1,70 @@
//! Centralt tema för TUI-WM.
//!
//! ALLA färger och återkommande stilar definieras här — ändra utseendet
//! på hela fönsterhanteraren från en enda fil. render.rs ska inte
//! hårdkoda `Color::`-literaler för sådant som temat täcker.
use ratatui::style::{Color, Modifier, Style};
// ─── Palett ──────────────────────────────────────────────────────────────
/// Fokusmarkering (fönsterram, logga, markerade menyrader)
pub const ACCENT: Color = Color::Cyan;
/// Pågående storleksändring
pub const RESIZE: Color = Color::Yellow;
/// Hover över fönsterkant (inför resize)
pub const RESIZE_HOVER: Color = Color::Indexed(220);
/// Ofokuserad fönsterram
pub const BORDER_DIM: Color = Color::Indexed(242);
/// Skrivbordsbakgrund (när ingen bakgrundsbild används)
pub const DESKTOP_BG: Color = Color::Indexed(235);
/// Panelens bakgrund
pub const PANEL_BG: Color = Color::Indexed(238);
/// Dropdown-menyns bakgrund
pub const MENU_BG: Color = Color::Indexed(236);
/// Normal text
pub const TEXT: Color = Color::White;
/// Panel-text (något nedtonad)
pub const PANEL_TEXT: Color = Color::Indexed(252);
/// Hints och inaktiva indikatorer
pub const HINT: Color = Color::Indexed(240);
/// Positiv indikator (skrivbordsfokus-punkten)
pub const OK: Color = Color::Green;
/// Destruktivt (stäng-knappen)
pub const DANGER: Color = Color::Red;
// ─── Stilar ──────────────────────────────────────────────────────────────
/// Ramstil för ett fönster utifrån dess tillstånd.
pub fn window_border(focused: bool, resizing: bool, resize_hover: bool) -> Style {
if resizing {
Style::default().fg(RESIZE)
} else if resize_hover {
Style::default().fg(RESIZE_HOVER)
} else if focused {
Style::default().fg(ACCENT)
} else {
Style::default().fg(BORDER_DIM)
}
}
/// Markerad rad/knapp (hover): inverterad accent.
pub fn hover_highlight() -> Style {
Style::default()
.fg(Color::Black)
.bg(ACCENT)
.add_modifier(Modifier::BOLD)
}
/// TUI-WM-loggan i panelen.
pub fn logo() -> Style {
Style::default().fg(ACCENT).add_modifier(Modifier::BOLD)
}
/// Panel-objekt (menyknappar) i normalläge.
pub fn panel_item() -> Style {
Style::default().fg(PANEL_TEXT).bg(PANEL_BG)
}
/// Fönstertitel.
pub fn window_title() -> Style {
Style::default().fg(TEXT).add_modifier(Modifier::BOLD)
}