feat(tahuantinsuyu): cluster shrink, label compacto, hover destacado con z-order
Cuatro ajustes finos al esquema visual de planetas natales/topo:
1. **Discos achicados en cluster**: glyphs en cluster compartido
(≥2 miembros) llevan un factor adicional `0.86×` sobre el
shrink residual. Visualmente quedan apenas más pequeños — al
estar pegados, achicar un poco evita la sensación de
"amontonamiento" sin perder el unicode.
2. **Pill compartida más chica + libre de "espacios negros"**:
- Cálculo del ancho ahora usa `text.chars().count()` (era
`text.len()` en bytes — los chars unicode astronómicos
cuentan 3 bytes c/u y inflaban el ancho).
- Mínimo de ancho bajado de `font*2.0` a `font*1.4` y
padding lateral reducido. Pills con 1-3 chars ya no llevan
"espacios en negro" que sobrescriben elementos vecinos.
- Font del label compartido normal bajado a 9.0×s (era 10);
el hovereado sube a 10×s. Diferencial claro.
- Label individual también bajó a 8.5×s.
3. **Hover destacado**: nuevo "hovered_idx" identifica el glyph
bajo el cursor (de `HoverInfo::Body`). El glyph hovereado se
pinta al FINAL del árbol DOM — queda con z-order encima del
resto. Border al color pleno (vs 0.85), disco 1.18× y font
1.12× para destacarlo.
4. **Label del cluster hovereado destacado**: el cluster que
contiene al planeta bajo el cursor se renderiza con `fg_text`
(vs `fg_muted` para los demás) y font un punto más grande.
11 tests verdes (sin cambios — los affectados son del path de
render, no del cómputo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1212,11 +1212,36 @@ fn render_wheel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let shrink = (1.0 - residual * 0.30).clamp(0.60, 1.0);
|
let shrink_residual = (1.0 - residual * 0.30).clamp(0.60, 1.0);
|
||||||
let disk_size = disk_size_base * shrink;
|
|
||||||
let font_size_eff = (font_size * shrink).max(11.0);
|
// El hovered glyph y su cluster reciben tratamiento
|
||||||
|
// especial: lo postponemos para pintarlo al FINAL del
|
||||||
|
// árbol (queda por encima del resto = z-order), y le
|
||||||
|
// damos un border más fuerte. Su label cluster también
|
||||||
|
// se destaca (color fg_text en lugar de fg_muted, font
|
||||||
|
// un punto más grande).
|
||||||
|
let hovered_sym: Option<&str> = match hover {
|
||||||
|
Some(HoverInfo::Body { symbol, .. }) => Some(symbol.as_str()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let hovered_idx: Option<usize> = hovered_sym.and_then(|sym| {
|
||||||
|
layer.glyphs.iter().position(|g| g.symbol == sym)
|
||||||
|
});
|
||||||
|
let hovered_cluster: Option<usize> = hovered_idx.map(|i| cluster_of[i]);
|
||||||
|
|
||||||
for (i, g) in layer.glyphs.iter().enumerate() {
|
for (i, g) in layer.glyphs.iter().enumerate() {
|
||||||
|
if Some(i) == hovered_idx {
|
||||||
|
continue; // se pinta al final
|
||||||
|
}
|
||||||
|
// Achicar discos cuando el glyph está en cluster
|
||||||
|
// (≥2 miembros) — al estar pegados se ven mejor
|
||||||
|
// un poco más pequeños.
|
||||||
|
let cluster_size = clusters[cluster_of[i]].len();
|
||||||
|
let in_cluster_shrink = if cluster_size >= 2 { 0.86 } else { 1.0 };
|
||||||
|
let effective_shrink = shrink_residual * in_cluster_shrink;
|
||||||
|
let disk_size = disk_size_base * effective_shrink;
|
||||||
|
let font_size_eff = (font_size * effective_shrink).max(11.0);
|
||||||
|
|
||||||
let display_deg = display_degs[i];
|
let display_deg = display_degs[i];
|
||||||
let (x, y) = polar_to_screen(display_deg, asc, rot_offset, ring);
|
let (x, y) = polar_to_screen(display_deg, asc, rot_offset, ring);
|
||||||
let color = with_alpha(planet_color(palette, &g.symbol), alpha);
|
let color = with_alpha(planet_color(palette, &g.symbol), alpha);
|
||||||
@@ -1240,7 +1265,6 @@ fn render_wheel(
|
|||||||
|
|
||||||
// Coord label individual: solo cuando el glyph
|
// Coord label individual: solo cuando el glyph
|
||||||
// está SOLO en su cluster (≥2 ⇒ label compartido).
|
// está SOLO en su cluster (≥2 ⇒ label compartido).
|
||||||
let cluster_size = clusters[cluster_of[i]].len();
|
|
||||||
if show_coords && (is_natal || is_topo) && cluster_size == 1 {
|
if show_coords && (is_natal || is_topo) && cluster_size == 1 {
|
||||||
let coord = format_coord_compact(g.deg);
|
let coord = format_coord_compact(g.deg);
|
||||||
let label_r = ring - disk_size * 1.3;
|
let label_r = ring - disk_size * 1.3;
|
||||||
@@ -1252,20 +1276,21 @@ fn render_wheel(
|
|||||||
coord.into(),
|
coord.into(),
|
||||||
theme.fg_muted,
|
theme.fg_muted,
|
||||||
halo_bg,
|
halo_bg,
|
||||||
9.5 * s,
|
8.5 * s,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label compartido para CADA cluster con ≥2 miembros.
|
// Label compartido para CADA cluster con ≥2 miembros.
|
||||||
// Texto = símbolos concatenados + coord del centroide
|
// El del cluster hovereado se destaca: color fg_text
|
||||||
// real. Posicionado sobre el centroide DISPLAY (donde
|
// (vs fg_muted) y font un punto más grande.
|
||||||
// se ven los discos tras el shift).
|
|
||||||
if show_coords && (is_natal || is_topo) {
|
if show_coords && (is_natal || is_topo) {
|
||||||
|
let disk_size_typical = disk_size_base * shrink_residual * 0.86;
|
||||||
for (ci, c) in clusters.iter().enumerate() {
|
for (ci, c) in clusters.iter().enumerate() {
|
||||||
if c.len() < 2 {
|
if c.len() < 2 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let highlighted = Some(ci) == hovered_cluster;
|
||||||
let center_display_deg = display_centroids[ci];
|
let center_display_deg = display_centroids[ci];
|
||||||
let center_real_deg = cluster_centroids[ci];
|
let center_real_deg = cluster_centroids[ci];
|
||||||
let symbols: String = c
|
let symbols: String = c
|
||||||
@@ -1275,17 +1300,68 @@ fn render_wheel(
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
let coord = format_coord_compact(center_real_deg);
|
let coord = format_coord_compact(center_real_deg);
|
||||||
let text = format!("{} {}", symbols, coord);
|
let text = format!("{} {}", symbols, coord);
|
||||||
let label_r = ring - disk_size * 1.5;
|
let label_r = ring - disk_size_typical * 1.5;
|
||||||
let (lx, ly) = polar_to_screen(
|
let (lx, ly) = polar_to_screen(
|
||||||
center_display_deg,
|
center_display_deg,
|
||||||
asc,
|
asc,
|
||||||
rot_offset,
|
rot_offset,
|
||||||
label_r,
|
label_r,
|
||||||
);
|
);
|
||||||
|
let (fg, font_sz) = if highlighted {
|
||||||
|
(theme.fg_text, 10.0 * s)
|
||||||
|
} else {
|
||||||
|
(theme.fg_muted, 9.0 * s)
|
||||||
|
};
|
||||||
wheel = wheel.child(coord_label(
|
wheel = wheel.child(coord_label(
|
||||||
cx_center + lx,
|
cx_center + lx,
|
||||||
cy_center + ly,
|
cy_center + ly,
|
||||||
text.into(),
|
text.into(),
|
||||||
|
fg,
|
||||||
|
halo_bg,
|
||||||
|
font_sz,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render del glyph hovered al FINAL: queda encima del
|
||||||
|
// resto en z-order. Disco un poco más grande y border
|
||||||
|
// más prominente para destacar.
|
||||||
|
if let Some(hi) = hovered_idx {
|
||||||
|
let g = &layer.glyphs[hi];
|
||||||
|
let display_deg = display_degs[hi];
|
||||||
|
let (x, y) = polar_to_screen(display_deg, asc, rot_offset, ring);
|
||||||
|
let color = with_alpha(planet_color(palette, &g.symbol), alpha);
|
||||||
|
let mut glyph_text = planet_unicode(&g.symbol).to_string();
|
||||||
|
if g.retrograde {
|
||||||
|
glyph_text.push('ᴿ');
|
||||||
|
}
|
||||||
|
if let Some(marker) = &g.dignity_marker {
|
||||||
|
glyph_text.push_str(marker);
|
||||||
|
}
|
||||||
|
let disk_size = disk_size_base * shrink_residual * 1.18;
|
||||||
|
let font_size_eff = font_size * shrink_residual * 1.12;
|
||||||
|
wheel = wheel.child(planet_glyph(
|
||||||
|
cx_center + x,
|
||||||
|
cy_center + y,
|
||||||
|
disk_size,
|
||||||
|
font_size_eff,
|
||||||
|
glyph_text.into(),
|
||||||
|
color,
|
||||||
|
halo_bg,
|
||||||
|
color, // border al color pleno (no .85) — destaca
|
||||||
|
));
|
||||||
|
// Si el hovered no está en cluster compartido,
|
||||||
|
// pintamos su coord individual destacada acá.
|
||||||
|
let cluster_size = clusters[cluster_of[hi]].len();
|
||||||
|
if show_coords && (is_natal || is_topo) && cluster_size == 1 {
|
||||||
|
let coord = format_coord_compact(g.deg);
|
||||||
|
let label_r = ring - disk_size * 1.3;
|
||||||
|
let (lx, ly) =
|
||||||
|
polar_to_screen(display_deg, asc, rot_offset, label_r);
|
||||||
|
wheel = wheel.child(coord_label(
|
||||||
|
cx_center + lx,
|
||||||
|
cy_center + ly,
|
||||||
|
coord.into(),
|
||||||
theme.fg_text,
|
theme.fg_text,
|
||||||
halo_bg,
|
halo_bg,
|
||||||
10.0 * s,
|
10.0 * s,
|
||||||
@@ -2862,10 +2938,14 @@ fn coord_label(
|
|||||||
halo_bg: Hsla,
|
halo_bg: Hsla,
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
) -> gpui::Div {
|
) -> gpui::Div {
|
||||||
// Estimación gruesa del ancho (caracteres × ~5.5 px a font 9.5).
|
// Estimación del ancho basada en `chars().count()` (NO `text.len()`
|
||||||
// Suficiente para no recortar; el flex centra dentro.
|
// — los chars unicode astronómicos cuentan 3 bytes pero ocupan
|
||||||
let w = (text.len() as f32 * (font_size * 0.58)).max(font_size * 2.0);
|
// ~1 columna de fuente). Padding lateral muy pequeño en lugar de
|
||||||
let h = font_size + 6.0;
|
// un mínimo grande: pills con 1-3 chars no llevan "espacios en
|
||||||
|
// negro" que sobrescriben elementos vecinos.
|
||||||
|
let char_count = text.chars().count() as f32;
|
||||||
|
let w = (char_count * font_size * 0.62 + font_size * 0.5).max(font_size * 1.4);
|
||||||
|
let h = font_size + 5.0;
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.left(px(x - w / 2.0))
|
.left(px(x - w / 2.0))
|
||||||
|
|||||||
Reference in New Issue
Block a user