🔒 Repository is read-only – file editing is disabled.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
//! `Card` — karta (powierzchnia podniesiona) z nagłówkiem i obszarem treści.
//!
//! Widget immediate: rysuje tło, obramowanie, nagłówek i separator, a [`Card::paint`] zwraca
//! obszar zawartości, w którym aplikacja umieszcza własne wiersze.
use libluna_core::style::weight;
use libluna_core::{Rect, Theme};
use crate::ctx::PaintCtx;
/// Karta z nagłówkiem.
#[derive(Debug, Clone, Default)]
pub struct Card {
title: String,
}
impl Card {
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
}
}
pub fn title(&self) -> &str {
&self.title
}
/// Wysokość nagłówka (z metryk).
pub fn header_height(&self, theme: &Theme) -> f64 {
theme.metrics.card.header
}
/// Obszar zawartości (poniżej nagłówka i separatora).
pub fn content_rect(&self, rect: Rect, theme: &Theme) -> Rect {
let h = self.header_height(theme);
Rect::from_xywh(
rect.x(),
rect.y() + h,
rect.width(),
(rect.height() - h).max(0.0),
)
}
/// Rysuje kartę; zwraca obszar zawartości.
pub fn paint(&self, ctx: &mut PaintCtx, rect: Rect) -> Rect {
let theme = *ctx.theme;
ctx.scene
.fill_rounded_rect(rect, theme.radius.large, theme.colors.card_bg);
ctx.scene
.stroke_rounded_rect(rect, theme.radius.large, 1.0, theme.colors.border);
let h = self.header_height(&theme);
ctx.label_left(
rect.x() + theme.metrics.card.pad,
rect.y() + h / 2.0,
&self.title,
theme.typography.body.size,
weight::BOLD,
theme.colors.text,
);
ctx.scene.fill_rect(
Rect::from_xywh(rect.x(), rect.y() + h, rect.width(), 1.0),
theme.colors.border,
);
self.content_rect(rect, &theme)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn content_is_below_header() {
let theme = Theme::light();
let card = Card::new("Wygląd");
let rect = Rect::from_xywh(0.0, 0.0, 300.0, 200.0);
let content = card.content_rect(rect, &theme);
assert!((content.y() - (rect.y() + theme.metrics.card.header)).abs() < 1e-9);
assert!((content.height() - (200.0 - theme.metrics.card.header)).abs() < 1e-9);
}
#[test]
fn header_height_from_metrics() {
let theme = Theme::light();
assert_eq!(
Card::new("X").header_height(&theme),
theme.metrics.card.header
);
}
}