bevy_pf 0.2.5

A XAML / WPF-like UI framework for Bevy: XAML in macros or files, styling with resources, and the common WPF control set.
Documentation
//! Vector icon set for `PackIcon` / `SymbolIcon` / `FontIcon`
//! (MaterialDesignInXaml `PackIcon`, MahApps `IconPacks`, WinUI `SymbolIcon`
//! equivalents).
//!
//! Icons are 24x24 path geometry rendered by the framework's own shape
//! engine — no icon font, so output is identical on native and wasm and
//! there is no fallback-font lottery. Subpaths rely on the WPF default
//! even-odd fill rule for holes (rings, cut-outs).
//!
//! ```xml
//! <PackIcon Kind="Home" Width="20" Height="20" Foreground="#FF444444"/>
//! ```

/// 24x24 path data for a named icon kind (case-insensitive), or `None`.
pub fn icon_path(kind: &str) -> Option<&'static str> {
    let idx = ICONS
        .binary_search_by(|(name, _)| {
            name.bytes()
                .map(|b| b.to_ascii_lowercase())
                .cmp(kind.bytes().map(|b| b.to_ascii_lowercase()))
        })
        .ok()?;
    Some(ICONS[idx].1)
}

/// Every icon kind this set defines, sorted.
pub fn icon_names() -> impl Iterator<Item = &'static str> {
    ICONS.iter().map(|(name, _)| *name)
}

/// Sorted by name (case-insensitive) for binary search.
static ICONS: &[(&str, &str)] = &[
    (
        "ArrowDown",
        "M11 4 H13 V16.2 L18.6 10.6 L20 12 L12 20 L4 12 L5.4 10.6 L11 16.2 Z",
    ),
    (
        "ArrowLeft",
        "M12 4 L13.4 5.4 L7.8 11 H20 V13 H7.8 L13.4 18.6 L12 20 L4 12 Z",
    ),
    (
        "ArrowRight",
        "M12 4 L10.6 5.4 L16.2 11 H4 V13 H16.2 L10.6 18.6 L12 20 L20 12 Z",
    ),
    (
        "ArrowUp",
        "M11 20 H13 V7.8 L18.6 13.4 L20 12 L12 4 L4 12 L5.4 13.4 L11 7.8 Z",
    ),
    (
        "Bell",
        "M12 2 C10.9 2 10 2.9 10 4 V4.6 C7.1 5.5 5 8.2 5 11.3 V17 L3 19 V20 H21 V19 L19 17 V11.3 C19 8.2 16.9 5.5 14 4.6 V4 C14 2.9 13.1 2 12 2 Z M9.5 21 A2.5 2.5 0 0 0 14.5 21 Z",
    ),
    ("Bookmark", "M6 2 H18 V22 L12 17.5 L6 22 Z"),
    (
        "Calendar",
        "M7 2 H9 V4 H15 V2 H17 V4 H20 V22 H4 V4 H7 Z M6 9 H18 V20 H6 Z",
    ),
    (
        "Check",
        "M9.5 16.2 L5.3 12 L3.9 13.4 L9.5 19 L20.1 8.4 L18.7 7 Z",
    ),
    (
        "ChevronDown",
        "M4.6 9 L3.2 10.4 L12 19.2 L20.8 10.4 L19.4 9 L12 16.4 Z",
    ),
    (
        "ChevronLeft",
        "M15 4.6 L13.6 3.2 L4.8 12 L13.6 20.8 L15 19.4 L7.6 12 Z",
    ),
    (
        "ChevronRight",
        "M9 4.6 L10.4 3.2 L19.2 12 L10.4 20.8 L9 19.4 L16.4 12 Z",
    ),
    (
        "ChevronUp",
        "M4.6 15 L3.2 13.6 L12 4.8 L20.8 13.6 L19.4 15 L12 7.6 Z",
    ),
    (
        "Clock",
        "M12 2 A10 10 0 1 0 12 22 A10 10 0 1 0 12 2 Z M12 4 A8 8 0 1 1 12 20 A8 8 0 1 1 12 4 Z M11.2 6 H12.8 V12.4 L17 14.8 L16.2 16.2 L11.2 13.3 Z",
    ),
    (
        "Close",
        "M5.7 4.3 L12 10.6 L18.3 4.3 L19.7 5.7 L13.4 12 L19.7 18.3 L18.3 19.7 L12 13.4 L5.7 19.7 L4.3 18.3 L10.6 12 L4.3 5.7 Z",
    ),
    (
        "Cloud",
        "M6.5 19 A4.5 4.5 0 0 1 6.1 10.1 A6 6 0 0 1 17.7 11.1 A4 4 0 0 1 17.5 19 Z",
    ),
    (
        "Delete",
        "M9 3 H15 V5 H20 V7 H4 V5 H9 Z M5 8 H19 L18 22 H6 Z",
    ),
    ("Document", "M5 2 H14 L19 7 V22 H5 Z M13 3.5 V8 H17.5 Z"),
    (
        "Download",
        "M11 3 H13 V12.2 L16.6 8.6 L18 10 L12 16 L6 10 L7.4 8.6 L11 12.2 Z M4 18 H20 V20 H4 Z",
    ),
    (
        "Edit",
        "M17.7 2.9 L21.1 6.3 L19 8.4 L15.6 5 Z M14.2 6.4 L17.6 9.8 L8.4 19 L4.4 19.6 L5 15.6 Z",
    ),
    (
        "Eye",
        "M12 5 C7 5 3.3 8.1 1.5 12 C3.3 15.9 7 19 12 19 C17 19 20.7 15.9 22.5 12 C20.7 8.1 17 5 12 5 Z M12 7.5 A4.5 4.5 0 1 0 12 16.5 A4.5 4.5 0 1 0 12 7.5 Z M12 9.5 A2.5 2.5 0 1 1 12 14.5 A2.5 2.5 0 1 1 12 9.5 Z",
    ),
    ("Filter", "M3 4 H21 L14 12.5 V20 L10 22 V12.5 Z"),
    ("Flag", "M5 2 H7 V22 H5 Z M8 3 H19 L16.5 7.5 L19 12 H8 Z"),
    ("Folder", "M3 4 H10 L12 7 H21 V20 H3 Z"),
    (
        "Grid",
        "M4 4 H11 V11 H4 Z M13 4 H20 V11 H13 Z M4 13 H11 V20 H4 Z M13 13 H20 V20 H13 Z",
    ),
    (
        "Heart",
        "M12 21 C5 14.5 2 11 2 7.5 C2 4.5 4.5 2.5 7 2.5 C9 2.5 11 3.5 12 5.5 C13 3.5 15 2.5 17 2.5 C19.5 2.5 22 4.5 22 7.5 C22 11 19 14.5 12 21 Z",
    ),
    ("Home", "M12 3 L21 11 H18 V20 H14 V14 H10 V20 H6 V11 H3 Z"),
    (
        "Info",
        "M12 2 A10 10 0 1 0 12 22 A10 10 0 1 0 12 2 Z M10.8 6.2 H13.2 V8.6 H10.8 Z M10.8 10.4 H13.2 V17.8 H10.8 Z",
    ),
    (
        "List",
        "M4 5 H6 V7 H4 Z M8 5 H20 V7 H8 Z M4 11 H6 V13 H4 Z M8 11 H20 V13 H8 Z M4 17 H6 V19 H4 Z M8 17 H20 V19 H8 Z",
    ),
    (
        "Location",
        "M12 2 C8 2 5 5 5 9 C5 14 12 22 12 22 C12 22 19 14 19 9 C19 5 16 2 12 2 Z M12 6 A3 3 0 1 0 12 12 A3 3 0 1 0 12 6 Z",
    ),
    (
        "Lock",
        "M12 2 C9.2 2 7 4.2 7 7 V10 H5 V22 H19 V10 H17 V7 C17 4.2 14.8 2 12 2 Z M12 4 C13.7 4 15 5.3 15 7 V10 H9 V7 C9 5.3 10.3 4 12 4 Z M11 14 H13 V18 H11 Z",
    ),
    (
        "Mail",
        "M2 5 H22 V19 H2 Z M4 7.3 V17 H20 V7.3 L12 13.3 Z M5.7 7 H18.3 L12 11.7 Z",
    ),
    (
        "Menu",
        "M3 6 H21 V8 H3 Z M3 11 H21 V13 H3 Z M3 16 H21 V18 H3 Z",
    ),
    ("Minus", "M5 11 H19 V13 H5 Z"),
    (
        "Moon",
        "M14.5 2.5 A10 10 0 1 0 21.5 9.5 A8 8 0 0 1 14.5 2.5 Z",
    ),
    ("Pause", "M6 4 H10 V20 H6 Z M14 4 H18 V20 H14 Z"),
    (
        "Person",
        "M12 3 A4.5 4.5 0 1 0 12 12 A4.5 4.5 0 1 0 12 3 Z M4 21 C4 16.6 7.6 13.5 12 13.5 C16.4 13.5 20 16.6 20 21 Z",
    ),
    ("Play", "M7 4 L20 12 L7 20 Z"),
    ("Plus", "M11 5 H13 V11 H19 V13 H13 V19 H11 V13 H5 V11 H11 Z"),
    (
        "Power",
        "M11 2 H13 V11 H11 Z M7.2 5.3 L8.6 6.7 A6.5 6.5 0 1 0 15.4 6.7 L16.8 5.3 A8.5 8.5 0 1 1 7.2 5.3 Z",
    ),
    (
        "Refresh",
        "M16.8 3.7 A9.6 9.6 0 1 1 3.7 7.2 L5.8 8.4 A7.2 7.2 0 1 0 15.6 5.8 Z M11.7 2.1 L18.5 0.7 L13.9 8.7 Z",
    ),
    (
        "Search",
        "M10 2 A8 8 0 1 0 10 18 A8 8 0 1 0 10 2 Z M10 5 A5 5 0 1 1 10 15 A5 5 0 1 1 10 5 Z M15.9 14.5 L21.5 20.1 L20.1 21.5 L14.5 15.9 Z",
    ),
    (
        "Settings",
        "M19.0 7.7 L20.0 10.2 L22.6 10.1 L22.6 13.9 L20.0 13.8 L19.0 16.3 L20.8 18.2 L18.2 20.8 L16.3 19.0 L13.8 20.0 L13.9 22.6 L10.1 22.6 L10.2 20.0 L7.7 19.0 L5.8 20.8 L3.2 18.2 L5.0 16.3 L4.0 13.8 L1.4 13.9 L1.4 10.1 L4.0 10.2 L5.0 7.7 L3.2 5.8 L5.8 3.2 L7.7 5.0 L10.2 4.0 L10.1 1.4 L13.9 1.4 L13.8 4.0 L16.3 5.0 L18.2 3.2 L20.8 5.8 L19.0 7.7 Z M15.6 12 A3.6 3.6 0 1 0 8.4 12 A3.6 3.6 0 1 0 15.6 12 Z",
    ),
    (
        "Star",
        "M12.0 2.4 L14.4 9.4 L21.7 9.4 L15.8 13.8 L18.0 20.9 L12.0 16.6 L6.0 20.9 L8.2 13.8 L2.3 9.4 L9.6 9.4 Z",
    ),
    ("Stop", "M5 5 H19 V19 H5 Z"),
    (
        "Sun",
        "M16.2 12 A4.2 4.2 0 1 0 7.8 12 A4.2 4.2 0 1 0 16.2 12 Z M18.5 11.3 L22.8 11.4 L22.8 12.6 L18.5 12.7 Z M17.1 16.1 L20.1 19.2 L19.2 20.1 L16.1 17.1 Z M12.7 18.5 L12.6 22.8 L11.4 22.8 L11.3 18.5 Z M7.9 17.1 L4.8 20.1 L3.9 19.2 L6.9 16.1 Z M5.5 12.7 L1.2 12.6 L1.2 11.4 L5.5 11.3 Z M6.9 7.9 L3.9 4.8 L4.8 3.9 L7.9 6.9 Z M11.3 5.5 L11.4 1.2 L12.6 1.2 L12.7 5.5 Z M16.1 6.9 L19.2 3.9 L20.1 4.8 L17.1 7.9 Z",
    ),
    (
        "Upload",
        "M12 3 L18 9 L16.6 10.4 L13 6.8 V16 H11 V6.8 L7.4 10.4 L6 9 Z M4 18 H20 V20 H4 Z",
    ),
    (
        "Warning",
        "M12 2 L23 21 H1 Z M11 9 H13 V14.5 H11 Z M11 16.3 H13 V18.3 H11 Z",
    ),
];

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

    #[test]
    fn icons_sorted_for_binary_search() {
        for pair in ICONS.windows(2) {
            assert!(
                pair[0].0.to_ascii_lowercase() < pair[1].0.to_ascii_lowercase(),
                "ICONS out of order: {} >= {}",
                pair[0].0,
                pair[1].0
            );
        }
    }

    #[test]
    fn every_icon_parses() {
        for (name, data) in ICONS {
            let parsed = bevy_pf_xaml::geometry::parse_path_data(data)
                .unwrap_or_else(|e| panic!("{name}: {e}"));
            assert!(!parsed.figures.is_empty(), "{name} has no figures");
            // control_bounds is conservative around arcs, so only figure
            // start anchors are checked against the 24x24 grid.
            for fig in &parsed.figures {
                let p = fig.start;
                assert!(
                    p.x >= -0.5 && p.y >= -0.5 && p.x <= 24.5 && p.y <= 24.5,
                    "{name} start escapes the grid"
                );
            }
        }
    }

    #[test]
    fn lookup_is_case_insensitive() {
        assert!(icon_path("home").is_some());
        assert!(icon_path("HOME").is_some());
        assert!(icon_path("Settings").is_some());
        assert!(icon_path("NoSuchIcon").is_none());
    }
}