egui-cha-ds 0.6.0

Design System for egui-cha (Atoms, Molecules, Theme)
Documentation
//! Icon atom - Phosphor Icons integration
//!
//! This module provides Phosphor Icons as an embedded font.
//! Icons are displayed using the `icons` font family registered in the runtime.
//!
//! # Available Icons
//!
//! Only commonly used icons are pre-defined here. For the full list of 1,500+ icons
//! and their codepoints, see the official Phosphor Icons resources:
//!
//! - **Icon Browser**: <https://phosphoricons.com/>
//! - **Codepoint Reference**: <https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css>
//! - **GitHub**: <https://github.com/phosphor-icons/core>
//!
//! # Adding Custom Icons
//!
//! To use an icon not defined here, find its codepoint from the CSS reference above
//! and create a constant:
//!
//! ```ignore
//! // From CSS: .ph.ph-alarm::before { content: "\e006"; }
//! const MY_ALARM: &str = "\u{e006}";
//!
//! // Use with Icon component
//! Icon::new(MY_ALARM).show(ui);
//! ```
//!
//! # Font Family
//!
//! Icons must be rendered with `FontFamily::Name("icons")` to display correctly.
//! The `Icon` component handles this automatically.
//!
//! # ⚠️ IMPORTANT: Do NOT Use Emoji for Icons
//!
//! **This is a native app, NOT a web browser.**
//!
//! Unlike web applications where emoji rendering is relatively consistent,
//! native apps have critical differences:
//!
//! - **OS/Device Dependent**: Emoji appearance varies wildly across macOS, Windows, Linux
//! - **Missing Glyphs**: Some systems may not render certain emoji at all
//! - **Version Inconsistency**: Emoji sets differ by OS version
//! - **No Fallback Control**: Unlike CSS, you cannot specify fallback fonts reliably
//!
//! ## Rule: Always Use Icon Fonts
//!
//! For critical UI elements like icons, **always use embedded icon fonts**
//! (Phosphor Icons, Font Awesome, Material Icons, etc.) with explicit `FontFamily`.
//!
//! ```ignore
//! // ✅ CORRECT: Use icon font with explicit FontFamily
//! RichText::new("\u{e182}")  // Phosphor check icon
//!     .family(FontFamily::Name("icons".into()))
//!
//! // ❌ WRONG: Never use emoji for icons
//! RichText::new("✅")  // OS-dependent, unreliable
//! ```
//!
//! This principle applies universally - even CSS frameworks (Bootstrap, Tailwind)
//! use icon fonts (Font Awesome, Heroicons) rather than emoji for reliable icons.

use egui::{Color32, Label, Response, RichText, Ui, Widget};
use egui_cha::ViewCtx;

/// Phosphor Icons codepoints (Regular weight)
///
/// See module documentation for how to add icons not listed here.
/// Full reference: <https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css>
pub mod icons {
    // Navigation
    pub const HOUSE: &str = "\u{e2c2}";
    pub const ARROW_LEFT: &str = "\u{e058}";
    pub const ARROW_RIGHT: &str = "\u{e06c}";

    // Actions
    pub const PLUS: &str = "\u{e3d4}";
    pub const MINUS: &str = "\u{e32a}";
    pub const X: &str = "\u{e4f6}";
    pub const CHECK: &str = "\u{e182}";

    // UI
    pub const GEAR: &str = "\u{e270}";
    pub const INFO: &str = "\u{e2ce}";
    pub const WARNING: &str = "\u{e4e0}";
    pub const HASH: &str = "\u{e2a2}";
    pub const USER: &str = "\u{e4c2}";

    // File operations
    pub const FLOPPY_DISK: &str = "\u{e248}";
    pub const TRASH: &str = "\u{e4a6}";
    pub const PENCIL_SIMPLE: &str = "\u{e3b4}";
    pub const FOLDER_SIMPLE: &str = "\u{e25a}";
    pub const FILE: &str = "\u{e230}";

    // Search & Refresh
    pub const MAGNIFYING_GLASS: &str = "\u{e30c}";
    pub const ARROWS_CLOCKWISE: &str = "\u{e094}";

    // Media
    pub const PLAY: &str = "\u{e3d0}";
    pub const PAUSE: &str = "\u{e39e}";
    pub const STOP: &str = "\u{e46c}";
    pub const RECORD: &str = "\u{e3f0}";

    // Misc
    pub const COPY: &str = "\u{e1ca}";
    pub const DOWNLOAD_SIMPLE: &str = "\u{e20c}";
    pub const UPLOAD_SIMPLE: &str = "\u{e4c0}";
    pub const LINK_SIMPLE: &str = "\u{e2e6}";
    pub const EYE: &str = "\u{e220}";
    pub const EYE_SLASH: &str = "\u{e222}";

    // Status / Alerts
    pub const FIRE: &str = "\u{e242}";
    pub const BUG: &str = "\u{e5f4}";
    pub const WRENCH: &str = "\u{e5d4}";
    pub const X_CIRCLE: &str = "\u{e4f8}";
    pub const SKULL: &str = "\u{e916}";

    // Window controls
    pub const CARET_UP: &str = "\u{e13c}";
    pub const CARET_DOWN: &str = "\u{e136}";
    pub const LOCK: &str = "\u{e2ec}";
    pub const LOCK_OPEN: &str = "\u{e2ee}";
    pub const CORNERS_OUT: &str = "\u{e1ce}"; // maximize
    pub const CORNERS_IN: &str = "\u{e1cc}"; // restore

    // Visual / Layers
    pub const STACK: &str = "\u{e45e}";
    pub const SLIDERS_HORIZONTAL: &str = "\u{e450}";
    pub const IMAGE: &str = "\u{e2c4}";
    pub const MONITOR_PLAY: &str = "\u{e338}";

    // Layout / Arrange
    pub const GRID_FOUR: &str = "\u{e296}";
    pub const ARROWS_OUT_LINE_HORIZONTAL: &str = "\u{e534}";
    pub const ARROWS_OUT_LINE_VERTICAL: &str = "\u{e536}";
    pub const SQUARES_FOUR: &str = "\u{e464}";
    pub const BROOM: &str = "\u{ec54}";
    pub const MAGNIFYING_GLASS_PLUS: &str = "\u{e310}";
    pub const FRAME_CORNERS: &str = "\u{e626}";
    pub const LOCK_KEY: &str = "\u{e2fe}"; // for Light lock
}

/// Icon component using Phosphor Icons
pub struct Icon {
    icon_char: &'static str,
    size: f32,
    color: Option<Color32>,
}

impl Icon {
    /// Create a new icon
    pub fn new(icon_char: &'static str) -> Self {
        Self {
            icon_char,
            size: 16.0,
            color: None,
        }
    }

    // Convenience constructors
    pub fn house() -> Self {
        Self::new(icons::HOUSE)
    }
    pub fn arrow_left() -> Self {
        Self::new(icons::ARROW_LEFT)
    }
    pub fn arrow_right() -> Self {
        Self::new(icons::ARROW_RIGHT)
    }
    pub fn plus() -> Self {
        Self::new(icons::PLUS)
    }
    pub fn minus() -> Self {
        Self::new(icons::MINUS)
    }
    pub fn x() -> Self {
        Self::new(icons::X)
    }
    pub fn check() -> Self {
        Self::new(icons::CHECK)
    }
    pub fn gear() -> Self {
        Self::new(icons::GEAR)
    }
    pub fn info() -> Self {
        Self::new(icons::INFO)
    }
    pub fn warning() -> Self {
        Self::new(icons::WARNING)
    }
    pub fn hash() -> Self {
        Self::new(icons::HASH)
    }
    pub fn user() -> Self {
        Self::new(icons::USER)
    }
    pub fn play() -> Self {
        Self::new(icons::PLAY)
    }
    pub fn pause() -> Self {
        Self::new(icons::PAUSE)
    }
    pub fn stop() -> Self {
        Self::new(icons::STOP)
    }
    pub fn record() -> Self {
        Self::new(icons::RECORD)
    }
    pub fn fire() -> Self {
        Self::new(icons::FIRE)
    }
    pub fn bug() -> Self {
        Self::new(icons::BUG)
    }
    pub fn wrench() -> Self {
        Self::new(icons::WRENCH)
    }
    pub fn x_circle() -> Self {
        Self::new(icons::X_CIRCLE)
    }
    pub fn caret_up() -> Self {
        Self::new(icons::CARET_UP)
    }
    pub fn caret_down() -> Self {
        Self::new(icons::CARET_DOWN)
    }
    pub fn lock() -> Self {
        Self::new(icons::LOCK)
    }
    pub fn lock_open() -> Self {
        Self::new(icons::LOCK_OPEN)
    }
    pub fn corners_out() -> Self {
        Self::new(icons::CORNERS_OUT)
    }
    pub fn corners_in() -> Self {
        Self::new(icons::CORNERS_IN)
    }
    pub fn stack() -> Self {
        Self::new(icons::STACK)
    }
    pub fn sliders_horizontal() -> Self {
        Self::new(icons::SLIDERS_HORIZONTAL)
    }
    pub fn image() -> Self {
        Self::new(icons::IMAGE)
    }
    pub fn monitor_play() -> Self {
        Self::new(icons::MONITOR_PLAY)
    }

    /// Set icon size
    pub fn size(mut self, size: f32) -> Self {
        self.size = size;
        self
    }

    /// Set icon color
    pub fn color(mut self, color: Color32) -> Self {
        self.color = Some(color);
        self
    }

    /// Show the icon
    pub fn show(self, ui: &mut Ui) -> Response {
        ui.add(self)
    }

    /// Show the icon using ViewCtx
    pub fn show_ctx<Msg>(self, ctx: &mut ViewCtx<Msg>) -> Response {
        ctx.ui.add(self)
    }
}

impl Widget for Icon {
    fn ui(self, ui: &mut Ui) -> Response {
        let mut text = RichText::new(self.icon_char)
            .size(self.size)
            .family(egui::FontFamily::Name("icons".into()));

        if let Some(color) = self.color {
            text = text.color(color);
        }

        ui.add(Label::new(text).selectable(false))
    }
}