Polish window frames: inset title chip, ✕ close button, drop shadow

- Title rendered as a chip inset in the border (┤ titel ├), accent
  colored when focused, dimmed when not
- Close button restyled to ┤✕├ matching the frame
- Subtle drop shadow below/right of windows (darkened cells, content
  underneath stays visible; skipped when a background image is active)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 19:19:21 +02:00
parent 373294e2b9
commit 71952ab90b
2 changed files with 55 additions and 6 deletions

View File

@@ -216,6 +216,12 @@ fn render_window(
WindowContent::PopupDialog { .. } => " TUI-WM ".to_string(),
};
// Diskret skugga nedanför/till höger om fönstret (inte när en
// bakgrundsbild används — då ska skrivbordet synas igenom).
if outer_bg != Color::Reset {
render_shadow(frame, full_rect);
}
frame.render_widget(Clear, full_rect);
// Ramzonen målas i skrivbordets färg — annars fylls även hörncellerna
// utanför de rundade hörnen med fönstrets bakgrund och rutan ser
@@ -233,16 +239,22 @@ fn render_window(
Block::default().style(Style::default().bg(content_bg)),
inner_rect,
);
// Titeln renderas som ett "chip" infällt i ramen: ┤ titel ├
let title_line = Line::from(vec![
Span::styled("", border_style),
Span::styled(title_text.clone(), theme::window_title(is_focused)),
Span::styled("", border_style),
]);
frame.render_widget(
Block::default()
.title(Span::styled(&title_text, theme::window_title()))
.title(title_line)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(border_style),
full_rect,
);
// Stäng-knapp [X]
// Stäng-knapp: ┤✕├ infälld i ramen (✕ U+2715 är ett smalt textglyf)
let cb = window.close_btn_rect();
if cb.x + 3 <= full_rect.x + full_rect.width {
let close_hov = hovered_close == Some(window.id);
@@ -251,7 +263,12 @@ fn render_window(
} else {
Style::default().fg(theme::DANGER)
};
frame.render_widget(Paragraph::new(Span::styled("[X]", close_style)), cb);
let close_line = Line::from(vec![
Span::styled("", border_style),
Span::styled("", close_style),
Span::styled("", border_style),
]);
frame.render_widget(Paragraph::new(close_line), cb);
}
// Resize-hints bara om fönstret är resizable
@@ -309,6 +326,31 @@ fn render_window(
}
}
/// Ritar en skugga: raden under och kolumnen till höger om fönstret får
/// mörkare bakgrund + dämpad text, så fönstren ser ut att sväva över
/// skrivbordet. Muterar cellerna direkt så att underliggande innehåll
/// (andra fönster/skrivbordet) syns kvar — bara mörkare.
fn render_shadow(frame: &mut Frame, rect: Rect) {
let area = frame.area();
let buf = frame.buffer_mut();
let mut shade = |x: u16, y: u16| {
if x < area.width && y < area.height {
if let Some(cell) = buf.cell_mut((x, y)) {
cell.set_bg(theme::SHADOW_BG);
cell.modifier.insert(Modifier::DIM);
}
}
};
// Rad under fönstret (förskjuten ett steg åt höger)
for x in (rect.x + 1)..(rect.x + rect.width + 1) {
shade(x, rect.y + rect.height);
}
// Kolumn till höger om fönstret
for y in (rect.y + 1)..(rect.y + rect.height) {
shade(rect.x + rect.width, y);
}
}
fn render_popup_dialog(
frame: &mut Frame,
window: &FloatingWindow,

View File

@@ -64,7 +64,14 @@ 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)
/// Fönstertitel — accentfärgad när fönstret har fokus.
pub fn window_title(focused: bool) -> Style {
if focused {
Style::default().fg(ACCENT).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(HINT)
}
}
/// Bakgrundsfärg för fönsterskuggan (mörkare än skrivbordet).
pub const SHADOW_BG: Color = Color::Indexed(233);