dash-frontend: ViewTrait, refactoring, `download_file::View` (wip)

This commit is contained in:
Aleksander 2026-04-12 16:42:25 +02:00 committed by galister
parent 24e3099b7b
commit 2035d9ba76
16 changed files with 400 additions and 183 deletions

View File

@ -0,0 +1,11 @@
<layout>
<elements>
<div align_items="center" justify_content="center">
<div flex_direction="column" gap="8">
<label translation="DOWNLOADING_FILE" size="14"/>
<label id="label_target_path" />
<label id="label_progress"/>
</div>
</div>
</elements>
</layout>

View File

@ -142,6 +142,8 @@
"CREATION_DATE": "Creation date", "CREATION_DATE": "Creation date",
"DEBUG_INFO": "Debug info", "DEBUG_INFO": "Debug info",
"DISPLAY_BRIGHTNESS": "Display brightness", "DISPLAY_BRIGHTNESS": "Display brightness",
"DOWNLOADER": "Downloader",
"DOWNLOADING_FILE": "Downloading file...",
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:", "FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
"GAME_LAUNCHED": "Game launched", "GAME_LAUNCHED": "Game launched",
"GAME_LIST": { "GAME_LIST": {
@ -170,6 +172,7 @@
"REMOVE": "Remove", "REMOVE": "Remove",
"SETTINGS": "Settings", "SETTINGS": "Settings",
"SHOW": "Show", "SHOW": "Show",
"TARGET_PATH": "Target path",
"TERMINATE_PROCESS": "Terminate process", "TERMINATE_PROCESS": "Terminate process",
"VERSION": "Version", "VERSION": "Version",
"WIDTH": "Width" "WIDTH": "Width"

View File

@ -10,7 +10,7 @@ use crate::{
frontend::Frontend, frontend::Frontend,
tab::{Tab, TabType}, tab::{Tab, TabType},
util::steam_utils::SteamUtils, util::steam_utils::SteamUtils,
views::{game_list, running_games_list}, views::{ViewTrait, ViewUpdateParams, game_list, running_games_list},
}; };
pub struct TabGames<T> { pub struct TabGames<T> {
@ -19,7 +19,6 @@ pub struct TabGames<T> {
view_game_list: game_list::View, view_game_list: game_list::View,
view_running_games_list: running_games_list::View, view_running_games_list: running_games_list::View,
steam_utils: SteamUtils,
marker: PhantomData<T>, marker: PhantomData<T>,
} }
@ -29,9 +28,11 @@ impl<T> Tab<T> for TabGames<T> {
} }
fn update(&mut self, frontend: &mut Frontend<T>, time_ms: u32, _data: &mut T) -> anyhow::Result<()> { fn update(&mut self, frontend: &mut Frontend<T>, time_ms: u32, _data: &mut T) -> anyhow::Result<()> {
self self.view_game_list.update(&mut ViewUpdateParams {
.view_game_list layout: &mut frontend.layout,
.update(&mut frontend.layout, &mut self.steam_utils, &frontend.executor)?; executor: &mut frontend.executor,
})?;
self.view_running_games_list.update(&mut frontend.layout, time_ms)?; self.view_running_games_list.update(&mut frontend.layout, time_ms)?;
Ok(()) Ok(())
} }
@ -54,16 +55,17 @@ impl<T> TabGames<T> {
let game_list_parent = state.get_widget_id("game_list_parent")?; let game_list_parent = state.get_widget_id("game_list_parent")?;
let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?; let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?;
let mut steam_utils = SteamUtils::new()?;
let view_game_list = game_list::View::new(game_list::Params { let view_game_list = game_list::View::new(game_list::Params {
executor: frontend.executor.clone(), executor: frontend.executor.clone(),
frontend_tasks: frontend.tasks.clone(), frontend_tasks: frontend.tasks.clone(),
globals: globals.clone(), globals: globals.clone(),
layout: &mut frontend.layout, layout: &mut frontend.layout,
parent_id: game_list_parent, parent_id: game_list_parent,
steam_utils: &steam_utils,
})?; })?;
let mut steam_utils = SteamUtils::new()?;
let view_running_games_list = running_games_list::View::new(running_games_list::Params { let view_running_games_list = running_games_list::View::new(running_games_list::Params {
globals: globals.clone(), globals: globals.clone(),
layout: &mut frontend.layout, layout: &mut frontend.layout,
@ -77,7 +79,6 @@ impl<T> TabGames<T> {
view_game_list, view_game_list,
view_running_games_list, view_running_games_list,
marker: PhantomData, marker: PhantomData,
steam_utils,
}) })
} }
} }

View File

@ -19,13 +19,12 @@ use wgui::{
}, },
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult}, windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
}; };
use wlx_common::{ use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
async_executor::AsyncExecutor, config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode,
};
use crate::{ use crate::{
frontend::{Frontend, FrontendTask, FrontendTasks}, frontend::{Frontend, FrontendTask, FrontendTasks},
tab::{Tab, TabType, settings::macros::MacroParams}, tab::{Tab, TabType, settings::macros::MacroParams},
views::ViewUpdateParams,
}; };
mod macros; mod macros;
@ -86,13 +85,8 @@ struct SettingsMountParams<'a> {
parent_id: WidgetID, parent_id: WidgetID,
} }
struct SettingsUpdateParams<'a> {
layout: &'a mut Layout,
executor: &'a AsyncExecutor,
}
trait SettingsTab { trait SettingsTab {
fn update(&mut self, _par: SettingsUpdateParams) -> anyhow::Result<()> { fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
} }
@ -117,7 +111,7 @@ impl<T> Tab<T> for TabSettings<T> {
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> { fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
if let Some(tab) = &mut self.current_tab { if let Some(tab) = &mut self.current_tab {
tab.update(SettingsUpdateParams { tab.update(&mut ViewUpdateParams {
layout: &mut frontend.layout, layout: &mut frontend.layout,
executor: &frontend.executor, executor: &frontend.executor,
})?; })?;

View File

@ -3,7 +3,7 @@ use crate::{
SettingType, SettingsMountParams, SettingsTab, SettingType, SettingsMountParams, SettingsTab,
macros::{options_category, options_checkbox}, macros::{options_category, options_checkbox},
}, },
views::skymap_list, views::{ViewTrait, ViewUpdateParams, skymap_list},
}; };
pub struct State { pub struct State {
@ -11,8 +11,8 @@ pub struct State {
} }
impl SettingsTab for State { impl SettingsTab for State {
fn update(&mut self, par: super::SettingsUpdateParams) -> anyhow::Result<()> { fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.skymap_list.update(par.layout, par.executor)?; self.skymap_list.update(par)?;
Ok(()) Ok(())
} }
} }

View File

@ -16,7 +16,10 @@ use wgui::{
}; };
use wlx_common::config::GeneralConfig; use wlx_common::config::GeneralConfig;
use crate::frontend::{FrontendTask, FrontendTasks}; use crate::{
frontend::{FrontendTask, FrontendTasks},
views::{ViewTrait, ViewUpdateParams},
};
pub struct PopupManagerParams { pub struct PopupManagerParams {
pub parent_id: WidgetID, pub parent_id: WidgetID,
@ -45,13 +48,19 @@ pub struct PopupHandle {
state: Rc<RefCell<MountedPopupState>>, state: Rc<RefCell<MountedPopupState>>,
} }
struct PopupHolderState<ViewType> { struct PopupHolderState<ViewType>
where
ViewType: ViewTrait,
{
popup_handle: PopupHandle, popup_handle: PopupHandle,
view: Option<ViewType>, view: Option<ViewType>,
} }
// we can't use #[derive(Default)] due to the fact that ViewType can't be Default. // we can't use #[derive(Default)] due to the fact that ViewType can't be Default.
impl<ViewType> Default for PopupHolderState<ViewType> { impl<ViewType> Default for PopupHolderState<ViewType>
where
ViewType: ViewTrait,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
popup_handle: Default::default(), popup_handle: Default::default(),
@ -60,11 +69,17 @@ impl<ViewType> Default for PopupHolderState<ViewType> {
} }
} }
pub struct PopupHolder<ViewType> { pub struct PopupHolder<ViewType>
where
ViewType: ViewTrait,
{
state: Rc<RefCell<PopupHolderState<ViewType>>>, state: Rc<RefCell<PopupHolderState<ViewType>>>,
} }
impl<ViewType> Default for PopupHolder<ViewType> { impl<ViewType> Default for PopupHolder<ViewType>
where
ViewType: ViewTrait,
{
fn default() -> Self { fn default() -> Self {
Self { Self {
state: Rc::new(RefCell::new(PopupHolderState::default())), state: Rc::new(RefCell::new(PopupHolderState::default())),
@ -72,7 +87,10 @@ impl<ViewType> Default for PopupHolder<ViewType> {
} }
} }
impl<ViewType> PopupHolderState<ViewType> { impl<ViewType> PopupHolderState<ViewType>
where
ViewType: ViewTrait,
{
fn close(&mut self) { fn close(&mut self) {
self.view = None; self.view = None;
self.popup_handle.close(); self.popup_handle.close();
@ -80,7 +98,10 @@ impl<ViewType> PopupHolderState<ViewType> {
} }
// we can't derive(Clone) due to the fact that ViewType is non-cloneable // we can't derive(Clone) due to the fact that ViewType is non-cloneable
impl<ViewType> Clone for PopupHolder<ViewType> { impl<ViewType> Clone for PopupHolder<ViewType>
where
ViewType: ViewTrait,
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
state: self.state.clone(), state: self.state.clone(),
@ -88,9 +109,17 @@ impl<ViewType> Clone for PopupHolder<ViewType> {
} }
} }
impl<ViewType> PopupHolder<ViewType> { impl<ViewType> PopupHolder<ViewType>
pub fn close(&self) { where
self.state.borrow_mut().close(); ViewType: ViewTrait,
{
pub fn update(&self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut();
let Some(view) = &mut state.view else {
return Ok(());
};
view.update(par)
} }
pub fn set_view(&self, handle: PopupHandle, view: ViewType) { pub fn set_view(&self, handle: PopupHandle, view: ViewType) {
@ -131,15 +160,20 @@ impl<ViewType> PopupHolder<ViewType> {
Ok(()) Ok(())
} }
pub fn get_close_callback(&self) -> Box<dyn Fn()> pub fn get_close_callback(&self, layout: &Layout) -> Box<dyn FnOnce()>
where where
ViewType: 'static, ViewType: 'static,
{ {
let layout_tasks = layout.tasks.clone();
let weak_state = Rc::downgrade(&self.state); let weak_state = Rc::downgrade(&self.state);
Box::new(move || { Box::new(move || {
if let Some(state) = weak_state.upgrade() { // we can't borrow State here yet, dispatch it.
state.borrow_mut().close(); layout_tasks.push(LayoutTask::Dispatch(Box::new(move |_common| {
} if let Some(state) = weak_state.upgrade() {
state.borrow_mut().close();
}
Ok(())
})));
}) })
} }
} }

View File

@ -2,6 +2,7 @@ use keyvalues_parser::{Obj, Vdf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone)]
pub struct SteamUtils { pub struct SteamUtils {
steam_root: PathBuf, steam_root: PathBuf,
} }

View File

@ -17,6 +17,7 @@ use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, deskto
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks, SoundType}, frontend::{FrontendTask, FrontendTasks, SoundType},
util::popup_manager::{MountPopupOnceParams, PopupHolder}, util::popup_manager::{MountPopupOnceParams, PopupHolder},
views::{ViewTrait, ViewUpdateParams},
}; };
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)] #[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
@ -69,7 +70,7 @@ struct LaunchParams<'a, T> {
interface: &'a mut BoxDashInterface<T>, interface: &'a mut BoxDashInterface<T>,
auto_start: bool, auto_start: bool,
data: &'a mut T, data: &'a mut T,
on_launched: &'a dyn Fn(), on_launched: Option<Box<dyn FnOnce()>>,
} }
pub struct View { pub struct View {
@ -94,7 +95,7 @@ pub struct View {
auto_start: bool, auto_start: bool,
on_launched: Box<dyn Fn()>, on_launched: Option<Box<dyn FnOnce()>>,
} }
pub struct Params<'a> { pub struct Params<'a> {
@ -104,7 +105,13 @@ pub struct Params<'a> {
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub config: &'a GeneralConfig, pub config: &'a GeneralConfig,
pub frontend_tasks: &'a FrontendTasks, pub frontend_tasks: &'a FrontendTasks,
pub on_launched: Box<dyn Fn()>, pub on_launched: Box<dyn FnOnce()>,
}
impl ViewTrait for View {
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
Ok(())
}
} }
impl View { impl View {
@ -283,7 +290,7 @@ impl View {
entry: params.entry, entry: params.entry,
frontend_tasks: params.frontend_tasks.clone(), frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals.clone(), globals: params.globals.clone(),
on_launched: params.on_launched, on_launched: Some(params.on_launched),
}) })
} }
@ -319,7 +326,7 @@ impl View {
auto_start: self.auto_start, auto_start: self.auto_start,
interface, interface,
data, data,
on_launched: &self.on_launched, on_launched: self.on_launched.take(),
}); });
} }
@ -337,7 +344,7 @@ impl View {
)))); ))));
} }
fn launch<T>(params: LaunchParams<T>) -> anyhow::Result<()> { fn launch<T>(mut params: LaunchParams<T>) -> anyhow::Result<()> {
let mut env = Vec::<String>::new(); let mut env = Vec::<String>::new();
if params.compositor_mode == CompositorMode::Native { if params.compositor_mode == CompositorMode::Native {
@ -393,7 +400,9 @@ impl View {
params.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Launch)); params.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Launch));
(*params.on_launched)(); if let Some(on_launched) = params.on_launched.take() {
on_launched();
}
// we're done! // we're done!
Ok(()) Ok(())
@ -430,6 +439,7 @@ pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: D
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_raw_text(&entry.app_name), Translation::from_raw_text(&entry.app_name),
Box::new(move |data| { Box::new(move |data| {
let on_launched = popup.get_close_callback(data.layout);
let view = View::new(Params { let view = View::new(Params {
entry: entry.clone(), entry: entry.clone(),
globals: &globals, globals: &globals,
@ -437,11 +447,11 @@ pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: D
parent_id: data.id_content, parent_id: data.id_content,
frontend_tasks: &frontend_tasks, frontend_tasks: &frontend_tasks,
config: data.config, config: data.config,
on_launched: popup.get_close_callback(), on_launched,
})?; })?;
popup.set_view(data.handle, view); popup.set_view(data.handle, view);
Ok(popup.get_close_callback()) Ok(popup.get_close_callback(data.layout))
}), }),
))); )));
} }

View File

@ -0,0 +1,112 @@
use crate::{
frontend::{FrontendTask, FrontendTasks},
util::popup_manager::{MountPopupOnceParams, PopupHolder},
views::{ViewTrait, ViewUpdateParams},
};
use std::path::PathBuf;
use wgui::{
assets::AssetPath,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
task::Tasks,
widget::label::WidgetLabel,
};
use wlx_common::async_executor::AsyncExecutor;
pub struct Params<'a> {
pub globals: &'a WguiGlobals,
pub layout: &'a mut Layout,
pub executor: &'a AsyncExecutor,
pub parent_id: WidgetID,
pub target_path: PathBuf,
pub url: String,
pub on_close_request: Box<dyn FnOnce()>,
}
#[derive(Clone)]
enum Task {}
pub struct View {
id_parent: WidgetID,
globals: WguiGlobals,
tasks: Tasks<Task>,
executor: AsyncExecutor,
#[allow(dead_code)]
parser_state: ParserState,
}
impl ViewTrait for View {
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
for task in self.tasks.drain() {
match task {}
}
Ok(())
}
}
impl View {
pub fn new(par: Params) -> anyhow::Result<Self> {
let tasks = Tasks::<Task>::new();
let doc_params = ParseDocumentParams {
globals: par.globals.clone(),
path: AssetPath::BuiltIn("gui/view/download_file.xml"),
extra: Default::default(),
};
let mut parser_state = wgui::parser::parse_from_assets(&doc_params, par.layout, par.parent_id)?;
let str_target_path = par.globals.i18n().translate("TARGET_PATH");
{
let label_target_path = parser_state
.fetch_widget(&par.layout.state, "label_target_path")?
.widget;
label_target_path.cast::<WidgetLabel>()?.set_text(
&mut par.layout.common(),
Translation::from_raw_text_string(format!("{}: {}", str_target_path, par.target_path.display())),
);
}
Ok(Self {
id_parent: par.parent_id,
tasks,
globals: par.globals.clone(),
executor: par.executor.clone(),
parser_state,
})
}
}
pub fn mount_popup(
frontend_tasks: FrontendTasks,
executor: AsyncExecutor,
globals: WguiGlobals,
popup: PopupHolder<View>,
target_path: PathBuf,
url: String,
) {
frontend_tasks
.clone()
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_translation_key("DOWNLOADER"),
Box::new(move |data| {
let on_close_request = popup.get_close_callback(data.layout);
let view = View::new(Params {
globals: &globals,
layout: data.layout,
executor: &executor,
parent_id: data.id_content,
on_close_request,
target_path,
url,
})?;
popup.set_view(data.handle, view);
Ok(popup.get_close_callback(data.layout))
}),
)));
}

View File

@ -7,7 +7,7 @@ use crate::{
popup_manager::{MountPopupOnceParams, PopupHolder}, popup_manager::{MountPopupOnceParams, PopupHolder},
steam_utils::{self, AppID, AppManifest}, steam_utils::{self, AppID, AppManifest},
}, },
views::game_cover, views::{ViewTrait, ViewUpdateParams, game_cover},
}; };
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
@ -35,13 +35,14 @@ pub struct Params<'a> {
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub frontend_tasks: &'a FrontendTasks, pub frontend_tasks: &'a FrontendTasks,
pub on_launched: Box<dyn Fn()>, pub on_launched: Box<dyn FnOnce()>,
} }
pub struct View { pub struct View {
#[allow(dead_code)] #[allow(dead_code)]
state: ParserState, state: ParserState,
tasks: Tasks<Task>, tasks: Tasks<Task>,
on_launched: Box<dyn Fn()>, on_launched: Option<Box<dyn FnOnce()>>,
frontend_tasks: FrontendTasks, frontend_tasks: FrontendTasks,
game_cover_view_common: game_cover::ViewCommon, game_cover_view_common: game_cover::ViewCommon,
@ -49,6 +50,30 @@ pub struct View {
app_id: AppID, app_id: AppID,
} }
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
Task::FillAppDetails(details) => self.action_fill_app_details(&mut par.layout, details)?,
Task::Launch => self.action_launch(),
Task::SetCoverArt(cover_art) => {
let _ = self
.view_cover
.set_cover_art(&mut self.game_cover_view_common, &mut par.layout, &cover_art);
}
}
}
}
Ok(())
}
}
impl View { impl View {
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) { async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) {
let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else { let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else {
@ -105,7 +130,7 @@ impl View {
Ok(Self { Ok(Self {
state, state,
tasks, tasks,
on_launched: params.on_launched, on_launched: Some(params.on_launched),
frontend_tasks: params.frontend_tasks.clone(), frontend_tasks: params.frontend_tasks.clone(),
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()), game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
view_cover, view_cover,
@ -113,28 +138,6 @@ impl View {
}) })
} }
pub fn update(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
Task::FillAppDetails(details) => self.action_fill_app_details(layout, details)?,
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);
}
}
}
}
Ok(())
}
fn action_fill_app_details( fn action_fill_app_details(
&mut self, &mut self,
layout: &mut Layout, layout: &mut Layout,
@ -189,7 +192,9 @@ impl View {
} }
} }
(*self.on_launched)(); if let Some(on_launched) = self.on_launched.take() {
on_launched();
}
} }
} }
@ -205,6 +210,7 @@ pub fn mount_popup(
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_raw_text(&manifest.name), Translation::from_raw_text(&manifest.name),
Box::new(move |data| { Box::new(move |data| {
let on_launched = popup.get_close_callback(data.layout);
let view = View::new(Params { let view = View::new(Params {
manifest: manifest.clone(), manifest: manifest.clone(),
executor: executor.clone(), executor: executor.clone(),
@ -212,11 +218,11 @@ pub fn mount_popup(
layout: data.layout, layout: data.layout,
parent_id: data.id_content, parent_id: data.id_content,
frontend_tasks: &frontend_tasks, frontend_tasks: &frontend_tasks,
on_launched: popup.get_close_callback(), on_launched,
})?; })?;
popup.set_view(data.handle, view); popup.set_view(data.handle, view);
Ok(popup.get_close_callback()) Ok(popup.get_close_callback(data.layout))
}), }),
))); )));
} }

View File

@ -22,7 +22,7 @@ use crate::{
popup_manager::PopupHolder, popup_manager::PopupHolder,
steam_utils::{self, AppID, SteamUtils}, steam_utils::{self, AppID, SteamUtils},
}, },
views::{self, game_cover}, views::{self, ViewTrait, ViewUpdateParams, game_cover},
}; };
#[derive(Clone)] #[derive(Clone)]
@ -41,6 +41,7 @@ pub struct Params<'a> {
pub frontend_tasks: FrontendTasks, pub frontend_tasks: FrontendTasks,
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub steam_utils: &'a SteamUtils,
} }
const MAX_GAMES_PER_PAGE: u32 = 30; const MAX_GAMES_PER_PAGE: u32 = 30;
@ -64,6 +65,31 @@ pub struct View {
page_count: u32, page_count: u32,
id_label_page: WidgetID, id_label_page: WidgetID,
view_launcher: PopupHolder<views::game_launcher::View>, view_launcher: PopupHolder<views::game_launcher::View>,
steam_utils: SteamUtils,
}
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
Task::LoadManifests => self.load_manifests(),
Task::FillPage(page_idx) => self.fill_page(&mut par.layout, &mut par.executor, page_idx)?,
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(&mut par.layout, app_id, cover_art),
Task::PrevPage => self.page_prev(),
Task::NextPage => self.page_next(),
}
}
}
self.view_launcher.update(par)?;
Ok(())
}
} }
impl View { impl View {
@ -106,36 +132,9 @@ impl View {
page_count: 0, page_count: 0,
id_label_page, id_label_page,
view_launcher: Default::default(), view_launcher: Default::default(),
steam_utils: params.steam_utils.clone(),
}) })
} }
pub fn update(
&mut self,
layout: &mut Layout,
steam_utils: &mut SteamUtils,
executor: &AsyncExecutor,
) -> anyhow::Result<()> {
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
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::PrevPage => self.page_prev(),
Task::NextPage => self.page_next(),
}
}
}
self.view_launcher.with_view_res(|view| view.update(layout))?;
Ok(())
}
} }
fn fill_game_list( fn fill_game_list(
@ -178,8 +177,11 @@ fn fill_game_list(
} }
impl View { impl View {
fn load_manifests(&mut self, steam_utils: &mut SteamUtils) { fn load_manifests(&mut self) {
match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) { match self
.steam_utils
.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)
{
Ok(manifests) => { Ok(manifests) => {
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE; self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
self.all_manifests = manifests; self.all_manifests = manifests;

View File

@ -1,5 +1,8 @@
use wlx_common::async_executor::AsyncExecutor;
pub mod app_launcher; pub mod app_launcher;
pub mod audio_settings; pub mod audio_settings;
pub mod download_file;
pub mod game_cover; pub mod game_cover;
pub mod game_launcher; pub mod game_launcher;
pub mod game_list; pub mod game_list;
@ -8,3 +11,12 @@ pub mod remote_skymap_list;
pub mod running_games_list; pub mod running_games_list;
pub mod skymap_list; pub mod skymap_list;
pub mod skymap_list_cell; pub mod skymap_list_cell;
pub struct ViewUpdateParams<'a> {
pub layout: &'a mut wgui::layout::Layout,
pub executor: &'a AsyncExecutor,
}
pub trait ViewTrait {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()>;
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashMap, rc::Rc}; use std::{collections::HashMap, path::PathBuf, rc::Rc};
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks}, frontend::{FrontendTask, FrontendTasks},
@ -6,6 +6,7 @@ use crate::{
networking::{self, skymap_catalog::SkymapResolution}, networking::{self, skymap_catalog::SkymapResolution},
popup_manager::{MountPopupOnceParams, PopupHolder}, popup_manager::{MountPopupOnceParams, PopupHolder},
}, },
views::{self, ViewTrait, ViewUpdateParams},
}; };
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
@ -24,9 +25,10 @@ pub struct Params<'a> {
pub globals: &'a WguiGlobals, pub globals: &'a WguiGlobals,
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub executor: &'a AsyncExecutor, pub executor: &'a AsyncExecutor,
pub frontend_tasks: FrontendTasks,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub entry: networking::skymap_catalog::SkymapCatalogEntry, pub entry: networking::skymap_catalog::SkymapCatalogEntry,
pub on_close_request: Box<dyn Fn()>, pub on_close_request: Box<dyn FnOnce()>,
pub preview_image: Option<CustomGlyphData>, pub preview_image: Option<CustomGlyphData>,
} }
@ -38,12 +40,15 @@ enum Task {
pub struct View { pub struct View {
id_parent: WidgetID, id_parent: WidgetID,
entry: networking::skymap_catalog::SkymapCatalogEntry, entry: networking::skymap_catalog::SkymapCatalogEntry,
frontend_tasks: FrontendTasks,
globals: WguiGlobals, globals: WguiGlobals,
tasks: Tasks<Task>, tasks: Tasks<Task>,
executor: AsyncExecutor, executor: AsyncExecutor,
#[allow(dead_code)] #[allow(dead_code)]
parser_state: ParserState, parser_state: ParserState,
popup_download: PopupHolder<views::download_file::View>,
} }
fn mount_resolution_button( fn mount_resolution_button(
@ -62,6 +67,19 @@ fn mount_resolution_button(
Ok(()) Ok(())
} }
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
for task in self.tasks.drain() {
match task {
Task::ResolutionClicked(skymap_resolution) => {
self.run_download(&mut par.layout, skymap_resolution)?;
}
}
}
Ok(())
}
}
impl View { impl View {
pub fn new(par: Params) -> anyhow::Result<Self> { pub fn new(par: Params) -> anyhow::Result<Self> {
let tasks = Tasks::<Task>::new(); let tasks = Tasks::<Task>::new();
@ -150,15 +168,25 @@ impl View {
executor: par.executor.clone(), executor: par.executor.clone(),
entry: par.entry, entry: par.entry,
parser_state, parser_state,
frontend_tasks: par.frontend_tasks,
popup_download: Default::default(),
}) })
} }
pub fn update(&mut self, layout: &mut Layout) -> anyhow::Result<()> { fn run_download(&mut self, layout: &mut Layout, resolution: SkymapResolution) -> anyhow::Result<()> {
for task in self.tasks.drain() { let target_path = PathBuf::from(format!(""));
match task { let Some(url) = self.entry.files.get_url_from_res(resolution) else {
Task::ResolutionClicked(skymap_resolution) => todo!(), return Ok(());
} };
}
views::download_file::mount_popup(
self.frontend_tasks.clone(),
self.executor.clone(),
self.globals.clone(),
self.popup_download.clone(),
target_path,
url,
);
Ok(()) Ok(())
} }
} }
@ -176,18 +204,20 @@ pub fn mount_popup(
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_raw_text(&entry.name), Translation::from_raw_text(&entry.name),
Box::new(move |data| { Box::new(move |data| {
let on_close_request = popup.get_close_callback(data.layout);
let view = View::new(Params { let view = View::new(Params {
globals: &globals, globals: &globals,
layout: data.layout, layout: data.layout,
executor: &executor, executor: &executor,
parent_id: data.id_content, parent_id: data.id_content,
entry, entry,
on_close_request: popup.get_close_callback(), on_close_request,
preview_image, preview_image,
frontend_tasks: frontend_tasks.clone(),
})?; })?;
popup.set_view(data.handle, view); popup.set_view(data.handle, view);
Ok(popup.get_close_callback()) Ok(popup.get_close_callback(data.layout))
}), }),
))); )));
} }

View File

@ -21,7 +21,7 @@ use crate::{
popup_manager::{MountPopupOnceParams, PopupHolder}, popup_manager::{MountPopupOnceParams, PopupHolder},
wgui_simple, wgui_simple,
}, },
views, views::{self, ViewTrait, ViewUpdateParams},
}; };
pub struct Params<'a> { pub struct Params<'a> {
@ -29,7 +29,7 @@ pub struct Params<'a> {
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
pub executor: &'a AsyncExecutor, pub executor: &'a AsyncExecutor,
pub parent_id: WidgetID, pub parent_id: WidgetID,
pub on_close_request: Box<dyn Fn()>, pub on_close_request: Box<dyn FnOnce()>,
pub frontend_tasks: FrontendTasks, pub frontend_tasks: FrontendTasks,
} }
@ -58,6 +58,44 @@ pub struct View {
popup_remote_skymap_downloader: PopupHolder<views::remote_skymap_downloader::View>, popup_remote_skymap_downloader: PopupHolder<views::remote_skymap_downloader::View>,
} }
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.popup_remote_skymap_downloader.update(par)?;
for task in self.tasks.drain() {
match task {
Task::SetSkymapCatalog(skymap_catalog) => {
par.layout.remove_widget(self.id_loading);
match &*skymap_catalog {
Ok(skymap_catalog) => {
self.mount_catalog(par.layout, skymap_catalog)?;
}
Err(e) => wgui_simple::create_label_error(
par.layout,
self.id_parent,
format!("Failed to fetch skymap catalog: {:?}", e),
)?,
}
}
Task::SetSkymapPreview((skymap_uuid, glyph_data)) => {
if let Some(cell) = &mut self
.mounted_cells
.iter_mut()
.find(|cell| cell.skymap_uuid == skymap_uuid)
{
cell.view.set_image(par.layout, glyph_data)?;
}
}
Task::ShowRemoteSkymapDownloader(skymap_uuid) => {
let preview_image = self.get_image_preview(skymap_uuid);
self.show_remote_skymap_downloader(skymap_uuid, preview_image)?;
}
}
}
Ok(())
}
}
impl View { impl View {
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) { async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
let res = networking::skymap_catalog::request_catalog(&executor).await; let res = networking::skymap_catalog::request_catalog(&executor).await;
@ -176,44 +214,6 @@ impl View {
} }
None None
} }
pub fn update(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
self
.popup_remote_skymap_downloader
.with_view_res(|view| view.update(layout))?;
for task in self.tasks.drain() {
match task {
Task::SetSkymapCatalog(skymap_catalog) => {
layout.remove_widget(self.id_loading);
match &*skymap_catalog {
Ok(skymap_catalog) => {
self.mount_catalog(layout, skymap_catalog)?;
}
Err(e) => wgui_simple::create_label_error(
layout,
self.id_parent,
format!("Failed to fetch skymap catalog: {:?}", e),
)?,
}
}
Task::SetSkymapPreview((skymap_uuid, glyph_data)) => {
if let Some(cell) = &mut self
.mounted_cells
.iter_mut()
.find(|cell| cell.skymap_uuid == skymap_uuid)
{
cell.view.set_image(layout, glyph_data)?;
}
}
Task::ShowRemoteSkymapDownloader(skymap_uuid) => {
let preview_image = self.get_image_preview(skymap_uuid);
self.show_remote_skymap_downloader(skymap_uuid, preview_image)?;
}
}
}
Ok(())
}
} }
pub fn mount_popup( pub fn mount_popup(
@ -227,17 +227,18 @@ pub fn mount_popup(
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_translation_key("APP_SETTINGS.DOWNLOAD_SKYMAPS"), Translation::from_translation_key("APP_SETTINGS.DOWNLOAD_SKYMAPS"),
Box::new(move |data| { Box::new(move |data| {
let on_close_request = popup.get_close_callback(data.layout);
let view = View::new(Params { let view = View::new(Params {
globals: &globals, globals: &globals,
layout: data.layout, layout: data.layout,
executor: &executor, executor: &executor,
parent_id: data.id_content, parent_id: data.id_content,
on_close_request: popup.get_close_callback(), on_close_request,
frontend_tasks, frontend_tasks,
})?; })?;
popup.set_view(data.handle, view); popup.set_view(data.handle, view);
Ok(popup.get_close_callback()) Ok(popup.get_close_callback(data.layout))
}), }),
))); )));
} }

View File

@ -12,7 +12,7 @@ use wlx_common::{async_executor::AsyncExecutor, config_io};
use crate::{ use crate::{
frontend::FrontendTasks, frontend::FrontendTasks,
util::{popup_manager::PopupHolder, wgui_simple}, util::{popup_manager::PopupHolder, wgui_simple},
views, views::{self, ViewTrait, ViewUpdateParams},
}; };
#[derive(Clone)] #[derive(Clone)]
@ -38,6 +38,31 @@ pub struct View {
popup_remote_skymap_list: PopupHolder<views::remote_skymap_list::View>, popup_remote_skymap_list: PopupHolder<views::remote_skymap_list::View>,
} }
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.popup_remote_skymap_list.update(par)?;
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
Task::DownloadSkymaps => {
self.download_skymaps(&par.executor)?;
}
Task::Refresh => {
self.refresh(&mut par.layout)?;
}
}
}
}
Ok(())
}
}
impl View { impl View {
pub fn new(params: Params) -> anyhow::Result<Self> { pub fn new(params: Params) -> anyhow::Result<Self> {
let doc_params = &ParseDocumentParams { let doc_params = &ParseDocumentParams {
@ -72,31 +97,6 @@ impl View {
}) })
} }
pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
self
.popup_remote_skymap_list
.with_view_res(|view| view.update(layout))?;
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
break;
}
for task in tasks {
match task {
Task::DownloadSkymaps => {
self.download_skymaps(executor)?;
}
Task::Refresh => {
self.refresh(layout)?;
}
}
}
}
Ok(())
}
fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> { fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> {
views::remote_skymap_list::mount_popup( views::remote_skymap_list::mount_popup(
self.frontend_tasks.clone(), self.frontend_tasks.clone(),

View File

@ -461,7 +461,7 @@ fn handle_mouse_motion(
{ {
if !swipe_manager.is_current_swipe_empty() { if !swipe_manager.is_current_swipe_empty() {
match &key.button_state { match &key.button_state {
KeyButtonData::Key { vk, pressed } => { KeyButtonData::Key { vk: _, pressed: _ } => {
if let Some(pos) = within_key_pos { if let Some(pos) = within_key_pos {
// check because mouse motion can trigger despite hover being false // check because mouse motion can trigger despite hover being false
if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 { if pos.x >= 0.0 && pos.x <= 1.0 && pos.y >= 0.0 && pos.y <= 1.0 {