diff --git a/dash-frontend/assets/dashboard/download.svg b/dash-frontend/assets/dashboard/download.svg
new file mode 100644
index 00000000..f446969b
--- /dev/null
+++ b/dash-frontend/assets/dashboard/download.svg
@@ -0,0 +1 @@
+
diff --git a/dash-frontend/assets/gui/view/skymap_list.xml b/dash-frontend/assets/gui/view/skymap_list.xml
new file mode 100644
index 00000000..00465088
--- /dev/null
+++ b/dash-frontend/assets/gui/view/skymap_list.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index 81fe3df7..122d8206 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -53,6 +53,7 @@
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
"DOUBLE_CURSOR_FIX": "Double cursor fix",
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
+ "DOWNLOAD_SKYMAPS": "Download skymaps",
"FEATURES": "Features",
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
"HANDSFREE_POINTER": "Handsfree mode",
@@ -72,6 +73,7 @@
"LONG_PRESS_DURATION": "Long press duration",
"LOOK_AND_FEEL": "Look & Feel",
"MISC": "Miscellaneous",
+ "NO_SKYMAPS_FOUND": "No skymaps found",
"NOTIFICATIONS_ENABLED": "Enable notifications",
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
"OPAQUE_BACKGROUND": "Opaque background",
diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs
index 0374fd95..1367f193 100644
--- a/dash-frontend/src/tab/settings/mod.rs
+++ b/dash-frontend/src/tab/settings/mod.rs
@@ -20,7 +20,9 @@ use wgui::{
},
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
};
-use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
+use wlx_common::{
+ async_executor::AsyncExecutor, config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode,
+};
use crate::{
frontend::{Frontend, FrontendTask},
@@ -78,12 +80,31 @@ enum Task {
SetTab(TabNameEnum),
}
+struct SettingsMountParams<'a> {
+ mp: &'a mut MacroParams<'a>,
+ globals: &'a WguiGlobals,
+ parent_id: WidgetID,
+}
+
+struct SettingsUpdateParams<'a> {
+ layout: &'a mut Layout,
+ executor: &'a AsyncExecutor,
+}
+
+trait SettingsTab {
+ fn update(&mut self, _par: SettingsUpdateParams) -> anyhow::Result<()> {
+ Ok(())
+ }
+}
+
pub struct TabSettings {
pub state: ParserState,
app_button_ids: Vec>,
context_menu: ContextMenu,
+ current_tab: Option>,
+
tasks: Tasks,
marker: PhantomData,
}
@@ -94,6 +115,13 @@ impl Tab for TabSettings {
}
fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
+ if let Some(tab) = &mut self.current_tab {
+ tab.update(SettingsUpdateParams {
+ layout: &mut frontend.layout,
+ executor: &frontend.executor,
+ })?;
+ }
+
let mut changed = false;
for task in self.tasks.drain() {
match task {
@@ -507,6 +535,7 @@ impl TabSettings {
let root = self.state.get_widget_id("settings_root")?;
frontend.layout.remove_children(root);
let globals = frontend.layout.state.globals.clone();
+ self.current_tab = None;
let mut mp = MacroParams {
layout: &mut frontend.layout,
@@ -537,7 +566,11 @@ impl TabSettings {
tab_troubleshooting::mount(&mut mp, root)?;
}
TabNameEnum::Skybox => {
- tab_skybox::mount(&mut mp, root)?;
+ self.current_tab = Some(Box::new(tab_skybox::State::mount(SettingsMountParams {
+ mp: &mut mp,
+ globals: &globals,
+ parent_id: root,
+ })?));
}
}
@@ -572,6 +605,7 @@ impl TabSettings {
state: parser_state,
marker: PhantomData,
context_menu: ContextMenu::default(),
+ current_tab: None,
})
}
}
diff --git a/dash-frontend/src/tab/settings/tab_skybox.rs b/dash-frontend/src/tab/settings/tab_skybox.rs
index 41d05831..3738f1aa 100644
--- a/dash-frontend/src/tab/settings/tab_skybox.rs
+++ b/dash-frontend/src/tab/settings/tab_skybox.rs
@@ -1,12 +1,34 @@
-use crate::tab::settings::{
- SettingType,
- macros::{MacroParams, options_category, options_checkbox},
+use crate::{
+ tab::settings::{
+ SettingType, SettingsMountParams, SettingsTab,
+ macros::{options_category, options_checkbox},
+ },
+ views::skymap_list,
};
-use wgui::layout::WidgetID;
-pub fn mount(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> {
- let c = options_category(mp, parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?;
- options_checkbox(mp, c, SettingType::UseSkybox)?;
- options_checkbox(mp, c, SettingType::OpaqueBackground)?;
- Ok(())
+pub struct State {
+ skymap_list: skymap_list::View,
+}
+
+impl SettingsTab for State {
+ fn update(&mut self, par: super::SettingsUpdateParams) -> anyhow::Result<()> {
+ self.skymap_list.update(par.layout, par.executor)?;
+ Ok(())
+ }
+}
+
+impl State {
+ pub fn mount(par: SettingsMountParams) -> anyhow::Result {
+ let c = options_category(par.mp, par.parent_id, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?;
+ options_checkbox(par.mp, c, SettingType::UseSkybox)?;
+ options_checkbox(par.mp, c, SettingType::OpaqueBackground)?;
+
+ let skymap_list = skymap_list::View::new(skymap_list::Params {
+ globals: par.globals.clone(),
+ layout: par.mp.layout,
+ parent_id: c,
+ })?;
+
+ Ok(Self { skymap_list })
+ }
}
diff --git a/dash-frontend/src/util/http_client.rs b/dash-frontend/src/util/http_client.rs
index 875d509e..69a018f2 100644
--- a/dash-frontend/src/util/http_client.rs
+++ b/dash-frontend/src/util/http_client.rs
@@ -18,6 +18,16 @@ pub struct HttpClientResponse {
pub data: Vec,
}
+impl HttpClientResponse {
+ pub fn as_json(self) -> anyhow::Result
+ where
+ T: for<'a> serde::Deserialize<'a>,
+ {
+ let utf8 = str::from_utf8(&self.data)?;
+ Ok(serde_json::from_str::(utf8)?)
+ }
+}
+
pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result {
log::info!("fetching URL \"{}\"", url);
diff --git a/dash-frontend/src/util/mod.rs b/dash-frontend/src/util/mod.rs
index e5290607..1bd01c98 100644
--- a/dash-frontend/src/util/mod.rs
+++ b/dash-frontend/src/util/mod.rs
@@ -1,5 +1,6 @@
pub mod cached_fetcher;
pub mod http_client;
+pub mod networking;
pub mod pactl_wrapper;
pub mod popup_manager;
pub mod steam_utils;
diff --git a/dash-frontend/src/util/networking/mod.rs b/dash-frontend/src/util/networking/mod.rs
new file mode 100644
index 00000000..5f6c078e
--- /dev/null
+++ b/dash-frontend/src/util/networking/mod.rs
@@ -0,0 +1,4 @@
+pub mod skymap_catalog;
+
+// pub const WAYVR_ROOT_URL: &'static str = "https://wayvr.org";
+pub const WAYVR_SKYMAPS_ROOT: &'static str = "https://wayvr.org/skymaps";
diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs
new file mode 100644
index 00000000..498c12f9
--- /dev/null
+++ b/dash-frontend/src/util/networking/skymap_catalog.rs
@@ -0,0 +1,42 @@
+#![allow(dead_code)] // TODO: Remove later
+use serde::Deserialize;
+use wlx_common::async_executor::AsyncExecutor;
+
+use crate::util::{http_client, networking};
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct SkymapCatalogEntryFiles {
+ pub size_16k: Option, // "my_skymap_16k.png"
+ pub size_8k: Option, // "my_skymap_8k.png"
+ pub size_4k: Option, // "my_skymap_4k.png"
+ pub size_2k: String, // we should have *at least* this
+ pub preview: String,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct SkymapCatalogEntry {
+ pub uuid: String,
+ pub created_at: String,
+ pub modified_at: String,
+ pub entry_version: u32,
+ pub name: String,
+ pub description: String,
+ pub author: String,
+ pub files: SkymapCatalogEntryFiles,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct SkymapCatalog {
+ pub version: u32,
+ pub r#type: String,
+ pub entries: Vec,
+}
+
+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::()?;
+
+ Ok(catalog)
+}
diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs
index d2cd9369..2f832208 100644
--- a/dash-frontend/src/views/mod.rs
+++ b/dash-frontend/src/views/mod.rs
@@ -4,3 +4,4 @@ pub mod game_cover;
pub mod game_launcher;
pub mod game_list;
pub mod running_games_list;
+pub mod skymap_list;
diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs
new file mode 100644
index 00000000..9a968f75
--- /dev/null
+++ b/dash-frontend/src/views/skymap_list.rs
@@ -0,0 +1,117 @@
+use wgui::{
+ assets::AssetPath,
+ components::button::ComponentButton,
+ globals::WguiGlobals,
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState},
+ task::Tasks,
+};
+use wlx_common::{async_executor::AsyncExecutor, config_io};
+
+use crate::util::{networking, wgui_simple};
+
+#[derive(Clone)]
+enum Task {
+ DownloadSkymaps,
+ SetSkymapCatalog(Result),
+ Refresh,
+}
+
+pub struct Params<'a> {
+ pub globals: WguiGlobals,
+ pub layout: &'a mut Layout,
+ pub parent_id: WidgetID,
+}
+
+pub struct View {
+ #[allow(dead_code)]
+ parser_state: ParserState,
+ tasks: Tasks,
+ list_parent: WidgetID,
+}
+
+impl View {
+ pub fn new(params: Params) -> anyhow::Result {
+ let doc_params = &ParseDocumentParams {
+ globals: params.globals.clone(),
+ path: AssetPath::BuiltIn("gui/view/skymap_list.xml"),
+ extra: Default::default(),
+ };
+
+ 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")?.id;
+ let tasks = Tasks::new();
+
+ tasks.push(Task::Refresh);
+
+ tasks.handle_button(
+ &parser_state.fetch_component_as::("btn_download_skymaps")?,
+ Task::DownloadSkymaps,
+ );
+
+ tasks.handle_button(
+ &parser_state.fetch_component_as::("btn_refresh")?,
+ Task::Refresh,
+ );
+
+ Ok(Self {
+ parser_state,
+ tasks,
+ list_parent,
+ })
+ }
+
+ pub fn update(&mut self, layout: &mut Layout, executor: &AsyncExecutor) -> anyhow::Result<()> {
+ 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)?;
+ }
+ Task::SetSkymapCatalog(skymap_catalog) => {
+ log::info!("{:?}", skymap_catalog);
+ }
+ }
+ }
+ }
+
+ 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();
+ Ok(())
+ }
+
+ fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
+ let skymaps_uuids = config_io::get_skymaps_uuids().unwrap_or_default();
+ log::info!("skymap uuids {:?}", skymaps_uuids);
+
+ layout.remove_children(self.list_parent);
+
+ if skymaps_uuids.is_empty() {
+ wgui_simple::create_label(
+ layout,
+ self.list_parent,
+ Translation::from_translation_key("APP_SETTINGS.NO_SKYMAPS_FOUND"),
+ )?;
+ return Ok(());
+ }
+
+ Ok(())
+ }
+}
diff --git a/wlx-common/src/config_io.rs b/wlx-common/src/config_io.rs
index 961e4b1a..7976a5de 100644
--- a/wlx-common/src/config_io.rs
+++ b/wlx-common/src/config_io.rs
@@ -21,6 +21,23 @@ pub fn get_config_root() -> PathBuf {
CONFIG_ROOT_PATH.clone()
}
+pub fn get_skymaps_root() -> PathBuf {
+ get_config_root().join("skymaps")
+}
+
+pub fn get_skymaps_uuids() -> anyhow::Result> {
+ let data = std::fs::read_to_string(get_skymaps_root().join("skymaps.txt"))?;
+ Ok(data.lines().filter(|line| !line.is_empty()).map(String::from).collect())
+}
+
+pub fn set_skymaps_uuids(uuids: &[String]) -> anyhow::Result<()> {
+ let skymaps_root = get_skymaps_root();
+ let _ = std::fs::create_dir_all(&skymaps_root);
+ let data = String::from_iter(uuids.iter().map(|uuid| format!("{}\n", uuid)));
+ std::fs::write(skymaps_root.join("skymaps.txt"), data)?;
+ Ok(())
+}
+
impl ConfigRoot {
pub fn get_conf_d_path(&self) -> PathBuf {
get_config_root().join(match self {