Game launcher (fully functional)
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
<layout>
|
<layout>
|
||||||
<elements>
|
<elements>
|
||||||
<div flex_direction="row" gap="16">
|
<div flex_direction="row" gap="16" align_items="center">
|
||||||
<div id="cover_art_parent" />
|
<div id="cover_art_parent" />
|
||||||
<div flex_direction="column" gap="16">
|
<div flex_direction="column" gap="16">
|
||||||
<label id="label_title" weight="bold" size="32" />
|
<label id="label_title" weight="bold" size="32" />
|
||||||
<div flex_direction="row" gap="8">
|
<div flex_direction="row" gap="8">
|
||||||
<label text="by" />
|
<label text="by" />
|
||||||
<label weight="bold" id="label_author" />
|
<label weight="bold" id="label_author" text="Unknown" />
|
||||||
</div>
|
</div>
|
||||||
<label id="label_description" wrap="1" />
|
<label id="label_description" wrap="1" text="No description available" />
|
||||||
<Button id="btn_launch" align_self="baseline" color="#44ce22FF" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_height="40">
|
<Button id="btn_launch" align_self="baseline" color="#44ce22FF" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_width="200" min_height="40">
|
||||||
<sprite src_builtin="dashboard/play.svg" width="32" height="32" />
|
<sprite src_builtin="dashboard/play.svg" width="32" height="32" />
|
||||||
<label text="Launch" weight="bold" size="17" shadow="#00000099" />
|
<label text="Launch" weight="bold" size="17" shadow="#00000099" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -68,5 +68,6 @@
|
|||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
"NO_GAMES_FOUND": "Keine Spiele gefunden"
|
"NO_GAMES_FOUND": "Keine Spiele gefunden"
|
||||||
},
|
},
|
||||||
"TERMINATE_PROCESS": "Prozess beenden"
|
"TERMINATE_PROCESS": "Prozess beenden",
|
||||||
|
"GAME_LAUNCHED": "Spiel gestartet"
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,8 @@
|
|||||||
"VOLUME": "Volume"
|
"VOLUME": "Volume"
|
||||||
},
|
},
|
||||||
"CLOSE_WINDOW": "Close window",
|
"CLOSE_WINDOW": "Close window",
|
||||||
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launcha application:",
|
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
|
||||||
|
"GAME_LAUNCHED": "Game launched",
|
||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
"NO_GAMES_FOUND": "No games found"
|
"NO_GAMES_FOUND": "No games found"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,5 +68,6 @@
|
|||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
"NO_GAMES_FOUND": "No se encontraron juegos"
|
"NO_GAMES_FOUND": "No se encontraron juegos"
|
||||||
},
|
},
|
||||||
"TERMINATE_PROCESS": "Finalizar proceso"
|
"TERMINATE_PROCESS": "Finalizar proceso",
|
||||||
|
"GAME_LAUNCHED": "Juego lanzado"
|
||||||
}
|
}
|
||||||
@@ -68,5 +68,6 @@
|
|||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
"NO_GAMES_FOUND": "ゲームが見つかりませんでした"
|
"NO_GAMES_FOUND": "ゲームが見つかりませんでした"
|
||||||
},
|
},
|
||||||
"TERMINATE_PROCESS": "プロセスを終了する"
|
"TERMINATE_PROCESS": "プロセスを終了する",
|
||||||
|
"GAME_LAUNCHED": "ゲームが起動しました"
|
||||||
}
|
}
|
||||||
@@ -68,5 +68,6 @@
|
|||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
"NO_GAMES_FOUND": "Nie znaleziono gier"
|
"NO_GAMES_FOUND": "Nie znaleziono gier"
|
||||||
},
|
},
|
||||||
"TERMINATE_PROCESS": "Zakończ proces"
|
"TERMINATE_PROCESS": "Zakończ proces",
|
||||||
|
"GAME_LAUNCHED": "Gra uruchomiona"
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ pub fn stop(app_id: AppID, force_kill: bool) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn launch(app_id: AppID) -> anyhow::Result<()> {
|
pub fn launch(app_id: &AppID) -> anyhow::Result<()> {
|
||||||
log::info!("Launching Steam game with AppID {}", app_id);
|
log::info!("Launching Steam game with AppID {}", app_id);
|
||||||
call_steam(&format!("steam://rungameid/{}", app_id))?;
|
call_steam(&format!("steam://rungameid/{}", app_id))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
317
dash-frontend/src/views/game_cover.rs
Normal file
317
dash-frontend/src/views/game_cover.rs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::{
|
||||||
|
self,
|
||||||
|
button::ComponentButton,
|
||||||
|
tooltip::{TooltipInfo, TooltipSide},
|
||||||
|
},
|
||||||
|
drawing::{self, GradientMode},
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID, WidgetPair},
|
||||||
|
renderer_vk::text::{FontWeight, HorizontalAlign, TextShadow, TextStyle, custom_glyph::CustomGlyphData},
|
||||||
|
taffy::{
|
||||||
|
self, AlignItems, AlignSelf, JustifyContent, JustifySelf,
|
||||||
|
prelude::{auto, length, percent},
|
||||||
|
},
|
||||||
|
widget::{
|
||||||
|
ConstructEssentials,
|
||||||
|
div::WidgetDiv,
|
||||||
|
image::{WidgetImage, WidgetImageParams},
|
||||||
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
|
rectangle,
|
||||||
|
util::WLength,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::util::{
|
||||||
|
cached_fetcher::{self, CoverArt},
|
||||||
|
steam_utils::{self, AppID},
|
||||||
|
various::AsyncExecutor,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ViewCommon {
|
||||||
|
img_placeholder: Option<CustomGlyphData>,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Params<'a, 'b> {
|
||||||
|
pub ess: &'a mut ConstructEssentials<'b>,
|
||||||
|
pub executor: &'a AsyncExecutor,
|
||||||
|
pub manifest: &'a steam_utils::AppManifest,
|
||||||
|
pub scale: f32,
|
||||||
|
pub on_loaded: Box<dyn FnOnce(CoverArt)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
pub button: Rc<ComponentButton>,
|
||||||
|
pair: WidgetPair,
|
||||||
|
id_image_parent: WidgetID,
|
||||||
|
app_name: String,
|
||||||
|
app_id: AppID,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BORDER_COLOR_DEFAULT: drawing::Color = drawing::Color::new(0.0, 0.0, 0.0, 0.35);
|
||||||
|
const BORDER_COLOR_HOVERED: drawing::Color = drawing::Color::new(1.0, 1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
const GAME_COVER_SIZE_X: f32 = 140.0;
|
||||||
|
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
async fn request_cover_image(
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
manifest: steam_utils::AppManifest,
|
||||||
|
on_loaded: Box<dyn FnOnce(CoverArt)>,
|
||||||
|
) {
|
||||||
|
let cover_art = match cached_fetcher::request_image(executor, manifest.app_id.clone()).await {
|
||||||
|
Ok(cover_art) => cover_art,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("request_cover_image failed: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
on_loaded(cover_art)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_image(&self, layout: &mut Layout, glyph: &CustomGlyphData) -> anyhow::Result<()> {
|
||||||
|
let image = WidgetImage::create(WidgetImageParams {
|
||||||
|
round: WLength::Units(10.0),
|
||||||
|
glyph_data: Some(glyph.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let (a, _) = layout.add_child(
|
||||||
|
self.id_image_parent,
|
||||||
|
image,
|
||||||
|
taffy::Style {
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(1.0),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
a.widget.state().flags.new_pass = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_placeholder_text(
|
||||||
|
&self,
|
||||||
|
globals: &WguiGlobals,
|
||||||
|
layout: &mut Layout,
|
||||||
|
parent: WidgetID,
|
||||||
|
text: &str,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let label = WidgetLabel::create(
|
||||||
|
&mut globals.get(),
|
||||||
|
WidgetLabelParams {
|
||||||
|
content: Translation::from_raw_text(text),
|
||||||
|
style: TextStyle {
|
||||||
|
weight: Some(FontWeight::Bold),
|
||||||
|
wrap: true,
|
||||||
|
size: Some(16.0),
|
||||||
|
align: Some(HorizontalAlign::Center),
|
||||||
|
shadow: Some(TextShadow {
|
||||||
|
color: drawing::Color::new(0.0, 0.0, 0.0, 1.0),
|
||||||
|
x: 2.0,
|
||||||
|
y: 2.0,
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
layout.add_child(
|
||||||
|
parent,
|
||||||
|
label,
|
||||||
|
taffy::Style {
|
||||||
|
position: taffy::Position::Absolute,
|
||||||
|
align_self: Some(AlignSelf::Baseline),
|
||||||
|
justify_self: Some(JustifySelf::Center),
|
||||||
|
margin: taffy::Rect {
|
||||||
|
top: length(32.0),
|
||||||
|
bottom: auto(),
|
||||||
|
left: auto(),
|
||||||
|
right: auto(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cover_art(
|
||||||
|
&mut self,
|
||||||
|
view_common: &mut ViewCommon,
|
||||||
|
layout: &mut Layout,
|
||||||
|
cover_art: &CoverArt,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
if cover_art.compressed_image_data.is_empty() {
|
||||||
|
// mount placeholder
|
||||||
|
let img = view_common.get_placeholder_image()?.clone();
|
||||||
|
self.mount_image(layout, &img)?;
|
||||||
|
self.mount_placeholder_text(&view_common.globals, layout, self.id_image_parent, &self.app_name)?;
|
||||||
|
} else {
|
||||||
|
// mount image
|
||||||
|
let path = format!("app:{:?}", self.app_id);
|
||||||
|
let glyph =
|
||||||
|
match CustomGlyphData::from_bytes_raster(&view_common.globals, &path, &cover_art.compressed_image_data) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to decode cover art image: {:?}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.mount_image(layout, &glyph)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||||
|
let (widget_button, button) = components::button::construct(
|
||||||
|
params.ess,
|
||||||
|
components::button::Params {
|
||||||
|
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0)),
|
||||||
|
border_color: Some(BORDER_COLOR_DEFAULT),
|
||||||
|
hover_border_color: Some(BORDER_COLOR_HOVERED),
|
||||||
|
round: WLength::Units(12.0),
|
||||||
|
border: 2.0,
|
||||||
|
tooltip: Some(TooltipInfo {
|
||||||
|
side: TooltipSide::Bottom,
|
||||||
|
text: Translation::from_raw_text(¶ms.manifest.name),
|
||||||
|
}),
|
||||||
|
style: taffy::Style {
|
||||||
|
position: taffy::Position::Relative,
|
||||||
|
align_items: Some(taffy::AlignItems::Center),
|
||||||
|
justify_content: Some(taffy::JustifyContent::Center),
|
||||||
|
size: taffy::Size {
|
||||||
|
width: length(GAME_COVER_SIZE_X * params.scale),
|
||||||
|
height: length(GAME_COVER_SIZE_Y * params.scale),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (image_parent, _) = params.ess.layout.add_child(
|
||||||
|
widget_button.id,
|
||||||
|
WidgetDiv::create(),
|
||||||
|
taffy::Style {
|
||||||
|
position: taffy::Position::Absolute,
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(1.0),
|
||||||
|
},
|
||||||
|
padding: taffy::Rect::length(2.0),
|
||||||
|
align_items: Some(AlignItems::Center),
|
||||||
|
justify_content: Some(JustifyContent::Center),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
|
||||||
|
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
|
||||||
|
color,
|
||||||
|
color2,
|
||||||
|
round: WLength::Units(12.0),
|
||||||
|
gradient: GradientMode::Vertical,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let rect_gradient_style = |align_self: taffy::AlignSelf, height: f32| taffy::Style {
|
||||||
|
position: taffy::Position::Absolute,
|
||||||
|
align_self: Some(align_self),
|
||||||
|
size: taffy::Size {
|
||||||
|
width: percent(1.0),
|
||||||
|
height: percent(height),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// top shine
|
||||||
|
let (top_shine, _) = params.ess.layout.add_child(
|
||||||
|
widget_button.id,
|
||||||
|
rect_gradient(
|
||||||
|
drawing::Color::new(1.0, 1.0, 1.0, 0.2),
|
||||||
|
drawing::Color::new(1.0, 1.0, 1.0, 0.02),
|
||||||
|
),
|
||||||
|
rect_gradient_style(taffy::AlignSelf::Baseline, 0.05),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// not optimal, this forces us to create a new pass for every created cover art just to overlay various rectangles at the top of the image cover art
|
||||||
|
top_shine.widget.state().flags.new_pass = true;
|
||||||
|
|
||||||
|
// top white gradient
|
||||||
|
params.ess.layout.add_child(
|
||||||
|
widget_button.id,
|
||||||
|
rect_gradient(
|
||||||
|
drawing::Color::new(1.0, 1.0, 1.0, 0.15),
|
||||||
|
drawing::Color::new(1.0, 1.0, 1.0, 0.0),
|
||||||
|
),
|
||||||
|
rect_gradient_style(taffy::AlignSelf::Baseline, 0.5),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// bottom black gradient
|
||||||
|
params.ess.layout.add_child(
|
||||||
|
widget_button.id,
|
||||||
|
rect_gradient(
|
||||||
|
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
|
||||||
|
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
|
||||||
|
),
|
||||||
|
rect_gradient_style(taffy::AlignSelf::End, 0.5),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// bottom shadow
|
||||||
|
params.ess.layout.add_child(
|
||||||
|
widget_button.id,
|
||||||
|
rect_gradient(
|
||||||
|
drawing::Color::new(0.0, 0.0, 0.0, 0.1),
|
||||||
|
drawing::Color::new(0.0, 0.0, 0.0, 0.9),
|
||||||
|
),
|
||||||
|
rect_gradient_style(taffy::AlignSelf::End, 0.05),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// request cover image data from the internet or disk cache
|
||||||
|
params
|
||||||
|
.executor
|
||||||
|
.spawn(View::request_cover_image(
|
||||||
|
params.executor.clone(),
|
||||||
|
params.manifest.clone(),
|
||||||
|
Box::new(params.on_loaded),
|
||||||
|
))
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Ok(View {
|
||||||
|
pair: widget_button,
|
||||||
|
button,
|
||||||
|
id_image_parent: image_parent.id,
|
||||||
|
app_name: params.manifest.name.clone(),
|
||||||
|
app_id: params.manifest.app_id.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewCommon {
|
||||||
|
pub fn new(globals: WguiGlobals) -> Self {
|
||||||
|
Self {
|
||||||
|
globals,
|
||||||
|
img_placeholder: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_placeholder_image(&mut self) -> anyhow::Result<&CustomGlyphData> {
|
||||||
|
if self.img_placeholder.is_none() {
|
||||||
|
let c = CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn("dashboard/placeholder_cover.png"))?;
|
||||||
|
self.img_placeholder = Some(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.img_placeholder.as_ref().unwrap()) // safe
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks},
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
util::{
|
util::{
|
||||||
cached_fetcher::{self},
|
cached_fetcher::{self, CoverArt},
|
||||||
steam_utils::{AppID, AppManifest},
|
steam_utils::{self, AppID, AppManifest},
|
||||||
various::AsyncExecutor,
|
various::AsyncExecutor,
|
||||||
},
|
},
|
||||||
|
views::game_cover,
|
||||||
};
|
};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
@@ -14,12 +17,13 @@ use wgui::{
|
|||||||
layout::{Layout, WidgetID},
|
layout::{Layout, WidgetID},
|
||||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
widget::label::WidgetLabel,
|
widget::{ConstructEssentials, label::WidgetLabel},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
FillAppDetails(cached_fetcher::AppDetailsJSONData),
|
FillAppDetails(cached_fetcher::AppDetailsJSONData),
|
||||||
|
SetCoverArt(Rc<CoverArt>),
|
||||||
Launch,
|
Launch,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,14 +43,9 @@ pub struct View {
|
|||||||
on_launched: Box<dyn Fn()>,
|
on_launched: Box<dyn Fn()>,
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: FrontendTasks,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
game_cover_view_common: game_cover::ViewCommon,
|
||||||
id_cover_art_parent: WidgetID,
|
view_cover: game_cover::View,
|
||||||
#[allow(dead_code)]
|
app_id: AppID,
|
||||||
executor: AsyncExecutor,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
globals: WguiGlobals,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
manifest: AppManifest,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
@@ -67,11 +66,13 @@ impl View {
|
|||||||
|
|
||||||
let state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
let state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||||
|
|
||||||
let mut label_title = state.fetch_widget_as::<WidgetLabel>(¶ms.layout.state, "label_title")?;
|
{
|
||||||
label_title.set_text_simple(
|
let mut label_title = state.fetch_widget_as::<WidgetLabel>(¶ms.layout.state, "label_title")?;
|
||||||
&mut params.globals.get(),
|
label_title.set_text_simple(
|
||||||
Translation::from_raw_text(¶ms.manifest.name),
|
&mut params.globals.get(),
|
||||||
);
|
Translation::from_raw_text(¶ms.manifest.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let tasks = Tasks::new();
|
let tasks = Tasks::new();
|
||||||
|
|
||||||
@@ -84,15 +85,30 @@ impl View {
|
|||||||
|
|
||||||
tasks.handle_button(&btn_launch, Task::Launch);
|
tasks.handle_button(&btn_launch, Task::Launch);
|
||||||
|
|
||||||
|
let view_cover = game_cover::View::new(game_cover::Params {
|
||||||
|
ess: &mut ConstructEssentials {
|
||||||
|
layout: params.layout,
|
||||||
|
parent: id_cover_art_parent,
|
||||||
|
},
|
||||||
|
executor: ¶ms.executor,
|
||||||
|
manifest: ¶ms.manifest,
|
||||||
|
on_loaded: {
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
Box::new(move |cover_art| {
|
||||||
|
tasks.push(Task::SetCoverArt(Rc::new(cover_art)));
|
||||||
|
})
|
||||||
|
},
|
||||||
|
scale: 1.5,
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state,
|
state,
|
||||||
tasks,
|
tasks,
|
||||||
on_launched: params.on_launched,
|
on_launched: params.on_launched,
|
||||||
id_cover_art_parent,
|
|
||||||
frontend_tasks: params.frontend_tasks.clone(),
|
frontend_tasks: params.frontend_tasks.clone(),
|
||||||
executor: params.executor.clone(),
|
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||||
globals: params.globals.clone(),
|
view_cover,
|
||||||
manifest: params.manifest,
|
app_id: params.manifest.app_id.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +122,11 @@ impl View {
|
|||||||
match task {
|
match task {
|
||||||
Task::FillAppDetails(details) => self.action_fill_app_details(layout, details)?,
|
Task::FillAppDetails(details) => self.action_fill_app_details(layout, details)?,
|
||||||
Task::Launch => self.action_launch(),
|
Task::Launch => self.action_launch(),
|
||||||
|
Task::SetCoverArt(cover_art) => {
|
||||||
|
let _ = self
|
||||||
|
.view_cover
|
||||||
|
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,9 +171,24 @@ impl View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn action_launch(&mut self) {
|
fn action_launch(&mut self) {
|
||||||
self
|
match steam_utils::launch(&self.app_id) {
|
||||||
.frontend_tasks
|
Ok(_) => {
|
||||||
.push(FrontendTask::PushToast(Translation::from_raw_text("Game launch TODO")));
|
self
|
||||||
|
.frontend_tasks
|
||||||
|
.push(FrontendTask::PushToast(Translation::from_translation_key(
|
||||||
|
"GAME_LAUNCHED",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
self
|
||||||
|
.frontend_tasks
|
||||||
|
.push(FrontendTask::PushToast(Translation::from_raw_text_string(format!(
|
||||||
|
"Failed to launch: {:?}",
|
||||||
|
e
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(*self.on_launched)();
|
(*self.on_launched)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,50 +2,32 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
|||||||
|
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{
|
|
||||||
self,
|
|
||||||
button::ComponentButton,
|
|
||||||
tooltip::{TooltipInfo, TooltipSide},
|
|
||||||
},
|
|
||||||
drawing::{self, GradientMode},
|
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, WidgetID, WidgetPair},
|
layout::{Layout, WidgetID},
|
||||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
renderer_vk::text::{
|
|
||||||
FontWeight, HorizontalAlign, TextShadow, TextStyle,
|
|
||||||
custom_glyph::{ CustomGlyphData},
|
|
||||||
},
|
|
||||||
taffy::{
|
|
||||||
self, AlignItems, AlignSelf, JustifyContent, JustifySelf,
|
|
||||||
prelude::{auto, length, percent},
|
|
||||||
},
|
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
widget::{
|
widget::{
|
||||||
ConstructEssentials,
|
ConstructEssentials,
|
||||||
div::WidgetDiv,
|
|
||||||
image::{WidgetImage, WidgetImageParams},
|
|
||||||
label::{WidgetLabel, WidgetLabelParams},
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
rectangle,
|
|
||||||
util::WLength,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks},
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
util::{
|
util::{
|
||||||
cached_fetcher::{self, CoverArt},
|
cached_fetcher::CoverArt,
|
||||||
popup_manager::{MountPopupParams, PopupHandle},
|
popup_manager::{MountPopupParams, PopupHandle},
|
||||||
steam_utils::{self, AppID, AppManifest, SteamUtils},
|
steam_utils::{self, AppID, AppManifest, SteamUtils},
|
||||||
various::AsyncExecutor,
|
various::AsyncExecutor,
|
||||||
},
|
},
|
||||||
views::{self, game_launcher},
|
views::{self, game_cover, game_launcher},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
AppManifestClicked(steam_utils::AppManifest),
|
AppManifestClicked(steam_utils::AppManifest),
|
||||||
SetCoverArt((AppID, Rc<CoverArt>)),
|
SetCoverArt(AppID, Rc<CoverArt>),
|
||||||
CloseLauncher,
|
CloseLauncher,
|
||||||
Refresh,
|
Refresh,
|
||||||
}
|
}
|
||||||
@@ -59,7 +41,7 @@ pub struct Params<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
image_parent: WidgetID,
|
view_cover: game_cover::View,
|
||||||
manifest: AppManifest,
|
manifest: AppManifest,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +58,7 @@ pub struct View {
|
|||||||
id_list_parent: WidgetID,
|
id_list_parent: WidgetID,
|
||||||
steam_utils: steam_utils::SteamUtils,
|
steam_utils: steam_utils::SteamUtils,
|
||||||
cells: HashMap<AppID, Cell>,
|
cells: HashMap<AppID, Cell>,
|
||||||
img_placeholder: Option<CustomGlyphData>,
|
game_cover_view_common: game_cover::ViewCommon,
|
||||||
executor: AsyncExecutor,
|
executor: AsyncExecutor,
|
||||||
state: Rc<RefCell<State>>,
|
state: Rc<RefCell<State>>,
|
||||||
}
|
}
|
||||||
@@ -106,7 +88,7 @@ impl View {
|
|||||||
id_list_parent: list_parent.id,
|
id_list_parent: list_parent.id,
|
||||||
steam_utils,
|
steam_utils,
|
||||||
cells: HashMap::new(),
|
cells: HashMap::new(),
|
||||||
img_placeholder: None,
|
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||||
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
||||||
executor: params.executor,
|
executor: params.executor,
|
||||||
})
|
})
|
||||||
@@ -122,7 +104,7 @@ impl View {
|
|||||||
match task {
|
match task {
|
||||||
Task::Refresh => self.refresh(layout, executor)?,
|
Task::Refresh => self.refresh(layout, executor)?,
|
||||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||||
Task::SetCoverArt((app_id, cover_art)) => self.action_set_cover_art(layout, &app_id, cover_art)?,
|
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art),
|
||||||
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
|
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,157 +123,7 @@ pub struct Games {
|
|||||||
manifests: Vec<steam_utils::AppManifest>,
|
manifests: Vec<steam_utils::AppManifest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const BORDER_COLOR_DEFAULT: drawing::Color = drawing::Color::new(0.0, 0.0, 0.0, 0.35);
|
|
||||||
const BORDER_COLOR_HOVERED: drawing::Color = drawing::Color::new(1.0, 1.0, 1.0, 1.0);
|
|
||||||
|
|
||||||
const GAME_COVER_SIZE_X: f32 = 140.0;
|
|
||||||
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
|
||||||
|
|
||||||
async fn request_cover_image(
|
|
||||||
executor: AsyncExecutor,
|
|
||||||
manifest: steam_utils::AppManifest,
|
|
||||||
on_loaded: Box<dyn FnOnce(CoverArt)>,
|
|
||||||
) {
|
|
||||||
let cover_art = match cached_fetcher::request_image(executor, manifest.app_id.clone()).await {
|
|
||||||
Ok(cover_art) => cover_art,
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("request_cover_image failed: {:?}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
on_loaded(cover_art)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn construct_game_cover(
|
|
||||||
ess: &mut ConstructEssentials,
|
|
||||||
executor: &AsyncExecutor,
|
|
||||||
_globals: &WguiGlobals,
|
|
||||||
manifest: &steam_utils::AppManifest,
|
|
||||||
on_loaded: Box<dyn FnOnce(CoverArt)>,
|
|
||||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>, Cell)> {
|
|
||||||
let (widget_button, button) = components::button::construct(
|
|
||||||
ess,
|
|
||||||
components::button::Params {
|
|
||||||
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0)),
|
|
||||||
border_color: Some(BORDER_COLOR_DEFAULT),
|
|
||||||
hover_border_color: Some(BORDER_COLOR_HOVERED),
|
|
||||||
round: WLength::Units(12.0),
|
|
||||||
border: 2.0,
|
|
||||||
tooltip: Some(TooltipInfo {
|
|
||||||
side: TooltipSide::Bottom,
|
|
||||||
text: Translation::from_raw_text(&manifest.name),
|
|
||||||
}),
|
|
||||||
style: taffy::Style {
|
|
||||||
position: taffy::Position::Relative,
|
|
||||||
align_items: Some(taffy::AlignItems::Center),
|
|
||||||
justify_content: Some(taffy::JustifyContent::Center),
|
|
||||||
size: taffy::Size {
|
|
||||||
width: length(GAME_COVER_SIZE_X),
|
|
||||||
height: length(GAME_COVER_SIZE_Y),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let (image_parent, _) = ess.layout.add_child(
|
|
||||||
widget_button.id,
|
|
||||||
WidgetDiv::create(),
|
|
||||||
taffy::Style {
|
|
||||||
position: taffy::Position::Absolute,
|
|
||||||
size: taffy::Size {
|
|
||||||
width: percent(1.0),
|
|
||||||
height: percent(1.0),
|
|
||||||
},
|
|
||||||
padding: taffy::Rect::length(2.0),
|
|
||||||
align_items: Some(AlignItems::Center),
|
|
||||||
justify_content: Some(JustifyContent::Center),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
|
|
||||||
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
|
|
||||||
color,
|
|
||||||
color2,
|
|
||||||
round: WLength::Units(12.0),
|
|
||||||
gradient: GradientMode::Vertical,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let rect_gradient_style = |align_self: taffy::AlignSelf, height: f32| taffy::Style {
|
|
||||||
position: taffy::Position::Absolute,
|
|
||||||
align_self: Some(align_self),
|
|
||||||
size: taffy::Size {
|
|
||||||
width: percent(1.0),
|
|
||||||
height: percent(height),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// top shine
|
|
||||||
let (top_shine, _) = ess.layout.add_child(
|
|
||||||
widget_button.id,
|
|
||||||
rect_gradient(
|
|
||||||
drawing::Color::new(1.0, 1.0, 1.0, 0.2),
|
|
||||||
drawing::Color::new(1.0, 1.0, 1.0, 0.02),
|
|
||||||
),
|
|
||||||
rect_gradient_style(taffy::AlignSelf::Baseline, 0.05),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// not optimal, this forces us to create a new pass for every created cover art just to overlay various rectangles at the top of the image cover art
|
|
||||||
top_shine.widget.state().flags.new_pass = true;
|
|
||||||
|
|
||||||
// top white gradient
|
|
||||||
ess.layout.add_child(
|
|
||||||
widget_button.id,
|
|
||||||
rect_gradient(
|
|
||||||
drawing::Color::new(1.0, 1.0, 1.0, 0.15),
|
|
||||||
drawing::Color::new(1.0, 1.0, 1.0, 0.0),
|
|
||||||
),
|
|
||||||
rect_gradient_style(taffy::AlignSelf::Baseline, 0.5),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// bottom black gradient
|
|
||||||
ess.layout.add_child(
|
|
||||||
widget_button.id,
|
|
||||||
rect_gradient(
|
|
||||||
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
|
|
||||||
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
|
|
||||||
),
|
|
||||||
rect_gradient_style(taffy::AlignSelf::End, 0.5),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// bottom shadow
|
|
||||||
ess.layout.add_child(
|
|
||||||
widget_button.id,
|
|
||||||
rect_gradient(
|
|
||||||
drawing::Color::new(0.0, 0.0, 0.0, 0.1),
|
|
||||||
drawing::Color::new(0.0, 0.0, 0.0, 0.9),
|
|
||||||
),
|
|
||||||
rect_gradient_style(taffy::AlignSelf::End, 0.05),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// request cover image data from the internet or disk cache
|
|
||||||
executor
|
|
||||||
.spawn(request_cover_image(executor.clone(), manifest.clone(), on_loaded))
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
widget_button,
|
|
||||||
button,
|
|
||||||
Cell {
|
|
||||||
image_parent: image_parent.id,
|
|
||||||
manifest: manifest.clone(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fill_game_list(
|
fn fill_game_list(
|
||||||
globals: &WguiGlobals,
|
|
||||||
ess: &mut ConstructEssentials,
|
ess: &mut ConstructEssentials,
|
||||||
executor: &AsyncExecutor,
|
executor: &AsyncExecutor,
|
||||||
cells: &mut HashMap<AppID, Cell>,
|
cells: &mut HashMap<AppID, Cell>,
|
||||||
@@ -302,14 +134,20 @@ fn fill_game_list(
|
|||||||
let on_loaded = {
|
let on_loaded = {
|
||||||
let app_id = manifest.app_id.clone();
|
let app_id = manifest.app_id.clone();
|
||||||
let tasks = tasks.clone();
|
let tasks = tasks.clone();
|
||||||
move |cover_art: CoverArt| {
|
Box::new(move |cover_art: CoverArt| {
|
||||||
tasks.push(Task::SetCoverArt((app_id, Rc::from(cover_art))));
|
tasks.push(Task::SetCoverArt(app_id, Rc::from(cover_art)));
|
||||||
}
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let (_, button, cell) = construct_game_cover(ess, executor, globals, manifest, Box::new(on_loaded))?;
|
let view_cover = game_cover::View::new(game_cover::Params {
|
||||||
|
ess,
|
||||||
|
executor,
|
||||||
|
manifest,
|
||||||
|
on_loaded,
|
||||||
|
scale: 1.0,
|
||||||
|
})?;
|
||||||
|
|
||||||
button.on_click({
|
view_cover.button.on_click({
|
||||||
let tasks = tasks.clone();
|
let tasks = tasks.clone();
|
||||||
let manifest = manifest.clone();
|
let manifest = manifest.clone();
|
||||||
Box::new(move |_, _| {
|
Box::new(move |_, _| {
|
||||||
@@ -318,7 +156,13 @@ fn fill_game_list(
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
cells.insert(manifest.app_id.clone(), cell);
|
cells.insert(
|
||||||
|
manifest.app_id.clone(),
|
||||||
|
Cell {
|
||||||
|
view_cover,
|
||||||
|
manifest: manifest.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -344,7 +188,6 @@ impl View {
|
|||||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||||
} else {
|
} else {
|
||||||
fill_game_list(
|
fill_game_list(
|
||||||
&self.globals,
|
|
||||||
&mut ConstructEssentials {
|
&mut ConstructEssentials {
|
||||||
layout,
|
layout,
|
||||||
parent: self.id_list_parent,
|
parent: self.id_list_parent,
|
||||||
@@ -376,6 +219,19 @@ impl View {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_cover_art(&mut self, layout: &mut Layout, app_id: AppID, cover_art: Rc<CoverArt>) {
|
||||||
|
let Some(cell) = &mut self.cells.get_mut(&app_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = cell
|
||||||
|
.view_cover
|
||||||
|
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art)
|
||||||
|
{
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn action_app_manifest_clicked(&mut self, manifest: steam_utils::AppManifest) -> anyhow::Result<()> {
|
fn action_app_manifest_clicked(&mut self, manifest: steam_utils::AppManifest) -> anyhow::Result<()> {
|
||||||
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
||||||
title: Translation::from_raw_text(&manifest.name),
|
title: Translation::from_raw_text(&manifest.name),
|
||||||
@@ -410,126 +266,4 @@ impl View {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_placeholder_image(&mut self) -> anyhow::Result<&CustomGlyphData> {
|
|
||||||
if self.img_placeholder.is_none() {
|
|
||||||
let c = CustomGlyphData::from_assets(
|
|
||||||
&self.globals,
|
|
||||||
AssetPath::BuiltIn("dashboard/placeholder_cover.png"),
|
|
||||||
)?;
|
|
||||||
self.img_placeholder = Some(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.img_placeholder.as_ref().unwrap()) // safe
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mount_image(layout: &mut Layout, cell: &Cell, glyph: &CustomGlyphData) -> anyhow::Result<()> {
|
|
||||||
let image = WidgetImage::create(WidgetImageParams {
|
|
||||||
round: WLength::Units(10.0),
|
|
||||||
glyph_data: Some(glyph.clone()),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
let (a, _) = layout.add_child(
|
|
||||||
cell.image_parent,
|
|
||||||
image,
|
|
||||||
taffy::Style {
|
|
||||||
size: taffy::Size {
|
|
||||||
width: percent(1.0),
|
|
||||||
height: percent(1.0),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
a.widget.state().flags.new_pass = true;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mount_placeholder_text(
|
|
||||||
globals: &WguiGlobals,
|
|
||||||
layout: &mut Layout,
|
|
||||||
parent: WidgetID,
|
|
||||||
text: &str,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let label = WidgetLabel::create(
|
|
||||||
&mut globals.get(),
|
|
||||||
WidgetLabelParams {
|
|
||||||
content: Translation::from_raw_text(text),
|
|
||||||
style: TextStyle {
|
|
||||||
weight: Some(FontWeight::Bold),
|
|
||||||
wrap: true,
|
|
||||||
size: Some(16.0),
|
|
||||||
align: Some(HorizontalAlign::Center),
|
|
||||||
shadow: Some(TextShadow {
|
|
||||||
color: drawing::Color::new(0.0, 0.0, 0.0, 1.0),
|
|
||||||
x: 2.0,
|
|
||||||
y: 2.0,
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
layout.add_child(
|
|
||||||
parent,
|
|
||||||
label,
|
|
||||||
taffy::Style {
|
|
||||||
position: taffy::Position::Absolute,
|
|
||||||
align_self: Some(AlignSelf::Baseline),
|
|
||||||
justify_self: Some(JustifySelf::Center),
|
|
||||||
margin: taffy::Rect {
|
|
||||||
top: length(32.0),
|
|
||||||
bottom: auto(),
|
|
||||||
left: auto(),
|
|
||||||
right: auto(),
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_set_cover_art(
|
|
||||||
&mut self,
|
|
||||||
layout: &mut Layout,
|
|
||||||
app_id: &AppID,
|
|
||||||
cover_art: Rc<CoverArt>,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if cover_art.compressed_image_data.is_empty() {
|
|
||||||
// mount placeholder
|
|
||||||
let img = self.get_placeholder_image()?.clone();
|
|
||||||
|
|
||||||
let Some(cell) = self.cells.get(app_id) else {
|
|
||||||
debug_assert!(false); // this shouldn't happen
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
View::mount_image(layout, cell, &img)?;
|
|
||||||
View::mount_placeholder_text(&self.globals, layout, cell.image_parent, &cell.manifest.name)?;
|
|
||||||
} else {
|
|
||||||
// mount image
|
|
||||||
|
|
||||||
let Some(cell) = self.cells.get(app_id) else {
|
|
||||||
debug_assert!(false); // this shouldn't happen
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = format!("app:{app_id:?}");
|
|
||||||
let glyph = match CustomGlyphData::from_bytes_raster(&self.globals, &path ,&cover_art.compressed_image_data) {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!(
|
|
||||||
"failed to decode cover art image for AppID {} ({:?}), skipping",
|
|
||||||
app_id,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
View::mount_image(layout, cell, &glyph)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
pub mod app_launcher;
|
pub mod app_launcher;
|
||||||
pub mod audio_settings;
|
pub mod audio_settings;
|
||||||
|
pub mod game_cover;
|
||||||
pub mod game_launcher;
|
pub mod game_launcher;
|
||||||
pub mod game_list;
|
pub mod game_list;
|
||||||
pub mod process_list;
|
pub mod process_list;
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
animation::{Animation, AnimationEasing},
|
animation::{Animation, AnimationEasing},
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{self, tooltip::ComponentTooltip, Component, ComponentBase, ComponentTrait, RefreshData},
|
components::{self, Component, ComponentBase, ComponentTrait, RefreshData, tooltip::ComponentTooltip},
|
||||||
drawing::{self, Boundary, Color},
|
drawing::{self, Boundary, Color},
|
||||||
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
|
event::{CallbackDataCommon, EventListenerCollection, EventListenerID, EventListenerKind},
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{LayoutTask, WidgetID, WidgetPair},
|
layout::{LayoutTask, WidgetID, WidgetPair},
|
||||||
renderer_vk::{
|
renderer_vk::{
|
||||||
text::{custom_glyph::CustomGlyphData, FontWeight, TextStyle},
|
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
|
||||||
util::centered_matrix,
|
util::centered_matrix,
|
||||||
},
|
},
|
||||||
widget::{
|
widget::{
|
||||||
self,
|
self, ConstructEssentials, EventResult, WidgetData,
|
||||||
label::{WidgetLabel, WidgetLabelParams},
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||||
sprite::{WidgetSprite, WidgetSpriteParams},
|
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||||
util::WLength,
|
util::WLength,
|
||||||
ConstructEssentials, EventResult, WidgetData,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use glam::{Mat4, Vec3};
|
use glam::{Mat4, Vec3};
|
||||||
@@ -25,7 +24,7 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use taffy::{prelude::length, AlignItems, JustifyContent};
|
use taffy::{AlignItems, JustifyContent, prelude::length};
|
||||||
|
|
||||||
pub struct Params<'a> {
|
pub struct Params<'a> {
|
||||||
pub text: Option<Translation>, // if unset, label will not be populated
|
pub text: Option<Translation>, // if unset, label will not be populated
|
||||||
|
|||||||
Reference in New Issue
Block a user