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:
+98
-25
@@ -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> {
|
||||||
|
|||||||
Reference in New Issue
Block a user