feat: responsive full-window settings layout

The Settings screen now follows the full window width/height instead of a
fixed 460x500 card. Device/privacy pickers and the noise-gate slider fill
the available width, and controls are laid out in 2 columns so a single
dropdown never stretches across the whole window:

- Input Device | Output Device
- Mic Sensitivity | Network Privacy (top-aligned)
- The 8 custom chime path fields remain a 2-column grid

Section spacing tightened so everything (incl. Back) fits on 1080p without
scrolling. The styled card now fills the page (max_width cap dropped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 15:37:56 -04:00
co-authored by Claude Opus 4.8
parent fe4c7f9ee5
commit 821102beb6
+64 -55
View File
@@ -482,68 +482,78 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
.on_input(move |val| AppMessage::CustomSoundPathChanged(sound, val)) .on_input(move |val| AppMessage::CustomSoundPathChanged(sound, val))
.style(t_style) .style(t_style)
.padding(8) .padding(8)
].spacing(4).width(iced::Length::Fixed(320.0)) ].spacing(4).width(iced::Length::Fill)
}; };
let settings_content = scrollable( let settings_content = scrollable(
column![ column![
text("Settings").size(24).color(color_blue), text("Settings").size(24).color(color_blue),
vertical_space(20.0), vertical_space(10.0),
text("Device Settings").size(14).color(color_subtext), text("Device Settings").size(14).color(color_subtext),
vertical_space(10.0), vertical_space(6.0),
column![ row![
text("Input Device").size(12).color(Color::from_rgb8(180, 180, 180)), column![
pick_list( text("Input Device").size(12).color(Color::from_rgb8(180, 180, 180)),
&state.input_devices[..], pick_list(
state.selected_input.as_ref(), &state.input_devices[..],
AppMessage::InputDeviceSelected, state.selected_input.as_ref(),
).width(iced::Length::Fixed(320.0)), AppMessage::InputDeviceSelected,
].spacing(4).width(iced::Length::Fixed(320.0)), ).width(iced::Length::Fill),
vertical_space(10.0), ].spacing(4).width(iced::Length::Fill),
column![ column![
text("Output Device").size(12).color(Color::from_rgb8(180, 180, 180)), text("Output Device").size(12).color(Color::from_rgb8(180, 180, 180)),
pick_list( pick_list(
&state.output_devices[..], &state.output_devices[..],
state.selected_output.as_ref(), state.selected_output.as_ref(),
AppMessage::OutputDeviceSelected, AppMessage::OutputDeviceSelected,
).width(iced::Length::Fixed(320.0)), ).width(iced::Length::Fill),
].spacing(4).width(iced::Length::Fixed(320.0)), ].spacing(4).width(iced::Length::Fill),
vertical_space(20.0), ].spacing(20).width(iced::Length::Fill),
column![ vertical_space(12.0),
text(format!("Mic Sensitivity (Noise Gate): {:.1}%", state.config.noise_gate_threshold * 100.0)).size(14).color(color_subtext), row![
slider(0.0..=0.1, state.config.noise_gate_threshold, AppMessage::NoiseGateChanged).step(0.001) column![
].spacing(10).width(iced::Length::Fixed(320.0)), text(format!("Mic Sensitivity (Noise Gate): {:.1}%", state.config.noise_gate_threshold * 100.0)).size(14).color(color_subtext),
vertical_space(20.0), slider(0.0..=0.1, state.config.noise_gate_threshold, AppMessage::NoiseGateChanged).step(0.001)
column![ ].spacing(8).width(iced::Length::Fill),
text("Network Privacy").size(14).color(color_subtext), column![
pick_list( text("Network Privacy").size(14).color(color_subtext),
&NetworkMode::ALL[..], pick_list(
Some(state.config.network_mode), &NetworkMode::ALL[..],
AppMessage::NetworkModeSelected, Some(state.config.network_mode),
).width(iced::Length::Fixed(320.0)), AppMessage::NetworkModeSelected,
text(network_mode_hint(state.config.network_mode)).size(11).color(color_subtext), ).width(iced::Length::Fill),
text("Takes effect on your next room join.").size(11).color(color_surface), text(network_mode_hint(state.config.network_mode)).size(11).color(color_subtext),
].spacing(6).width(iced::Length::Fixed(320.0)), text("Takes effect on your next room join.").size(11).color(color_surface),
vertical_space(20.0), ].spacing(4).width(iced::Length::Fill),
].spacing(20).align_y(iced::alignment::Vertical::Top).width(iced::Length::Fill),
vertical_space(12.0),
column![ column![
text("Notification Chimes").size(14).color(color_subtext), text("Notification Chimes").size(14).color(color_subtext),
checkbox(state.config.notifications_enabled) checkbox(state.config.notifications_enabled)
.label("Enable sound notifications") .label("Enable sound notifications")
.on_toggle(AppMessage::ToggleNotifications), .on_toggle(AppMessage::ToggleNotifications),
].spacing(10).width(iced::Length::Fixed(320.0)), ].spacing(8).width(iced::Length::Fill),
vertical_space(20.0), vertical_space(12.0),
text("Custom Chime Files (WAV Paths)").size(14).color(color_subtext), text("Custom Chime Files (WAV Paths)").size(14).color(color_subtext),
path_field("Self Join", Sound::SelfJoin), row![
path_field("Self Leave", Sound::SelfLeave), path_field("Self Join", Sound::SelfJoin),
path_field("Peer Join", Sound::PeerJoin), path_field("Peer Join", Sound::PeerJoin),
path_field("Peer Leave", Sound::PeerLeave), ].spacing(20).width(iced::Length::Fill),
path_field("Reconnect Attempt", Sound::ReconnectAttempt), row![
path_field("Reconnected", Sound::Reconnected), path_field("Self Leave", Sound::SelfLeave),
path_field("Mic Toggle", Sound::MicToggle), path_field("Peer Leave", Sound::PeerLeave),
path_field("Reconnect Failed", Sound::ReconnectFailed), ].spacing(20).width(iced::Length::Fill),
row![
path_field("Reconnect Attempt", Sound::ReconnectAttempt),
path_field("Reconnected", Sound::Reconnected),
].spacing(20).width(iced::Length::Fill),
row![
path_field("Mic Toggle", Sound::MicToggle),
path_field("Reconnect Failed", Sound::ReconnectFailed),
].spacing(20).width(iced::Length::Fill),
vertical_space(30.0), vertical_space(14.0),
button( button(
text("Back") text("Back")
.size(16) .size(16)
@@ -556,21 +566,20 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
] ]
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::alignment::Horizontal::Center)
.spacing(10) .spacing(10)
.width(iced::Length::Fill)
); );
let settings_box = container(settings_content) let settings_box = container(settings_content)
.style(c_style(color_mantle, color_surface, 12.0)) .style(c_style(color_mantle, color_surface, 12.0))
.padding(30) .padding(24)
.width(460) .width(iced::Length::Fill)
.height(500); .height(iced::Length::Fill);
let content = column![settings_box].align_x(iced::alignment::Horizontal::Center); return container(settings_box)
return container(content)
.width(iced::Length::Fill) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.padding(24)
.center_x(iced::Length::Fill) .center_x(iced::Length::Fill)
.center_y(iced::Length::Fill)
.style(c_style(color_crust, Color::TRANSPARENT, 0.0)) .style(c_style(color_crust, Color::TRANSPARENT, 0.0))
.into(); .into();
} }