feat(shuma-shell): divisores arrastrables entre los paneles

Cada panel lateral tiene ahora un divisor de 5px que se arrastra para
redimensionarlo (cursor resize, resaltado al hover). El arrastre se
sigue a nivel de ventana —on_mouse_move/up en la raíz— así que no se
pierde aunque el cursor salga del divisor. Ancho acotado 130–420px;
los divisores desaparecen con el panel colapsado.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-20 18:39:53 +00:00
parent 2b2a92a72b
commit 90607bd7c0
+90 -15
View File
@@ -20,9 +20,10 @@ use std::panic;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use gpui::{
div, point, prelude::*, px, App, Bounds, Context, Element, ElementId, FocusHandle,
GlobalElementId, Hsla, InspectorElementId, IntoElement, KeyDownEvent, LayoutId, PathBuilder,
Pixels, Render, SharedString, Style, Window,
div, point, prelude::*, px, App, Bounds, Context, CursorStyle, Element, ElementId, FocusHandle,
GlobalElementId, Hsla, InspectorElementId, IntoElement, KeyDownEvent, LayoutId, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PathBuilder, Pixels, Render, SharedString, Style,
Window,
};
use nahual_launcher::launch_app;
use nahual_theme::Theme;
@@ -201,6 +202,22 @@ impl Element for CurveElement {
// El shell.
// =====================================================================
/// Qué panel lateral está redimensionando un drag activo.
#[derive(Clone, Copy)]
enum Side {
Left,
Right,
}
/// Estado de un arrastre de divisor en curso.
struct Drag {
side: Side,
/// Posición X del cursor al iniciar el arrastre.
start_x: f32,
/// Ancho del panel al iniciar el arrastre.
start_w: f32,
}
struct Shell {
line: LineState,
/// La sesión de trabajo: cwd, historial y grupos.
@@ -215,6 +232,11 @@ struct Shell {
snapshot: Snapshot,
left_collapsed: bool,
right_collapsed: bool,
/// Anchos de los paneles laterales (los divisores los ajustan).
left_width: f32,
right_width: f32,
/// Arrastre de divisor en curso, si lo hay.
drag: Option<Drag>,
focus: FocusHandle,
focused_once: bool,
}
@@ -248,6 +270,9 @@ impl Shell {
},
left_collapsed: false,
right_collapsed: false,
left_width: 176.0,
right_width: 188.0,
drag: None,
focus: cx.focus_handle(),
focused_once: false,
};
@@ -687,7 +712,7 @@ impl Render for Shell {
.collect();
div()
.id("run-panel")
.w(px(176.))
.w(px(self.left_width))
.flex()
.flex_col()
.gap(px(6.))
@@ -796,7 +821,7 @@ impl Render for Shell {
div()
.id("sens-panel")
.w(px(184.))
.w(px(self.right_width))
.flex()
.flex_col()
.gap(px(10.))
@@ -949,6 +974,40 @@ impl Render for Shell {
}
}
// --- Divisores arrastrables ---
let divider = |side: Side, cx: &mut Context<Self>| {
div()
.w(px(5.))
.bg(node_bg)
.cursor(CursorStyle::ResizeLeftRight)
.hover(|s| s.bg(accent))
.on_mouse_down(
MouseButton::Left,
cx.listener(move |shell, ev: &MouseDownEvent, _w, cx| {
let start_w = match side {
Side::Left => shell.left_width,
Side::Right => shell.right_width,
};
shell.drag = Some(Drag {
side,
start_x: ev.position.x.into(),
start_w,
});
cx.notify();
}),
)
};
let mut middle = div().flex().flex_row().flex_1().overflow_hidden().child(left);
if !self.left_collapsed {
middle = middle.child(divider(Side::Left, cx));
}
middle = middle.child(canvas);
if !self.right_collapsed {
middle = middle.child(divider(Side::Right, cx));
}
middle = middle.child(right);
// --- Composición ---
div()
.size_full()
@@ -959,17 +1018,33 @@ impl Render for Shell {
.track_focus(&self.focus)
.key_context("ShumaShell")
.on_key_down(cx.listener(Self::handle_key))
.child(status)
.child(
div()
.flex()
.flex_row()
.flex_1()
.overflow_hidden()
.child(left)
.child(canvas)
.child(right),
.on_mouse_move(cx.listener(|shell, ev: &MouseMoveEvent, _w, cx| {
if let Some(drag) = &shell.drag {
let cur: f32 = ev.position.x.into();
let delta = cur - drag.start_x;
match drag.side {
// El panel izquierdo crece al arrastrar a la derecha.
Side::Left => {
shell.left_width = (drag.start_w + delta).clamp(130.0, 420.0)
}
// El derecho crece al arrastrar a la izquierda.
Side::Right => {
shell.right_width = (drag.start_w - delta).clamp(130.0, 420.0)
}
}
cx.notify();
}
}))
.on_mouse_up(
MouseButton::Left,
cx.listener(|shell, _ev: &MouseUpEvent, _w, cx| {
if shell.drag.take().is_some() {
cx.notify();
}
}),
)
.child(status)
.child(middle)
.child(prompt)
.children(popup_layer)
}