mirror of https://github.com/wayvr-org/wayvr.git
dash-frontend: Dialog boxes, Skymap removal dialog, Skymap resolution selector dialog
This commit is contained in:
parent
c59e195d94
commit
0d8937ff80
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zM9 17h2V8H9zm4 0h2V8h-2zM7 6v13z"/></svg>
|
||||
|
After Width: | Height: | Size: 360 B |
|
|
@ -0,0 +1,15 @@
|
|||
<layout>
|
||||
<template name="DialogBoxButton">
|
||||
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"/>
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
<div flex_direction="column" align_items="center" justify_content="center" width="100%" gap="32">
|
||||
<label id="label_message" size="18" weight="bold"/>
|
||||
|
||||
<div id="buttons" gap="8">
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,15 @@ impl SkymapResolution {
|
|||
SkymapResolution::Res8k => "8K",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_display_str_simple(text: &str) -> Option<SkymapResolution> {
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -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<ButtonEntry>,
|
||||
pub message: Translation,
|
||||
pub on_action_click: Box<dyn FnOnce(&'static str)>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
ActionClicked(&'static str),
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
tasks: Tasks<Task>,
|
||||
|
||||
#[allow(dead_code)]
|
||||
parser_state: ParserState,
|
||||
|
||||
on_action_click: Option<Box<dyn FnOnce(&'static str)>>,
|
||||
on_close_request: Option<Box<dyn FnOnce()>>,
|
||||
}
|
||||
|
||||
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<dyn FnOnce()>,
|
||||
par: Params,
|
||||
) -> anyhow::Result<Self> {
|
||||
let tasks = Tasks::<Task>::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::<WidgetLabel>()?
|
||||
.set_text(&mut layout.common(), par.message);
|
||||
}
|
||||
|
||||
for entry in par.entries {
|
||||
let mut t_par = HashMap::<Rc<str>, Rc<str>>::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::<ComponentButton>("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<View>, 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))
|
||||
}),
|
||||
)));
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<views::download_file::View>,
|
||||
popup_dialog_box: PopupHolder<views::dialog_box::View>,
|
||||
|
||||
preview_image_compressed: Rc<Vec<u8>>,
|
||||
on_updated_library: Rc<dyn Fn()>,
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<views::remote_skymap_list::View>,
|
||||
popup_dialog_box: PopupHolder<views::dialog_box::View>,
|
||||
cells: Vec<Cell>,
|
||||
}
|
||||
|
||||
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::<views::dialog_box::ButtonEntry>::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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue