fix(tahuantinsuyu): crash al abrir modal + simplificación de anillos

- Crash fix (panic en gpui entity_map.rs:138 / double_lease):
  `render_chart_form` hacía `cx.entity().read(cx)` mientras
  estaba dentro del `render()` del tree — la entity ya estaba
  leased como `&mut self` y un read concurrente disparaba el
  double_lease_panic. Se cambió la firma para recibir
  `picker_open` y `city_atlas` como parámetros desde
  `render_modal` (que sí tiene `&self`).

- Simplificación de anillos: el carril de planetas se acerca
  (bodies 0.60·r / bodies_inner 0.57·r) — antes 0.05 de
  separación, ahora 0.03, se ve como "carril" en lugar de dos
  anillos sueltos. El stroke visible del círculo de aspectos
  se elimina — `radii.aspects` queda solo como punto de
  anclaje para las líneas. El `bodies_inner` cambia a stroke
  plano más sutil (no 3D) para no competir con `bodies`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-18 16:26:34 +00:00
parent 9acdf68d67
commit e9369371db
2 changed files with 52 additions and 32 deletions
@@ -1533,15 +1533,19 @@ impl Radii {
houses_outer: r * 0.78, houses_outer: r * 0.78,
houses_inner: r * 0.66, houses_inner: r * 0.66,
midpoints: r * 0.62, midpoints: r * 0.62,
bodies: r * 0.58, bodies: r * 0.60,
bodies_inner: r * 0.53, // bodies_inner cerca de bodies — los dos anillos juntos
// aspects pegado al cinturón de cuerpos pero adentro: // forman un "carril" estrecho que delimita la franja de
// las líneas entran al "círculo de aspectos" justo bajo // planetas, no dos líneas separadas que confunden.
// los glyphs en lugar de cruzar el centro. bodies_inner: r * 0.57,
aspects: r * 0.49, // aspects justo bajo el carril de cuerpos. Las líneas
progression: r * 0.43, // de aspecto entran a este radio, pero el círculo en sí
solar_arc: r * 0.36, // no se pinta — son las líneas las que importan, no
composite: r * 0.28, // un anillo extra que sume ruido.
aspects: r * 0.54,
progression: r * 0.46,
solar_arc: r * 0.38,
composite: r * 0.30,
} }
} }
@@ -1690,20 +1694,22 @@ fn paint_wheel(
} }
} }
// 2.5. Cinturón de planetas + círculo de aspectos. El cinturón // 2.5. Carril de planetas. `bodies` + `bodies_inner` muy juntos
// (bodies + bodies_inner) marca la franja donde viven los // delimitan la franja estrecha donde viven los glyphs natales.
// glyphs natales. El círculo de aspectos queda apenas más // El círculo de aspectos NO se pinta — `radii.aspects` solo
// adentro — las líneas de aspecto se anclan ahí, no en el // existe como punto donde se anclan las líneas; un stroke ahí
// centro, así "conectan" cuerpos cercanos a su anillo en lugar // sería un anillo extra que confunde sin aportar.
// de cruzar toda la rueda.
if show(LayerKind::Bodies) { if show(LayerKind::Bodies) {
let belt_color = with_alpha(palette.dial_ring, 0.55); let belt_color = with_alpha(palette.dial_ring, 0.55);
stroke_circle_3d(window, cx, cy, radii.bodies, 1.0, belt_color, theme); stroke_circle_3d(window, cx, cy, radii.bodies, 0.9, belt_color, theme);
stroke_circle_3d(window, cx, cy, radii.bodies_inner, 0.9, belt_color, theme); stroke_circle(
} window,
if show(LayerKind::Aspects) { cx,
let aspect_ring_color = with_alpha(palette.dial_ring, 0.45); cy,
stroke_circle_3d(window, cx, cy, radii.aspects, 0.9, aspect_ring_color, theme); radii.bodies_inner,
0.6,
with_alpha(palette.dial_ring, 0.35),
);
} }
// 3. Aspectos. Cada module_id usa su par de radios — natal-natal // 3. Aspectos. Cada module_id usa su par de radios — natal-natal
@@ -1370,12 +1370,24 @@ impl TahuantinsuyuTree {
input.clone(), input.clone(),
"Enter = crear — Escape = cancelar", "Enter = crear — Escape = cancelar",
), ),
Modal::CreateChart { form, error, .. } => { Modal::CreateChart { form, error, .. } => render_chart_form(
render_chart_form(theme, "Nueva carta natal", form, error.clone(), cx) theme,
} "Nueva carta natal",
Modal::EditChart { form, error, .. } => { form,
render_chart_form(theme, "Editar carta natal", form, error.clone(), cx) error.clone(),
} self.city_picker_open,
&self.city_atlas,
cx,
),
Modal::EditChart { form, error, .. } => render_chart_form(
theme,
"Editar carta natal",
form,
error.clone(),
self.city_picker_open,
&self.city_atlas,
cx,
),
}; };
div() div()
@@ -1454,6 +1466,12 @@ fn render_chart_form(
title: &str, title: &str,
form: &ChartForm, form: &ChartForm,
error: Option<SharedString>, error: Option<SharedString>,
// Datos del tree que el form necesita renderizar — recibidos por
// parámetro porque esta función se llama desde `render()` y la
// entity del tree ya está leased; un `cx.entity().read(cx)`
// adentro causa double_lease_panic en gpui.
picker_open: bool,
city_atlas: &[CityPreset],
cx: &mut Context<'_, TahuantinsuyuTree>, cx: &mut Context<'_, TahuantinsuyuTree>,
) -> gpui::Div { ) -> gpui::Div {
let labeled = |label: &'static str, input: Entity<TextInput>| -> gpui::Div { let labeled = |label: &'static str, input: Entity<TextInput>| -> gpui::Div {
@@ -1526,10 +1544,6 @@ fn render_chart_form(
// Header del form: title + botón "Ciudad rápida" con dropdown // Header del form: title + botón "Ciudad rápida" con dropdown
// que autocompleta place/lat/lon/tz al elegir un preset. // que autocompleta place/lat/lon/tz al elegir un preset.
let (picker_open, atlas_snapshot) = {
let me = cx.entity().read(cx);
(me.city_picker_open, me.city_atlas.clone())
};
let city_btn = div() let city_btn = div()
.id("tts-form-city-btn") .id("tts-form-city-btn")
.px(px(10.0)) .px(px(10.0))
@@ -1580,7 +1594,7 @@ fn render_chart_form(
.flex() .flex()
.flex_col() .flex_col()
.overflow_y_scroll(); .overflow_y_scroll();
for preset in atlas_snapshot.iter().cloned() { for preset in city_atlas.iter().cloned() {
let row_id: SharedString = let row_id: SharedString =
SharedString::from(format!("tts-city-{}", preset.name)); SharedString::from(format!("tts-city-{}", preset.name));
let label = preset.name.clone(); let label = preset.name.clone();