feat(tahuantinsuyu): fase 4 — jog-dial perimetral, hotkeys y panel interactivo
Time scrubbing por drag en el aro exterior del wheel: rota visualmente mientras dura el drag, al soltar traduce el delta angular a minutos (1° = 4 min sideral, CW = forward) y emite CanvasEvent::TimeOffsetChanged. La Shell recomputa con engine::compute_at_offset y el ascendant rotado queda en la nueva posición. Snap visual a 0° tras commit. - engine: nueva variante compute_at_offset(chart, minutes) que suma segundos al UTC base via add_seconds + Instant::from_utc y corre la pipeline normal. compute() es ahora wrapper con offset=0. - canvas: estado nuevo layer_visibility + drag_jog. Mouse handlers registrados desde el paint callback (mismo patrón que splitter/tiled). Hotkeys D/H/X/P toggle SignDial/Houses/Aspects/Bodies, R resetea offset. FocusHandle + click-to-focus para recibir teclas. Indicador ⏱ ±Xd HH:MM en el footer con color highlight cuando el offset != 0. paint_wheel + glyph overlays respetan layer_visibility (skip capas ocultas). - modules: NatalModule.controls() ahora expone show_sign_dial / show_houses / show_aspects / show_bodies con hotkeys [D/H/X/P], más el slider de armónico. - panel: ControlPanel mantiene toggle_state cache (module_id, key) → bool, inicializa desde defaults al cambiar de ChartKind. Click invierte el toggle visualmente y emite ControlChanged. Nuevo set_toggle(module, key, value) para que la Shell mantenga sync cuando el canvas se autotogglea por hotkey. - shell: nuevo current_chart + current_offset_minutes. render_current() delega a compute_at_offset. Suscripción a CanvasEvent traduce TimeOffsetChanged → re-render, LayerVisibilityChanged → panel sync. Suscripción a PanelEvent::ControlChanged traduce show_* keys a set_layer_visible sobre el canvas. Todos los tests verdes. La fase 5 sumará módulos extra (transit, progression, synastry, uranian) + extracción de eternal de lo que falte. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -173,12 +173,12 @@ fn aspect_kind_id(k: EAspectKind) -> &'static str {
|
||||
// compute()
|
||||
// =====================================================================
|
||||
|
||||
pub fn compute(chart: &Chart) -> Result<RenderModel, EngineError> {
|
||||
pub fn compute_at_offset(chart: &Chart, offset_minutes: i64) -> Result<RenderModel, EngineError> {
|
||||
let t0 = Instant::now();
|
||||
chart.validate()?;
|
||||
|
||||
let bd = &chart.birth_data;
|
||||
let instant = ESInstant::from_civil_local(
|
||||
let base_instant = ESInstant::from_civil_local(
|
||||
bd.year,
|
||||
u8::try_from(bd.month).map_err(|_| {
|
||||
EngineError::Eternal(format!("mes fuera de u8: {}", bd.month))
|
||||
@@ -197,6 +197,17 @@ pub fn compute(chart: &Chart) -> Result<RenderModel, EngineError> {
|
||||
)
|
||||
.map_err(|e| EngineError::Eternal(format!("Instant::from_civil_local: {:?}", e)))?;
|
||||
|
||||
// Aplicar el offset (en minutos) sumando segundos al UTC y
|
||||
// reconstruyendo el `Instant`. `UTC::add_seconds` está disponible
|
||||
// pero opera sobre la representación interna; el `Instant` la
|
||||
// re-envuelve vía `from_utc`.
|
||||
let instant = if offset_minutes == 0 {
|
||||
base_instant
|
||||
} else {
|
||||
let shifted_utc = base_instant.utc().add_seconds((offset_minutes as f64) * 60.0);
|
||||
ESInstant::from_utc(shifted_utc)
|
||||
};
|
||||
|
||||
let observer = Observer::from_degrees(bd.latitude_deg, bd.longitude_deg, bd.altitude_m);
|
||||
|
||||
let mut birth_e = BirthData::new(instant, observer);
|
||||
|
||||
Reference in New Issue
Block a user