feat(tahuantinsuyu): fase 17 — filtros de aspectos + editor + cleanup + labels
Fase completa con 4 mejoras independientes que reusan toda la
infraestructura previa:
## A — Filtros de aspectos en NatalModule
NatalModule gana 3 controles nuevos que SÍ recomponen (a diferencia
de los show_* que solo togglean visibilidad):
- Toggle "Mayores (☌ ☍ △ □ ⚹)" default true
- Toggle "Menores (quincunx, semi-…)" default false
- Slider "Multiplicador de orbe" range 0.25..2.5 step 0.25 default 1.0
Engine API extendida sin romper la existente:
- pub struct NatalOptions { show_majors, show_minors, orb_multiplier }
- pub fn compose_with_options(chart, offset, requests, &NatalOptions)
- compose() queda como wrapper con NatalOptions::default()
- bridge::compose acepta el natal_options, construye OrbTable escalada
(build_orb_table multiplier) y filtra aspects antes de pasarlos a
build_render_model. Build_render_model dejó de filtrar majors
internamente — ahora respeta lo que recibe.
Shell wire:
- build_natal_options() lee aspect_majors/aspect_minors/orb_multiplier
desde module_configs["natal"] con defaults seguros.
- on_panel_event para natal: si key empieza con "show_" → canvas
visibility (sin recompose); otherwise → update module_configs +
persist + render_current.
- render_current pasa natal_options a compose_with_options.
## B — Editor de carta natal existente
- Store::update_chart(id, label, &birth, &config) — actualiza tres
columnas preservando id/contact_id/related/created_at_ms y todo el
module_state asociado (la FK CASCADE no se dispara por UPDATE).
- Tree: Modal::EditChart { id, form, error } reusa ChartForm que ya
manejaba el create. open_edit_chart(id, w, cx) lee la carta con
store.get_chart, pre-carga cada TextInput con el valor existente
(label, birthplace, año, mes, día, hora, min, tz, lat, lon, alt).
submit_modal::EditChart lee form, llama update_chart, preserva el
config existente (zodiac/house_system/bodies no se editan acá).
Menú contextual del chart agrega "Editar…" entre "Abrir" y
"Renombrar".
- render_chart_form ahora toma `title: &str` parameter para que el
modal muestre "Editar carta natal" vs "Nueva carta natal". El
botón cambia "Crear carta" → "Guardar cambios" según el title.
## C — Single source of truth para OUTER_RING_MODULES
- engine exporta `pub const OUTER_RING_MODULES: &[&str] = &["transit",
"synastry", "planetary_return"]`
- shell elimina su const local, importa del engine
- canvas elimina 4 listas hardcodeadas (paint_wheel outer ring active
check + glyphs overlay + aspect_endpoints match) y usa contains() o
early-return sobre el slice. Próximo módulo outer-ring solo necesita
agregarse al const, no buscar copias.
## D — Labels ASC/MC/DESC/IC en el perímetro
Cuatro centered_glyphs en radii.sign_outer * 1.06 (justo afuera del
dial zodiacal, dentro del WHEEL_MARGIN) con color angle_highlight y
font 10px. El ojo identifica los 4 ángulos inmediatamente sin tener
que mapear la línea radial gruesa al ángulo correspondiente.
Las posiciones rotan con la rueda (drag del jog-dial los lleva).
`cargo check` y `cargo test` verdes. La fase agregó 6 controles
visibles al panel del NatalModule (4 view + 2 aspect filter + 1
slider) sin tocar la arquitectura de fases 6-15.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,7 @@ use gpui::{
|
||||
linear_color_stop, linear_gradient, point, prelude::*, px,
|
||||
};
|
||||
|
||||
use tahuantinsuyu_engine::{Geometry, Layer, LayerKind, RenderModel};
|
||||
use tahuantinsuyu_engine::{Geometry, Layer, LayerKind, OUTER_RING_MODULES, RenderModel};
|
||||
use tahuantinsuyu_model::{ChartId, ContactId, GroupId};
|
||||
use tahuantinsuyu_theme::{AspectKind as TAspectKind, AstroPalette, Element, Planet};
|
||||
use yahweh_theme::Theme;
|
||||
@@ -584,9 +584,7 @@ fn render_wheel(
|
||||
if visible.get(&LayerKind::Outer).copied().unwrap_or(true) {
|
||||
for layer in &render.layers {
|
||||
if matches!(layer.kind, LayerKind::Outer)
|
||||
&& (layer.module_id == "transit"
|
||||
|| layer.module_id == "synastry"
|
||||
|| layer.module_id == "planetary_return")
|
||||
&& (OUTER_RING_MODULES.contains(&layer.module_id.as_str()))
|
||||
{
|
||||
for g in &layer.glyphs {
|
||||
let (x, y) = polar_to_screen(g.deg, asc, rot_offset, radii.transits);
|
||||
@@ -609,6 +607,29 @@ fn render_wheel(
|
||||
}
|
||||
}
|
||||
|
||||
// Labels ASC/MC/DESC/IC en el perímetro. Texto pequeño en el
|
||||
// margen exterior (radius * 1.05) para que no se monte con los
|
||||
// glifos de los signos. Color angle_highlight para que el ojo los
|
||||
// reconozca como los cuatro ángulos cardinales.
|
||||
let angle_labels = [
|
||||
(asc, "ASC"),
|
||||
(render.midheaven_deg, "MC"),
|
||||
(render.descendant_deg, "DESC"),
|
||||
(render.imum_coeli_deg, "IC"),
|
||||
];
|
||||
let label_r = r_outer * 1.06;
|
||||
for (deg, label) in angle_labels {
|
||||
let (x, y) = polar_to_screen(deg, asc, rot_offset, label_r);
|
||||
wheel = wheel.child(centered_glyph(
|
||||
cx_center + x,
|
||||
cy_center + y,
|
||||
32.0,
|
||||
10.0,
|
||||
label.into(),
|
||||
palette.angle_highlight,
|
||||
));
|
||||
}
|
||||
|
||||
// --- Header + footer + indicador de tiempo ---
|
||||
let header = div()
|
||||
.flex()
|
||||
@@ -789,12 +810,14 @@ impl Radii {
|
||||
|
||||
/// Resuelve qué radios corresponden a una capa de aspectos según el
|
||||
/// `module_id`: natal-natal en `aspects`, cross con cada overlay
|
||||
/// desde `bodies` (extremo natal) al ring del módulo. Synastry y
|
||||
/// Planetary Return comparten el outer ring de tránsito (los tres
|
||||
/// son mutuamente excluyentes a nivel de Shell).
|
||||
/// desde `bodies` (extremo natal) al ring del módulo. Los módulos
|
||||
/// del outer ring (OUTER_RING_MODULES) comparten el slot de
|
||||
/// tránsito (son mutuamente excluyentes a nivel de Shell).
|
||||
fn aspect_endpoints(&self, module_id: &str) -> (f32, f32) {
|
||||
if OUTER_RING_MODULES.contains(&module_id) {
|
||||
return (self.bodies, self.transits);
|
||||
}
|
||||
match module_id {
|
||||
"transit" | "synastry" | "planetary_return" => (self.bodies, self.transits),
|
||||
"progression" => (self.bodies, self.progression),
|
||||
"solar_arc" => (self.bodies, self.solar_arc),
|
||||
_ => (self.aspects, self.aspects),
|
||||
@@ -1012,9 +1035,7 @@ fn paint_wheel(
|
||||
// si alguno de los dos está prendido, pintamos el slot.
|
||||
let outer_active = layers.iter().any(|l| {
|
||||
matches!(l.kind, LayerKind::Outer)
|
||||
&& (l.module_id == "transit"
|
||||
|| l.module_id == "synastry"
|
||||
|| l.module_id == "planetary_return")
|
||||
&& OUTER_RING_MODULES.contains(&l.module_id.as_str())
|
||||
});
|
||||
if outer_active && show(LayerKind::Outer) {
|
||||
stroke_circle(
|
||||
@@ -1037,9 +1058,7 @@ fn paint_wheel(
|
||||
let dot_r = (radii.sign_outer * 0.017).max(2.0);
|
||||
for layer in layers {
|
||||
if matches!(layer.kind, LayerKind::Outer)
|
||||
&& (layer.module_id == "transit"
|
||||
|| layer.module_id == "synastry"
|
||||
|| layer.module_id == "planetary_return")
|
||||
&& (OUTER_RING_MODULES.contains(&layer.module_id.as_str()))
|
||||
{
|
||||
for g in &layer.glyphs {
|
||||
let color = with_alpha(planet_color(palette, &g.symbol), 0.85);
|
||||
|
||||
Reference in New Issue
Block a user