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:
@@ -7,6 +7,7 @@ mod log;
|
||||
mod pty;
|
||||
mod render;
|
||||
mod server;
|
||||
mod theme;
|
||||
|
||||
use app::{App, AppIpcOut, WindowContent};
|
||||
use config::Config;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::app::{App, DropdownState, FloatingWindow, ResizeEdge, WindowContent};
|
||||
use crate::theme;
|
||||
use crate::config::StatusAlign;
|
||||
use ratatui::{
|
||||
layout::Rect,
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||
widgets::{Block, BorderType, Borders, Clear, Paragraph, Wrap},
|
||||
Frame,
|
||||
};
|
||||
|
||||
@@ -15,7 +16,7 @@ pub fn render(frame: &mut Frame, app: &App) {
|
||||
let bg_color = if app.config.background_image.is_some() {
|
||||
Color::Reset
|
||||
} else {
|
||||
Color::Indexed(235)
|
||||
theme::DESKTOP_BG
|
||||
};
|
||||
frame.render_widget(
|
||||
Block::default().style(Style::default().bg(bg_color)),
|
||||
@@ -76,15 +77,15 @@ fn render_panel(
|
||||
) {
|
||||
// Panelens bakgrund
|
||||
frame.render_widget(
|
||||
Block::default().style(Style::default().bg(Color::Indexed(238))),
|
||||
Block::default().style(Style::default().bg(theme::PANEL_BG)),
|
||||
rect,
|
||||
);
|
||||
|
||||
// Logotyp – klickbar meny-knapp med hover-highlight
|
||||
let logo_style = if hovered_tui {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
theme::hover_highlight()
|
||||
} else {
|
||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
theme::logo()
|
||||
};
|
||||
frame.render_widget(
|
||||
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
|
||||
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(
|
||||
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),
|
||||
);
|
||||
|
||||
@@ -103,9 +104,9 @@ fn render_panel(
|
||||
let Some(&ir) = item_rects.get(ii) else { continue };
|
||||
let is_hovered = hovered == Some((panel_idx, ii));
|
||||
let style = if is_hovered {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
theme::hover_highlight()
|
||||
} else {
|
||||
Style::default().fg(Color::White).bg(Color::Indexed(238))
|
||||
Style::default().fg(theme::TEXT).bg(theme::PANEL_BG)
|
||||
};
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(format!(" {} ", item.label), style)),
|
||||
@@ -131,7 +132,7 @@ fn render_panel(
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(
|
||||
padded,
|
||||
Style::default().fg(Color::Indexed(252)).bg(Color::Indexed(238)),
|
||||
theme::panel_item(),
|
||||
)),
|
||||
draw_rect,
|
||||
);
|
||||
@@ -150,7 +151,7 @@ fn render_panel(
|
||||
frame.render_widget(
|
||||
Paragraph::new(Span::styled(
|
||||
padded,
|
||||
Style::default().fg(Color::Indexed(252)).bg(Color::Indexed(238)),
|
||||
theme::panel_item(),
|
||||
)),
|
||||
draw_rect,
|
||||
);
|
||||
@@ -164,16 +165,17 @@ fn render_dropdown(frame: &mut Frame, dd: &DropdownState) {
|
||||
frame.render_widget(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Color::Cyan))
|
||||
.style(Style::default().bg(Color::Indexed(236))),
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(Style::default().fg(theme::ACCENT))
|
||||
.style(Style::default().bg(theme::MENU_BG)),
|
||||
border_rect,
|
||||
);
|
||||
for (i, (item, &ir)) in dd.items.iter().zip(dd.item_rects.iter()).enumerate() {
|
||||
let is_hovered = dd.hovered == Some(i);
|
||||
let style = if is_hovered {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
theme::hover_highlight()
|
||||
} 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
|
||||
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_resizing = window.resizing.is_some();
|
||||
|
||||
let border_style = if is_resizing {
|
||||
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 border_style = theme::window_border(is_focused, is_resizing, resize_hover.is_some());
|
||||
|
||||
let title_text = match &window.content {
|
||||
WindowContent::Terminal { title, .. } => format!(" {} ", title),
|
||||
@@ -219,11 +213,9 @@ fn render_window(
|
||||
frame.render_widget(Clear, full_rect);
|
||||
frame.render_widget(
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
&title_text,
|
||||
Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
|
||||
))
|
||||
.title(Span::styled(&title_text, theme::window_title()))
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.border_style(border_style)
|
||||
.style(Style::default().bg(match &window.content {
|
||||
WindowContent::Terminal { .. } => terminal_bg,
|
||||
@@ -237,9 +229,9 @@ fn render_window(
|
||||
if cb.x + 3 <= full_rect.x + full_rect.width {
|
||||
let close_hov = hovered_close == Some(window.id);
|
||||
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 {
|
||||
Style::default().fg(Color::Red)
|
||||
Style::default().fg(theme::DANGER)
|
||||
};
|
||||
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() {
|
||||
let w = label.len() as u16;
|
||||
let style = if i == *selected {
|
||||
Style::default().fg(Color::Black).bg(Color::Cyan).add_modifier(Modifier::BOLD)
|
||||
theme::hover_highlight()
|
||||
} 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 {
|
||||
frame.render_widget(
|
||||
|
||||
70
src/theme.rs
Normal file
70
src/theme.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user