feat(ui): selectable room layouts with a thumbnail picker
Add three in-call room layouts — 3-Column (Participants | Chat | Controls), Bottom Dock (Participants+Controls over a full-width Chat strip), and Drawer (Participants | Controls with a collapsible Chat panel) — chosen via one persisted RoomLayout config setting and applied live. Picker UX: a square layout button (drawn LayoutIcon glyph) in the top bar of the launch and in-call screens opens a popup gallery (dimmed click-to-dismiss backdrop + centered panel) of clickable schematic thumbnails; the Settings screen shows the same thumbnails inline (no button). Thumbnails are drawn with the canvas widget (new LayoutThumb program — colored panel boxes, blue border on the selected one), so no image-decoding dependency is added. Each layout's panel boundaries are draggable (DividerKind gains Controls + ChatDrawer for the 3-column right divider and the drawer's left edge; new clamp_controls_width / clamp_chat_drawer_width, persisted + re-clamped on resize). Participants width is shared across layouts but capped per layout at render time so a fixed panel can't starve the Fill panel (e.g. a wide Participants width set in the dock layout won't collapse Chat in 3-column or Controls in the drawer). The Drawer layout adds a header chat-toggle. +1 clamp test (now 127 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,34 @@ impl NetworkMode {
|
||||
[NetworkMode::RelayNoDiscovery, NetworkMode::N0Full, NetworkMode::DirectOnly];
|
||||
}
|
||||
|
||||
/// Arrangement of the in-call room screen, chosen via the layout picker.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum RoomLayout {
|
||||
/// Participants | Chat | Controls, all columns visible at once.
|
||||
ThreeColumn,
|
||||
/// Participants + Controls on top, full-width Chat docked along the bottom.
|
||||
#[default]
|
||||
BottomDock,
|
||||
/// Participants | Controls, with a collapsible Chat drawer on the right edge.
|
||||
Drawer,
|
||||
}
|
||||
|
||||
impl RoomLayout {
|
||||
/// All variants, in picker display order.
|
||||
pub const ALL: [RoomLayout; 3] =
|
||||
[RoomLayout::ThreeColumn, RoomLayout::BottomDock, RoomLayout::Drawer];
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RoomLayout {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
RoomLayout::ThreeColumn => "3-Column",
|
||||
RoomLayout::BottomDock => "Bottom Dock",
|
||||
RoomLayout::Drawer => "Drawer",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NetworkMode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let label = match self {
|
||||
@@ -56,6 +84,14 @@ fn default_chat_height() -> f32 {
|
||||
180.0
|
||||
}
|
||||
|
||||
fn default_controls_width() -> f32 {
|
||||
280.0
|
||||
}
|
||||
|
||||
fn default_chat_drawer_width() -> f32 {
|
||||
320.0
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AppConfig {
|
||||
/// Last nickname used to join/create a room; pre-filled on the launch screen.
|
||||
@@ -84,6 +120,15 @@ pub struct AppConfig {
|
||||
pub participants_width: f32,
|
||||
#[serde(default = "default_chat_height")]
|
||||
pub chat_height: f32,
|
||||
/// Controls panel width for the 3-column layout (px).
|
||||
#[serde(default = "default_controls_width")]
|
||||
pub controls_width: f32,
|
||||
/// Chat drawer width for the drawer layout (px).
|
||||
#[serde(default = "default_chat_drawer_width")]
|
||||
pub chat_drawer_width: f32,
|
||||
/// Chosen arrangement of the in-call room screen.
|
||||
#[serde(default)]
|
||||
pub room_layout: RoomLayout,
|
||||
#[serde(default)]
|
||||
pub custom_sound_self_join: Option<String>,
|
||||
#[serde(default)]
|
||||
@@ -116,6 +161,9 @@ impl Default for AppConfig {
|
||||
notifications_enabled: true,
|
||||
participants_width: default_participants_width(),
|
||||
chat_height: default_chat_height(),
|
||||
controls_width: default_controls_width(),
|
||||
chat_drawer_width: default_chat_drawer_width(),
|
||||
room_layout: RoomLayout::default(),
|
||||
custom_sound_self_join: None,
|
||||
custom_sound_peer_join: None,
|
||||
custom_sound_peer_leave: None,
|
||||
@@ -186,6 +234,10 @@ mod tests {
|
||||
assert_eq!(deserialized.chat_height, 180.0);
|
||||
// Configs predating the remembered username load the default nickname.
|
||||
assert_eq!(deserialized.username, "Peer");
|
||||
// Configs predating the layout picker load the default layout + sizes.
|
||||
assert_eq!(deserialized.room_layout, RoomLayout::BottomDock);
|
||||
assert_eq!(deserialized.controls_width, 280.0);
|
||||
assert_eq!(deserialized.chat_drawer_width, 320.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());
|
||||
|
||||
Reference in New Issue
Block a user