feat: llimphi standalone — framework UI soberano extraído del monorepo
Motor gráfico Llimphi como workspace independiente: bucle Elm (input→update→view→layout→raster→present) sobre wgpu+vello+taffy+parley. Núcleo (hal/raster/layout/text/ui/theme/surface/motion/icons) + ~40 widgets + módulos, sin dependencias al resto del monorepo. cargo check --workspace pasa (64 crates). Puerta de entrada: cargo run -p llimphi-ui --example counter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,824 @@
|
||||
//! `app_icons` — iconos de marca, uno por dominio/app de gioser.
|
||||
//!
|
||||
//! A diferencia del set canónico de [`crate::Icon`] (glifos genéricos de
|
||||
//! acción: file, save, search…), acá vive **un glifo distintivo por app**.
|
||||
//! Cada app tiene su símbolo y su **color de marca** propios, pero todos
|
||||
//! comparten el mismo lenguaje visual:
|
||||
//!
|
||||
//! - **Mismo grid lógico 24×24**, origen top-left, eje Y hacia abajo.
|
||||
//! - **Stroke-based, sin fill**: trazos con `Join::Round` + `Cap::Round`.
|
||||
//! - **Geometría minimal**: reconocible al primer vistazo aún en 16×16.
|
||||
//! - **Aire de ~3 unidades** en los bordes para que respire dentro de un chip.
|
||||
//!
|
||||
//! La idea es que un dock/spotlight/menú pinte `app_icon_view(AppIcon::Pluma)`
|
||||
//! y obtenga el glifo de la pluma en su color de tinta, sin que la app tenga
|
||||
//! que cargar un PNG ni declarar su propia geometría.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use llimphi_icons::app_icons::{AppIcon, app_icon_view};
|
||||
//!
|
||||
//! // Resuelve desde el id del registro de apps:
|
||||
//! if let Some(icon) = AppIcon::from_app_id("cosmos") {
|
||||
//! let chip = View::new(style).children(vec![app_icon_view(icon, 1.8)]);
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
use llimphi_ui::llimphi_layout::taffy::{
|
||||
prelude::{percent, Size, Style},
|
||||
Position,
|
||||
};
|
||||
use llimphi_ui::llimphi_raster::kurbo::{Affine, BezPath, Cap, Join, Stroke};
|
||||
use llimphi_ui::llimphi_raster::peniko::Color;
|
||||
use llimphi_ui::View;
|
||||
|
||||
/// Una app de gioser con icono de marca. El identificador (`name`) coincide
|
||||
/// con el `id` del `AppEntry` en `app-bus`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum AppIcon {
|
||||
// --- 00_unanchay · PERCIBIR ---
|
||||
Chaka,
|
||||
Khipu,
|
||||
Pineal,
|
||||
Pluma,
|
||||
Puriy,
|
||||
Rimay,
|
||||
// --- 01_yachay · CONOCER ---
|
||||
Cosmos,
|
||||
Dominium,
|
||||
Iniy,
|
||||
Nakui,
|
||||
Tinkuy,
|
||||
// --- 02_ruway · HACER ---
|
||||
Ayni,
|
||||
Cards,
|
||||
Chasqui,
|
||||
Llimphi,
|
||||
Media,
|
||||
Mirada,
|
||||
Nada,
|
||||
Nahual,
|
||||
Shuma,
|
||||
Supay,
|
||||
Takiy,
|
||||
Tullpu,
|
||||
Wawa,
|
||||
// --- 03_ukupacha · RAÍZ ---
|
||||
Agora,
|
||||
Arje,
|
||||
Minga,
|
||||
Sandokan,
|
||||
WawaExplorer,
|
||||
}
|
||||
|
||||
/// Las 29 apps, en orden de cuadrante. Útil para iterar (galerías, tests).
|
||||
pub const ALL: [AppIcon; 29] = [
|
||||
AppIcon::Chaka,
|
||||
AppIcon::Khipu,
|
||||
AppIcon::Pineal,
|
||||
AppIcon::Pluma,
|
||||
AppIcon::Puriy,
|
||||
AppIcon::Rimay,
|
||||
AppIcon::Cosmos,
|
||||
AppIcon::Dominium,
|
||||
AppIcon::Iniy,
|
||||
AppIcon::Nakui,
|
||||
AppIcon::Tinkuy,
|
||||
AppIcon::Ayni,
|
||||
AppIcon::Cards,
|
||||
AppIcon::Chasqui,
|
||||
AppIcon::Llimphi,
|
||||
AppIcon::Media,
|
||||
AppIcon::Mirada,
|
||||
AppIcon::Nada,
|
||||
AppIcon::Nahual,
|
||||
AppIcon::Shuma,
|
||||
AppIcon::Supay,
|
||||
AppIcon::Takiy,
|
||||
AppIcon::Tullpu,
|
||||
AppIcon::Wawa,
|
||||
AppIcon::Agora,
|
||||
AppIcon::Arje,
|
||||
AppIcon::Minga,
|
||||
AppIcon::Sandokan,
|
||||
AppIcon::WawaExplorer,
|
||||
];
|
||||
|
||||
impl AppIcon {
|
||||
/// Id estable de la app (coincide con `AppEntry.id` / nombre del dominio).
|
||||
pub const fn name(self) -> &'static str {
|
||||
match self {
|
||||
AppIcon::Chaka => "chaka",
|
||||
AppIcon::Khipu => "khipu",
|
||||
AppIcon::Pineal => "pineal",
|
||||
AppIcon::Pluma => "pluma",
|
||||
AppIcon::Puriy => "puriy",
|
||||
AppIcon::Rimay => "rimay",
|
||||
AppIcon::Cosmos => "cosmos",
|
||||
AppIcon::Dominium => "dominium",
|
||||
AppIcon::Iniy => "iniy",
|
||||
AppIcon::Nakui => "nakui",
|
||||
AppIcon::Tinkuy => "tinkuy",
|
||||
AppIcon::Ayni => "ayni",
|
||||
AppIcon::Cards => "cards",
|
||||
AppIcon::Chasqui => "chasqui",
|
||||
AppIcon::Llimphi => "llimphi",
|
||||
AppIcon::Media => "media",
|
||||
AppIcon::Mirada => "mirada",
|
||||
AppIcon::Nada => "nada",
|
||||
AppIcon::Nahual => "nahual",
|
||||
AppIcon::Shuma => "shuma",
|
||||
AppIcon::Supay => "supay",
|
||||
AppIcon::Takiy => "takiy",
|
||||
AppIcon::Tullpu => "tullpu",
|
||||
AppIcon::Wawa => "wawa",
|
||||
AppIcon::Agora => "agora",
|
||||
AppIcon::Arje => "arje",
|
||||
AppIcon::Minga => "minga",
|
||||
AppIcon::Sandokan => "sandokan",
|
||||
AppIcon::WawaExplorer => "wawa-explorer",
|
||||
}
|
||||
}
|
||||
|
||||
/// Resuelve una app desde su `id` del registro. Acepta tanto
|
||||
/// `"wawa-explorer"` como `"wawa_explorer"`.
|
||||
pub fn from_app_id(id: &str) -> Option<AppIcon> {
|
||||
let id = id.trim().to_ascii_lowercase();
|
||||
let id = id.replace('_', "-");
|
||||
ALL.into_iter().find(|a| a.name() == id)
|
||||
}
|
||||
|
||||
/// Color de marca de la app — el que el dock/menú debería usar para
|
||||
/// pintar el glifo por default.
|
||||
pub const fn brand(self) -> Color {
|
||||
let (r, g, b) = match self {
|
||||
AppIcon::Chaka => (43, 166, 164),
|
||||
AppIcon::Khipu => (181, 101, 29),
|
||||
AppIcon::Pineal => (108, 79, 216),
|
||||
AppIcon::Pluma => (61, 59, 142),
|
||||
AppIcon::Puriy => (63, 163, 77),
|
||||
AppIcon::Rimay => (232, 131, 58),
|
||||
AppIcon::Cosmos => (230, 184, 0),
|
||||
AppIcon::Dominium => (74, 111, 165),
|
||||
AppIcon::Iniy => (124, 179, 66),
|
||||
AppIcon::Nakui => (194, 84, 157),
|
||||
AppIcon::Tinkuy => (217, 83, 79),
|
||||
AppIcon::Ayni => (42, 168, 196),
|
||||
AppIcon::Cards => (142, 99, 206),
|
||||
AppIcon::Chasqui => (52, 179, 106),
|
||||
AppIcon::Llimphi => (229, 91, 122),
|
||||
AppIcon::Media => (226, 62, 87),
|
||||
AppIcon::Mirada => (45, 125, 210),
|
||||
AppIcon::Nada => (136, 147, 160),
|
||||
AppIcon::Nahual => (124, 77, 191),
|
||||
AppIcon::Shuma => (224, 165, 38),
|
||||
AppIcon::Supay => (155, 63, 181),
|
||||
AppIcon::Takiy => (229, 99, 155),
|
||||
AppIcon::Tullpu => (224, 96, 58),
|
||||
AppIcon::Wawa => (91, 141, 239),
|
||||
AppIcon::Agora => (47, 158, 143),
|
||||
AppIcon::Arje => (176, 141, 87),
|
||||
AppIcon::Minga => (224, 123, 57),
|
||||
AppIcon::Sandokan => (192, 57, 43),
|
||||
AppIcon::WawaExplorer => (110, 160, 240),
|
||||
};
|
||||
Color::from_rgb8(r, g, b)
|
||||
}
|
||||
|
||||
/// `BezPath` del glifo en coords del grid 24×24.
|
||||
pub fn path(self) -> BezPath {
|
||||
match self {
|
||||
AppIcon::Chaka => path_chaka(),
|
||||
AppIcon::Khipu => path_khipu(),
|
||||
AppIcon::Pineal => path_pineal(),
|
||||
AppIcon::Pluma => path_pluma(),
|
||||
AppIcon::Puriy => path_puriy(),
|
||||
AppIcon::Rimay => path_rimay(),
|
||||
AppIcon::Cosmos => path_cosmos(),
|
||||
AppIcon::Dominium => path_dominium(),
|
||||
AppIcon::Iniy => path_iniy(),
|
||||
AppIcon::Nakui => path_nakui(),
|
||||
AppIcon::Tinkuy => path_tinkuy(),
|
||||
AppIcon::Ayni => path_ayni(),
|
||||
AppIcon::Cards => path_cards(),
|
||||
AppIcon::Chasqui => path_chasqui(),
|
||||
AppIcon::Llimphi => path_llimphi(),
|
||||
AppIcon::Media => path_media(),
|
||||
AppIcon::Mirada => path_mirada(),
|
||||
AppIcon::Nada => path_nada(),
|
||||
AppIcon::Nahual => path_nahual(),
|
||||
AppIcon::Shuma => path_shuma(),
|
||||
AppIcon::Supay => path_supay(),
|
||||
AppIcon::Takiy => path_takiy(),
|
||||
AppIcon::Tullpu => path_tullpu(),
|
||||
AppIcon::Wawa => path_wawa(),
|
||||
AppIcon::Agora => path_agora(),
|
||||
AppIcon::Arje => path_arje(),
|
||||
AppIcon::Minga => path_minga(),
|
||||
AppIcon::Sandokan => path_sandokan(),
|
||||
AppIcon::WawaExplorer => path_wawa_explorer(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `View` que pinta el icono de app en su **color de marca**, ocupando todo
|
||||
/// el rect del padre, escalado uniforme y centrado.
|
||||
///
|
||||
/// - `stroke_width` en unidades del grid 24×24 (típico de marca: `1.8`).
|
||||
pub fn app_icon_view<Msg: Clone + 'static>(icon: AppIcon, stroke_width: f32) -> View<Msg> {
|
||||
app_icon_view_colored(icon, icon.brand(), stroke_width)
|
||||
}
|
||||
|
||||
/// Igual que [`app_icon_view`] pero forzando un color (p.ej. monocromo
|
||||
/// `theme.fg_text` para un menú denso donde el color distrae).
|
||||
pub fn app_icon_view_colored<Msg: Clone + 'static>(
|
||||
icon: AppIcon,
|
||||
color: Color,
|
||||
stroke_width: f32,
|
||||
) -> View<Msg> {
|
||||
View::new(Style {
|
||||
position: Position::Absolute,
|
||||
size: Size {
|
||||
width: percent(1.0_f32),
|
||||
height: percent(1.0_f32),
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.paint_with(move |scene, _ts, rect| {
|
||||
paint_app_icon(scene, rect, icon, color, stroke_width);
|
||||
})
|
||||
}
|
||||
|
||||
/// Pintor crudo — para stampear varios iconos de app dentro del mismo
|
||||
/// `paint_with` (una grilla de launcher, por ejemplo).
|
||||
pub fn paint_app_icon(
|
||||
scene: &mut llimphi_ui::llimphi_raster::vello::Scene,
|
||||
rect: llimphi_ui::PaintRect,
|
||||
icon: AppIcon,
|
||||
color: Color,
|
||||
stroke_width: f32,
|
||||
) {
|
||||
let side = rect.w.min(rect.h) as f64;
|
||||
if side <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let scale = side / 24.0;
|
||||
let tx = rect.x as f64 + (rect.w as f64 - side) * 0.5;
|
||||
let ty = rect.y as f64 + (rect.h as f64 - side) * 0.5;
|
||||
let xform = Affine::translate((tx, ty)) * Affine::scale(scale);
|
||||
|
||||
let stroke = Stroke::new(stroke_width as f64)
|
||||
.with_join(Join::Round)
|
||||
.with_caps(Cap::Round);
|
||||
let path = icon.path();
|
||||
scene.stroke(&stroke, xform, color, None, &path);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Helpers
|
||||
// =====================================================================
|
||||
|
||||
/// Círculo aproximado con `segments` lados rectos (liso por el Cap::Round).
|
||||
fn circle(cx: f64, cy: f64, r: f64, segments: usize) -> BezPath {
|
||||
let mut p = BezPath::new();
|
||||
for i in 0..=segments {
|
||||
let theta = std::f64::consts::TAU * (i as f64) / (segments as f64);
|
||||
let x = cx + r * theta.cos();
|
||||
let y = cy + r * theta.sin();
|
||||
if i == 0 {
|
||||
p.move_to((x, y));
|
||||
} else {
|
||||
p.line_to((x, y));
|
||||
}
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
/// Empuja todos los elementos de `src` dentro de `dst` (para componer
|
||||
/// glifos hechos de varias subformas).
|
||||
fn push_all(dst: &mut BezPath, src: BezPath) {
|
||||
for el in src.elements() {
|
||||
dst.push(*el);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Glifos — uno por app. Grid 24×24, margen ~3.
|
||||
// =====================================================================
|
||||
|
||||
// --- 00_unanchay · PERCIBIR ---
|
||||
|
||||
fn path_chaka() -> BezPath {
|
||||
// chaka = puente: tablero recto + arco + dos pilotes.
|
||||
let mut p = BezPath::new();
|
||||
// Tablero.
|
||||
p.move_to((3.0, 9.0));
|
||||
p.line_to((21.0, 9.0));
|
||||
// Arco bajo el tablero.
|
||||
p.move_to((5.0, 18.0));
|
||||
p.curve_to((5.0, 11.0), (19.0, 11.0), (19.0, 18.0));
|
||||
// Pilotes que conectan tablero y arco.
|
||||
p.move_to((9.0, 9.0));
|
||||
p.line_to((9.0, 12.5));
|
||||
p.move_to((15.0, 9.0));
|
||||
p.line_to((15.0, 12.5));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_khipu() -> BezPath {
|
||||
// khipu: cordón principal + tres ramales con nudos (puntos).
|
||||
let mut p = BezPath::new();
|
||||
// Cordón superior.
|
||||
p.move_to((4.0, 6.0));
|
||||
p.line_to((20.0, 6.0));
|
||||
// Ramales.
|
||||
p.move_to((7.0, 6.0));
|
||||
p.line_to((7.0, 19.0));
|
||||
p.move_to((12.0, 6.0));
|
||||
p.line_to((12.0, 20.0));
|
||||
p.move_to((17.0, 6.0));
|
||||
p.line_to((17.0, 18.0));
|
||||
// Nudos.
|
||||
push_all(&mut p, circle(7.0, 12.0, 1.3, 10));
|
||||
push_all(&mut p, circle(12.0, 10.0, 1.3, 10));
|
||||
push_all(&mut p, circle(12.0, 16.0, 1.3, 10));
|
||||
push_all(&mut p, circle(17.0, 11.0, 1.3, 10));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_pineal() -> BezPath {
|
||||
// pineal = tercer ojo: párpado almendrado + iris + antena/rayo arriba.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 12.0));
|
||||
p.curve_to((8.0, 7.0), (16.0, 7.0), (20.0, 12.0));
|
||||
p.curve_to((16.0, 17.0), (8.0, 17.0), (4.0, 12.0));
|
||||
push_all(&mut p, circle(12.0, 12.0, 2.6, 14));
|
||||
p.move_to((12.0, 3.0));
|
||||
p.line_to((12.0, 5.5));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_pluma() -> BezPath {
|
||||
// pluma = plumín: rombo apuntando abajo + ranura + ojal.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((12.0, 3.0));
|
||||
p.line_to((16.0, 9.0));
|
||||
p.line_to((13.5, 20.0));
|
||||
p.line_to((10.5, 20.0));
|
||||
p.line_to((8.0, 9.0));
|
||||
p.close_path();
|
||||
// Ranura.
|
||||
p.move_to((12.0, 11.5));
|
||||
p.line_to((12.0, 19.0));
|
||||
// Ojal.
|
||||
push_all(&mut p, circle(12.0, 9.5, 1.2, 10));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_puriy() -> BezPath {
|
||||
// puriy = caminar/recorrido: senda curva ascendente con flecha.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((6.0, 20.0));
|
||||
p.curve_to((6.0, 12.0), (18.0, 12.0), (18.0, 4.0));
|
||||
// Cabeza de flecha.
|
||||
p.move_to((15.0, 6.0));
|
||||
p.line_to((18.0, 4.0));
|
||||
p.line_to((20.5, 6.5));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_rimay() -> BezPath {
|
||||
// rimay = palabra/habla: globo de diálogo con cola + dos renglones.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 6.0));
|
||||
p.line_to((20.0, 6.0));
|
||||
p.line_to((20.0, 15.0));
|
||||
p.line_to((11.0, 15.0));
|
||||
p.line_to((8.0, 19.0));
|
||||
p.line_to((8.0, 15.0));
|
||||
p.line_to((4.0, 15.0));
|
||||
p.close_path();
|
||||
// Renglones.
|
||||
p.move_to((8.0, 9.5));
|
||||
p.line_to((16.0, 9.5));
|
||||
p.move_to((8.0, 12.0));
|
||||
p.line_to((13.0, 12.0));
|
||||
p
|
||||
}
|
||||
|
||||
// --- 01_yachay · CONOCER ---
|
||||
|
||||
fn path_cosmos() -> BezPath {
|
||||
// cosmos = destello de 4 puntas + dos estrellas pequeñas.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((12.0, 4.0));
|
||||
p.line_to((13.4, 10.6));
|
||||
p.line_to((20.0, 12.0));
|
||||
p.line_to((13.4, 13.4));
|
||||
p.line_to((12.0, 20.0));
|
||||
p.line_to((10.6, 13.4));
|
||||
p.line_to((4.0, 12.0));
|
||||
p.line_to((10.6, 10.6));
|
||||
p.close_path();
|
||||
// Estrellas chicas.
|
||||
push_all(&mut p, circle(19.0, 6.0, 0.8, 8));
|
||||
push_all(&mut p, circle(5.5, 18.0, 0.8, 8));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_dominium() -> BezPath {
|
||||
// dominium = ERP/libro mayor: barras de distinta altura sobre una base.
|
||||
let mut p = BezPath::new();
|
||||
// Base.
|
||||
p.move_to((3.0, 20.0));
|
||||
p.line_to((21.0, 20.0));
|
||||
// Columnas.
|
||||
p.move_to((6.0, 14.0));
|
||||
p.line_to((9.0, 14.0));
|
||||
p.line_to((9.0, 20.0));
|
||||
p.line_to((6.0, 20.0));
|
||||
p.close_path();
|
||||
p.move_to((10.5, 8.0));
|
||||
p.line_to((13.5, 8.0));
|
||||
p.line_to((13.5, 20.0));
|
||||
p.line_to((10.5, 20.0));
|
||||
p.close_path();
|
||||
p.move_to((15.0, 11.0));
|
||||
p.line_to((18.0, 11.0));
|
||||
p.line_to((18.0, 20.0));
|
||||
p.line_to((15.0, 20.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_iniy() -> BezPath {
|
||||
// iniy = aliento/creer: brote con tallo y dos hojas.
|
||||
let mut p = BezPath::new();
|
||||
// Tallo.
|
||||
p.move_to((12.0, 20.0));
|
||||
p.line_to((12.0, 10.0));
|
||||
// Hoja izquierda.
|
||||
p.move_to((12.0, 14.0));
|
||||
p.curve_to((8.0, 14.0), (6.0, 11.0), (7.0, 8.0));
|
||||
p.curve_to((10.0, 9.0), (12.0, 11.0), (12.0, 14.0));
|
||||
// Hoja derecha.
|
||||
p.move_to((12.0, 12.0));
|
||||
p.curve_to((15.5, 12.0), (17.0, 9.0), (16.5, 6.0));
|
||||
p.curve_to((14.0, 7.0), (12.0, 9.0), (12.0, 12.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_nakui() -> BezPath {
|
||||
// nakui = grafo de morfismos: tres nodos + aristas.
|
||||
let mut p = BezPath::new();
|
||||
// Aristas (primero, para que queden bajo los nodos).
|
||||
p.move_to((7.5, 9.0));
|
||||
p.line_to((16.5, 9.0));
|
||||
p.move_to((7.5, 9.8));
|
||||
p.line_to((10.8, 16.0));
|
||||
p.move_to((16.5, 9.8));
|
||||
p.line_to((13.2, 16.0));
|
||||
// Nodos.
|
||||
push_all(&mut p, circle(6.0, 8.0, 2.2, 14));
|
||||
push_all(&mut p, circle(18.0, 8.0, 2.2, 14));
|
||||
push_all(&mut p, circle(12.0, 18.0, 2.2, 14));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_tinkuy() -> BezPath {
|
||||
// tinkuy = encuentro/choque: dos flechas que convergen + chispa.
|
||||
let mut p = BezPath::new();
|
||||
// Flecha izquierda →
|
||||
p.move_to((3.0, 12.0));
|
||||
p.line_to((9.5, 12.0));
|
||||
p.move_to((7.5, 10.0));
|
||||
p.line_to((9.5, 12.0));
|
||||
p.line_to((7.5, 14.0));
|
||||
// Flecha derecha ←
|
||||
p.move_to((21.0, 12.0));
|
||||
p.line_to((14.5, 12.0));
|
||||
p.move_to((16.5, 10.0));
|
||||
p.line_to((14.5, 12.0));
|
||||
p.line_to((16.5, 14.0));
|
||||
// Chispa central.
|
||||
push_all(&mut p, circle(12.0, 12.0, 1.6, 10));
|
||||
p
|
||||
}
|
||||
|
||||
// --- 02_ruway · HACER ---
|
||||
|
||||
fn path_ayni() -> BezPath {
|
||||
// ayni = reciprocidad: dos flechas curvas en ciclo.
|
||||
let mut p = BezPath::new();
|
||||
// Arco superior, flecha hacia la derecha-abajo.
|
||||
p.move_to((6.0, 8.0));
|
||||
p.curve_to((9.0, 4.0), (15.0, 4.0), (18.0, 8.5));
|
||||
p.move_to((15.5, 8.0));
|
||||
p.line_to((18.0, 8.5));
|
||||
p.line_to((18.5, 5.8));
|
||||
// Arco inferior, flecha hacia la izquierda-arriba.
|
||||
p.move_to((18.0, 16.0));
|
||||
p.curve_to((15.0, 20.0), (9.0, 20.0), (6.0, 15.5));
|
||||
p.move_to((8.5, 16.0));
|
||||
p.line_to((6.0, 15.5));
|
||||
p.line_to((5.5, 18.2));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_cards() -> BezPath {
|
||||
// cards = naipes apilados: carta frontal + borde de la de atrás.
|
||||
let mut p = BezPath::new();
|
||||
// Carta de atrás (asoma arriba y a la derecha).
|
||||
p.move_to((8.0, 5.0));
|
||||
p.line_to((19.0, 5.0));
|
||||
p.line_to((19.0, 16.0));
|
||||
// Carta frontal.
|
||||
p.move_to((5.0, 9.0));
|
||||
p.line_to((15.0, 9.0));
|
||||
p.line_to((15.0, 20.0));
|
||||
p.line_to((5.0, 20.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_chasqui() -> BezPath {
|
||||
// chasqui = mensajero: avión de papel.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 11.0));
|
||||
p.line_to((20.0, 4.0));
|
||||
p.line_to((13.0, 20.0));
|
||||
p.line_to((11.0, 13.0));
|
||||
p.close_path();
|
||||
// Pliegue central.
|
||||
p.move_to((11.0, 13.0));
|
||||
p.line_to((20.0, 4.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_llimphi() -> BezPath {
|
||||
// llimphi = pintura/color: paleta con apoyo para el pulgar + 3 gotas.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 12.0));
|
||||
p.curve_to((4.0, 6.0), (11.0, 4.0), (15.0, 5.0));
|
||||
p.curve_to((20.0, 6.5), (21.0, 12.0), (18.0, 15.0));
|
||||
p.curve_to((16.0, 16.5), (16.5, 13.5), (14.0, 14.0));
|
||||
p.curve_to((11.5, 14.5), (12.5, 18.0), (9.0, 18.0));
|
||||
p.curve_to((5.5, 18.0), (4.0, 15.0), (4.0, 12.0));
|
||||
p.close_path();
|
||||
// Gotas de pintura.
|
||||
push_all(&mut p, circle(8.0, 9.0, 1.1, 10));
|
||||
push_all(&mut p, circle(12.0, 8.0, 1.1, 10));
|
||||
push_all(&mut p, circle(15.5, 10.0, 1.1, 10));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_media() -> BezPath {
|
||||
// media = reproducción: marco + triángulo de play.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 6.0));
|
||||
p.line_to((20.0, 6.0));
|
||||
p.line_to((20.0, 18.0));
|
||||
p.line_to((4.0, 18.0));
|
||||
p.close_path();
|
||||
// Play.
|
||||
p.move_to((10.0, 9.0));
|
||||
p.line_to((10.0, 15.0));
|
||||
p.line_to((16.0, 12.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_mirada() -> BezPath {
|
||||
// mirada = ojo: párpado + iris + pupila.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((3.0, 12.0));
|
||||
p.curve_to((8.0, 6.0), (16.0, 6.0), (21.0, 12.0));
|
||||
p.curve_to((16.0, 18.0), (8.0, 18.0), (3.0, 12.0));
|
||||
p.close_path();
|
||||
push_all(&mut p, circle(12.0, 12.0, 3.4, 18));
|
||||
push_all(&mut p, circle(12.0, 12.0, 1.0, 8));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_nada() -> BezPath {
|
||||
// nada = vacío: conjunto vacío ∅ (anillo + diagonal).
|
||||
let mut p = circle(12.0, 12.0, 8.0, 28);
|
||||
p.move_to((6.5, 17.5));
|
||||
p.line_to((17.5, 6.5));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_nahual() -> BezPath {
|
||||
// nahual = máscara/mutación de forma: antifaz con dos ojos.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 9.0));
|
||||
p.curve_to((4.0, 6.5), (8.0, 6.0), (10.0, 7.5));
|
||||
p.curve_to((11.0, 8.2), (13.0, 8.2), (14.0, 7.5));
|
||||
p.curve_to((16.0, 6.0), (20.0, 6.5), (20.0, 9.0));
|
||||
p.curve_to((20.0, 13.5), (16.0, 16.5), (12.0, 15.5));
|
||||
p.curve_to((8.0, 16.5), (4.0, 13.5), (4.0, 9.0));
|
||||
p.close_path();
|
||||
push_all(&mut p, circle(9.0, 10.0, 1.3, 10));
|
||||
push_all(&mut p, circle(15.0, 10.0, 1.3, 10));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_shuma() -> BezPath {
|
||||
// shuma = discernir: embudo/filtro.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((4.0, 6.0));
|
||||
p.line_to((20.0, 6.0));
|
||||
p.line_to((13.0, 14.0));
|
||||
p.line_to((13.0, 19.0));
|
||||
p.line_to((11.0, 20.0));
|
||||
p.line_to((11.0, 14.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_supay() -> BezPath {
|
||||
// supay = espíritu del ukhupacha: llama doble.
|
||||
let mut p = BezPath::new();
|
||||
// Llama exterior.
|
||||
p.move_to((12.0, 3.0));
|
||||
p.curve_to((17.0, 9.0), (16.0, 14.0), (12.0, 21.0));
|
||||
p.curve_to((8.0, 14.0), (7.0, 9.0), (12.0, 3.0));
|
||||
p.close_path();
|
||||
// Llama interior.
|
||||
p.move_to((12.0, 9.0));
|
||||
p.curve_to((14.0, 12.0), (13.0, 16.0), (12.0, 18.0));
|
||||
p.curve_to((11.0, 16.0), (10.0, 12.0), (12.0, 9.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_takiy() -> BezPath {
|
||||
// takiy = cantar: corchea + ondas de sonido.
|
||||
let mut p = BezPath::new();
|
||||
// Cabeza de nota.
|
||||
push_all(&mut p, circle(8.0, 18.0, 2.4, 16));
|
||||
// Plica.
|
||||
p.move_to((10.4, 18.0));
|
||||
p.line_to((10.4, 6.0));
|
||||
// Banderola.
|
||||
p.move_to((10.4, 6.0));
|
||||
p.curve_to((13.5, 7.0), (14.5, 9.0), (13.5, 11.0));
|
||||
// Ondas.
|
||||
p.move_to((16.0, 9.0));
|
||||
p.curve_to((18.0, 11.0), (18.0, 13.0), (16.0, 15.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_tullpu() -> BezPath {
|
||||
// tullpu = tinte/color: tres gotas.
|
||||
let mut p = BezPath::new();
|
||||
// Gota 1.
|
||||
p.move_to((8.0, 5.0));
|
||||
p.curve_to((11.0, 9.0), (11.0, 11.0), (8.0, 12.0));
|
||||
p.curve_to((5.0, 11.0), (5.0, 9.0), (8.0, 5.0));
|
||||
p.close_path();
|
||||
// Gota 2.
|
||||
p.move_to((16.0, 6.0));
|
||||
p.curve_to((19.0, 10.0), (19.0, 12.0), (16.0, 13.0));
|
||||
p.curve_to((13.0, 12.0), (13.0, 10.0), (16.0, 6.0));
|
||||
p.close_path();
|
||||
// Gota 3.
|
||||
p.move_to((12.0, 13.0));
|
||||
p.curve_to((15.0, 17.0), (15.0, 19.0), (12.0, 20.0));
|
||||
p.curve_to((9.0, 19.0), (9.0, 17.0), (12.0, 13.0));
|
||||
p.close_path();
|
||||
p
|
||||
}
|
||||
|
||||
fn path_wawa() -> BezPath {
|
||||
// wawa = célula/semilla (el SO en gestación): membrana + núcleo.
|
||||
let mut p = circle(12.0, 12.0, 8.0, 28);
|
||||
push_all(&mut p, circle(12.0, 12.0, 3.0, 16));
|
||||
p
|
||||
}
|
||||
|
||||
// --- 03_ukupacha · RAÍZ ---
|
||||
|
||||
fn path_agora() -> BezPath {
|
||||
// agora = firma/confianza: escudo con check.
|
||||
let mut p = BezPath::new();
|
||||
p.move_to((12.0, 3.0));
|
||||
p.line_to((20.0, 6.0));
|
||||
p.line_to((20.0, 12.0));
|
||||
p.curve_to((20.0, 17.0), (16.0, 20.0), (12.0, 21.0));
|
||||
p.curve_to((8.0, 20.0), (4.0, 17.0), (4.0, 12.0));
|
||||
p.line_to((4.0, 6.0));
|
||||
p.close_path();
|
||||
// Check.
|
||||
p.move_to((8.5, 12.0));
|
||||
p.line_to((11.0, 14.5));
|
||||
p.line_to((16.0, 8.5));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_arje() -> BezPath {
|
||||
// arje = arché/raíz de confianza: ancla.
|
||||
let mut p = BezPath::new();
|
||||
// Anillo.
|
||||
push_all(&mut p, circle(12.0, 5.0, 2.2, 14));
|
||||
// Caña.
|
||||
p.move_to((12.0, 7.2));
|
||||
p.line_to((12.0, 19.0));
|
||||
// Travesaño.
|
||||
p.move_to((8.0, 10.0));
|
||||
p.line_to((16.0, 10.0));
|
||||
// Uñas/brazos.
|
||||
p.move_to((6.0, 14.0));
|
||||
p.curve_to((6.0, 18.5), (9.0, 20.0), (12.0, 20.0));
|
||||
p.move_to((18.0, 14.0));
|
||||
p.curve_to((18.0, 18.5), (15.0, 20.0), (12.0, 20.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_minga() -> BezPath {
|
||||
// minga = trabajo comunal: tres figuras.
|
||||
let mut p = BezPath::new();
|
||||
// Figura central.
|
||||
push_all(&mut p, circle(12.0, 7.0, 2.2, 14));
|
||||
p.move_to((8.0, 18.0));
|
||||
p.curve_to((8.0, 13.0), (16.0, 13.0), (16.0, 18.0));
|
||||
// Figura izquierda.
|
||||
push_all(&mut p, circle(5.5, 10.0, 1.6, 12));
|
||||
p.move_to((2.5, 18.0));
|
||||
p.curve_to((2.5, 14.5), (6.0, 13.5), (7.5, 15.0));
|
||||
// Figura derecha.
|
||||
push_all(&mut p, circle(18.5, 10.0, 1.6, 12));
|
||||
p.move_to((21.5, 18.0));
|
||||
p.curve_to((21.5, 14.5), (18.0, 13.5), (16.5, 15.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_sandokan() -> BezPath {
|
||||
// sandokan = caja/contenedor aislado: cubo isométrico.
|
||||
let mut p = BezPath::new();
|
||||
// Cara frontal.
|
||||
p.move_to((5.0, 8.0));
|
||||
p.line_to((14.0, 8.0));
|
||||
p.line_to((14.0, 18.0));
|
||||
p.line_to((5.0, 18.0));
|
||||
p.close_path();
|
||||
// Tapa.
|
||||
p.move_to((5.0, 8.0));
|
||||
p.line_to((9.0, 4.0));
|
||||
p.line_to((18.0, 4.0));
|
||||
p.line_to((14.0, 8.0));
|
||||
// Cara lateral.
|
||||
p.move_to((14.0, 8.0));
|
||||
p.line_to((18.0, 4.0));
|
||||
p.line_to((18.0, 14.0));
|
||||
p.line_to((14.0, 18.0));
|
||||
p
|
||||
}
|
||||
|
||||
fn path_wawa_explorer() -> BezPath {
|
||||
// wawa-explorer = launchpad: grilla 2×2.
|
||||
let mut p = BezPath::new();
|
||||
for (x, y) in &[(5.0, 5.0), (13.0, 5.0), (5.0, 13.0), (13.0, 13.0)] {
|
||||
p.move_to((*x, *y));
|
||||
p.line_to((*x + 6.0, *y));
|
||||
p.line_to((*x + 6.0, *y + 6.0));
|
||||
p.line_to((*x, *y + 6.0));
|
||||
p.close_path();
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn all_app_icons_have_nonempty_path() {
|
||||
for icon in ALL {
|
||||
let p = icon.path();
|
||||
assert!(
|
||||
p.elements().len() > 0,
|
||||
"icono de app {} produjo path vacío",
|
||||
icon.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_names_are_unique() {
|
||||
let mut names: Vec<&str> = ALL.iter().map(|i| i.name()).collect();
|
||||
let n = names.len();
|
||||
names.sort();
|
||||
names.dedup();
|
||||
assert_eq!(names.len(), n, "nombres duplicados en AppIcon::name()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_app_id_roundtrips() {
|
||||
for icon in ALL {
|
||||
assert_eq!(AppIcon::from_app_id(icon.name()), Some(icon));
|
||||
}
|
||||
// Tolera underscores y mayúsculas.
|
||||
assert_eq!(AppIcon::from_app_id("WAWA_EXPLORER"), Some(AppIcon::WawaExplorer));
|
||||
assert_eq!(AppIcon::from_app_id("desconocida"), None);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user