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