refactor(setup): redesign launcher picking screen

Replaced the flat form layout with a polished card-based design:
- Section cards with subtle borders matching the dashboard style
- Blue accent labels for section headings (Launcher, Install Location)
- Hint text explaining the Wine prefix folder
- Error status styled in orange-red instead of plain text
- Next button right-aligned with top spacing
- Header with muted subtitle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-04-18 23:37:26 -07:00
parent 0c22e23ad3
commit 3c78e1586f
+98 -25
View File
@@ -3,7 +3,7 @@ use anyhow::Result;
use iced::widget::{ use iced::widget::{
button, column, container, pick_list, progress_bar, row, scrollable, text, text_input, Column, button, column, container, pick_list, progress_bar, row, scrollable, text, text_input, Column,
}; };
use iced::{Color, Element, Length, Subscription, Task, Theme}; use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Subscription, Task, Theme};
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{BufRead, BufReader, Read, Write}; use std::io::{BufRead, BufReader, Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -352,10 +352,40 @@ fn view(state: &State) -> Element<'_, Message> {
view_install(state) view_install(state)
} }
fn view_picking(state: &State) -> Element<'_, Message> { fn section_card<'a>(content: impl Into<Element<'a, Message>>) -> Element<'a, Message> {
let header = text("Add a Launcher").size(24); container(content)
let sub = text("Choose a launcher and where to install it.").size(13); .padding(Padding::from([12, 16]))
.width(Length::Fill)
.style(|theme: &Theme| {
let p = theme.extended_palette();
container::Style {
background: Some(Background::Color(p.background.weak.color)),
border: Border {
color: p.background.strong.color,
width: 1.0,
radius: 6.0.into(),
},
..Default::default()
}
})
.into()
}
fn view_picking(state: &State) -> Element<'_, Message> {
// ── Header ───────────────────────────────────────────────────────────────
let header = container(
column![
text("Add a Launcher").size(26),
text("Choose a launcher and set its install location.").size(13)
.style(|_: &Theme| text::Style {
color: Some(Color::from_rgb(0.6, 0.6, 0.6)),
}),
]
.spacing(4),
)
.padding(Padding { top: 24.0, right: 24.0, bottom: 8.0, left: 24.0 });
// ── Launcher picker card ──────────────────────────────────────────────────
let picker = pick_list( let picker = pick_list(
state.template_options.as_slice(), state.template_options.as_slice(),
state.selected_template.clone(), state.selected_template.clone(),
@@ -364,7 +394,18 @@ fn view_picking(state: &State) -> Element<'_, Message> {
.placeholder("Select a launcher…") .placeholder("Select a launcher…")
.width(Length::Fill); .width(Length::Fill);
let prefix_input = text_input("/home/user/Games/battlenet", &state.prefix_input) let launcher_card = section_card(
column![
text("Launcher").size(12).style(|_: &Theme| text::Style {
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
}),
picker,
]
.spacing(6),
);
// ── Install location card ─────────────────────────────────────────────────
let prefix_input = text_input("e.g. /home/user/Games/battlenet", &state.prefix_input)
.on_input(Message::PrefixChanged) .on_input(Message::PrefixChanged)
.padding(8) .padding(8)
.width(Length::Fill); .width(Length::Fill);
@@ -373,30 +414,62 @@ fn view_picking(state: &State) -> Element<'_, Message> {
.on_press(Message::BrowsePrefix) .on_press(Message::BrowsePrefix)
.style(button::secondary); .style(button::secondary);
let location_card = section_card(
column![
text("Install Location").size(12).style(|_: &Theme| text::Style {
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
}),
row![prefix_input, browse_btn]
.spacing(8)
.align_y(Alignment::Center),
text("The folder where the launcher's Wine prefix will be created.")
.size(11)
.style(|_: &Theme| text::Style {
color: Some(Color::from_rgb(0.45, 0.45, 0.45)),
}),
]
.spacing(6),
);
// ── Status / error ────────────────────────────────────────────────────────
let status_el: Element<Message> = if !state.status.is_empty() {
container(
text(&state.status).size(13).style(|_: &Theme| text::Style {
color: Some(Color::from_rgb(1.0, 0.55, 0.45)),
}),
)
.padding(Padding::from([6, 0]))
.into()
} else {
text("").into()
};
// ── Next button ───────────────────────────────────────────────────────────
let can_confirm = let can_confirm =
state.selected_template.is_some() && !state.prefix_input.trim().is_empty(); state.selected_template.is_some() && !state.prefix_input.trim().is_empty();
let confirm_btn = button(text("Next →")) let next_btn = container(
.on_press_maybe(can_confirm.then_some(Message::ConfirmLauncher)) button(text("Next →").size(14))
.style(button::primary); .on_press_maybe(can_confirm.then_some(Message::ConfirmLauncher))
.style(button::primary),
)
.padding(Padding { top: 4.0, right: 0.0, bottom: 0.0, left: 0.0 })
.width(Length::Fill)
.align_x(Alignment::End);
let mut body = column![ container(
header, column![
sub, header,
text("Launcher:").size(13), container(
picker, column![launcher_card, location_card, status_el, next_btn]
text("Install location (Wine prefix):").size(13), .spacing(12)
row![prefix_input, browse_btn].spacing(8).align_y(iced::Alignment::Center), .padding(Padding { top: 8.0, right: 24.0, bottom: 24.0, left: 24.0 }),
confirm_btn, ),
] ]
.spacing(10) )
.padding(20); .width(Length::Fill)
.height(Length::Fill)
if !state.status.is_empty() { .into()
body = body.push(text(state.status.clone()).size(13));
}
container(body).into()
} }
fn view_install(state: &State) -> Element<'_, Message> { fn view_install(state: &State) -> Element<'_, Message> {