diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs index c3ebef2..b0b6d4e 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs @@ -626,7 +626,7 @@ fn render_wheel( } else { palette.angle_highlight }; - let footer = div() + let info_row = div() .flex() .flex_row() .gap(px(10.0)) @@ -652,6 +652,27 @@ fn render_wheel( .child("[D]ial [H]ouses as[X]pects [P]lanets [T]ransits [R]eset"), ); + // Badges de overlays activos. Cada uno se pinta como pill con + // background sutil y border tenue. Solo aparecen cuando hay + // overlays — la carta natal pura ve solo el info_row. + let badges_row = if render.overlays.is_empty() { + None + } else { + let mut row = div().flex().flex_row().flex_wrap().gap(px(6.0)); + // Badge "natal" base, siempre presente cuando hay overlays — + // ayuda al usuario a leer la pila de izquierda a derecha. + row = row.child(badge(theme, palette, "natal", "Natal", true)); + for ov in &render.overlays { + row = row.child(badge(theme, palette, &ov.module_id, &ov.label, false)); + } + Some(row) + }; + + let mut footer = div().flex().flex_col().items_center().gap(px(4.0)).child(info_row); + if let Some(b) = badges_row { + footer = footer.child(b); + } + div() .flex() .flex_col() @@ -662,6 +683,33 @@ fn render_wheel( .child(footer) } +/// Pequeña pill con la etiqueta de un overlay activo. El borde toma +/// color según el "tipo" del módulo para ayudar a mapear a su anillo +/// en el wheel: natal = neutro, outer ring share (transit/synastry/ +/// planetary_return) = palette.angle_highlight, inner overlays +/// (progression/solar_arc) = palette.house_cusp. +fn badge(theme: &Theme, palette: &AstroPalette, module_id: &str, label: &str, is_natal: bool) -> gpui::Div { + let border = if is_natal { + theme.border + } else { + match module_id { + "transit" | "synastry" | "planetary_return" => palette.angle_highlight, + "progression" | "solar_arc" => palette.house_cusp, + _ => theme.border, + } + }; + div() + .px(px(8.0)) + .py(px(2.0)) + .rounded(px(10.0)) + .bg(theme.bg_panel_alt.clone()) + .border_1() + .border_color(border) + .text_size(px(10.0)) + .text_color(theme.fg_text) + .child(SharedString::from(label.to_string())) +} + fn format_offset(minutes: i64) -> String { if minutes == 0 { return "⏱ ahora".to_string(); diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs index aaf25fc..29870e5 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/bridge.rs @@ -17,7 +17,7 @@ use eternal_sky::{Ayanamsha, Body, EphemerisSession, Instant as ESInstant, Obser use tahuantinsuyu_model::{Chart, HouseSystem, StoredChartConfig, Zodiac}; -use crate::{EngineError, Geometry, Glyph, Layer, LayerKind, LineSeg, RenderModel}; +use crate::{EngineError, Geometry, Glyph, Layer, LayerKind, LineSeg, OverlayMeta, RenderModel}; // ===================================================================== // Sesión global cacheada @@ -254,15 +254,32 @@ pub fn compose( match req { crate::PipelineRequest::Transit => { build_transit_overlay(&natal, &config_e, observer, ESInstant::now(), &mut render)?; + push_overlay_meta(&mut render, "transit", "Tránsito ahora".into()); } crate::PipelineRequest::SecondaryProgression { target_age_years } => { build_progression_overlay(&natal, *target_age_years, &mut render)?; + push_overlay_meta( + &mut render, + "progression", + format!("Progresión {:.1}a", target_age_years), + ); } crate::PipelineRequest::SolarArc { target_age_years } => { build_solar_arc_overlay(&natal, *target_age_years, &mut render)?; + push_overlay_meta( + &mut render, + "solar_arc", + format!("Solar Arc {:.1}a", target_age_years), + ); } crate::PipelineRequest::Synastry { partner_chart } => { + let partner_label = partner_chart.label.clone(); build_synastry_overlay(&natal, partner_chart, &mut render)?; + push_overlay_meta( + &mut render, + "synastry", + format!("Sinastría · {}", partner_label), + ); } crate::PipelineRequest::PlanetaryReturn { body, @@ -282,6 +299,11 @@ pub fn compose( *target_age_years, &mut render, )?; + push_overlay_meta( + &mut render, + "planetary_return", + format!("{} return {:.0}a", body_e.name(), target_age_years), + ); } } } @@ -808,9 +830,17 @@ fn build_render_model( descendant_deg, imum_coeli_deg, layers: vec![sign_dial, houses, bodies, aspects_layer], + overlays: Vec::new(), } } +fn push_overlay_meta(render: &mut RenderModel, module_id: &str, label: String) { + render.overlays.push(OverlayMeta { + module_id: module_id.to_string(), + label, + }); +} + /// Mapea el orb absoluto a una opacidad — los aspectos más exactos se /// pintan más fuerte, los flojos casi se desvanecen. fn orb_to_opacity(orb_deg: f64, kind: EAspectKind) -> f32 { diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs index c04c9e0..533cfca 100644 --- a/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs +++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-engine/src/lib.rs @@ -61,6 +61,21 @@ pub struct RenderModel { /// Capas a pintar. Orden = z-order ascendente. pub layers: Vec, + /// Metadata humana por overlay activo (transit, progresión, + /// sinastría, retorno...). Vacío para una carta natal pura. La UI + /// la pinta como badges en el footer. + #[serde(default)] + pub overlays: Vec, +} + +/// Etiqueta legible de un overlay para el footer del canvas. La engine +/// la pushea desde cada `build_*_overlay`; el canvas solo lee y pinta. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OverlayMeta { + pub module_id: String, + /// Etiqueta corta — ej. "Tránsito ahora", "Progresión 38.2a", + /// "Sinastría · Ana", "Saturn return 29a". + pub label: String, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -277,6 +292,7 @@ pub fn compute_mock(chart: &Chart) -> RenderModel { descendant_deg: 180.0, imum_coeli_deg: 90.0, layers: vec![sign_dial], + overlays: Vec::new(), } }