tawasuyu

tawasuyu/brahman

Rust 202TOML 61JSON 7HTML 12471 Definitions

feat(tahuantinsuyu): distinción eclíptica/casas, coord labels, jog-dial con Ctrl

- Anillo de casas claramente distinto del dial zodiacal: nuevo
  `house_ring_color(palette)` que toma `house_cusp` y le aplica
  un hue shift de 140° en paletas con color (en BW devuelve el
  color original — un shift cromático en monocromo es ruido sin
  información). El sistema ascensional (casas) ya no se confunde
  con el eclíptico (signos): dorado vs verde/teal.

- Coordenadas permanentes en planetas y cusps de casa: por
  default visibles, togglean con hotkey `C` o desde el panel
  vía `state.show_coords`. Cada planeta natal lleva una pill
  pequeña afuera del disco con "DD°{signo}" (ej. "14°♈"); cada
  cusp de casa lleva la misma pill por dentro del anillo
  interior. Helpers nuevos: `format_coord_compact`,
  `coord_label`.

- Jog-dial requiere Ctrl/Cmd para activar. Sin modifier, LMB
  drag es siempre pan — sobre o fuera del anillo. Con modifier
  + LMB sobre el anillo se activa la rotación de tiempo (el
  control de rectificación). Evita rotaciones accidentales al
  navegar la rueda.

- Hint del info_row actualizado: incluye [C]oords y la
  convención "Ctrl+drag = tiempo".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

8ede06f8c4fb668a877e5864d655bf877cf00550 · by sergio · (5 months ago) · parent e9369371d · browse the tree at this commit

Semantic delta · 12

crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs

1 files · +165 −11

diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
index a18161a..8d4d5eb 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-canvas/src/lib.rs
@@ -131,6 +131,10 @@ pub struct CanvasState {
     pub view_pan_y: f32,
     /// Por-LayerKind: `true` = visible. Default = todo visible.
     pub layer_visibility: HashMap<LayerKind, bool>,
+    /// Indicadores de grado al lado de cada planeta y cusp de casa.
+    /// Default `true` — el usuario los espera ver para leer la
+    /// carta. Se togglean con `C` (Coords) o desde el panel.
+    pub show_coords: bool,
     /// Planeta hovered actualmente (para tooltip). `None` cuando el
     /// mouse no está sobre ningún cuerpo.
     pub hover: Option<HoverInfo>,
@@ -215,6 +219,7 @@ impl Default for CanvasState {
             view_pan_x: 0.0,
             view_pan_y: 0.0,
             layer_visibility: HashMap::new(),
+            show_coords: true,
             hover: None,
             drag_jog: None,
             drag_pan: None,
@@ -291,6 +296,11 @@ impl AstrologyCanvas {
         cx.notify();
     }
 
+    pub fn toggle_coords(&mut self, cx: &mut Context<'_, Self>) {
+        self.state.show_coords = !self.state.show_coords;
+        cx.notify();
+    }
+
     /// Resetea zoom y pan a sus defaults (1.0 y 0,0). No toca rotation
     /// ni time offset — esos son ortogonales y tienen su propio reset.
     pub fn reset_view(&mut self, cx: &mut Context<'_, Self>) {
@@ -336,15 +346,24 @@ impl AstrologyCanvas {
 
     // ----- Internos: handlers de jog-dial -----
 
-    /// Despacha el LMB down entre jog-dial (sobre el anillo de signos)
-    /// y pan (cualquier otra parte del canvas). El jog-dial es el
-    /// control de rectificación de hora; el pan es navegación libre.
+    /// Despacha el LMB down entre jog-dial y pan. El jog-dial es un
+    /// control "fuerte" (mueve el tiempo de la carta), así que se
+    /// activa SOLO con modifier Ctrl/Cmd + click sobre el anillo de
+    /// signos — sin modifier es siempre pan, incluso sobre el anillo,
+    /// para que no haya rotaciones accidentales al manipular la
+    /// rueda.
     fn on_primary_down(
         &mut self,
         position: Point<Pixels>,
+        modifiers: gpui::Modifiers,
         bounds: Bounds<Pixels>,
         cx: &mut Context<'_, Self>,
     ) {
+        // Sin modifier: pan, sin importar dónde caiga el click.
+        if !(modifiers.control || modifiers.platform) {
+            self.on_pan_down(position, cx);
+            return;
+        }
         let (cx_px, cy_px) = bounds_center(bounds);
         let mx: f32 = position.x.into();
         let my: f32 = position.y.into();
@@ -361,6 +380,8 @@ impl AstrologyCanvas {
                 accumulated_delta_deg: 0.0,
             });
         } else {
+            // Ctrl+click fuera del anillo: pan también — el modifier
+            // habilita el jog-dial pero no impide la navegación.
             self.on_pan_down(position, cx);
         }
     }
@@ -650,6 +671,10 @@ impl AstrologyCanvas {
                 self.reset_view(cx);
                 return;
             }
+            "c" | "C" => {
+                self.toggle_coords(cx);
+                return;
+            }
             "s" | "S" => {
                 cx.emit(CanvasEvent::ExportSvgRequested);
                 return;
@@ -753,6 +778,7 @@ impl Render for AstrologyCanvas {
                 self.state.view_pan_x,
                 self.state.view_pan_y,
                 &self.state.layer_visibility,
+                self.state.show_coords,
                 self.state.hover.as_ref(),
                 entity,
             ),
@@ -874,6 +900,7 @@ fn render_wheel(
     view_pan_x: f32,
     view_pan_y: f32,
     layer_visibility: &HashMap<LayerKind, bool>,
+    show_coords: bool,
     hover: Option<&HoverInfo>,
     entity: gpui::Entity<AstrologyCanvas>,
 ) -> gpui::Div {
@@ -935,8 +962,9 @@ fn render_wheel(
                 }
                 match ev.button {
                     MouseButton::Left => {
+                        let mods = ev.modifiers;
                         entity_d.update(cx, |this, cx| {
-                            this.on_primary_down(ev.position, bounds, cx)
+                            this.on_primary_down(ev.position, mods, bounds, cx)
                         });
                     }
                     MouseButton::Middle => {
@@ -999,6 +1027,10 @@ fn render_wheel(
     // por view_scale para que el zoom afecte uniformemente todo el
     // contenido visual del wheel, no solo la geometría del canvas.
     let s = view_scale;
+    // Color del halo para los discos detrás de glyphs y pills — se
+    // calcula una sola vez, lo usan planetas, casas, ASC/MC y los
+    // coord labels.
+    let halo_bg = glyph_halo(theme);
     // Sign glyphs.
     if visible.get(&LayerKind::SignDial).copied().unwrap_or(true) {
         let sign_ring_mid = (radii.sign_outer + radii.sign_inner) / 2.0;
@@ -1020,9 +1052,10 @@ fn render_wheel(
         }
     }
 
-    // House numbers.
+    // House numbers + (opcional) coord del cusp.
     if visible.get(&LayerKind::Houses).copied().unwrap_or(true) {
         let house_label_r = (radii.houses_outer + radii.houses_inner) / 2.0;
+        let house_label_color = house_ring_color(palette);
         for layer in &render.layers {
             if matches!(layer.kind, LayerKind::Houses) {
                 for g in &layer.glyphs {
@@ -1034,8 +1067,25 @@ fn render_wheel(
                             16.0 * s,
                             11.0 * s,
                             format!("{}", h).into(),
-                            palette.house_cusp,
+                            house_label_color,
                         ));
+                        // Coord del cusp justo dentro del anillo de
+                        // casas — los grados se imprimen en una pill
+                        // pequeña pegada al radio del cusp.
+                        if show_coords {
+                            let coord = format_coord_compact(g.deg);
+                            let label_r = radii.houses_inner - 8.0 * s;
+                            let (lx, ly) =
+                                polar_to_screen(g.deg, asc, rot_offset, label_r);
+                            wheel = wheel.child(coord_label(
+                                cx_center + lx,
+                                cy_center + ly,
+                                coord.into(),
+                                theme.fg_muted,
+                                halo_bg,
+                                8.5 * s,
+                            ));
+                        }
                     }
                 }
             }
@@ -1046,7 +1096,6 @@ fn render_wheel(
     // solar_arc) en sus rings, ambos con disco-halo para legibilidad
     // contra cualquier fondo. El natal lleva un tamaño un poco mayor
     // que los overlays para que se lea como "el cuerpo principal".
-    let halo_bg = glyph_halo(theme);
     if visible.get(&LayerKind::Bodies).copied().unwrap_or(true) {
         for layer in &render.layers {
             if matches!(layer.kind, LayerKind::Bodies) {
@@ -1075,6 +1124,26 @@ fn render_wheel(
                         halo_bg,
                         with_alpha(color, 0.85),
                     ));
+
+                    // Coord label: grado dentro del signo + glyph del
+                    // signo, pintado justo afuera del disco del
+                    // planeta (radialmente). Sólo en natal (los
+                    // overlays ya cargan info en su badge / tooltip).
+                    if show_coords && is_natal {
+                        let coord = format_coord_compact(g.deg);
+                        let label_r = ring + disk_size * 0.7;
+                        let (lx, ly) = polar_to_screen(g.deg, asc, rot_offset, label_r);
+                        wheel = wheel.child(
+                            coord_label(
+                                cx_center + lx,
+                                cy_center + ly,
+                                coord.into(),
+                                theme.fg_muted,
+                                halo_bg,
+                                9.5 * s,
+                            ),
+                        );
+                    }
                 }
             }
         }
@@ -1324,7 +1393,9 @@ fn render_wheel(
             div()
                 .text_size(px(10.0))
                 .text_color(theme.fg_disabled)
-                .child("[D]ial [H]ouses as[X]pects [P]lanets [T]ransits [S]vg [R]eset"),
+                .child(
+                    "[D]ial [H]ouses as[X]pects [P]lanets [T]ransits [C]oords  ·  Ctrl+drag = tiempo  ·  [0] reset zoom  ·  [R] reset tiempo  ·  [S]vg",
+                ),
         );
 
     // Badges de overlays activos. Cada uno se pinta como pill con
@@ -1633,9 +1704,13 @@ fn paint_wheel(
 
     // 2. Casas — doble anillo (inner + outer) + cusps radiales +
     // énfasis Asc/IC/Desc/MC. La doble línea vuelve a la zona de
-    // casas una "corona" claramente identificable contra el resto.
+    // casas una "corona" claramente identificable. Color derivado
+    // de `house_cusp` con un hue shift para que el sistema
+    // ascensional (casas) se distinga visualmente del eclíptico
+    // (dial zodiacal) que va en dorado.
     if show(LayerKind::Houses) {
-        let house_color = with_alpha(palette.house_cusp, 0.85);
+        let house_base = house_ring_color(palette);
+        let house_color = with_alpha(house_base, 0.85);
         stroke_circle_3d(window, cx, cy, radii.houses_outer, 1.1, house_color, theme);
         stroke_circle_3d(window, cx, cy, radii.houses_inner, 1.1, house_color, theme);
 
@@ -1647,7 +1722,7 @@ fn paint_wheel(
                         let color = if is_angle {
                             palette.angle_highlight
                         } else {
-                            with_alpha(palette.house_cusp, 0.7)
+                            with_alpha(house_base, 0.75)
                         };
                         let width = if is_angle { 2.0 } else { 0.8 };
                         paint_radial_line(
@@ -2223,6 +2298,62 @@ fn planet_glyph(
         .child(text)
 }
 
+/// Formato compacto de un grado eclíptico: "DD°SS" donde SS es el
+/// glyph del signo zodiacal (♈♉♊…). Ej: 14.93° → "14°♈". Los
+/// minutos se omiten — la pill es pequeña y los grados enteros
+/// alcanzan para orientación visual. El tooltip muestra el detalle.
+fn format_coord_compact(deg: f32) -> String {
+    let normalized = deg.rem_euclid(360.0);
+    let sign_idx = ((normalized / 30.0).floor() as usize) % 12;
+    let deg_in_sign = (normalized - (sign_idx as f32) * 30.0).floor() as i32;
+    let sign_glyph = match sign_idx {
+        0 => "♈",
+        1 => "♉",
+        2 => "♊",
+        3 => "♋",
+        4 => "♌",
+        5 => "♍",
+        6 => "♎",
+        7 => "♏",
+        8 => "♐",
+        9 => "♑",
+        10 => "♒",
+        _ => "♓",
+    };
+    format!("{}°{}", deg_in_sign, sign_glyph)
+}
+
+/// Pill pequeña con un coord ("14°♈") junto al glyph de un planeta
+/// o cusp. Fondo halo + texto fg_muted, padding mínimo para no
+/// saturar la rueda con etiquetas grandes.
+fn coord_label(
+    x: f32,
+    y: f32,
+    text: SharedString,
+    fg: Hsla,
+    halo_bg: Hsla,
+    font_size: f32,
+) -> gpui::Div {
+    // Estimación gruesa del ancho (caracteres × ~5.5 px a font 9.5).
+    // Suficiente para no recortar; el flex centra dentro.
+    let w = (text.len() as f32 * (font_size * 0.58)).max(font_size * 2.0);
+    let h = font_size + 6.0;
+    div()
+        .absolute()
+        .left(px(x - w / 2.0))
+        .top(px(y - h / 2.0))
+        .w(px(w))
+        .h(px(h))
+        .flex()
+        .items_center()
+        .justify_center()
+        .rounded(px(h / 2.0))
+        .bg(halo_bg)
+        .text_size(px(font_size))
+        .text_color(fg)
+        .child(text)
+}
+
 /// Color HSL semi-opaco para los halos de los glyphs — derivado del
 /// theme. En dark va casi negro; en light casi blanco. Alpha alta para
 /// que el char quede legible contra cualquier cosa que haya detrás
@@ -2246,6 +2377,29 @@ fn adjust_luma(c: Hsla, delta: f32) -> Hsla {
     hsla(c.h, c.s, (c.l + delta).clamp(0.0, 1.0), c.a)
 }
 
+/// Devuelve `c` con el hue desplazado `delta_deg` grados sobre el
+/// círculo cromático (wrap a [0,1] en la escala normalizada de gpui).
+/// Usado para derivar el color del anillo de casas desde el del dial
+/// zodiacal — los dos sistemas (eclíptica vs ascensional) deben
+/// distinguirse a primera vista pero compartir "familia" cromática.
+fn shift_hue(c: Hsla, delta_deg: f32) -> Hsla {
+    let new_h = (c.h + delta_deg / 360.0).rem_euclid(1.0);
+    hsla(new_h, c.s, c.l, c.a)
+}
+
+/// Color para los anillos del sistema de casas (ascensional). En
+/// paletas con color, lo derivamos de `house_cusp` con un hue shift
+/// de ~140° para diferenciar de la eclíptica (que va con el dorado
+/// de `dial_ring`). En BW devolvemos `house_cusp` tal cual — un
+/// shift cromático en monocromo es ruido sin información.
+fn house_ring_color(palette: &AstroPalette) -> Hsla {
+    if palette.is_monochrome() {
+        palette.house_cusp
+    } else {
+        shift_hue(palette.house_cusp, 140.0)
+    }
+}
+
 /// Stroke con efecto embossed: 3 trazos concéntricos. El highlight va
 /// 0.7 px hacia el centro con luminancia subida; el principal en `r`;
 /// el shadow 0.7 px hacia afuera con luminancia bajada. La dirección