fix(home): responsive friends-panel layout (stack vertically when narrow)
The home-screen Connect + Friends cards were a fixed-width row, so below ~860px the 380px Connect card squeezed the Friends card until its node-ID field, status, and remove button clipped away. Extract the Connect card into a free `connect_card(state)` fn (mirroring `friends_panel`) and wrap both in `responsive`: side-by-side row at >=900px, stacked column below. Verified at 560/760/1000/1912px. clippy clean, 256 lib tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+130
-75
@@ -9,7 +9,7 @@ use crate::theme::{AppTheme, Palette};
|
||||
use iced::widget::{
|
||||
container, column, row, text, button, text_input, scrollable, slider, checkbox, pick_list,
|
||||
radio, tooltip, progress_bar, canvas, Canvas, Column, stack, mouse_area,
|
||||
rich_text, span,
|
||||
rich_text, span, responsive,
|
||||
};
|
||||
use iced::widget::canvas::{Action, Frame, Geometry, Path, Program};
|
||||
use iced::{
|
||||
@@ -1206,6 +1206,113 @@ fn vertical_space(height: f32) -> iced::widget::Space {
|
||||
/// the add-by-node-ID form, and the presence posture selector. Self-contained:
|
||||
/// recomputes the palette + the few style helpers it needs so it doesn't depend on
|
||||
/// `view`'s locals.
|
||||
// The home-screen "Connect" card (left panel): create / join a room. Extracted
|
||||
// into its own free fn (mirroring `friends_panel`) so the responsive home layout
|
||||
// can rebuild it per layout pass and place it in either a row or a column.
|
||||
fn connect_card(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let pal = state.config.theme.palette();
|
||||
let color_crust = pal.crust;
|
||||
let color_mantle = pal.mantle;
|
||||
let color_surface = pal.surface;
|
||||
let color_text = pal.text;
|
||||
let color_subtext = pal.subtext;
|
||||
let color_blue = pal.blue;
|
||||
let color_lavender = pal.lavender;
|
||||
|
||||
let c_style = move |bg: Color, b_color: Color, radius: f32| {
|
||||
move |_theme: &Theme| container::Style {
|
||||
text_color: Some(color_text),
|
||||
background: Some(Background::Color(bg)),
|
||||
border: Border {
|
||||
color: b_color,
|
||||
width: if b_color == Color::TRANSPARENT { 0.0 } else { 1.0 },
|
||||
radius: radius.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
let b_style = move |bg: Color, hover_bg: Color, text_c: Color, radius: f32| {
|
||||
move |_theme: &Theme, status: button::Status| {
|
||||
let active_bg = match status {
|
||||
button::Status::Hovered => hover_bg,
|
||||
_ => bg,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(active_bg)),
|
||||
text_color: text_c,
|
||||
border: Border { color: Color::TRANSPARENT, width: 0.0, radius: radius.into() },
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
};
|
||||
let t_style = move |_theme: &Theme, _status: text_input::Status| text_input::Style {
|
||||
background: Background::Color(color_crust),
|
||||
border: Border { color: color_surface, width: 1.0, radius: 6.0.into() },
|
||||
icon: color_subtext,
|
||||
placeholder: pal.overlay,
|
||||
value: color_text,
|
||||
selection: color_blue,
|
||||
};
|
||||
|
||||
let logo = text("PEERSPEAK").size(36).color(color_blue);
|
||||
let subtitle = text("NAT-traversing full-mesh voice chat").size(16).color(color_subtext);
|
||||
|
||||
let nickname_input = column![
|
||||
text("Nickname").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Enter nickname...", &state.name)
|
||||
.on_input(AppMessage::NicknameChanged)
|
||||
.style(t_style)
|
||||
.padding(10)
|
||||
];
|
||||
|
||||
let create_btn = button(btn_content(IconKind::Create, "Create New Room", color_crust))
|
||||
.on_press(AppMessage::CreatePressed)
|
||||
.style(b_style(color_blue, color_lavender, color_crust, 8.0))
|
||||
.padding(12)
|
||||
.width(iced::Length::Fill);
|
||||
|
||||
let join_group = column![
|
||||
text("Join Existing Room").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Paste room ticket here...", &state.ticket_input)
|
||||
.on_input(AppMessage::TicketInputChanged)
|
||||
.style(t_style)
|
||||
.padding(10),
|
||||
vertical_space(8.0),
|
||||
button(text("Join Room").size(16).align_x(iced::alignment::Horizontal::Center))
|
||||
.on_press(AppMessage::JoinPressed)
|
||||
.style(b_style(color_surface, color_blue, color_text, 8.0))
|
||||
.padding(12)
|
||||
.width(iced::Length::Fill)
|
||||
];
|
||||
|
||||
let status = text(&state.status_message).size(14).color(color_subtext);
|
||||
|
||||
container(
|
||||
column![
|
||||
logo,
|
||||
subtitle,
|
||||
vertical_space(20.0),
|
||||
nickname_input,
|
||||
vertical_space(16.0),
|
||||
create_btn,
|
||||
vertical_space(16.0),
|
||||
text("— OR —").size(12).color(color_surface).align_x(iced::alignment::Horizontal::Center),
|
||||
vertical_space(16.0),
|
||||
join_group,
|
||||
vertical_space(10.0),
|
||||
status
|
||||
]
|
||||
.spacing(10)
|
||||
.align_x(iced::alignment::Horizontal::Center),
|
||||
)
|
||||
.style(c_style(color_mantle, color_surface, 12.0))
|
||||
.padding(30)
|
||||
.width(380)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn friends_panel(state: &AppState) -> Element<'_, AppMessage> {
|
||||
let pal = state.config.theme.palette();
|
||||
let color_crust = pal.crust;
|
||||
@@ -2008,86 +2115,34 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
|
||||
if state.current_screen == Screen::Home {
|
||||
// --- HOME SCREEN ---
|
||||
let logo = text("PEERSPEAK")
|
||||
.size(36)
|
||||
.color(color_blue);
|
||||
|
||||
let subtitle = text("NAT-traversing full-mesh voice chat")
|
||||
.size(16)
|
||||
.color(color_subtext);
|
||||
|
||||
let nickname_input = column![
|
||||
text("Nickname").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Enter nickname...", &state.name)
|
||||
.on_input(AppMessage::NicknameChanged)
|
||||
.style(t_style)
|
||||
.padding(10)
|
||||
];
|
||||
|
||||
let create_btn = button(btn_content(IconKind::Create, "Create New Room", color_crust))
|
||||
.on_press(AppMessage::CreatePressed)
|
||||
.style(b_style(color_blue, color_lavender, color_crust, 8.0))
|
||||
.padding(12)
|
||||
.width(iced::Length::Fill);
|
||||
|
||||
let join_group = column![
|
||||
text("Join Existing Room").size(14).color(color_subtext),
|
||||
vertical_space(4.0),
|
||||
text_input("Paste room ticket here...", &state.ticket_input)
|
||||
.on_input(AppMessage::TicketInputChanged)
|
||||
.style(t_style)
|
||||
.padding(10),
|
||||
vertical_space(8.0),
|
||||
button(
|
||||
text("Join Room")
|
||||
.size(16)
|
||||
// Two cards: Connect (left) + the live Friends list (right). They sit
|
||||
// side-by-side when the window is wide enough, and stack vertically on a
|
||||
// narrow window so the Friends card never gets crushed — below ~860px the
|
||||
// fixed-width Connect card would otherwise squeeze it until its node-ID
|
||||
// field and remove button clip away. `responsive` measures the available
|
||||
// width each layout pass and picks the orientation accordingly.
|
||||
let body = responsive(move |size| {
|
||||
let cards: Element<AppMessage> = if size.width < 900.0 {
|
||||
column![connect_card(state), friends_panel(state)]
|
||||
.spacing(20)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
.on_press(AppMessage::JoinPressed)
|
||||
.style(b_style(color_surface, color_blue, color_text, 8.0))
|
||||
.padding(12)
|
||||
.width(iced::Length::Fill)
|
||||
];
|
||||
|
||||
let status = text(&state.status_message)
|
||||
.size(14)
|
||||
.color(color_subtext);
|
||||
|
||||
let content = container(
|
||||
column![
|
||||
logo,
|
||||
subtitle,
|
||||
vertical_space(20.0),
|
||||
nickname_input,
|
||||
vertical_space(16.0),
|
||||
create_btn,
|
||||
vertical_space(16.0),
|
||||
text("— OR —").size(12).color(color_surface).align_x(iced::alignment::Horizontal::Center),
|
||||
vertical_space(16.0),
|
||||
join_group,
|
||||
vertical_space(10.0),
|
||||
status
|
||||
]
|
||||
.spacing(10)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
.style(c_style(color_mantle, color_surface, 12.0))
|
||||
.padding(30)
|
||||
.width(380);
|
||||
|
||||
// Two side-by-side cards: connect (left) + the live friends list (right),
|
||||
// centered together. The friends panel was moved here from Settings.
|
||||
let panels = row![content, friends_panel(state)]
|
||||
.spacing(20)
|
||||
.align_y(iced::alignment::Vertical::Top);
|
||||
let scroll = scrollable(container(panels).center_x(iced::Length::Fill));
|
||||
.into()
|
||||
} else {
|
||||
row![connect_card(state), friends_panel(state)]
|
||||
.spacing(20)
|
||||
.align_y(iced::alignment::Vertical::Top)
|
||||
.into()
|
||||
};
|
||||
scrollable(container(cards).center_x(iced::Length::Fill))
|
||||
.width(iced::Length::Fill)
|
||||
.into()
|
||||
});
|
||||
|
||||
let home = container(
|
||||
column![
|
||||
top_bar,
|
||||
vertical_space(20.0),
|
||||
scroll
|
||||
body
|
||||
].align_x(iced::alignment::Horizontal::Center)
|
||||
)
|
||||
.width(iced::Length::Fill)
|
||||
|
||||
Reference in New Issue
Block a user