diff --git a/dash-frontend/assets/gui/view/skymap_list_cell.xml b/dash-frontend/assets/gui/view/skymap_list_cell.xml
new file mode 100644
index 00000000..f2289aba
--- /dev/null
+++ b/dash-frontend/assets/gui/view/skymap_list_cell.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index 122d8206..27822267 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -155,6 +155,7 @@
"HIDE": "Hide",
"HOME_SCREEN": "Home",
"MONADO_RUNTIME": "Monado runtime",
+ "LOADING": "Loading...",
"POPUP_ADD_DISPLAY": {
"RESOLUTION": "Resolution"
},
diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs
index 26a1970c..d924d39b 100644
--- a/dash-frontend/src/frontend.rs
+++ b/dash-frontend/src/frontend.rs
@@ -28,7 +28,7 @@ use crate::{
assets,
tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
util::{
- popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
+ popup_manager::{MountPopupOnceParams, MountPopupParams, PopupManager, PopupManagerParams},
toast_manager::ToastManager,
},
views,
@@ -102,6 +102,7 @@ pub enum FrontendTask {
RefreshClock,
RefreshBackground,
MountPopup(MountPopupParams),
+ MountPopupOnce(MountPopupOnceParams),
RefreshPopupManager,
ShowAudioSettings,
UpdateAudioSettingsView,
@@ -310,13 +311,18 @@ impl
Frontend {
fn mount_popup(&mut self, params: MountPopupParams, data: &mut T) -> anyhow::Result<()> {
let config = self.interface.general_config(data);
- self.popup_manager.mount_popup(
- self.globals.clone(),
- &mut self.layout,
- self.tasks.clone(),
- params,
- config,
- )?;
+ self
+ .popup_manager
+ .mount_popup(&self.globals, &mut self.layout, &self.tasks, params, config)?;
+ Ok(())
+ }
+
+ fn mount_popup_once(&mut self, params: MountPopupOnceParams, data: &mut T) -> anyhow::Result<()> {
+ let config = self.interface.general_config(data);
+
+ self
+ .popup_manager
+ .mount_popup_once(&self.globals, &mut self.layout, &self.tasks, params, config)?;
Ok(())
}
@@ -357,6 +363,7 @@ impl Frontend {
FrontendTask::RefreshClock => self.update_time(params.data)?,
FrontendTask::RefreshBackground => self.update_background(params.data)?,
FrontendTask::MountPopup(popup_params) => self.mount_popup(popup_params, params.data)?,
+ FrontendTask::MountPopupOnce(popup_params) => self.mount_popup_once(popup_params, params.data)?,
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs
index 7e17ff39..c735c0e4 100644
--- a/dash-frontend/src/tab/apps.rs
+++ b/dash-frontend/src/tab/apps.rs
@@ -9,7 +9,6 @@ use wgui::{
assets::AssetPath,
components::button::{ButtonClickCallback, ComponentButton},
globals::WguiGlobals,
- i18n::Translation,
layout::{WidgetID, WidgetPair},
parser::{Fetchable, ParseDocumentParams, ParserState},
task::Tasks,
@@ -17,18 +16,19 @@ use wgui::{
use wlx_common::desktop_finder::DesktopEntry;
use crate::{
- frontend::{Frontend, FrontendTask, FrontendTasks},
+ frontend::{Frontend, FrontendTasks},
tab::{Tab, TabType},
- util::popup_manager::{MountPopupParams, PopupHandle},
- views::{self, app_launcher},
+ util::popup_manager::PopupHolder,
+ views::{self},
};
+#[derive(Clone)]
enum Task {
CloseLauncher,
}
struct State {
- view_launcher: Option<(PopupHandle, views::app_launcher::View)>,
+ view_launcher: Option>,
}
pub struct TabApps {
@@ -82,37 +82,16 @@ fn on_app_click(
tasks: Tasks,
) -> ButtonClickCallback {
Rc::new(move |_common, _evt| {
- frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
- title: Translation::from_raw_text(&entry.app_name),
- on_content: {
- // this is awful
+ views::app_launcher::mount_popup(
+ frontend_tasks.clone(),
+ globals.clone(),
+ entry.clone(),
+ tasks.make_callback_box(Task::CloseLauncher),
+ Box::new({
let state = state.clone();
- let entry = entry.clone();
- let globals = globals.clone();
- let frontend_tasks = frontend_tasks.clone();
- let tasks = tasks.clone();
-
- Rc::new(move |data| {
- let on_launched = {
- let tasks = tasks.clone();
- Box::new(move || tasks.push(Task::CloseLauncher))
- };
-
- let view = app_launcher::View::new(app_launcher::Params {
- entry: entry.clone(),
- globals: &globals,
- layout: data.layout,
- parent_id: data.id_content,
- frontend_tasks: &frontend_tasks,
- config: data.config,
- on_launched,
- })?;
-
- state.borrow_mut().view_launcher = Some((data.handle, view));
- Ok(())
- })
- },
- }));
+ move |popup| state.borrow_mut().view_launcher = Some(popup)
+ }),
+ );
Ok(())
})
}
diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs
index 3194e11e..cf3ca617 100644
--- a/dash-frontend/src/tab/settings/mod.rs
+++ b/dash-frontend/src/tab/settings/mod.rs
@@ -25,7 +25,7 @@ use wlx_common::{
};
use crate::{
- frontend::{Frontend, FrontendTask},
+ frontend::{Frontend, FrontendTask, FrontendTasks},
tab::{Tab, TabType, settings::macros::MacroParams},
};
@@ -83,6 +83,7 @@ enum Task {
struct SettingsMountParams<'a> {
mp: &'a mut MacroParams<'a>,
globals: &'a WguiGlobals,
+ frontend_tasks: &'a FrontendTasks,
parent_id: WidgetID,
}
@@ -107,6 +108,7 @@ pub struct TabSettings {
tasks: Tasks,
marker: PhantomData,
+ frontend_tasks: FrontendTasks,
}
impl Tab for TabSettings {
@@ -550,6 +552,7 @@ impl TabSettings {
mp: &mut mp,
globals: &globals,
parent_id: root,
+ frontend_tasks: &self.frontend_tasks,
};
match name {
@@ -611,6 +614,7 @@ impl TabSettings {
marker: PhantomData,
context_menu: ContextMenu::default(),
current_tab: None,
+ frontend_tasks: frontend.tasks.clone(),
})
}
}
diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs
index 3738f1aa..5e7dda79 100644
--- a/dash-frontend/src/tab/settings/tab_skybox.rs
+++ b/dash-frontend/src/tab/settings/tab_skybox.rs
@@ -27,6 +27,7 @@ impl State {
globals: par.globals.clone(),
layout: par.mp.layout,
parent_id: c,
+ frontend_tasks: par.frontend_tasks,
})?;
Ok(Self { skymap_list })
diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs
index 498c12f9..00c0f849 100644
--- a/dash-frontend/src/util/networking/skymap_catalog.rs
+++ b/dash-frontend/src/util/networking/skymap_catalog.rs
@@ -4,6 +4,8 @@ use wlx_common::async_executor::AsyncExecutor;
use crate::util::{http_client, networking};
+pub type SkymapUuid = String;
+
#[derive(Clone, Debug, Deserialize)]
pub struct SkymapCatalogEntryFiles {
pub size_16k: Option, // "my_skymap_16k.png"
@@ -15,10 +17,10 @@ pub struct SkymapCatalogEntryFiles {
#[derive(Clone, Debug, Deserialize)]
pub struct SkymapCatalogEntry {
- pub uuid: String,
+ pub uuid: SkymapUuid,
pub created_at: String,
pub modified_at: String,
- pub entry_version: u32,
+ pub version: u32,
pub name: String,
pub description: String,
pub author: String,
@@ -32,11 +34,25 @@ pub struct SkymapCatalog {
pub entries: Vec,
}
+impl SkymapCatalog {
+ fn validate(&self) -> anyhow::Result<()> {
+ if self.version != 1 {
+ anyhow::bail!("Unsupported version");
+ }
+
+ if self.r#type != "wayvr_skymaps" {
+ anyhow::bail!("Unsupported type");
+ }
+ Ok(())
+ }
+}
+
pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result {
log::info!("Fetching skymap list");
let res = http_client::get(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
let catalog = res.as_json::()?;
+ catalog.validate()?;
Ok(catalog)
}
diff --git a/dash-frontend/src/util/popup_manager.rs b/dash-frontend/src/util/popup_manager.rs
index ff46f515..6f61b947 100644
--- a/dash-frontend/src/util/popup_manager.rs
+++ b/dash-frontend/src/util/popup_manager.rs
@@ -43,6 +43,8 @@ pub struct PopupHandle {
state: Rc>,
}
+pub type PopupHolder = (PopupHandle, ViewType);
+
impl PopupHandle {
pub fn close(&self) {
self.state.borrow_mut().mounted_popup = None; // Drop will be called
@@ -67,6 +69,23 @@ pub struct MountPopupParams {
pub on_content: Rc anyhow::Result<()>>,
}
+// we need to implement Clone here, but the underlying function can be called only once.
+// on_content will be cleared after the first call
+#[derive(Clone)]
+pub struct MountPopupOnceParams {
+ title: Translation,
+ on_content: Rc anyhow::Result<()>>>>>,
+}
+
+impl MountPopupOnceParams {
+ pub fn new(title: Translation, on_content: Box anyhow::Result<()>>) -> Self {
+ Self {
+ title,
+ on_content: Rc::new(RefCell::new(Some(on_content))),
+ }
+ }
+}
+
impl Drop for MountedPopup {
fn drop(&mut self) {
self.layout_tasks.push(LayoutTask::RemoveWidget(self.id_root));
@@ -116,16 +135,13 @@ impl PopupManager {
state.refresh_stack(alterables);
}
- /// Mount a new popup on top of the existing popup stack.
- /// Only the topmost popup is visible.
- pub fn mount_popup(
- &mut self,
- globals: WguiGlobals,
+ fn mount_popup_prepare(
+ &self,
+ globals: &WguiGlobals,
layout: &mut Layout,
- frontend_tasks: FrontendTasks,
- params: MountPopupParams,
- config: &GeneralConfig,
- ) -> anyhow::Result<()> {
+ frontend_tasks: &FrontendTasks,
+ popup_title: &Translation,
+ ) -> anyhow::Result<(PopupHandle, WidgetID /* content widget ID */)> {
let doc_params = &ParseDocumentParams {
globals: globals.clone(),
path: AssetPath::BuiltIn("gui/view/popup_window.xml"),
@@ -138,7 +154,7 @@ impl PopupManager {
{
let mut label_title = state.fetch_widget_as::(&layout.state, "popup_title")?;
- label_title.set_text_simple(&mut globals.get(), params.title);
+ label_title.set_text_simple(&mut globals.get(), popup_title.clone());
}
let but_back = state.fetch_component_as::("but_back")?;
@@ -172,6 +188,48 @@ impl PopupManager {
});
frontend_tasks.push(FrontendTask::RefreshPopupManager);
+ Ok((popup_handle, id_content))
+ }
+
+ /// Mount a new popup on top of the existing popup stack (non-cloneable version).
+ /// Only the topmost popup is visible.
+ pub fn mount_popup_once(
+ &mut self,
+ globals: &WguiGlobals,
+ layout: &mut Layout,
+ frontend_tasks: &FrontendTasks,
+ params: MountPopupOnceParams,
+ config: &GeneralConfig,
+ ) -> anyhow::Result<()> {
+ let mut func = params.on_content.borrow_mut();
+ let Some(on_content_func) = func.take() else {
+ anyhow::bail!("mount_popup_once called more than once");
+ };
+
+ let (popup_handle, id_content) = self.mount_popup_prepare(globals, layout, frontend_tasks, ¶ms.title)?;
+
+ // mount user-set popup content
+ on_content_func(PopupContentFuncData {
+ layout,
+ handle: popup_handle.clone(),
+ id_content,
+ config,
+ })?;
+
+ Ok(())
+ }
+
+ /// Mount a new popup on top of the existing popup stack.
+ /// Only the topmost popup is visible.
+ pub fn mount_popup(
+ &mut self,
+ globals: &WguiGlobals,
+ layout: &mut Layout,
+ frontend_tasks: &FrontendTasks,
+ params: MountPopupParams,
+ config: &GeneralConfig,
+ ) -> anyhow::Result<()> {
+ let (popup_handle, id_content) = self.mount_popup_prepare(globals, layout, frontend_tasks, ¶ms.title)?;
// mount user-set popup content
(*params.on_content)(PopupContentFuncData {
diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs
index 44dad5ee..6dbdcab9 100644
--- a/dash-frontend/src/util/wgui_simple.rs
+++ b/dash-frontend/src/util/wgui_simple.rs
@@ -1,11 +1,18 @@
+use glam::Mat4;
use wgui::{
+ animation::{Animation, AnimationEasing},
+ assets::AssetPath,
+ drawing,
i18n::Translation,
- layout::{Layout, WidgetID},
- renderer_vk::text::TextStyle,
+ layout::{Layout, LayoutTask, WidgetID},
+ parser::{Fetchable, ParseDocumentParams},
+ renderer_vk::{
+ text::{FontWeight, TextStyle},
+ util::centered_matrix,
+ },
widget::label::{WidgetLabel, WidgetLabelParams},
};
-#[allow(dead_code)]
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
let label = WidgetLabel::create(
&mut layout.state,
@@ -22,3 +29,73 @@ pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation)
Ok(())
}
+
+pub fn create_label_error(layout: &mut Layout, parent: WidgetID, content: String) -> anyhow::Result<()> {
+ let label = WidgetLabel::create(
+ &mut layout.state,
+ WidgetLabelParams {
+ content: Translation::from_raw_text_string(content),
+ style: TextStyle {
+ wrap: true,
+ color: Some(drawing::Color::new(1.0, 0.5, 0.0, 1.0)),
+ weight: Some(FontWeight::Bold),
+ ..Default::default()
+ },
+ },
+ );
+
+ layout.add_child(parent, label, Default::default())?;
+
+ Ok(())
+}
+
+pub struct CreateLoadingParams<'a> {
+ pub layout: &'a mut Layout,
+ pub parent_id: WidgetID,
+ pub with_text: bool,
+}
+
+pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result {
+ let doc_params = ParseDocumentParams {
+ globals: par.layout.state.globals.clone(),
+ path: AssetPath::BuiltIn("gui/t_loading.xml"),
+ extra: Default::default(),
+ };
+
+ let mut parser_state = wgui::parser::parse_from_assets(&doc_params, par.layout, par.parent_id)?;
+
+ let data = parser_state.realize_template(
+ &doc_params,
+ if par.with_text {
+ "LoadingWithText"
+ } else {
+ "LoadingWithoutText"
+ },
+ par.layout,
+ par.parent_id,
+ Default::default(),
+ )?;
+
+ let id_root = data.get_widget_id("root")?;
+ let id_sprite_loading = data.get_widget_id("sprite_loading")?;
+
+ par.layout.animations.add(Animation::new(
+ id_sprite_loading,
+ 60 * 30, /* spin it for 30 seconds at most */
+ AnimationEasing::Linear,
+ Box::new(move |common, data| {
+ // spin it
+ data.data.transform = centered_matrix(data.widget_boundary.size, &Mat4::from_rotation_z(data.pos * 400.0));
+ if data.pos == 1.0 {
+ // remove the spinner, do not waste energy
+ common
+ .alterables
+ .tasks
+ .push(LayoutTask::RemoveWidget(id_sprite_loading));
+ }
+ common.alterables.mark_redraw();
+ }),
+ ));
+
+ Ok(id_root)
+}
diff --git a/dash-frontend/src/views/app_launcher.rs b/dash-frontend/src/views/app_launcher.rs
index 76c5b524..2d419962 100644
--- a/dash-frontend/src/views/app_launcher.rs
+++ b/dash-frontend/src/views/app_launcher.rs
@@ -14,7 +14,10 @@ use wgui::{
};
use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
-use crate::frontend::{FrontendTask, FrontendTasks, SoundType};
+use crate::{
+ frontend::{FrontendTask, FrontendTasks, SoundType},
+ util::popup_manager::{MountPopupOnceParams, PopupHolder},
+};
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum PosMode {
@@ -420,3 +423,31 @@ impl View {
[width as u32, height as u32]
}
}
+
+pub fn mount_popup(
+ frontend_tasks: FrontendTasks,
+ globals: WguiGlobals,
+ entry: DesktopEntry,
+ on_close_request: Box,
+ set_holder: Box)>,
+) {
+ frontend_tasks
+ .clone()
+ .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
+ Translation::from_raw_text(&entry.app_name),
+ Box::new(move |data| {
+ let view = View::new(Params {
+ entry: entry.clone(),
+ globals: &globals,
+ layout: data.layout,
+ parent_id: data.id_content,
+ frontend_tasks: &frontend_tasks,
+ config: data.config,
+ on_launched: on_close_request,
+ })?;
+
+ set_holder((data.handle, view));
+ Ok(())
+ }),
+ )));
+}
diff --git a/dash-frontend/src/views/download_skymaps.rs b/dash-frontend/src/views/download_skymaps.rs
new file mode 100644
index 00000000..066ef46b
--- /dev/null
+++ b/dash-frontend/src/views/download_skymaps.rs
@@ -0,0 +1,140 @@
+use wgui::{
+ assets::AssetPath,
+ globals::WguiGlobals,
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams},
+ task::Tasks,
+};
+use wlx_common::async_executor::AsyncExecutor;
+
+use crate::{
+ frontend::{FrontendTask, FrontendTasks},
+ util::{
+ networking::{self},
+ popup_manager::{MountPopupOnceParams, PopupHolder},
+ wgui_simple,
+ },
+ views,
+};
+
+pub struct Params<'a> {
+ pub globals: &'a WguiGlobals,
+ pub layout: &'a mut Layout,
+ pub executor: &'a AsyncExecutor,
+ pub parent_id: WidgetID,
+ pub on_close_request: Box,
+}
+
+enum Task {
+ SetSkymapCatalog(anyhow::Result),
+}
+
+pub struct View {
+ id_parent: WidgetID,
+ id_list: WidgetID,
+ id_loading: WidgetID,
+ globals: WguiGlobals,
+ tasks: Tasks,
+ mounted_cells: Vec,
+}
+
+impl View {
+ async fn skymap_catalog_request_wrapper(tasks: Tasks, executor: AsyncExecutor) {
+ let res = networking::skymap_catalog::request_catalog(&executor).await;
+ tasks.push(Task::SetSkymapCatalog(res))
+ }
+
+ pub fn new(par: Params) -> anyhow::Result {
+ let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
+ layout: par.layout,
+ parent_id: par.parent_id,
+ with_text: true,
+ })?;
+ let tasks = Tasks::::new();
+ let fut = View::skymap_catalog_request_wrapper(tasks.clone(), par.executor.clone());
+ par.executor.spawn(fut).detach();
+ Ok(Self {
+ id_parent: par.parent_id,
+ id_list: WidgetID::default(),
+ id_loading,
+ tasks,
+ globals: par.globals.clone(),
+ mounted_cells: Vec::new(),
+ })
+ }
+
+ fn mount_catalog(
+ &mut self,
+ layout: &mut Layout,
+ catalog: networking::skymap_catalog::SkymapCatalog,
+ ) -> anyhow::Result<()> {
+ let doc_params = &ParseDocumentParams {
+ globals: self.globals.clone(),
+ path: AssetPath::BuiltIn("gui/view/download_skymaps.xml"),
+ extra: Default::default(),
+ };
+
+ let parser_state = wgui::parser::parse_from_assets(&doc_params, layout, self.id_parent)?;
+
+ let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
+
+ for entry in catalog.entries {
+ let view_cell = views::skymap_list_cell::View::new(views::skymap_list_cell::Params {
+ id_parent: id_list,
+ layout,
+ entry,
+ })?;
+ self.mounted_cells.push(view_cell);
+ }
+
+ Ok(())
+ }
+
+ pub fn update(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
+ 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),
+ )?,
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+pub fn mount_popup(
+ frontend_tasks: FrontendTasks,
+ executor: AsyncExecutor,
+ globals: WguiGlobals,
+ on_close_request: Box,
+ set_holder: Box)>,
+) {
+ frontend_tasks
+ .clone()
+ .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
+ Translation::from_translation_key("APP_SETTINGS.DOWNLOAD_SKYMAPS"),
+ Box::new(move |data| {
+ let view = View::new(Params {
+ globals: &globals,
+ layout: data.layout,
+ executor: &executor,
+ parent_id: data.id_content,
+ on_close_request,
+ })?;
+
+ set_holder((data.handle, view));
+ Ok(())
+ }),
+ )));
+}
diff --git a/dash-frontend/src/views/game_cover.rs b/dash-frontend/src/views/game_cover.rs
index 6adc4110..5258b041 100644
--- a/dash-frontend/src/views/game_cover.rs
+++ b/dash-frontend/src/views/game_cover.rs
@@ -30,6 +30,7 @@ use wlx_common::async_executor::AsyncExecutor;
use crate::util::{
cached_fetcher::{self, CoverArt},
steam_utils::{self, AppID},
+ wgui_simple,
};
pub struct ViewCommon {
@@ -48,6 +49,7 @@ pub struct Params<'a, 'b> {
pub struct View {
pub button: Rc,
id_image_parent: WidgetID,
+ id_loading: WidgetID,
app_name: String,
app_id: AppID,
}
@@ -143,6 +145,8 @@ impl View {
layout: &mut Layout,
cover_art: &CoverArt,
) -> anyhow::Result<()> {
+ layout.remove_widget(self.id_loading);
+
if cover_art.compressed_image_data.is_empty() {
// mount placeholder
let img = view_common.get_placeholder_image()?.clone();
@@ -271,6 +275,12 @@ impl View {
rect_gradient_style(taffy::AlignSelf::End, 0.05),
)?;
+ let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
+ layout: params.ess.layout,
+ parent_id: image_parent.id,
+ with_text: false,
+ })?;
+
// request cover image data from the internet or disk cache
params
.executor
@@ -286,6 +296,7 @@ impl View {
id_image_parent: image_parent.id,
app_name: params.manifest.name.clone(),
app_id: params.manifest.app_id.clone(),
+ id_loading,
})
}
}
diff --git a/dash-frontend/src/views/game_launcher.rs b/dash-frontend/src/views/game_launcher.rs
index 03d26d77..44959465 100644
--- a/dash-frontend/src/views/game_launcher.rs
+++ b/dash-frontend/src/views/game_launcher.rs
@@ -4,6 +4,7 @@ use crate::{
frontend::{FrontendTask, FrontendTasks, SoundType},
util::{
cached_fetcher::{self, CoverArt},
+ popup_manager::{MountPopupOnceParams, PopupHolder},
steam_utils::{self, AppID, AppManifest},
},
views::game_cover,
@@ -193,3 +194,32 @@ impl View {
(*self.on_launched)();
}
}
+
+pub fn mount_popup(
+ frontend_tasks: FrontendTasks,
+ executor: AsyncExecutor,
+ globals: WguiGlobals,
+ manifest: AppManifest,
+ on_close_request: Box,
+ set_holder: Box)>,
+) {
+ frontend_tasks
+ .clone()
+ .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
+ Translation::from_raw_text(&manifest.name),
+ Box::new(move |data| {
+ let view = View::new(Params {
+ manifest: manifest.clone(),
+ executor: executor.clone(),
+ globals: &globals,
+ layout: data.layout,
+ parent_id: data.id_content,
+ frontend_tasks: &frontend_tasks,
+ on_launched: on_close_request,
+ })?;
+
+ set_holder((data.handle, view));
+ Ok(())
+ }),
+ )));
+}
diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs
index 5de3cf64..cd1acfdd 100644
--- a/dash-frontend/src/views/game_list.rs
+++ b/dash-frontend/src/views/game_list.rs
@@ -16,13 +16,13 @@ use wgui::{
use wlx_common::async_executor::AsyncExecutor;
use crate::{
- frontend::{FrontendTask, FrontendTasks},
+ frontend::FrontendTasks,
util::{
cached_fetcher::CoverArt,
- popup_manager::{MountPopupParams, PopupHandle},
+ popup_manager::PopupHolder,
steam_utils::{self, AppID, SteamUtils},
},
- views::{self, game_cover, game_launcher},
+ views::{self, game_cover},
};
#[derive(Clone)]
@@ -51,7 +51,7 @@ pub struct GameCoverCell {
}
struct State {
- view_launcher: Option<(PopupHandle, views::game_launcher::View)>,
+ view_launcher: Option>,
}
pub struct View {
@@ -283,36 +283,17 @@ impl View {
}
fn action_app_manifest_clicked(&mut self, manifest: steam_utils::AppManifest) -> anyhow::Result<()> {
- self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
- title: Translation::from_raw_text(&manifest.name),
- on_content: {
+ views::game_launcher::mount_popup(
+ self.frontend_tasks.clone(),
+ self.executor.clone(),
+ self.globals.clone(),
+ manifest,
+ self.tasks.make_callback_box(Task::CloseLauncher),
+ Box::new({
let state = self.state.clone();
- let tasks = self.tasks.clone();
- let executor = self.executor.clone();
- let globals = self.globals.clone();
- let frontend_tasks = self.frontend_tasks.clone();
-
- Rc::new(move |data| {
- let on_launched = {
- let tasks = tasks.clone();
- Box::new(move || tasks.push(Task::CloseLauncher))
- };
-
- let view = game_launcher::View::new(game_launcher::Params {
- manifest: manifest.clone(),
- executor: executor.clone(),
- globals: &globals,
- layout: data.layout,
- parent_id: data.id_content,
- frontend_tasks: &frontend_tasks,
- on_launched,
- })?;
-
- state.borrow_mut().view_launcher = Some((data.handle, view));
- Ok(())
- })
- },
- }));
+ move |popup| state.borrow_mut().view_launcher = Some(popup)
+ }),
+ );
Ok(())
}
diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs
index 2f832208..ecf9e5f7 100644
--- a/dash-frontend/src/views/mod.rs
+++ b/dash-frontend/src/views/mod.rs
@@ -1,7 +1,9 @@
pub mod app_launcher;
pub mod audio_settings;
+pub mod download_skymaps;
pub mod game_cover;
pub mod game_launcher;
pub mod game_list;
pub mod running_games_list;
pub mod skymap_list;
+pub mod skymap_list_cell;
diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs
index 9a968f75..b712a1ea 100644
--- a/dash-frontend/src/views/skymap_list.rs
+++ b/dash-frontend/src/views/skymap_list.rs
@@ -1,3 +1,5 @@
+use std::{cell::RefCell, rc::Rc};
+
use wgui::{
assets::AssetPath,
components::button::ComponentButton,
@@ -9,12 +11,16 @@ use wgui::{
};
use wlx_common::{async_executor::AsyncExecutor, config_io};
-use crate::util::{networking, wgui_simple};
+use crate::{
+ frontend::FrontendTasks,
+ util::{popup_manager::PopupHolder, wgui_simple},
+ views,
+};
#[derive(Clone)]
enum Task {
DownloadSkymaps,
- SetSkymapCatalog(Result),
+ ClosePopupDownloadSkymaps,
Refresh,
}
@@ -22,6 +28,11 @@ pub struct Params<'a> {
pub globals: WguiGlobals,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
+ pub frontend_tasks: &'a FrontendTasks,
+}
+
+pub struct State {
+ popup_download_skymaps: Option>,
}
pub struct View {
@@ -29,6 +40,9 @@ pub struct View {
parser_state: ParserState,
tasks: Tasks,
list_parent: WidgetID,
+ frontend_tasks: FrontendTasks,
+ globals: WguiGlobals,
+ state: Rc>,
}
impl View {
@@ -55,14 +69,28 @@ impl View {
Task::Refresh,
);
+ let state = Rc::new(RefCell::new(State {
+ popup_download_skymaps: None,
+ }));
+
Ok(Self {
parser_state,
tasks,
list_parent,
+ frontend_tasks: params.frontend_tasks.clone(),
+ state,
+ globals: params.globals.clone(),
})
}
pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
+ {
+ let mut state = self.state.borrow_mut();
+ if let Some(popup) = &mut state.popup_download_skymaps {
+ popup.1.update(layout)?;
+ }
+ }
+
loop {
let tasks = self.tasks.drain();
if tasks.is_empty() {
@@ -76,8 +104,8 @@ impl View {
Task::Refresh => {
self.refresh(layout)?;
}
- Task::SetSkymapCatalog(skymap_catalog) => {
- log::info!("{:?}", skymap_catalog);
+ Task::ClosePopupDownloadSkymaps => {
+ (*self.state.borrow_mut()).popup_download_skymaps = None;
}
}
}
@@ -86,14 +114,19 @@ impl View {
Ok(())
}
- async fn skymap_catalog_request_wrapper(tasks: Tasks, executor: AsyncExecutor) {
- let res = networking::skymap_catalog::request_catalog(&executor).await;
- tasks.push(Task::SetSkymapCatalog(res.map_err(|e| format!("{}", e))));
- }
-
fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> {
- let fut = View::skymap_catalog_request_wrapper(self.tasks.clone(), executor.clone());
- executor.spawn(fut).detach();
+ views::download_skymaps::mount_popup(
+ self.frontend_tasks.clone(),
+ executor.clone(),
+ self.globals.clone(),
+ self.tasks.make_callback_box(Task::ClosePopupDownloadSkymaps),
+ Box::new({
+ let state = self.state.clone();
+ move |popup| {
+ state.borrow_mut().popup_download_skymaps = Some(popup);
+ }
+ }),
+ );
Ok(())
}
diff --git a/dash-frontend/src/views/skymap_list_cell.rs b/dash-frontend/src/views/skymap_list_cell.rs
new file mode 100644
index 00000000..0f1ccd0b
--- /dev/null
+++ b/dash-frontend/src/views/skymap_list_cell.rs
@@ -0,0 +1,56 @@
+use wgui::{
+ assets::AssetPath,
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState},
+ widget::label::WidgetLabel,
+};
+
+use crate::util::{networking, wgui_simple};
+
+pub struct Params<'a> {
+ pub id_parent: WidgetID,
+ pub layout: &'a mut Layout,
+ pub entry: networking::skymap_catalog::SkymapCatalogEntry,
+}
+
+pub struct View {
+ #[allow(dead_code)]
+ parser_state: ParserState,
+}
+
+impl View {
+ pub fn new(par: Params) -> anyhow::Result {
+ let globals = par.layout.state.globals.clone();
+ let doc_params = &ParseDocumentParams {
+ globals: globals.clone(),
+ path: AssetPath::BuiltIn("gui/view/skymap_list_cell.xml"),
+ extra: Default::default(),
+ };
+
+ let mut parser_state = wgui::parser::parse_from_assets(&doc_params, par.layout, par.id_parent)?;
+
+ let data = parser_state.realize_template(&doc_params, "Cell", par.layout, par.id_parent, Default::default())?;
+
+ let id_image_preview = data.get_widget_id("image_preview")?;
+
+ {
+ let mut label_title = data.fetch_widget_as::(&par.layout.state, "label_title")?;
+ let mut label_author = data.fetch_widget_as::(&par.layout.state, "label_author")?;
+
+ label_title.set_text_simple(&mut globals.get(), Translation::from_raw_text_string(par.entry.name));
+ label_author.set_text_simple(
+ &mut globals.get(),
+ Translation::from_raw_text_string(format!("by {}", par.entry.author)),
+ );
+ }
+
+ wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
+ layout: par.layout,
+ parent_id: id_image_preview,
+ with_text: false,
+ })?;
+
+ Ok(Self { parser_state })
+ }
+}
diff --git a/wgui/src/animation.rs b/wgui/src/animation.rs
index c89d29cb..2269e716 100644
--- a/wgui/src/animation.rs
+++ b/wgui/src/animation.rs
@@ -133,16 +133,16 @@ impl Animations {
anim.pos_prev = anim.pos;
anim.pos = pos;
- anim.call(state, alterables, 1.0);
if anim.last_tick {
+ anim.call(state, alterables, 1.0);
alterables.needs_redraw = true;
+ } else {
+ anim.ticks_remaining -= 1;
}
-
- anim.ticks_remaining -= 1;
}
- self.running_animations.retain(|anim| anim.ticks_remaining > 0);
+ self.running_animations.retain(|anim| !anim.last_tick);
}
pub fn process(&mut self, state: &LayoutState, alterables: &mut EventAlterables, alpha: f32) {
diff --git a/wgui/src/task.rs b/wgui/src/task.rs
index e3437cf7..58e33186 100644
--- a/wgui/src/task.rs
+++ b/wgui/src/task.rs
@@ -41,10 +41,17 @@ impl Tasks {
});
}
- pub fn make_callback(&self, task: TaskType) -> Rc {
+ pub fn make_callback_rc(&self, task: TaskType) -> Rc {
let this = self.clone();
Rc::new(move || {
this.push(task.clone());
})
}
+
+ pub fn make_callback_box(&self, task: TaskType) -> Box {
+ let this = self.clone();
+ Box::new(move || {
+ this.push(task.clone());
+ })
+ }
}