settings: add Launch Protontricks button
This commit is contained in:
+149
-95
@@ -61,6 +61,7 @@ pub enum Message {
|
||||
ServiceInstall,
|
||||
ServiceUninstall,
|
||||
ServiceActionDone(Result<(), String>),
|
||||
LaunchProtontricks,
|
||||
}
|
||||
|
||||
struct Dashboard {
|
||||
@@ -538,6 +539,14 @@ fn update(state: &mut Dashboard, msg: Message) -> Task<Message> {
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::LaunchProtontricks => {
|
||||
std::thread::spawn(|| {
|
||||
let _ = std::process::Command::new("protontricks")
|
||||
.arg("--gui")
|
||||
.spawn();
|
||||
});
|
||||
Task::none()
|
||||
}
|
||||
Message::ServiceInstall => {
|
||||
state.service_busy = true;
|
||||
state.service_status = "Installing autostart…".into();
|
||||
@@ -778,16 +787,14 @@ fn launcher_card<'a>(
|
||||
scan_busy: bool,
|
||||
) -> Element<'a, Message> {
|
||||
let status_color = if running {
|
||||
Color::from_rgb(0.4, 0.9, 0.4)
|
||||
Color::from_rgb(0.35, 0.85, 0.45)
|
||||
} else if installed {
|
||||
Color::from_rgb(0.55, 0.75, 1.0)
|
||||
} else {
|
||||
Color::from_rgb(0.5, 0.5, 0.5)
|
||||
Color::from_rgb(0.45, 0.45, 0.45)
|
||||
};
|
||||
let status_str = if running { "● Running" } else if installed { "○ Installed" } else { "· Not installed" };
|
||||
let status_el: Element<Message> = text(status_str).size(12).style(move |_: &Theme| text::Style {
|
||||
color: Some(status_color),
|
||||
}).into();
|
||||
let status_dot = if running { "●" } else if installed { "●" } else { "○" };
|
||||
let status_str = if running { "Running" } else if installed { "Installed" } else { "Not installed" };
|
||||
|
||||
let action: Element<Message> = {
|
||||
let n = l.name.clone();
|
||||
@@ -810,27 +817,37 @@ fn launcher_card<'a>(
|
||||
};
|
||||
|
||||
let version_badge: Element<Message> = if let Some(v) = &l.proton_version {
|
||||
text(format!(" [{v}]"))
|
||||
.size(11)
|
||||
text(format!(" {v}"))
|
||||
.size(10)
|
||||
.style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
|
||||
color: Some(Color::from_rgb(0.45, 0.55, 0.7)),
|
||||
})
|
||||
.into()
|
||||
} else {
|
||||
text("").into()
|
||||
};
|
||||
|
||||
let header = row![
|
||||
let name_row = row![
|
||||
text(&l.display).size(15),
|
||||
text(" ").size(12),
|
||||
status_el,
|
||||
version_badge,
|
||||
iced::widget::horizontal_space(),
|
||||
action,
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let mut rows: Vec<Element<Message>> = vec![header.into()];
|
||||
let status_row = row![
|
||||
text(status_dot).size(10).style(move |_: &Theme| text::Style {
|
||||
color: Some(status_color),
|
||||
}),
|
||||
text(format!(" {status_str}")).size(11).style(move |_: &Theme| text::Style {
|
||||
color: Some(status_color),
|
||||
}),
|
||||
version_badge,
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let mut rows: Vec<Element<Message>> = vec![
|
||||
column![name_row, status_row].spacing(3).into(),
|
||||
];
|
||||
|
||||
if l.games.is_empty() && scan_results.map(|r| r.is_empty()).unwrap_or(true) && !scan_busy {
|
||||
let lname = l.name.clone();
|
||||
@@ -842,65 +859,66 @@ fn launcher_card<'a>(
|
||||
.on_press(Message::BrowseGameExe(lname2))
|
||||
.style(button::secondary);
|
||||
rows.push(
|
||||
row![
|
||||
text(" ").size(12),
|
||||
text("No games added yet.").size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.55, 0.55)),
|
||||
}),
|
||||
iced::widget::horizontal_space(),
|
||||
scan_btn,
|
||||
browse_btn,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(6)
|
||||
container(
|
||||
row![
|
||||
text("No games added yet.").size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.45, 0.45, 0.45)),
|
||||
}),
|
||||
iced::widget::horizontal_space(),
|
||||
scan_btn,
|
||||
browse_btn,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(6),
|
||||
)
|
||||
.padding(Padding { top: 4.0, left: 2.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
if scan_busy {
|
||||
rows.push(text(" Scanning…").size(12).into());
|
||||
rows.push(
|
||||
container(text("Scanning…").size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
|
||||
}))
|
||||
.padding(Padding { top: 4.0, left: 2.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Scan results (exes found in prefix, not yet added)
|
||||
if let Some(results) = scan_results {
|
||||
for (display, exe) in results {
|
||||
let lname = l.name.clone();
|
||||
let d = display.clone();
|
||||
let e = exe.clone();
|
||||
let add_btn = button(text("+").size(11))
|
||||
let add_btn = button(text("+ Add").size(11))
|
||||
.on_press(Message::AddScannedGame(lname, d, e))
|
||||
.style(button::primary);
|
||||
rows.push(
|
||||
row![
|
||||
text(" ").size(12),
|
||||
text(display).size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.75, 0.75, 0.75)),
|
||||
}),
|
||||
iced::widget::horizontal_space(),
|
||||
add_btn,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(4)
|
||||
container(
|
||||
row![
|
||||
text(display).size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.75, 0.75, 0.75)),
|
||||
}),
|
||||
iced::widget::horizontal_space(),
|
||||
add_btn,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(4),
|
||||
)
|
||||
.padding(Padding { top: 2.0, left: 2.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if !results.is_empty() {
|
||||
if results.is_empty() && !scan_busy {
|
||||
rows.push(
|
||||
text(" Found in Wine prefix — click + to add")
|
||||
.size(10)
|
||||
.style(|_: &Theme| text::Style {
|
||||
container(
|
||||
text("No new executables found.").size(11).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.45, 0.45, 0.45)),
|
||||
})
|
||||
.into(),
|
||||
);
|
||||
} else if !scan_busy {
|
||||
rows.push(
|
||||
text(" No new executables found in the Wine prefix.")
|
||||
.size(11)
|
||||
.style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.45, 0.45, 0.45)),
|
||||
})
|
||||
.into(),
|
||||
}),
|
||||
)
|
||||
.padding(Padding { top: 4.0, left: 2.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -911,42 +929,48 @@ fn launcher_card<'a>(
|
||||
|
||||
let play = button(text("▶").size(11))
|
||||
.on_press(Message::Play(lname.clone(), gname.clone()))
|
||||
.style(button::primary);
|
||||
.style(button::primary)
|
||||
.padding([4, 8]);
|
||||
|
||||
let gamemode_btn = button(text("GameMode").size(11))
|
||||
let gm_btn = button(text("GM").size(10))
|
||||
.on_press(Message::ToggleGameMode(lname.clone(), gname.clone()))
|
||||
.style(if g.gamemode { button::primary } else { button::secondary });
|
||||
.style(if g.gamemode { button::primary } else { button::secondary })
|
||||
.padding([3, 7]);
|
||||
|
||||
let mangohud_btn = button(text("MangoHud").size(11))
|
||||
let hud_btn = button(text("HUD").size(10))
|
||||
.on_press(Message::ToggleMangoHud(lname.clone(), gname.clone()))
|
||||
.style(if g.mangohud { button::primary } else { button::secondary });
|
||||
.style(if g.mangohud { button::primary } else { button::secondary })
|
||||
.padding([3, 7]);
|
||||
|
||||
let gamescope_btn = button(text("Gamescope").size(11))
|
||||
let gs_btn = button(text("GS").size(10))
|
||||
.on_press(Message::ToggleGamescope(lname.clone(), gname.clone()))
|
||||
.style(if g.gamescope.is_some() { button::primary } else { button::secondary });
|
||||
.style(if g.gamescope.is_some() { button::primary } else { button::secondary })
|
||||
.padding([3, 7]);
|
||||
|
||||
let remove_game = button(text("✕").size(10))
|
||||
let remove_game = button(text("×").size(11))
|
||||
.on_press(Message::RemoveGame(lname, gname))
|
||||
.style(button::danger);
|
||||
.style(button::danger)
|
||||
.padding([3, 7]);
|
||||
|
||||
rows.push(
|
||||
row![
|
||||
text(" ").size(13),
|
||||
play,
|
||||
text(&g.display).size(13),
|
||||
iced::widget::horizontal_space(),
|
||||
gamemode_btn,
|
||||
mangohud_btn,
|
||||
gamescope_btn,
|
||||
remove_game,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(6)
|
||||
container(
|
||||
row![
|
||||
play,
|
||||
text(&g.display).size(13),
|
||||
iced::widget::horizontal_space(),
|
||||
gm_btn,
|
||||
hud_btn,
|
||||
gs_btn,
|
||||
remove_game,
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(5),
|
||||
)
|
||||
.padding(Padding { top: 2.0, left: 0.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
// Inline add-game form
|
||||
if let Some((name_val, exe_val)) = add_form {
|
||||
let lname = l.name.clone();
|
||||
let lname2 = l.name.clone();
|
||||
@@ -955,12 +979,12 @@ fn launcher_card<'a>(
|
||||
let lname5 = l.name.clone();
|
||||
let name_input = text_input("Game name", name_val)
|
||||
.on_input(move |v| Message::AddGameNameChanged(lname.clone(), v))
|
||||
.padding(5)
|
||||
.padding(6)
|
||||
.size(12)
|
||||
.width(Length::FillPortion(2));
|
||||
let exe_input = text_input("Exe path (relative to drive_c/)", exe_val)
|
||||
.on_input(move |v| Message::AddGameExeChanged(lname2.clone(), v))
|
||||
.padding(5)
|
||||
.padding(6)
|
||||
.size(12)
|
||||
.width(Length::FillPortion(3));
|
||||
let browse_btn = button(text("Browse…").size(11))
|
||||
@@ -974,10 +998,21 @@ fn launcher_card<'a>(
|
||||
.on_press(Message::AddGamePressed(lname4))
|
||||
.style(button::secondary);
|
||||
rows.push(
|
||||
row![name_input, exe_input, browse_btn, confirm_btn, cancel_btn]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(6)
|
||||
.into(),
|
||||
container(
|
||||
column![
|
||||
row![name_input, exe_input, browse_btn]
|
||||
.align_y(Alignment::Center)
|
||||
.spacing(6),
|
||||
container(
|
||||
row![confirm_btn, cancel_btn].spacing(6),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.align_x(Alignment::End),
|
||||
]
|
||||
.spacing(6),
|
||||
)
|
||||
.padding(Padding { top: 4.0, left: 0.0, right: 0.0, bottom: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -991,16 +1026,22 @@ fn launcher_card<'a>(
|
||||
|
||||
rows.push(
|
||||
container(add_game_btn)
|
||||
.padding(Padding { top: 4.0, right: 0.0, bottom: 0.0, left: 0.0 })
|
||||
.padding(Padding { top: 6.0, right: 0.0, bottom: 0.0, left: 0.0 })
|
||||
.into(),
|
||||
);
|
||||
|
||||
Column::with_children(rows).spacing(6).into()
|
||||
Column::with_children(rows).spacing(5).into()
|
||||
}
|
||||
|
||||
fn context_menu_card(l: &crate::config::Launcher) -> Element<'_, Message> {
|
||||
let header = row![
|
||||
text(&l.display).size(15),
|
||||
column![
|
||||
text(&l.display).size(15),
|
||||
text("Actions").size(11).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.45, 0.45, 0.45)),
|
||||
}),
|
||||
]
|
||||
.spacing(2),
|
||||
iced::widget::horizontal_space(),
|
||||
button(text("✕").size(12))
|
||||
.on_press(Message::HideContextMenu)
|
||||
@@ -1008,17 +1049,17 @@ fn context_menu_card(l: &crate::config::Launcher) -> Element<'_, Message> {
|
||||
]
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let open_prefix = button(text("Open prefix folder").size(13))
|
||||
let open_prefix = button(text("Open install folder").size(13))
|
||||
.on_press(Message::OpenPrefix(l.name.clone()))
|
||||
.style(button::secondary)
|
||||
.width(Length::Fill);
|
||||
|
||||
let rerun_setup = button(text("Re-run setup").size(13))
|
||||
let rerun_setup = button(text("Re-run setup wizard").size(13))
|
||||
.on_press(Message::RerunSetup(l.name.clone()))
|
||||
.style(button::secondary)
|
||||
.width(Length::Fill);
|
||||
|
||||
let diagnose = button(text("Diagnose").size(13))
|
||||
let diagnose = button(text("Run diagnostics").size(13))
|
||||
.on_press(Message::DiagnosePressed(l.name.clone()))
|
||||
.style(button::secondary)
|
||||
.width(Length::Fill);
|
||||
@@ -1036,7 +1077,7 @@ fn context_menu_card(l: &crate::config::Launcher) -> Element<'_, Message> {
|
||||
diagnose,
|
||||
remove,
|
||||
]
|
||||
.spacing(4)
|
||||
.spacing(5)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1045,7 +1086,13 @@ fn diagnose_card<'a>(
|
||||
checks: Option<&'a [diagnose::CheckResult]>,
|
||||
) -> Element<'a, Message> {
|
||||
let header = row![
|
||||
text(format!("Diagnose: {}", l.display)).size(15),
|
||||
column![
|
||||
text(format!("Diagnostics")).size(15),
|
||||
text(&l.display).size(11).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
|
||||
}),
|
||||
]
|
||||
.spacing(2),
|
||||
iced::widget::horizontal_space(),
|
||||
button(text("✕").size(12))
|
||||
.on_press(Message::HideDiagnose)
|
||||
@@ -1054,15 +1101,17 @@ fn diagnose_card<'a>(
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
let body: Element<Message> = match checks {
|
||||
None => text("Running checks…").size(12).into(),
|
||||
None => text("Running checks…").size(12).style(|_: &Theme| text::Style {
|
||||
color: Some(Color::from_rgb(0.55, 0.75, 1.0)),
|
||||
}).into(),
|
||||
Some(results) => {
|
||||
let rows: Vec<Element<Message>> = results
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let (sym, color) = if c.pass {
|
||||
("✓", Color::from_rgb(0.4, 0.9, 0.4))
|
||||
("✓", Color::from_rgb(0.35, 0.85, 0.45))
|
||||
} else {
|
||||
("✗", Color::from_rgb(1.0, 0.45, 0.45))
|
||||
("✗", Color::from_rgb(1.0, 0.45, 0.35))
|
||||
};
|
||||
row![
|
||||
text(sym).size(12).style(move |_: &Theme| text::Style {
|
||||
@@ -1074,14 +1123,14 @@ fn diagnose_card<'a>(
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
scrollable(Column::with_children(rows).spacing(3))
|
||||
scrollable(Column::with_children(rows).spacing(4))
|
||||
.height(Length::Fixed(160.0))
|
||||
.into()
|
||||
}
|
||||
};
|
||||
|
||||
column![header, iced::widget::horizontal_rule(1), body,]
|
||||
.spacing(6)
|
||||
column![header, iced::widget::horizontal_rule(1), body]
|
||||
.spacing(8)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1152,6 +1201,11 @@ fn view_settings(state: &Dashboard) -> Element<'_, Message> {
|
||||
row![compat_dir_input, browse_compat_btn].spacing(8).align_y(Alignment::Center),
|
||||
save_btn,
|
||||
iced::widget::horizontal_rule(1),
|
||||
text("Tools").size(13),
|
||||
button(text("Launch Protontricks").size(13))
|
||||
.on_press(Message::LaunchProtontricks)
|
||||
.style(button::secondary),
|
||||
iced::widget::horizontal_rule(1),
|
||||
text(svc_status_label).size(13),
|
||||
row![svc_install_btn, svc_uninstall_btn,].spacing(10),
|
||||
text(&state.service_status).size(12),
|
||||
|
||||
Reference in New Issue
Block a user