feat(ui): draggable, persisted dividers between room panels

Add a reusable Divider canvas widget and place two in the room screen: a
vertical divider between the Participants and Controls panels (drag to resize
the Participants width) and a horizontal divider between the main row and the
Chat dock (drag to resize the dock height). The Participants panel and Chat dock
size from persisted config values; the Controls panel and main row fill the rest.

The widget reports drag motion as a pixel delta along its axis (mirroring the
GateMeter drag handling, so a drag continues past the thin strip). update()
applies the delta and clamps it: clamp_participants_width / clamp_chat_height
keep both sides of each divider above a minimum. Sizes are re-clamped on window
resize (window size tracked from window::Event::Resized) and clamped again on
load (a size saved under a different window could be out of range).

Persistence: participants_width / chat_height are new serde-default AppConfig
fields; the divider publishes PersistConfig on drag release so the final
position is written once (not per pixel). 3 clamp unit tests (incl. a tiny-window
degenerate case) + config backward-compat assertions for the new fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 22:44:53 -04:00
co-authored by Claude Opus 4.8
parent 68d78ff411
commit 5279a53400
2 changed files with 282 additions and 7 deletions
+19
View File
@@ -44,6 +44,14 @@ fn default_volume() -> f32 {
1.0
}
fn default_participants_width() -> f32 {
540.0
}
fn default_chat_height() -> f32 {
180.0
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AppConfig {
pub input_device: String,
@@ -63,6 +71,12 @@ pub struct AppConfig {
pub echo_cancellation_enabled: bool,
#[serde(default = "default_true")]
pub notifications_enabled: bool,
/// Persisted room-screen divider positions (px): Participants panel width and
/// Chat dock height. Re-clamped to the window on load and on resize.
#[serde(default = "default_participants_width")]
pub participants_width: f32,
#[serde(default = "default_chat_height")]
pub chat_height: f32,
#[serde(default)]
pub custom_sound_self_join: Option<String>,
#[serde(default)]
@@ -92,6 +106,8 @@ impl Default for AppConfig {
network_mode: NetworkMode::default(),
echo_cancellation_enabled: false,
notifications_enabled: true,
participants_width: default_participants_width(),
chat_height: default_chat_height(),
custom_sound_self_join: None,
custom_sound_peer_join: None,
custom_sound_peer_leave: None,
@@ -157,6 +173,9 @@ mod tests {
// Configs predating the volume sliders must load at unity gain.
assert_eq!(deserialized.input_volume, 1.0);
assert_eq!(deserialized.output_volume, 1.0);
// Configs predating the draggable dividers must load the default sizes.
assert_eq!(deserialized.participants_width, 540.0);
assert_eq!(deserialized.chat_height, 180.0);
assert!(deserialized.custom_sound_self_join.is_none());
assert!(deserialized.custom_sound_peer_join.is_none());
assert!(deserialized.custom_sound_peer_leave.is_none());