dash-frontend: games: pagination
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
use std::{path::PathBuf, rc::Rc};
|
||||
|
||||
use anyhow::Context;
|
||||
use chrono::Timelike;
|
||||
use glam::Vec2;
|
||||
use wgui::{
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
@@ -29,7 +30,10 @@ enum Task {
|
||||
AppManifestClicked(steam_utils::AppManifest),
|
||||
SetCoverArt(AppID, Rc<CoverArt>),
|
||||
CloseLauncher,
|
||||
Refresh,
|
||||
LoadManifests,
|
||||
FillPage(u32),
|
||||
PrevPage,
|
||||
NextPage,
|
||||
}
|
||||
|
||||
pub struct Params<'a> {
|
||||
@@ -40,7 +44,9 @@ pub struct Params<'a> {
|
||||
pub parent_id: WidgetID,
|
||||
}
|
||||
|
||||
pub struct Cell {
|
||||
const MAX_GAMES_PER_PAGE: u32 = 30;
|
||||
|
||||
pub struct GameCoverCell {
|
||||
view_cover: game_cover::View,
|
||||
}
|
||||
|
||||
@@ -55,10 +61,14 @@ pub struct View {
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
id_list_parent: WidgetID,
|
||||
cells: HashMap<AppID, Cell>,
|
||||
game_cover_view_common: game_cover::ViewCommon,
|
||||
executor: AsyncExecutor,
|
||||
state: Rc<RefCell<State>>,
|
||||
mounted_game_covers: HashMap<AppID, GameCoverCell>,
|
||||
all_manifests: Vec<steam_utils::AppManifest>,
|
||||
cur_page: u32,
|
||||
page_count: u32,
|
||||
id_label_page: WidgetID,
|
||||
}
|
||||
|
||||
impl View {
|
||||
@@ -71,10 +81,21 @@ impl View {
|
||||
|
||||
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?;
|
||||
let id_label_page = parser_state.get_widget_id("label_page")?;
|
||||
|
||||
let tasks = Tasks::new();
|
||||
|
||||
tasks.push(Task::Refresh);
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_prev")?,
|
||||
Task::PrevPage,
|
||||
);
|
||||
|
||||
tasks.handle_button(
|
||||
&parser_state.fetch_component_as::<ComponentButton>("btn_next")?,
|
||||
Task::NextPage,
|
||||
);
|
||||
|
||||
tasks.push(Task::LoadManifests);
|
||||
|
||||
Ok(Self {
|
||||
parser_state,
|
||||
@@ -82,10 +103,14 @@ impl View {
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
globals: params.globals.clone(),
|
||||
id_list_parent: list_parent.id,
|
||||
cells: HashMap::new(),
|
||||
mounted_game_covers: HashMap::new(),
|
||||
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
||||
executor: params.executor,
|
||||
all_manifests: Vec::new(),
|
||||
cur_page: 0,
|
||||
page_count: 0,
|
||||
id_label_page,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,10 +127,13 @@ impl View {
|
||||
}
|
||||
for task in tasks {
|
||||
match task {
|
||||
Task::Refresh => self.refresh(layout, steam_utils, executor)?,
|
||||
Task::LoadManifests => self.load_manifests(steam_utils),
|
||||
Task::FillPage(page_idx) => self.fill_page(layout, executor, page_idx)?,
|
||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||
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::PrevPage => self.page_prev(),
|
||||
Task::NextPage => self.page_next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,18 +147,14 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Games {
|
||||
manifests: Vec<steam_utils::AppManifest>,
|
||||
}
|
||||
|
||||
fn fill_game_list(
|
||||
ess: &mut ConstructEssentials,
|
||||
executor: &AsyncExecutor,
|
||||
cells: &mut HashMap<AppID, Cell>,
|
||||
games: &Games,
|
||||
mounted_game_covers: &mut HashMap<AppID, GameCoverCell>,
|
||||
manifests: &[steam_utils::AppManifest],
|
||||
tasks: &Tasks<Task>,
|
||||
) -> anyhow::Result<()> {
|
||||
for manifest in &games.manifests {
|
||||
for manifest in manifests {
|
||||
let on_loaded = {
|
||||
let app_id = manifest.app_id.clone();
|
||||
let tasks = tasks.clone();
|
||||
@@ -156,49 +180,81 @@ fn fill_game_list(
|
||||
})
|
||||
});
|
||||
|
||||
cells.insert(manifest.app_id.clone(), Cell { view_cover });
|
||||
mounted_game_covers.insert(manifest.app_id.clone(), GameCoverCell { view_cover });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl View {
|
||||
fn game_list(&self, steam_utils: &mut SteamUtils) -> anyhow::Result<Games> {
|
||||
let manifests = steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)?;
|
||||
|
||||
Ok(Games { manifests })
|
||||
fn load_manifests(&mut self, steam_utils: &mut SteamUtils) {
|
||||
match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) {
|
||||
Ok(manifests) => {
|
||||
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
|
||||
self.all_manifests = manifests;
|
||||
self.tasks.push(Task::FillPage(0));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to list installed games: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh(
|
||||
&mut self,
|
||||
layout: &mut Layout,
|
||||
steam_utils: &mut SteamUtils,
|
||||
executor: &AsyncExecutor,
|
||||
) -> anyhow::Result<()> {
|
||||
fn page_prev(&mut self) {
|
||||
if self.cur_page == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cur_page -= 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn page_next(&mut self) {
|
||||
if self.cur_page >= self.page_count - 1 {
|
||||
return;
|
||||
}
|
||||
self.cur_page += 1;
|
||||
self.tasks.push(Task::FillPage(self.cur_page));
|
||||
}
|
||||
|
||||
fn fill_page(&mut self, layout: &mut Layout, executor: &AsyncExecutor, page_idx: u32) -> anyhow::Result<()> {
|
||||
layout.remove_children(self.id_list_parent);
|
||||
self.cells.clear();
|
||||
self.mounted_game_covers.clear();
|
||||
|
||||
let idx_from = (page_idx * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
let idx_to = ((page_idx + 1) * MAX_GAMES_PER_PAGE).min(self.all_manifests.len() as u32);
|
||||
|
||||
let page_manifests = &self.all_manifests[idx_from as usize..idx_to as usize];
|
||||
|
||||
let mut text: Option<Translation> = None;
|
||||
match self.game_list(steam_utils) {
|
||||
Ok(list) => {
|
||||
if list.manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
} else {
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.cells,
|
||||
&list,
|
||||
&self.tasks,
|
||||
)?
|
||||
}
|
||||
}
|
||||
Err(e) => text = Some(Translation::from_raw_text(&format!("Error: {:?}", e))),
|
||||
|
||||
if page_manifests.is_empty() {
|
||||
text = Some(Translation::from_translation_key("GAME_LIST.NO_GAMES_FOUND"))
|
||||
}
|
||||
|
||||
// set page text
|
||||
let mut c = layout.start_common();
|
||||
{
|
||||
let mut common = c.common();
|
||||
let mut widget = common.state.widgets.cast_as::<WidgetLabel>(self.id_label_page)?;
|
||||
widget.set_text(
|
||||
&mut common,
|
||||
Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)),
|
||||
);
|
||||
}
|
||||
c.finish()?;
|
||||
|
||||
fill_game_list(
|
||||
&mut ConstructEssentials {
|
||||
layout,
|
||||
parent: self.id_list_parent,
|
||||
},
|
||||
executor,
|
||||
&mut self.mounted_game_covers,
|
||||
page_manifests,
|
||||
&self.tasks,
|
||||
)?;
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
@@ -217,11 +273,11 @@ impl View {
|
||||
}
|
||||
|
||||
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 {
|
||||
let Some(cover) = &mut self.mounted_game_covers.get_mut(&app_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = cell
|
||||
if let Err(e) = cover
|
||||
.view_cover
|
||||
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art)
|
||||
{
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::button::ComponentButton,
|
||||
event::StyleSetRequest,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
layout::{Layout, LayoutTask, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
taffy::Display,
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
frontend::{FrontendTask, FrontendTasks},
|
||||
util::{
|
||||
steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
|
||||
wgui_simple,
|
||||
},
|
||||
util::steam_utils::{self, AppID, AppManifest, GameSortMethod, SteamUtils},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -39,6 +38,7 @@ pub struct View {
|
||||
id_list_parent: WidgetID,
|
||||
installed_games: Vec<AppManifest>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
parent_id: WidgetID,
|
||||
}
|
||||
|
||||
fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
||||
@@ -58,7 +58,7 @@ impl View {
|
||||
let installed_games = params
|
||||
.steam_utils
|
||||
.list_installed_games(GameSortMethod::None)
|
||||
.unwrap_or(Vec::new());
|
||||
.unwrap_or_default();
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
|
||||
@@ -72,6 +72,7 @@ impl View {
|
||||
id_list_parent,
|
||||
installed_games,
|
||||
frontend_tasks: params.frontend_tasks,
|
||||
parent_id: params.parent_id,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ impl View {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_name_from_appid<'a>(app_id: &AppID, manifests: &[AppManifest]) -> String {
|
||||
fn extract_name_from_appid(app_id: &AppID, manifests: &[AppManifest]) -> String {
|
||||
for manifest in manifests {
|
||||
if manifest.app_id == *app_id {
|
||||
return manifest.name.clone();
|
||||
@@ -110,14 +111,19 @@ impl View {
|
||||
|
||||
fn fill_list(&mut self, layout: &mut Layout, games: Vec<steam_utils::RunningGame>) -> anyhow::Result<()> {
|
||||
if games.is_empty() {
|
||||
wgui_simple::create_label(
|
||||
layout,
|
||||
self.id_list_parent,
|
||||
Translation::from_translation_key("GAME_LIST.NO_RUNNING_GAME_FOUND"),
|
||||
)?;
|
||||
// hide self
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::None),
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
||||
self.parent_id,
|
||||
StyleSetRequest::Display(Display::DEFAULT),
|
||||
));
|
||||
|
||||
for game in games {
|
||||
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user