🔒 Repository is read-only – file editing is disabled.

luna/crates/libluna-widgets/src/image.rs main

123 lines Raw ← Back
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
//! `Image` — obrazek (GTK4) oparty na wbudowanych ikonach wektorowych.
//!
//! libLuna nie dekoduje plików graficznych (świadomie — bez balastu kodeków); `Image`
//! pokazuje natomiast zestaw ikon rysowanych wektorowo (folder, plik, lupka, krzyżyk,
//! hamburger), skalowanych do zadanego rozmiaru.

use libluna_core::{Rect, Theme};

use crate::ctx::PaintCtx;
use crate::icon;

/// Zestaw wbudowanych ikon [`Image`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImageIcon {
    #[default]
    Folder,
    File,
    Search,
    Close,
    Burger,
}

/// Obrazek z wbudowanej ikony wektorowej.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Image {
    icon: ImageIcon,
    size: f64,
}

impl Default for Image {
    fn default() -> Self {
        Self {
            icon: ImageIcon::Folder,
            size: 48.0,
        }
    }
}

impl Image {
    pub fn new(icon: ImageIcon) -> Self {
        Self {
            icon,
            ..Self::default()
        }
    }

    pub fn icon(&self) -> ImageIcon {
        self.icon
    }

    pub fn set_icon(&mut self, icon: ImageIcon) {
        self.icon = icon;
    }

    pub fn size(mut self, size: f64) -> Self {
        self.size = size.max(0.0);
        self
    }

    pub fn width(&self) -> f64 {
        self.size
    }

    pub fn height(&self) -> f64 {
        self.size
    }

    /// Kwadrat obrazka wyśrodkowany w `rect` (o boku `min(rect, size)`).
    pub fn image_rect(&self, rect: Rect) -> Rect {
        let side = self.size.min(rect.width()).min(rect.height()).max(0.0);
        let c = rect.center();
        Rect::from_xywh(c.x - side / 2.0, c.y - side / 2.0, side, side)
    }

    pub fn paint(&self, ctx: &mut PaintCtx, rect: Rect) {
        let theme = *ctx.theme;
        let box_ = self.image_rect(rect);
        let color = theme.colors.text;
        match self.icon {
            ImageIcon::Folder => icon::folder(ctx, color, box_),
            ImageIcon::File => icon::file(ctx, color, box_),
            ImageIcon::Search => icon::search(ctx, color, box_.center(), box_.width() * 0.9),
            ImageIcon::Close => icon::close(ctx, color, box_, box_.width() * 0.22),
            ImageIcon::Burger => icon::burger(ctx, color, box_.center(), box_.width() * 0.34, 1.8),
        }
    }
}

/// Rozmiar obrazka domyślnie ustawiany przez motyw (bok kwadratu).
pub fn default_size(theme: &Theme) -> f64 {
    theme.spacing.xl * 3.0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn image_rect_is_square_and_centered() {
        let img = Image::new(ImageIcon::Folder).size(40.0);
        let rect = Rect::from_xywh(0.0, 0.0, 100.0, 60.0);
        let r = img.image_rect(rect);
        assert_eq!(r.width(), 40.0);
        assert_eq!(r.height(), 40.0);
        assert_eq!(r.center().x, 50.0);
        assert_eq!(r.center().y, 30.0);
    }

    #[test]
    fn image_shrinks_to_fit_small_rect() {
        let img = Image::new(ImageIcon::File).size(80.0);
        let rect = Rect::from_xywh(0.0, 0.0, 30.0, 20.0);
        let r = img.image_rect(rect);
        assert_eq!(r.width(), 20.0);
        assert_eq!(r.height(), 20.0);
    }

    #[test]
    fn size_defaults_from_integer_scale() {
        let theme = Theme::light();
        assert!(default_size(&theme) > 0.0);
    }
}