🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
//! Współdzielone ikony rysowane wektorowo (bez zależności od fontu).
use libluna_core::{Color, Point, Rect};
use crate::ctx::PaintCtx;
/// Strzałka w dół (chevron); środek w `center`, rozpiętość `size`, grubość `width`.
pub fn chevron_down(paint: &mut PaintCtx, color: Color, center: Point, size: f64, width: f64) {
paint.scene.stroke_line(
Point::new(center.x - size, center.y - size * 0.5),
Point::new(center.x, center.y + size * 0.5),
width,
color,
);
paint.scene.stroke_line(
Point::new(center.x, center.y + size * 0.5),
Point::new(center.x + size, center.y - size * 0.5),
width,
color,
);
}
/// Strzałka w górę (chevron); środek w `center`, rozpiętość `size`, grubość `width`.
pub fn chevron_up(paint: &mut PaintCtx, color: Color, center: Point, size: f64, width: f64) {
paint.scene.stroke_line(
Point::new(center.x - size, center.y + size * 0.5),
Point::new(center.x, center.y - size * 0.5),
width,
color,
);
paint.scene.stroke_line(
Point::new(center.x, center.y - size * 0.5),
Point::new(center.x + size, center.y + size * 0.5),
width,
color,
);
}
/// Strzałka w prawo (chevron); środek w `center`, rozpiętość `size`, grubość `width`.
pub fn chevron_right(paint: &mut PaintCtx, color: Color, center: Point, size: f64, width: f64) {
paint.scene.stroke_line(
Point::new(center.x - size * 0.45, center.y - size),
Point::new(center.x + size * 0.45, center.y),
width,
color,
);
paint.scene.stroke_line(
Point::new(center.x + size * 0.45, center.y),
Point::new(center.x - size * 0.45, center.y + size),
width,
color,
);
}
/// Strzałka w lewo (chevron); środek w `center`, rozpiętość `size`, grubość `width`.
pub fn chevron_left(paint: &mut PaintCtx, color: Color, center: Point, size: f64, width: f64) {
paint.scene.stroke_line(
Point::new(center.x + size * 0.45, center.y - size),
Point::new(center.x - size * 0.45, center.y),
width,
color,
);
paint.scene.stroke_line(
Point::new(center.x - size * 0.45, center.y),
Point::new(center.x + size * 0.45, center.y + size),
width,
color,
);
}
/// Lupka (wyszukiwanie); środek w `center`, bok `size`.
pub fn search(paint: &mut PaintCtx, color: Color, center: Point, size: f64) {
let r = size * 0.34;
let c = Point::new(center.x - size * 0.08, center.y - size * 0.08);
paint.scene.stroke_rounded_rect(
Rect::from_xywh(c.x - r, c.y - r, r * 2.0, r * 2.0),
r,
1.6,
color,
);
let a = Point::new(c.x + r * 0.75, c.y + r * 0.75);
paint.scene.stroke_line(
a,
Point::new(a.x + size * 0.28, a.y + size * 0.28),
1.6,
color,
);
}
/// Ikona katalogu (folder) w prostokącie `box_`.
pub fn folder(paint: &mut PaintCtx, color: Color, box_: Rect) {
let tab_h = box_.height() * 0.24;
let tab_w = box_.width() * 0.46;
paint.scene.fill_rounded_rect(
Rect::from_xywh(box_.x(), box_.y(), tab_w, tab_h * 1.6),
tab_h * 0.4,
color,
);
paint.scene.fill_rounded_rect(
Rect::from_xywh(
box_.x(),
box_.y() + tab_h * 0.9,
box_.width(),
box_.height() - tab_h * 0.9,
),
box_.width() * 0.1,
color,
);
}
/// Ikona pliku (kartka z zagiętym rogiem) w prostokącie `box_`.
pub fn file(paint: &mut PaintCtx, color: Color, box_: Rect) {
let w = box_.width();
paint.scene.stroke_rounded_rect(box_, w * 0.12, 1.8, color);
let fold = w * 0.34;
paint.scene.stroke_line(
Point::new(box_.right() - fold, box_.y()),
Point::new(box_.right() - fold, box_.y() + fold),
1.8,
color,
);
paint.scene.stroke_line(
Point::new(box_.right() - fold, box_.y() + fold),
Point::new(box_.right(), box_.y() + fold),
1.8,
color,
);
}
/// Krzyżyk (zamknięcie); środek w `box_`, ramię `arm`.
pub fn close(paint: &mut PaintCtx, color: Color, box_: Rect, arm: f64) {
let c = box_.center();
paint.scene.stroke_line(
Point::new(c.x - arm, c.y - arm),
Point::new(c.x + arm, c.y + arm),
2.0,
color,
);
paint.scene.stroke_line(
Point::new(c.x + arm, c.y - arm),
Point::new(c.x - arm, c.y + arm),
2.0,
color,
);
}
/// Pozioma kreska (minimalizacja).
pub fn minimize(paint: &mut PaintCtx, color: Color, box_: Rect, arm: f64) {
let c = box_.center();
paint.scene.stroke_line(
Point::new(c.x - arm, c.y),
Point::new(c.x + arm, c.y),
2.0,
color,
);
}
/// Kwadrat (maksymalizacja).
pub fn maximize(paint: &mut PaintCtx, color: Color, box_: Rect, half: f64) {
let c = box_.center();
paint.scene.stroke_rounded_rect(
Rect::from_xywh(c.x - half, c.y - half, half * 2.0, half * 2.0),
2.0,
2.0,
color,
);
}
/// Oko (podgląd hasła); środek w `center`, półszerokość `size`.
pub fn eye(paint: &mut PaintCtx, color: Color, center: Point, size: f64) {
use std::f64::consts::PI;
let w = size;
let h = size * 0.62;
let n = 12;
let mut prev = Point::new(center.x - w, center.y);
for i in 1..=n {
let t = i as f64 / n as f64;
let p = Point::new(center.x - w + 2.0 * w * t, center.y - h * (PI * t).sin());
paint.scene.stroke_line(prev, p, 1.6, color);
prev = p;
}
for i in 1..=n {
let t = i as f64 / n as f64;
let p = Point::new(center.x - w + 2.0 * w * t, center.y + h * (PI * t).sin());
paint.scene.stroke_line(prev, p, 1.6, color);
prev = p;
}
let r = size * 0.22;
paint.scene.fill_rounded_rect(
Rect::from_xywh(center.x - r, center.y - r, r * 2.0, r * 2.0),
r,
color,
);
}
/// Oko przekreślone (hasło ukryte).
pub fn eye_off(paint: &mut PaintCtx, color: Color, center: Point, size: f64) {
eye(paint, color, center, size);
paint.scene.stroke_line(
Point::new(center.x - size * 0.9, center.y + size * 0.9),
Point::new(center.x + size * 0.9, center.y - size * 0.9),
1.6,
color,
);
}
/// Trzy poziome kreski (menu „hamburger”); środek w `center`, szerokość `size`, grubość `width`.
pub fn burger(paint: &mut PaintCtx, color: Color, center: Point, size: f64, width: f64) {
for i in -1..=1 {
let y = center.y + i as f64 * size * 0.42;
paint.scene.stroke_line(
Point::new(center.x - size, y),
Point::new(center.x + size, y),
width,
color,
);
}
}
// ─────────────────────────────────────────────────────────────────────────────
// Nazwany pakiet ikon symbolicznych — rysowany wektorowo, w stylu Luny.
// ─────────────────────────────────────────────────────────────────────────────
/// Nazwa ikony z pakietu Luny.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IconName {
Menu,
Search,
Files,
Settings,
Terminal,
Music,
Star,
Clock,
Store,
Edit,
Wifi,
Volume,
Battery,
Power,
Grid,
List,
Carousel,
ChevronLeft,
ChevronRight,
Close,
Minimize,
Maximize,
Check,
Plus,
Home,
User,
Info,
App,
Brightness,
// Ikony aplikacji (taskbar/launcher) — spójny zestaw wektorowy Luny.
Globe,
Mail,
Chat,
Code,
Image,
Video,
Document,
Calculator,
Game,
Archive,
Printer,
Bluetooth,
Monitor,
Screenshot,
Sliders,
Play,
Pause,
Moon,
}
impl IconName {
/// Wszystkie ikony z pakietu (kolejność = kolejność w galerii).
pub const ALL: [IconName; 47] = [
IconName::Menu,
IconName::Search,
IconName::Files,
IconName::Settings,
IconName::Terminal,
IconName::Music,
IconName::Star,
IconName::Clock,
IconName::Store,
IconName::Edit,
IconName::Wifi,
IconName::Volume,
IconName::Battery,
IconName::Power,
IconName::Grid,
IconName::List,
IconName::Carousel,
IconName::ChevronLeft,
IconName::ChevronRight,
IconName::Close,
IconName::Minimize,
IconName::Maximize,
IconName::Check,
IconName::Plus,
IconName::Home,
IconName::User,
IconName::Info,
IconName::App,
IconName::Brightness,
IconName::Globe,
IconName::Mail,
IconName::Chat,
IconName::Code,
IconName::Image,
IconName::Video,
IconName::Document,
IconName::Calculator,
IconName::Game,
IconName::Archive,
IconName::Printer,
IconName::Bluetooth,
IconName::Monitor,
IconName::Screenshot,
IconName::Sliders,
IconName::Play,
IconName::Pause,
IconName::Moon,
];
/// Nazwa ikony (do galerii/diagnostyki).
pub fn name(self) -> &'static str {
match self {
IconName::Menu => "Menu",
IconName::Search => "Search",
IconName::Files => "Files",
IconName::Settings => "Settings",
IconName::Terminal => "Terminal",
IconName::Music => "Music",
IconName::Star => "Star",
IconName::Clock => "Clock",
IconName::Store => "Store",
IconName::Edit => "Edit",
IconName::Wifi => "Wifi",
IconName::Volume => "Volume",
IconName::Battery => "Battery",
IconName::Power => "Power",
IconName::Grid => "Grid",
IconName::List => "List",
IconName::Carousel => "Carousel",
IconName::ChevronLeft => "ChevronLeft",
IconName::ChevronRight => "ChevronRight",
IconName::Close => "Close",
IconName::Minimize => "Minimize",
IconName::Maximize => "Maximize",
IconName::Check => "Check",
IconName::Plus => "Plus",
IconName::Home => "Home",
IconName::User => "User",
IconName::Info => "Info",
IconName::App => "App",
IconName::Brightness => "Brightness",
IconName::Globe => "Globe",
IconName::Mail => "Mail",
IconName::Chat => "Chat",
IconName::Code => "Code",
IconName::Image => "Image",
IconName::Video => "Video",
IconName::Document => "Document",
IconName::Calculator => "Calculator",
IconName::Game => "Game",
IconName::Archive => "Archive",
IconName::Printer => "Printer",
IconName::Bluetooth => "Bluetooth",
IconName::Monitor => "Monitor",
IconName::Screenshot => "Screenshot",
IconName::Sliders => "Sliders",
IconName::Play => "Play",
IconName::Pause => "Pause",
IconName::Moon => "Moon",
}
}
}
/// Współrzędne w siatce 24×24 mapowane na prostokąt docelowy.
struct Grid {
ox: f64,
oy: f64,
s: f64,
}
impl Grid {
fn new(rect: Rect) -> Self {
let side = rect.width().min(rect.height()).max(1.0);
let s = side / 24.0;
Self {
ox: rect.x() + (rect.width() - 24.0 * s) / 2.0,
oy: rect.y() + (rect.height() - 24.0 * s) / 2.0,
s,
}
}
fn p(&self, x: f64, y: f64) -> Point {
Point::new(self.ox + x * self.s, self.oy + y * self.s)
}
fn d(&self, v: f64) -> f64 {
v * self.s
}
}
/// Punkty łuku (środek `c`, promień `r`, kąty w stopniach, y w dół).
fn arc(c: Point, r: f64, a0: f64, a1: f64, segs: usize) -> Vec<Point> {
(0..=segs)
.map(|i| {
let t = a0 + (a1 - a0) * i as f64 / segs as f64;
let a = t.to_radians();
Point::new(c.x + r * a.cos(), c.y + r * a.sin())
})
.collect()
}
/// Rysuje ikonę z pakietu w prostokącie `rect` w zadanym kolorze (styl symboliczny).
pub fn paint_icon(paint: &mut PaintCtx, name: IconName, rect: Rect, color: Color) {
let g = Grid::new(rect);
let w = g.d(2.0);
let scene = &mut *paint.scene;
match name {
IconName::Menu => {
for y in [6.5, 12.0, 17.5] {
scene.stroke_line(g.p(4.0, y), g.p(20.0, y), w, color);
}
}
IconName::Search => {
scene.stroke_circle(g.p(10.5, 10.5), g.d(6.5), w, color);
scene.stroke_line(g.p(15.3, 15.3), g.p(20.5, 20.5), w, color);
}
IconName::Files => {
scene.fill_polygon(
&[
g.p(3.0, 6.5),
g.p(9.0, 6.5),
g.p(11.0, 8.8),
g.p(21.0, 8.8),
g.p(21.0, 19.5),
g.p(3.0, 19.5),
],
color,
);
}
IconName::Settings => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(3.4), w, color);
for k in 0..8 {
let a = (k as f64 * 45.0).to_radians();
let (c, s) = (a.cos(), a.sin());
scene.stroke_line(
g.p(12.0 + 5.2 * c, 12.0 + 5.2 * s),
g.p(12.0 + 8.4 * c, 12.0 + 8.4 * s),
g.d(2.2),
color,
);
}
}
IconName::Terminal => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 4.5).x, g.p(3.0, 4.5).y, g.d(18.0), g.d(15.0)),
g.d(2.5),
w,
color,
);
scene.stroke_polyline(&[g.p(7.0, 10.0), g.p(9.5, 12.0), g.p(7.0, 14.0)], w, color);
scene.stroke_line(g.p(12.0, 15.0), g.p(16.0, 15.0), w, color);
}
IconName::Music => {
scene.stroke_circle(g.p(9.5, 17.0), g.d(2.6), w, color);
scene.stroke_polyline(&[g.p(12.0, 17.0), g.p(12.0, 6.5), g.p(17.0, 8.0)], w, color);
}
IconName::Star => {
let pts: Vec<Point> = (0..10)
.map(|i| {
let a = (-90.0 + i as f64 * 36.0).to_radians();
let r = if i % 2 == 0 { 9.0 } else { 3.8 };
g.p(12.0 + r * a.cos(), 12.5 + r * a.sin())
})
.collect();
scene.fill_polygon(&pts, color);
}
IconName::Clock => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(8.0), w, color);
scene.stroke_polyline(&[g.p(12.0, 12.0), g.p(12.0, 7.5)], w, color);
scene.stroke_polyline(&[g.p(12.0, 12.0), g.p(16.0, 13.8)], w, color);
}
IconName::Store => {
scene.fill_polygon(
&[
g.p(5.5, 8.0),
g.p(18.5, 8.0),
g.p(17.5, 20.0),
g.p(6.5, 20.0),
],
color,
);
scene.stroke_polyline(
&[g.p(9.0, 8.0), g.p(9.0, 6.0), g.p(15.0, 6.0), g.p(15.0, 8.0)],
g.d(1.8),
color,
);
}
IconName::Edit => {
scene.fill_polygon(
&[
g.p(15.5, 3.5),
g.p(20.5, 8.5),
g.p(9.0, 20.0),
g.p(4.0, 21.0),
g.p(5.0, 16.0),
],
color,
);
}
IconName::Wifi => {
scene.fill_circle(g.p(12.0, 18.0), g.d(1.4), color);
for r in [4.0, 7.5, 11.0] {
scene.stroke_polyline(
&arc(g.p(12.0, 18.0), g.d(r), 200.0, 340.0, 10),
g.d(1.9),
color,
);
}
}
IconName::Volume => {
scene.fill_polygon(
&[
g.p(3.0, 9.5),
g.p(7.0, 9.5),
g.p(11.0, 5.0),
g.p(11.0, 19.0),
g.p(7.0, 14.5),
g.p(3.0, 14.5),
],
color,
);
for r in [5.0, 8.0] {
scene.stroke_polyline(
&arc(g.p(11.0, 12.0), g.d(r), -50.0, 50.0, 8),
g.d(1.9),
color,
);
}
}
IconName::Battery => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(2.0, 7.0).x, g.p(2.0, 7.0).y, g.d(17.0), g.d(10.0)),
g.d(2.5),
w,
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(19.5, 10.5).x, g.p(19.5, 10.5).y, g.d(2.5), g.d(3.0)),
g.d(1.0),
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(4.5, 9.5).x, g.p(4.5, 9.5).y, g.d(9.5), g.d(5.0)),
g.d(1.2),
color,
);
}
IconName::Power => {
scene.stroke_polyline(&arc(g.p(12.0, 12.0), g.d(7.5), -60.0, 240.0, 28), w, color);
scene.stroke_line(g.p(12.0, 3.5), g.p(12.0, 12.0), w, color);
}
IconName::Grid => {
for (x, y) in [(4.0, 4.0), (13.5, 4.0), (4.0, 13.5), (13.5, 13.5)] {
scene.fill_rounded_rect(
Rect::from_xywh(g.p(x, y).x, g.p(x, y).y, g.d(6.5), g.d(6.5)),
g.d(1.6),
color,
);
}
}
IconName::List => {
for y in [6.5, 12.0, 17.5] {
scene.fill_circle(g.p(5.5, y), g.d(1.4), color);
scene.stroke_line(g.p(9.5, y), g.p(19.5, y), g.d(1.8), color);
}
}
IconName::Carousel => {
scene.fill_rounded_rect(
Rect::from_xywh(g.p(3.0, 8.5).x, g.p(3.0, 8.5).y, g.d(3.2), g.d(7.0)),
g.d(1.2),
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(17.8, 8.5).x, g.p(17.8, 8.5).y, g.d(3.2), g.d(7.0)),
g.d(1.2),
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(8.0, 6.0).x, g.p(8.0, 6.0).y, g.d(8.0), g.d(12.0)),
g.d(1.6),
color,
);
}
IconName::ChevronLeft => {
scene.stroke_polyline(
&[g.p(14.5, 6.0), g.p(8.5, 12.0), g.p(14.5, 18.0)],
g.d(2.4),
color,
);
}
IconName::ChevronRight => {
scene.stroke_polyline(
&[g.p(9.5, 6.0), g.p(15.5, 12.0), g.p(9.5, 18.0)],
g.d(2.4),
color,
);
}
IconName::Close => {
scene.stroke_line(g.p(6.5, 6.5), g.p(17.5, 17.5), w, color);
scene.stroke_line(g.p(17.5, 6.5), g.p(6.5, 17.5), w, color);
}
IconName::Minimize => {
scene.stroke_line(g.p(5.0, 12.0), g.p(19.0, 12.0), w, color);
}
IconName::Maximize => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(5.0, 5.0).x, g.p(5.0, 5.0).y, g.d(14.0), g.d(14.0)),
g.d(1.5),
w,
color,
);
}
IconName::Check => {
scene.stroke_polyline(
&[g.p(5.0, 12.5), g.p(10.0, 17.5), g.p(19.0, 6.5)],
g.d(2.2),
color,
);
}
IconName::Plus => {
scene.stroke_line(g.p(12.0, 5.0), g.p(12.0, 19.0), w, color);
scene.stroke_line(g.p(5.0, 12.0), g.p(19.0, 12.0), w, color);
}
IconName::Home => {
scene.fill_polygon(
&[
g.p(12.0, 3.5),
g.p(21.0, 11.5),
g.p(18.5, 11.5),
g.p(18.5, 20.0),
g.p(13.5, 20.0),
g.p(13.5, 15.0),
g.p(10.5, 15.0),
g.p(10.5, 20.0),
g.p(5.5, 20.0),
g.p(5.5, 11.5),
g.p(3.0, 11.5),
],
color,
);
}
IconName::User => {
scene.stroke_circle(g.p(12.0, 9.0), g.d(4.0), w, color);
scene.stroke_polyline(
&[
g.p(5.5, 20.5),
g.p(5.5, 18.5),
g.p(8.5, 15.5),
g.p(15.5, 15.5),
g.p(18.5, 18.5),
g.p(18.5, 20.5),
],
w,
color,
);
}
IconName::Info => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(8.0), w, color);
scene.fill_circle(g.p(12.0, 7.8), g.d(1.3), color);
scene.stroke_line(g.p(12.0, 11.0), g.p(12.0, 16.5), w, color);
}
IconName::App => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(4.0, 4.0).x, g.p(4.0, 4.0).y, g.d(16.0), g.d(16.0)),
g.d(3.0),
w,
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(8.5, 8.5).x, g.p(8.5, 8.5).y, g.d(7.0), g.d(7.0)),
g.d(1.5),
color,
);
}
IconName::Brightness => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(4.0), w, color);
for k in 0..8 {
let a = (k as f64 * 45.0).to_radians();
let (c, s) = (a.cos(), a.sin());
scene.stroke_line(
g.p(12.0 + 6.4 * c, 12.0 + 6.4 * s),
g.p(12.0 + 9.2 * c, 12.0 + 9.2 * s),
g.d(2.0),
color,
);
}
}
// ── Ikony aplikacji (taskbar/launcher) — ten sam grid i grubość kreski ──
IconName::Globe => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(8.0), w, color);
scene.stroke_line(g.p(4.0, 12.0), g.p(20.0, 12.0), g.d(1.6), color);
scene.stroke_line(g.p(12.0, 4.0), g.p(12.0, 20.0), g.d(1.6), color);
for sign in [-1.0_f64, 1.0] {
let pts: Vec<Point> = (0..=8)
.map(|i| {
let a =
-std::f64::consts::FRAC_PI_2 + i as f64 * std::f64::consts::PI / 8.0;
g.p(12.0 + sign * 4.0 * a.cos(), 12.0 + 8.0 * a.sin())
})
.collect();
scene.stroke_polyline(&pts, g.d(1.4), color);
}
}
IconName::Mail => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 5.0).x, g.p(3.0, 5.0).y, g.d(18.0), g.d(14.0)),
g.d(2.0),
w,
color,
);
scene.stroke_polyline(
&[g.p(4.5, 7.0), g.p(12.0, 12.5), g.p(19.5, 7.0)],
g.d(1.6),
color,
);
}
IconName::Chat => {
scene.fill_rounded_rect(
Rect::from_xywh(g.p(3.0, 4.0).x, g.p(3.0, 4.0).y, g.d(18.0), g.d(13.0)),
g.d(4.0),
color,
);
scene.fill_polygon(&[g.p(7.5, 16.5), g.p(6.0, 21.0), g.p(11.5, 16.5)], color);
}
IconName::Code => {
scene.stroke_polyline(&[g.p(9.0, 7.0), g.p(4.0, 12.0), g.p(9.0, 17.0)], w, color);
scene.stroke_polyline(
&[g.p(15.0, 7.0), g.p(20.0, 12.0), g.p(15.0, 17.0)],
w,
color,
);
scene.stroke_line(g.p(13.5, 5.0), g.p(10.5, 19.0), g.d(1.6), color);
}
IconName::Image => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 4.5).x, g.p(3.0, 4.5).y, g.d(18.0), g.d(15.0)),
g.d(2.0),
w,
color,
);
scene.fill_circle(g.p(8.5, 9.5), g.d(1.8), color);
scene.fill_polygon(
&[
g.p(5.0, 17.5),
g.p(9.5, 11.5),
g.p(13.0, 15.0),
g.p(15.5, 12.5),
g.p(19.0, 17.5),
],
color,
);
}
IconName::Video => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 5.5).x, g.p(3.0, 5.5).y, g.d(18.0), g.d(13.0)),
g.d(2.0),
w,
color,
);
scene.fill_polygon(&[g.p(10.0, 9.0), g.p(16.0, 12.0), g.p(10.0, 15.0)], color);
}
IconName::Document => {
scene.stroke_polyline(
&[
g.p(6.0, 3.0),
g.p(14.0, 3.0),
g.p(19.0, 8.0),
g.p(19.0, 21.0),
g.p(6.0, 21.0),
g.p(6.0, 3.0),
],
w,
color,
);
scene.stroke_polyline(
&[g.p(14.0, 3.0), g.p(14.0, 8.0), g.p(19.0, 8.0)],
g.d(1.6),
color,
);
for y in [12.0, 16.0] {
scene.stroke_line(g.p(9.0, y), g.p(16.0, y), g.d(1.6), color);
}
}
IconName::Calculator => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(5.0, 3.0).x, g.p(5.0, 3.0).y, g.d(14.0), g.d(18.0)),
g.d(2.5),
w,
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(7.0, 5.5).x, g.p(7.0, 5.5).y, g.d(10.0), g.d(4.0)),
g.d(1.0),
color,
);
for (x, y) in [
(8.5, 13.0),
(12.0, 13.0),
(15.5, 13.0),
(8.5, 16.5),
(12.0, 16.5),
(15.5, 16.5),
] {
scene.fill_circle(g.p(x, y), g.d(1.1), color);
}
}
IconName::Game => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(2.5, 8.0).x, g.p(2.5, 8.0).y, g.d(19.0), g.d(10.5)),
g.d(5.0),
w,
color,
);
scene.stroke_line(g.p(7.0, 11.5), g.p(7.0, 15.5), g.d(1.8), color);
scene.stroke_line(g.p(5.0, 13.5), g.p(9.0, 13.5), g.d(1.8), color);
scene.fill_circle(g.p(16.5, 12.5), g.d(1.3), color);
scene.fill_circle(g.p(18.8, 14.8), g.d(1.3), color);
}
IconName::Archive => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 5.0).x, g.p(3.0, 5.0).y, g.d(18.0), g.d(15.0)),
g.d(2.0),
w,
color,
);
scene.stroke_line(g.p(3.0, 9.5), g.p(21.0, 9.5), g.d(1.6), color);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(9.0, 3.0).x, g.p(9.0, 3.0).y, g.d(6.0), g.d(4.5)),
g.d(1.0),
color,
);
}
IconName::Printer => {
scene.fill_rect(
Rect::from_xywh(g.p(7.0, 4.0).x, g.p(7.0, 4.0).y, g.d(10.0), g.d(5.0)),
color,
);
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 9.0).x, g.p(3.0, 9.0).y, g.d(18.0), g.d(9.0)),
g.d(2.0),
w,
color,
);
scene.fill_rect(
Rect::from_xywh(g.p(7.0, 15.0).x, g.p(7.0, 15.0).y, g.d(10.0), g.d(5.0)),
color,
);
}
IconName::Bluetooth => {
scene.stroke_polyline(
&[
g.p(8.0, 7.5),
g.p(16.0, 16.5),
g.p(12.0, 21.0),
g.p(12.0, 3.0),
g.p(16.0, 7.5),
g.p(8.0, 16.5),
],
g.d(1.8),
color,
);
}
IconName::Monitor => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 4.5).x, g.p(3.0, 4.5).y, g.d(18.0), g.d(12.0)),
g.d(2.0),
w,
color,
);
scene.stroke_line(g.p(12.0, 16.5), g.p(12.0, 19.5), g.d(1.8), color);
scene.stroke_line(g.p(8.0, 20.5), g.p(16.0, 20.5), g.d(1.8), color);
scene.stroke_polyline(
&[
g.p(6.0, 11.0),
g.p(9.0, 11.0),
g.p(10.5, 8.0),
g.p(12.5, 14.0),
g.p(14.0, 11.0),
g.p(18.0, 11.0),
],
g.d(1.6),
color,
);
}
IconName::Screenshot => {
scene.stroke_rounded_rect(
Rect::from_xywh(g.p(3.0, 7.0).x, g.p(3.0, 7.0).y, g.d(18.0), g.d(12.0)),
g.d(2.0),
w,
color,
);
scene.stroke_circle(g.p(12.0, 13.0), g.d(3.4), g.d(1.8), color);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(9.0, 4.5).x, g.p(9.0, 4.5).y, g.d(6.0), g.d(3.5)),
g.d(1.2),
color,
);
}
IconName::Sliders => {
for (y, knob_x) in [(7.0, 15.0), (12.0, 9.0), (17.0, 13.5)] {
scene.stroke_line(g.p(4.0, y), g.p(20.0, y), g.d(1.8), color);
scene.fill_circle(g.p(knob_x, y), g.d(2.4), color);
}
}
IconName::Play => {
scene.fill_polygon(&[g.p(8.0, 5.0), g.p(19.0, 12.0), g.p(8.0, 19.0)], color);
}
IconName::Pause => {
scene.fill_rounded_rect(
Rect::from_xywh(g.p(7.5, 5.5).x, g.p(7.5, 5.5).y, g.d(3.2), g.d(13.0)),
g.d(1.0),
color,
);
scene.fill_rounded_rect(
Rect::from_xywh(g.p(13.3, 5.5).x, g.p(13.3, 5.5).y, g.d(3.2), g.d(13.0)),
g.d(1.0),
color,
);
}
IconName::Moon => {
scene.stroke_circle(g.p(12.0, 12.0), g.d(7.5), w, color);
// Wewnętrzny łuk — sierp księżyca / ikona motywu ciemnego.
let pts: Vec<Point> = (0..=12)
.map(|i| {
let a = -std::f64::consts::FRAC_PI_2 + i as f64 * std::f64::consts::PI / 12.0;
g.p(15.0 + 4.5 * a.cos(), 12.0 + 6.5 * a.sin())
})
.collect();
scene.stroke_polyline(&pts, g.d(1.6), color);
}
}
}
#[cfg(test)]
mod icon_tests {
use super::*;
use libluna_core::Theme;
use libluna_render::{Scene, TextEngine};
#[test]
fn every_icon_draws_without_panic() {
let theme = Theme::dark();
let text = TextEngine::none();
for name in IconName::ALL {
let mut scene = Scene::new();
let mut ctx = PaintCtx::new(&mut scene, &theme, &text);
paint_icon(
&mut ctx,
name,
Rect::from_xywh(0.0, 0.0, 24.0, 24.0),
theme.colors.text,
);
assert!(!scene.is_empty(), "ikona {name:?} nic nie rysuje");
}
}
}