feat(tahuantinsuyu): fase 8 — slider interactivo + slider de edad en progression
Los `Control::Slider` del panel ya no son display-only — son arrastrables con el mismo patrón del splitter (canvas absoluto sobre el track + window mouse handlers en cada frame). El `ProgressionModule` ahora expone un slider de `target_age_years` (0..120) que el shell inicializa con la edad actual del sujeto al cargar la carta. - panel: SliderDrag struct + slider_state HashMap + slider_drag Option + métodos start/continue/end_slider_drag + apply_slider_position que calcula fraction desde la posición del mouse relativa al track y emite ControlChanged con el valor float. set_slider(module, key, val) para sincronización externa. set_active_kind ahora inicializa también los sliders desde sus defaults. render_slider pinta track + portion filled + thumb circular + canvas overlay con handlers de drag. Los Slider tienen un valor visible "X.X (min...max)" en el header. - modules: ProgressionModule agrega Control::Slider target_age_years con range 0..120, step 0.25, default 30 (placeholder — el shell lo reescribe con la edad real al cargar la carta). - shell: apply_selection(Chart) ahora calcula current_age, lo inserta en module_configs["progression"]["target_age_years"], y empuja al panel via set_slider. build_requests ya leía target_age_years desde el map (de fase 7), así que ahora el slider lo controla. Mecánica: si activás "Progresión secundaria", el slider arranca en la edad actual del sujeto. Arrastralo a la izquierda y la rueda recompone la carta progresada para esa edad simbólica — vas viendo cómo el sujeto "evoluciona" o "involuciona" a través de su línea temporal interna, con los planetas progresados moviéndose por el anillo interno y los cross aspects con la natal reorganizándose en tiempo real. Same pattern aplica de aquí en más para cualquier slider futuro (harmonic en NatalModule, target_year en SolarArc, orb_multiplier, …). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -305,15 +305,26 @@ pub mod progression {
|
||||
false
|
||||
}
|
||||
fn controls(&self) -> Vec<Control> {
|
||||
vec![Control::Toggle {
|
||||
key: "enabled".into(),
|
||||
label: "Activar".into(),
|
||||
default: false,
|
||||
// Sin hotkey por ahora — el toggle vive en el panel.
|
||||
// Fase 8 puede agregar [G] vía un canal genérico de
|
||||
// ModuleToggleRequested.
|
||||
hotkey: None,
|
||||
}]
|
||||
vec![
|
||||
Control::Toggle {
|
||||
key: "enabled".into(),
|
||||
label: "Activar".into(),
|
||||
default: false,
|
||||
hotkey: None,
|
||||
},
|
||||
// El default (30.0) es un placeholder — el shell empuja
|
||||
// la edad actual del sujeto al cargar una carta vía
|
||||
// panel.set_slider("progression", "target_age_years",
|
||||
// current_age).
|
||||
Control::Slider {
|
||||
key: "target_age_years".into(),
|
||||
label: "Edad objetivo (años)".into(),
|
||||
min: 0.0,
|
||||
max: 120.0,
|
||||
step: 0.25,
|
||||
default: 30.0,
|
||||
},
|
||||
]
|
||||
}
|
||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||
Vec::new()
|
||||
|
||||
@@ -4,15 +4,19 @@
|
||||
//! [`tahuantinsuyu_modules::Registry::for_kind`]) y pinta sus
|
||||
//! [`Control`]s como toggles / sliders / selects. Cada cambio emite
|
||||
//! [`PanelEvent`] que la app traduce a mutaciones de visibilidad sobre
|
||||
//! el canvas (fase 4) y eventualmente a `ModuleState` en la store.
|
||||
//! el canvas y al `module_configs` del shell.
|
||||
//!
|
||||
//! ## Estado interno
|
||||
//!
|
||||
//! El panel mantiene un cache `toggle_state` con los valores actuales
|
||||
//! de los toggles por (module_id, key). Inicializa desde los defaults
|
||||
//! declarados por el módulo y se actualiza con cada click. Los sliders
|
||||
//! / selects todavía no son interactivos en fase 4 — quedan como
|
||||
//! display de "valor default".
|
||||
//! - `toggle_state: HashMap<(module_id, key), bool>` — valor actual de
|
||||
//! cada toggle. Se inicializa lazy desde los defaults del módulo al
|
||||
//! cambiar de `ChartKind`.
|
||||
//! - `slider_state: HashMap<(module_id, key), f64>` — valor actual de
|
||||
//! cada slider. El shell puede sobreescribirlo via [`Self::set_slider`]
|
||||
//! cuando cambia la carta activa (ej. inicializar `target_age_years`
|
||||
//! con la edad actual del sujeto).
|
||||
//! - `slider_drag: Option<SliderDrag>` — slider que está bajo drag
|
||||
//! activo. Mutuamente excluyente: solo se arrastra un slider a la vez.
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
@@ -20,8 +24,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use gpui::{
|
||||
ClickEvent, Context, EventEmitter, IntoElement, ParentElement, Render, SharedString, Styled,
|
||||
Window, div, prelude::*, px,
|
||||
Bounds, ClickEvent, Context, EventEmitter, IntoElement, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, SharedString, Styled,
|
||||
Window, canvas, div, prelude::*, px,
|
||||
};
|
||||
|
||||
use tahuantinsuyu_model::ChartKind;
|
||||
@@ -42,15 +47,27 @@ pub enum PanelEvent {
|
||||
},
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Estado interno
|
||||
// =====================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SliderDrag {
|
||||
module_id: String,
|
||||
key: String,
|
||||
min: f64,
|
||||
max: f64,
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Widget
|
||||
// =====================================================================
|
||||
|
||||
pub struct ControlPanel {
|
||||
active_kind: Option<ChartKind>,
|
||||
/// Cache de toggles por (module_id, key). Se popula lazy desde los
|
||||
/// defaults la primera vez que se renderea un kind.
|
||||
toggle_state: HashMap<(String, String), bool>,
|
||||
slider_state: HashMap<(String, String), f64>,
|
||||
slider_drag: Option<SliderDrag>,
|
||||
registry: Registry,
|
||||
}
|
||||
|
||||
@@ -62,20 +79,29 @@ impl ControlPanel {
|
||||
Self {
|
||||
active_kind: None,
|
||||
toggle_state: HashMap::new(),
|
||||
slider_state: HashMap::new(),
|
||||
slider_drag: None,
|
||||
registry: Registry::with_builtins(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_active_kind(&mut self, kind: Option<ChartKind>, cx: &mut Context<Self>) {
|
||||
// Si cambia el kind, inicializamos defaults para sus módulos.
|
||||
if self.active_kind != kind {
|
||||
if let Some(k) = kind {
|
||||
for m in self.registry.for_kind(k) {
|
||||
for c in m.controls() {
|
||||
if let Control::Toggle { key, default, .. } = c {
|
||||
self.toggle_state
|
||||
.entry((m.id().to_string(), key))
|
||||
.or_insert(default);
|
||||
match c {
|
||||
Control::Toggle { key, default, .. } => {
|
||||
self.toggle_state
|
||||
.entry((m.id().to_string(), key))
|
||||
.or_insert(default);
|
||||
}
|
||||
Control::Slider { key, default, .. } => {
|
||||
self.slider_state
|
||||
.entry((m.id().to_string(), key))
|
||||
.or_insert(default);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,14 +111,25 @@ impl ControlPanel {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Setea un toggle desde afuera (sin emitir evento). Útil cuando el
|
||||
/// canvas se autotoggleó via hotkey y queremos sincronizar el panel.
|
||||
/// Setea un toggle desde afuera (sin emitir evento). Usado por el
|
||||
/// shell para sincronizar cuando el canvas se autotoggleó via hotkey.
|
||||
pub fn set_toggle(&mut self, module_id: &str, key: &str, value: bool, cx: &mut Context<Self>) {
|
||||
self.toggle_state
|
||||
.insert((module_id.to_string(), key.to_string()), value);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Setea un slider desde afuera (sin emitir evento). El shell la
|
||||
/// usa, por ejemplo, para inicializar `progression.target_age_years`
|
||||
/// con la edad actual del sujeto al cargar una carta nueva.
|
||||
pub fn set_slider(&mut self, module_id: &str, key: &str, value: f64, cx: &mut Context<Self>) {
|
||||
self.slider_state
|
||||
.insert((module_id.to_string(), key.to_string()), value);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// ----- internos: handlers -----
|
||||
|
||||
fn on_toggle_click(&mut self, module_id: String, key: String, cx: &mut Context<Self>) {
|
||||
let entry = self
|
||||
.toggle_state
|
||||
@@ -107,13 +144,83 @@ impl ControlPanel {
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn start_slider_drag(
|
||||
&mut self,
|
||||
module_id: String,
|
||||
key: String,
|
||||
min: f64,
|
||||
max: f64,
|
||||
bounds: Bounds<Pixels>,
|
||||
position: Point<Pixels>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.slider_drag = Some(SliderDrag {
|
||||
module_id: module_id.clone(),
|
||||
key: key.clone(),
|
||||
min,
|
||||
max,
|
||||
});
|
||||
self.apply_slider_position(bounds, position, cx);
|
||||
}
|
||||
|
||||
fn continue_slider_drag(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
position: Point<Pixels>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.slider_drag.is_some() {
|
||||
self.apply_slider_position(bounds, position, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn end_slider_drag(&mut self, cx: &mut Context<Self>) {
|
||||
if self.slider_drag.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_slider_position(
|
||||
&mut self,
|
||||
bounds: Bounds<Pixels>,
|
||||
position: Point<Pixels>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(drag) = self.slider_drag.as_ref().cloned() else {
|
||||
return;
|
||||
};
|
||||
let track_x: f32 = bounds.origin.x.into();
|
||||
let track_w: f32 = bounds.size.width.into();
|
||||
let mouse_x: f32 = position.x.into();
|
||||
let fraction = if track_w > 0.0 {
|
||||
((mouse_x - track_x) / track_w).clamp(0.0, 1.0) as f64
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let value = drag.min + fraction * (drag.max - drag.min);
|
||||
self.slider_state
|
||||
.insert((drag.module_id.clone(), drag.key.clone()), value);
|
||||
cx.emit(PanelEvent::ControlChanged {
|
||||
module_id: drag.module_id,
|
||||
key: drag.key,
|
||||
value: serde_json::json!(value),
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Render
|
||||
// =====================================================================
|
||||
|
||||
const SLIDER_TRACK_W: f32 = 140.0;
|
||||
const SLIDER_TRACK_H: f32 = 8.0;
|
||||
const SLIDER_THUMB: f32 = 12.0;
|
||||
|
||||
impl Render for ControlPanel {
|
||||
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = Theme::global(cx).clone();
|
||||
// Snapshot de los módulos a renderear — borrowing isssues si
|
||||
// dejáramos el iterador vivo mientras mutamos en el closure.
|
||||
let modules: Vec<(String, String, String, Vec<Control>)> = match self.active_kind {
|
||||
Some(k) => self
|
||||
.registry
|
||||
@@ -221,7 +328,7 @@ impl ControlPanel {
|
||||
}
|
||||
|
||||
div()
|
||||
.min_w(px(240.0))
|
||||
.min_w(px(260.0))
|
||||
.p(px(8.0))
|
||||
.rounded(px(6.0))
|
||||
.bg(theme.bg_panel_alt.clone())
|
||||
@@ -247,123 +354,242 @@ impl ControlPanel {
|
||||
label,
|
||||
default,
|
||||
hotkey,
|
||||
} => {
|
||||
let active = self
|
||||
.toggle_state
|
||||
.get(&(module_id.to_string(), key.clone()))
|
||||
.copied()
|
||||
.unwrap_or(*default);
|
||||
let dot_color = if active {
|
||||
theme.accent
|
||||
} else {
|
||||
theme.fg_disabled
|
||||
};
|
||||
let id_str: SharedString =
|
||||
SharedString::from(format!("tts-toggle-{}-{}", module_id, key));
|
||||
let id_for_listener = (module_id.to_string(), key.clone());
|
||||
let row = div()
|
||||
.id(gpui::ElementId::from(id_str))
|
||||
} => self.render_toggle(theme, module_id, key, label, *default, hotkey.as_deref(), cx),
|
||||
Control::Slider {
|
||||
key,
|
||||
label,
|
||||
min,
|
||||
max,
|
||||
default,
|
||||
..
|
||||
} => self.render_slider(theme, module_id, key, label, *min, *max, *default, cx),
|
||||
Control::Select { label, default, .. } => display_row(theme, label, default),
|
||||
Control::TextInput { label, default, .. } => display_row(theme, label, default),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_toggle(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
module_id: &str,
|
||||
key: &str,
|
||||
label: &str,
|
||||
default: bool,
|
||||
hotkey: Option<&str>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::Div {
|
||||
let active = self
|
||||
.toggle_state
|
||||
.get(&(module_id.to_string(), key.to_string()))
|
||||
.copied()
|
||||
.unwrap_or(default);
|
||||
let dot_color = if active {
|
||||
theme.accent
|
||||
} else {
|
||||
theme.fg_disabled
|
||||
};
|
||||
let id_str: SharedString =
|
||||
SharedString::from(format!("tts-toggle-{}-{}", module_id, key));
|
||||
let id_for_listener = (module_id.to_string(), key.to_string());
|
||||
let hotkey_str = hotkey
|
||||
.map(|h| format!("[{}]", h))
|
||||
.unwrap_or_default();
|
||||
let row = div()
|
||||
.id(gpui::ElementId::from(id_str))
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.rounded(px(4.0))
|
||||
.hover(|s| s.bg(theme.bg_row_hover))
|
||||
.child(div().size(px(8.0)).rounded(px(4.0)).bg(dot_color))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.to_string())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(hotkey_str)),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||
let (m, k) = id_for_listener.clone();
|
||||
this.on_toggle_click(m, k, cx);
|
||||
}));
|
||||
div().child(row)
|
||||
}
|
||||
|
||||
fn render_slider(
|
||||
&self,
|
||||
theme: &Theme,
|
||||
module_id: &str,
|
||||
key: &str,
|
||||
label: &str,
|
||||
min: f64,
|
||||
max: f64,
|
||||
default: f64,
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::Div {
|
||||
let value = self
|
||||
.slider_state
|
||||
.get(&(module_id.to_string(), key.to_string()))
|
||||
.copied()
|
||||
.unwrap_or(default);
|
||||
let range = (max - min).max(f64::EPSILON);
|
||||
let fraction = ((value - min) / range).clamp(0.0, 1.0) as f32;
|
||||
let filled_w = fraction * SLIDER_TRACK_W;
|
||||
let thumb_x = (fraction * SLIDER_TRACK_W) - SLIDER_THUMB / 2.0;
|
||||
|
||||
let entity = cx.entity();
|
||||
let mod_for_mouse = module_id.to_string();
|
||||
let key_for_mouse = key.to_string();
|
||||
let canvas_overlay = canvas(
|
||||
move |_bounds: Bounds<Pixels>, _w, _cx| (),
|
||||
move |bounds: Bounds<Pixels>, _, window, _| {
|
||||
// MouseDown sobre el track → start drag + valor inmediato.
|
||||
let entity_d = entity.clone();
|
||||
let mod_d = mod_for_mouse.clone();
|
||||
let key_d = key_for_mouse.clone();
|
||||
window.on_mouse_event(move |ev: &MouseDownEvent, _, _w, cx| {
|
||||
if ev.button != MouseButton::Left {
|
||||
return;
|
||||
}
|
||||
if !bounds.contains(&ev.position) {
|
||||
return;
|
||||
}
|
||||
entity_d.update(cx, |this, cx| {
|
||||
this.start_slider_drag(
|
||||
mod_d.clone(),
|
||||
key_d.clone(),
|
||||
min,
|
||||
max,
|
||||
bounds,
|
||||
ev.position,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// MouseMove (durante drag) → continuar solo si ESTE
|
||||
// slider es el que está bajo drag.
|
||||
let entity_m = entity.clone();
|
||||
let mod_m = mod_for_mouse.clone();
|
||||
let key_m = key_for_mouse.clone();
|
||||
window.on_mouse_event(move |ev: &MouseMoveEvent, _, _w, cx| {
|
||||
if !ev.dragging() {
|
||||
return;
|
||||
}
|
||||
entity_m.update(cx, |this, cx| {
|
||||
let is_mine = this
|
||||
.slider_drag
|
||||
.as_ref()
|
||||
.map(|d| d.module_id == mod_m && d.key == key_m)
|
||||
.unwrap_or(false);
|
||||
if is_mine {
|
||||
this.continue_slider_drag(bounds, ev.position, cx);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// MouseUp anywhere → terminar drag.
|
||||
let entity_u = entity.clone();
|
||||
window.on_mouse_event(move |_: &MouseUpEvent, _, _w, cx| {
|
||||
entity_u.update(cx, |this, cx| this.end_slider_drag(cx));
|
||||
});
|
||||
},
|
||||
)
|
||||
.absolute()
|
||||
.w(px(SLIDER_TRACK_W))
|
||||
.h(px(SLIDER_TRACK_H));
|
||||
|
||||
let track = div()
|
||||
.relative()
|
||||
.w(px(SLIDER_TRACK_W))
|
||||
.h(px(SLIDER_TRACK_H))
|
||||
.bg(theme.bg_input())
|
||||
.rounded(px(SLIDER_TRACK_H / 2.0))
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(0.0))
|
||||
.top(px(0.0))
|
||||
.h(px(SLIDER_TRACK_H))
|
||||
.w(px(filled_w))
|
||||
.bg(theme.accent)
|
||||
.rounded(px(SLIDER_TRACK_H / 2.0)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left(px(thumb_x))
|
||||
.top(px(-2.0))
|
||||
.w(px(SLIDER_THUMB))
|
||||
.h(px(SLIDER_THUMB))
|
||||
.rounded(px(SLIDER_THUMB / 2.0))
|
||||
.bg(theme.fg_text)
|
||||
.border_1()
|
||||
.border_color(theme.border_strong),
|
||||
)
|
||||
.child(canvas_overlay);
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap(px(4.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.rounded(px(4.0))
|
||||
.hover(|s| s.bg(theme.bg_row_hover))
|
||||
.child(div().size(px(8.0)).rounded(px(4.0)).bg(dot_color))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.clone())),
|
||||
.child(SharedString::from(label.to_string())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(
|
||||
hotkey
|
||||
.clone()
|
||||
.map(|h| format!("[{}]", h))
|
||||
.unwrap_or_default(),
|
||||
)),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _: &ClickEvent, _, cx| {
|
||||
let (m, k) = id_for_listener.clone();
|
||||
this.on_toggle_click(m, k, cx);
|
||||
}));
|
||||
// `id()` devuelve `Stateful<Div>`; envolvemos para
|
||||
// mantener uniforme el return type del match.
|
||||
div().child(row)
|
||||
}
|
||||
Control::Slider {
|
||||
label,
|
||||
min,
|
||||
max,
|
||||
default,
|
||||
..
|
||||
} => div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.clone())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(format!("{} ({}…{})", default, min, max))),
|
||||
),
|
||||
Control::Select { label, default, .. } => div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.clone())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(default.clone())),
|
||||
),
|
||||
Control::TextInput { label, default, .. } => div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.clone())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(default.clone())),
|
||||
),
|
||||
}
|
||||
.child(SharedString::from(format!(
|
||||
"{:.1} ({}…{})",
|
||||
value, min, max
|
||||
))),
|
||||
),
|
||||
)
|
||||
.child(track)
|
||||
}
|
||||
}
|
||||
|
||||
fn display_row(theme: &Theme, label: &str, value: &str) -> gpui::Div {
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.items_center()
|
||||
.gap(px(8.0))
|
||||
.px(px(6.0))
|
||||
.py(px(3.0))
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(theme.fg_text)
|
||||
.child(SharedString::from(label.to_string())),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.ml_auto()
|
||||
.text_size(px(10.0))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(SharedString::from(value.to_string())),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user