diff --git a/dash-frontend/assets/dashboard/trash.svg b/dash-frontend/assets/dashboard/trash.svg
new file mode 100644
index 00000000..914fd402
--- /dev/null
+++ b/dash-frontend/assets/dashboard/trash.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/dash-frontend/assets/gui/view/dialog_box.xml b/dash-frontend/assets/gui/view/dialog_box.xml
new file mode 100644
index 00000000..0c3f87cf
--- /dev/null
+++ b/dash-frontend/assets/gui/view/dialog_box.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index 5ab10456..1bff5c4f 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -101,8 +101,10 @@
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
"SCROLL_SPEED": "Scroll speed",
+ "SELECT_VARIANT": "Select variant",
"SETS_ON_WATCH": "Sets on watch",
"SKYBOX": "Skybox",
+ "SKYMAP_ALREADY_DOWNLOADED": "This skymap is already downloaded. Select desired action.",
"SPACE_DRAG_MULTIPLIER": "Space drag multiplier",
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
@@ -144,6 +146,7 @@
"DEBUG_INFO": "Debug info",
"DISPLAY_BRIGHTNESS": "Display brightness",
"DOWNLOADER": "Downloader",
+ "DOWNLOAD_AGAIN": "Download again",
"DOWNLOADING_FILE": "Downloading file...",
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
"GAME_LAUNCHED": "Game launched",
diff --git a/dash-frontend/src/util/networking/skymap_catalog.rs b/dash-frontend/src/util/networking/skymap_catalog.rs
index 5591349b..db1feee6 100644
--- a/dash-frontend/src/util/networking/skymap_catalog.rs
+++ b/dash-frontend/src/util/networking/skymap_catalog.rs
@@ -32,6 +32,15 @@ impl SkymapResolution {
SkymapResolution::Res8k => "8K",
}
}
+
+ pub fn from_display_str_simple(text: &str) -> Option {
+ match text {
+ "2K" => Some(SkymapResolution::Res2k),
+ "4K" => Some(SkymapResolution::Res4k),
+ "8K" => Some(SkymapResolution::Res8k),
+ _ => None,
+ }
+ }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -69,6 +78,19 @@ impl SkymapCatalogEntryFiles {
Some(format!("{}/files/{}", WAYVR_SKYMAPS_ROOT, filename))
}
+
+ pub fn get_preview_path(&self) -> PathBuf {
+ config_io::get_skymaps_root().join(&self.preview)
+ }
+
+ pub fn save_preview_to_file(&self, data: &[u8]) -> anyhow::Result<()> {
+ std::fs::write(self.get_preview_path(), data)?;
+ Ok(())
+ }
+
+ pub fn remove_preview_file(&self) {
+ let _dont_care = std::fs::remove_file(self.get_preview_path());
+ }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -104,11 +126,29 @@ impl SkymapCatalogEntry {
Ok(std::fs::exists(full_path)?)
}
+ pub fn has_any_downloaded(&self) -> bool {
+ self.is_downloaded(SkymapResolution::Res2k).unwrap_or(false)
+ || self.is_downloaded(SkymapResolution::Res4k).unwrap_or(false)
+ || self.is_downloaded(SkymapResolution::Res8k).unwrap_or(false)
+ }
+
+ pub fn remove_file(&self, resolution: SkymapResolution) {
+ let Some(full_path) = self.get_destination_path(resolution) else {
+ return;
+ };
+
+ let _dont_care = std::fs::remove_file(full_path);
+ }
+
pub fn save_metadata(&self) -> anyhow::Result<()> {
let json = serde_json::to_string_pretty(self)?;
std::fs::write(self.get_destination_metadata_path(), json)?;
Ok(())
}
+
+ pub fn remove_metadata(&self) {
+ let _dont_care = std::fs::remove_file(self.get_destination_metadata_path());
+ }
}
#[derive(Clone, Debug, Deserialize)]
diff --git a/dash-frontend/src/views/dialog_box.rs b/dash-frontend/src/views/dialog_box.rs
new file mode 100644
index 00000000..eba712af
--- /dev/null
+++ b/dash-frontend/src/views/dialog_box.rs
@@ -0,0 +1,127 @@
+use std::{collections::HashMap, rc::Rc};
+
+use crate::{
+ frontend::{FrontendTask, FrontendTasks},
+ util::popup_manager::{MountPopupOnceParams, PopupHolder},
+ views::{ViewTrait, ViewUpdateParams},
+};
+use wgui::{
+ assets::AssetPath,
+ components::button::ComponentButton,
+ globals::WguiGlobals,
+ i18n::Translation,
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState},
+ task::Tasks,
+ widget::label::WidgetLabel,
+};
+
+pub struct ButtonEntry {
+ pub content: Translation, // button text
+ pub icon: &'static str, // sprite_src_builtin
+ pub action: &'static str, // action name (will be passed into on_action_click)
+}
+
+pub struct Params {
+ pub globals: WguiGlobals,
+ pub entries: Vec,
+ pub message: Translation,
+ pub on_action_click: Box,
+}
+
+#[derive(Clone)]
+enum Task {
+ ActionClicked(&'static str),
+}
+
+pub struct View {
+ tasks: Tasks,
+
+ #[allow(dead_code)]
+ parser_state: ParserState,
+
+ on_action_click: Option>,
+ on_close_request: Option>,
+}
+
+fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
+ ParseDocumentParams {
+ globals: globals.clone(),
+ path: AssetPath::BuiltIn("gui/view/dialog_box.xml"),
+ extra: Default::default(),
+ }
+}
+
+impl ViewTrait for View {
+ fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
+ for task in self.tasks.drain() {
+ match task {
+ Task::ActionClicked(action) => {
+ if let Some(func) = self.on_action_click.take() {
+ func(action);
+ }
+
+ if let Some(on_close) = self.on_close_request.take() {
+ on_close();
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl View {
+ pub fn new(
+ layout: &mut Layout,
+ id_parent: WidgetID,
+ on_close_request: Box,
+ par: Params,
+ ) -> anyhow::Result {
+ let tasks = Tasks::::new();
+
+ let mut parser_state = wgui::parser::parse_from_assets(&doc_params(&par.globals), layout, id_parent)?;
+ let id_buttons = parser_state.get_widget_id("buttons")?;
+
+ {
+ let label_message = parser_state.fetch_widget(&layout.state, "label_message")?.widget;
+ label_message
+ .cast::()?
+ .set_text(&mut layout.common(), par.message);
+ }
+
+ for entry in par.entries {
+ let mut t_par = HashMap::, Rc>::new();
+ t_par.insert(Rc::from("icon"), Rc::from(entry.icon));
+
+ let data =
+ parser_state.realize_template(&doc_params(&par.globals), "DialogBoxButton", layout, id_buttons, t_par)?;
+
+ let button = data.fetch_component_as::("btn")?;
+ button.set_text(&mut layout.common(), entry.content.clone());
+ button.on_click(tasks.get_button_click_callback(Task::ActionClicked(entry.action)));
+ }
+
+ Ok(Self {
+ tasks,
+ parser_state,
+ on_action_click: Some(par.on_action_click),
+ on_close_request: Some(on_close_request),
+ })
+ }
+}
+
+pub fn mount_popup(popup: PopupHolder, frontend_tasks: FrontendTasks, params: Params) {
+ frontend_tasks
+ .clone()
+ .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
+ Translation::from_raw_text("Info"),
+ Box::new(move |data| {
+ let on_close_request = popup.get_close_callback(data.layout);
+ let view = View::new(data.layout, data.id_content, on_close_request, params)?;
+
+ popup.set_view(data.handle, view, None);
+ Ok(popup.get_close_callback(data.layout))
+ }),
+ )));
+}
diff --git a/dash-frontend/src/views/mod.rs b/dash-frontend/src/views/mod.rs
index e916e5bf..91cdee11 100644
--- a/dash-frontend/src/views/mod.rs
+++ b/dash-frontend/src/views/mod.rs
@@ -2,6 +2,7 @@ use wlx_common::async_executor::AsyncExecutor;
pub mod app_launcher;
pub mod audio_settings;
+pub mod dialog_box;
pub mod download_file;
pub mod game_cover;
pub mod game_launcher;
diff --git a/dash-frontend/src/views/remote_skymap_downloader.rs b/dash-frontend/src/views/remote_skymap_downloader.rs
index 61573c62..84e03350 100644
--- a/dash-frontend/src/views/remote_skymap_downloader.rs
+++ b/dash-frontend/src/views/remote_skymap_downloader.rs
@@ -37,8 +37,10 @@ pub struct Params<'a> {
#[derive(Clone)]
enum Task {
Refresh,
- ResolutionClicked(networking::skymap_catalog::SkymapResolution),
+ ResolutionClicked(SkymapResolution),
DownloadFinished,
+ RunDownload(SkymapResolution),
+ RemoveFile(SkymapResolution),
}
pub struct View {
@@ -54,6 +56,8 @@ pub struct View {
parser_state: ParserState,
popup_download: PopupHolder,
+ popup_dialog_box: PopupHolder,
+
preview_image_compressed: Rc>,
on_updated_library: Rc,
}
@@ -90,8 +94,8 @@ 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(skymap_resolution)?;
+ Task::ResolutionClicked(resolution) => {
+ self.resolution_clicked(resolution)?;
}
Task::Refresh => {
self.refresh(par.layout)?;
@@ -99,10 +103,17 @@ impl ViewTrait for View {
Task::DownloadFinished => {
self.download_finished()?;
}
+ Task::RunDownload(resolution) => {
+ self.run_download(resolution)?;
+ }
+ Task::RemoveFile(resolution) => {
+ self.remove_file(resolution)?;
+ }
}
}
self.popup_download.update(par)?;
+ self.popup_dialog_box.update(par)?;
Ok(())
}
}
@@ -180,6 +191,7 @@ impl View {
parser_state,
frontend_tasks: par.frontend_tasks,
popup_download: Default::default(),
+ popup_dialog_box: Default::default(),
id_resolution_buttons,
preview_image_compressed: par.preview_image_compressed,
on_updated_library: par.on_updated_library,
@@ -212,6 +224,57 @@ impl View {
Ok(())
}
+ fn resolution_clicked(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
+ let is_downloaded = self.entry.is_downloaded(resolution).unwrap_or(false);
+ if !is_downloaded {
+ self.tasks.push(Task::RunDownload(resolution));
+ } else {
+ self.show_dialog_box_action(resolution)?;
+ }
+ Ok(())
+ }
+
+ fn show_dialog_box_action(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
+ const ACTION_REMOVE: &'static str = "remove";
+ const ACTION_DOWNLOAD_AGAIN: &'static str = "download_again";
+
+ let tasks = self.tasks.clone();
+
+ views::dialog_box::mount_popup(
+ self.popup_dialog_box.clone(),
+ self.frontend_tasks.clone(),
+ views::dialog_box::Params {
+ globals: self.globals.clone(),
+ message: Translation::from_translation_key("APP_SETTINGS.SKYMAP_ALREADY_DOWNLOADED"),
+ entries: vec![
+ views::dialog_box::ButtonEntry {
+ content: Translation::from_translation_key("REMOVE"),
+ icon: "dashboard/trash.svg",
+ action: ACTION_REMOVE,
+ },
+ views::dialog_box::ButtonEntry {
+ content: Translation::from_translation_key("DOWNLOAD_AGAIN"),
+ icon: "dashboard/download.svg",
+ action: ACTION_DOWNLOAD_AGAIN,
+ },
+ ],
+ on_action_click: Box::new(move |action| match action {
+ ACTION_REMOVE => {
+ tasks.push(Task::RemoveFile(resolution));
+ tasks.push(Task::Refresh);
+ }
+ ACTION_DOWNLOAD_AGAIN => {
+ tasks.push(Task::RunDownload(resolution));
+ tasks.push(Task::Refresh);
+ }
+ _ => unreachable!(),
+ }),
+ },
+ );
+
+ Ok(())
+ }
+
fn download_finished(&mut self) -> anyhow::Result<()> {
self.entry.save_metadata()?;
let mut uuids = config_io::get_skymaps_uuids().unwrap_or_default();
@@ -222,8 +285,7 @@ impl View {
config_io::set_skymaps_uuids(&uuids)?;
// Save preview image
- let preview_path = config_io::get_skymaps_root().join(&self.entry.files.preview);
- std::fs::write(preview_path, self.preview_image_compressed.as_ref())?;
+ self.entry.files.save_preview_to_file(&self.preview_image_compressed)?;
(*self.on_updated_library)();
@@ -253,6 +315,27 @@ impl View {
);
Ok(())
}
+
+ fn remove_file(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
+ self.entry.remove_file(resolution);
+
+ if !self.entry.has_any_downloaded() {
+ // all skymaps of this uuid are removed, clean-up files
+ self.entry.remove_metadata();
+
+ // remove uuid of this entry from downloaded skymaps uuid and save the file again
+ let mut uuids = config_io::get_skymaps_uuids().unwrap_or_default();
+ uuids.retain(|uuid| *uuid != self.entry.uuid.to_string());
+ config_io::set_skymaps_uuids(&uuids)?;
+
+ // remove "_preview.dds" files from the disk too
+ self.entry.files.remove_preview_file();
+ }
+
+ (*self.on_updated_library)();
+
+ Ok(())
+ }
}
pub fn mount_popup(
diff --git a/dash-frontend/src/views/skymap_list.rs b/dash-frontend/src/views/skymap_list.rs
index cc2bd6ce..66a58635 100644
--- a/dash-frontend/src/views/skymap_list.rs
+++ b/dash-frontend/src/views/skymap_list.rs
@@ -25,7 +25,8 @@ use crate::{
enum Task {
DownloadSkymaps,
Refresh,
- SetSkymap(SkymapCatalogEntry),
+ ShowSkymapResolutionSelector(SkymapCatalogEntry),
+ SetSkymap(SkymapCatalogEntry, SkymapResolution),
}
pub struct Params<'a> {
@@ -48,12 +49,14 @@ pub struct View {
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
popup_remote_skymap_list: PopupHolder,
+ popup_dialog_box: PopupHolder,
cells: Vec| ,
}
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.popup_remote_skymap_list.update(par)?;
+ self.popup_dialog_box.update(par)?;
loop {
let tasks = self.tasks.drain();
@@ -68,8 +71,11 @@ impl ViewTrait for View {
Task::Refresh => {
self.refresh(&mut par.layout)?;
}
- Task::SetSkymap(entry) => {
- self.set_skymap(entry)?;
+ Task::ShowSkymapResolutionSelector(entry) => {
+ self.show_skymap_resolution_selector(entry);
+ }
+ Task::SetSkymap(entry, resolution) => {
+ self.set_skymap(entry, resolution)?;
}
}
}
@@ -110,6 +116,7 @@ impl View {
frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals.clone(),
popup_remote_skymap_list: Default::default(),
+ popup_dialog_box: Default::default(),
cells: Vec::new(),
})
}
@@ -125,19 +132,60 @@ impl View {
Ok(())
}
- fn set_skymap(&mut self, entry: SkymapCatalogEntry) -> anyhow::Result<()> {
+ fn set_skymap(&mut self, entry: SkymapCatalogEntry, resolution: SkymapResolution) -> anyhow::Result<()> {
let skymap_file_path = entry
- .get_destination_path(SkymapResolution::Res2k /* todo: resolution selector */)
+ .get_destination_path(resolution)
.context("Skymap not found" /* you shouldn't really see this, like ever. */)?;
log::error!(
- "not implemented (skymap path to be loaded: {})",
- skymap_file_path.to_string_lossy()
+ "not implemented (skymap path to be loaded: {} with resolution {:?})",
+ skymap_file_path.to_string_lossy(),
+ resolution
);
Ok(())
}
+ fn show_skymap_resolution_selector(&mut self, entry: SkymapCatalogEntry) {
+ let tasks = self.tasks.clone();
+
+ let mut entries = Vec::::new();
+
+ let mut add_entry_resolution = |resolution: SkymapResolution| {
+ if entry.is_downloaded(resolution).unwrap_or(false) {
+ entries.push(views::dialog_box::ButtonEntry {
+ content: Translation::from_raw_text(resolution.get_display_str()),
+ icon: "dashboard/globe.svg",
+ action: resolution.get_display_str_simple(),
+ });
+ }
+ };
+
+ add_entry_resolution(SkymapResolution::Res2k);
+ add_entry_resolution(SkymapResolution::Res4k);
+ add_entry_resolution(SkymapResolution::Res8k);
+
+ if entries.is_empty() {
+ // if the skymap is not downloaded, how did we get there?!
+ debug_assert!(false);
+ return;
+ }
+
+ views::dialog_box::mount_popup(
+ self.popup_dialog_box.clone(),
+ self.frontend_tasks.clone(),
+ views::dialog_box::Params {
+ globals: self.globals.clone(),
+ entries,
+ message: Translation::from_translation_key("APP_SETTINGS.SELECT_VARIANT"),
+ on_action_click: Box::new(move |action| {
+ let resolution = SkymapResolution::from_display_str_simple(action).unwrap(); // safety: never fails.
+ tasks.push(Task::SetSkymap(entry, resolution))
+ }),
+ },
+ );
+ }
+
fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
let entries = match skymap_catalog::get_entries_from_disk() {
Ok(entries) => entries,
@@ -166,7 +214,9 @@ impl View {
id_parent: self.list_parent,
layout,
entry: entry.clone(),
- on_click: self.tasks.get_button_click_callback(Task::SetSkymap(entry.clone())),
+ on_click: self
+ .tasks
+ .get_button_click_callback(Task::ShowSkymapResolutionSelector(entry.clone())),
})?;
// load preview image
diff --git a/dash-frontend/src/views/skymap_list_cell.rs b/dash-frontend/src/views/skymap_list_cell.rs
index 540a6d39..26bfcce3 100644
--- a/dash-frontend/src/views/skymap_list_cell.rs
+++ b/dash-frontend/src/views/skymap_list_cell.rs
@@ -59,7 +59,7 @@ fn populate_res_pips(
tpar.insert(
Rc::from("color"),
if downloaded {
- Rc::from("#116625")
+ Rc::from("#11aa40")
} else {
Rc::from("#444444")
},
|