mirror of https://github.com/wayvr-org/wayvr.git
dash-frontend: list skymaps from the server (wip), loading spinner, wgui: animation bugfix
This commit is contained in:
parent
a3460fbe70
commit
f2dcdcc6c3
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8"/></svg>
|
||||
|
After Width: | Height: | Size: 286 B |
|
|
@ -0,0 +1,16 @@
|
|||
<layout>
|
||||
<template name="LoadingWithText">
|
||||
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||
<div flex_direction="row" gap="8" align_items="center" >
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||
<label translation="LOADING" weight="bold"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="LoadingWithoutText">
|
||||
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||
</div>
|
||||
</template>
|
||||
</layout>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<layout>
|
||||
<elements>
|
||||
<div id="list" gap="8" flex_wrap="wrap">
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<layout>
|
||||
<elements>
|
||||
<div flex_direction="column">
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" justify_content="center" />
|
||||
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" overflow_y="scroll"/>
|
||||
</div>
|
||||
<div flex_direction="row" gap="4">
|
||||
<Button id="btn_refresh" tooltip="REFRESH" width="32" height="32" sprite_src_builtin="dashboard/refresh.svg" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
<layout>
|
||||
|
||||
<!--
|
||||
ids:
|
||||
"image_preview"
|
||||
"label_title"
|
||||
"label_desc"
|
||||
-->
|
||||
<template name="Cell">
|
||||
<rectangle
|
||||
border="2"
|
||||
color="~color_bg_translucent"
|
||||
border_color="~color_accent"
|
||||
padding="8"
|
||||
round="8"
|
||||
flex_direction="column"
|
||||
gap="4"
|
||||
width="256"
|
||||
align_items="center"
|
||||
align_self="start">
|
||||
<image id="image_preview" width="100%" height="128"/>
|
||||
<label id="label_title" wrap="1" weight="bold"/>
|
||||
<label id="label_author" wrap="1"/>
|
||||
</rectangle>
|
||||
</template>
|
||||
</layout>
|
||||
|
|
@ -155,6 +155,7 @@
|
|||
"HIDE": "Hide",
|
||||
"HOME_SCREEN": "Home",
|
||||
"MONADO_RUNTIME": "Monado runtime",
|
||||
"LOADING": "Loading...",
|
||||
"POPUP_ADD_DISPLAY": {
|
||||
"RESOLUTION": "Resolution"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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<T: 'static> Frontend<T> {
|
|||
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<T: 'static> Frontend<T> {
|
|||
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()?,
|
||||
|
|
|
|||
|
|
@ -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<PopupHolder<views::app_launcher::View>>,
|
||||
}
|
||||
|
||||
pub struct TabApps<T> {
|
||||
|
|
@ -82,37 +82,16 @@ fn on_app_click(
|
|||
tasks: Tasks<Task>,
|
||||
) -> 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(())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T> {
|
|||
|
||||
tasks: Tasks<Task>,
|
||||
marker: PhantomData<T>,
|
||||
frontend_tasks: FrontendTasks,
|
||||
}
|
||||
|
||||
impl<T> Tab<T> for TabSettings<T> {
|
||||
|
|
@ -550,6 +552,7 @@ impl<T> TabSettings<T> {
|
|||
mp: &mut mp,
|
||||
globals: &globals,
|
||||
parent_id: root,
|
||||
frontend_tasks: &self.frontend_tasks,
|
||||
};
|
||||
|
||||
match name {
|
||||
|
|
@ -611,6 +614,7 @@ impl<T> TabSettings<T> {
|
|||
marker: PhantomData,
|
||||
context_menu: ContextMenu::default(),
|
||||
current_tab: None,
|
||||
frontend_tasks: frontend.tasks.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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<String>, // "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<SkymapCatalogEntry>,
|
||||
}
|
||||
|
||||
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<SkymapCatalog> {
|
||||
log::info!("Fetching skymap list");
|
||||
|
||||
let res = http_client::get(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
|
||||
let catalog = res.as_json::<SkymapCatalog>()?;
|
||||
catalog.validate()?;
|
||||
|
||||
Ok(catalog)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ pub struct PopupHandle {
|
|||
state: Rc<RefCell<MountedPopupState>>,
|
||||
}
|
||||
|
||||
pub type PopupHolder<ViewType> = (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<dyn Fn(PopupContentFuncData) -> 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<RefCell<Option<Box<dyn FnOnce(PopupContentFuncData) -> anyhow::Result<()>>>>>,
|
||||
}
|
||||
|
||||
impl MountPopupOnceParams {
|
||||
pub fn new(title: Translation, on_content: Box<dyn FnOnce(PopupContentFuncData) -> 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::<WidgetLabel>(&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::<ComponentButton>("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 {
|
||||
|
|
|
|||
|
|
@ -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<WidgetID> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<dyn Fn()>,
|
||||
set_holder: Box<dyn FnOnce(PopupHolder<View>)>,
|
||||
) {
|
||||
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(())
|
||||
}),
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<dyn Fn()>,
|
||||
}
|
||||
|
||||
enum Task {
|
||||
SetSkymapCatalog(anyhow::Result<networking::skymap_catalog::SkymapCatalog>),
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
id_parent: WidgetID,
|
||||
id_list: WidgetID,
|
||||
id_loading: WidgetID,
|
||||
globals: WguiGlobals,
|
||||
tasks: Tasks<Task>,
|
||||
mounted_cells: Vec<views::skymap_list_cell::View>,
|
||||
}
|
||||
|
||||
impl View {
|
||||
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
|
||||
let res = networking::skymap_catalog::request_catalog(&executor).await;
|
||||
tasks.push(Task::SetSkymapCatalog(res))
|
||||
}
|
||||
|
||||
pub fn new(par: Params) -> anyhow::Result<Self> {
|
||||
let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
|
||||
layout: par.layout,
|
||||
parent_id: par.parent_id,
|
||||
with_text: true,
|
||||
})?;
|
||||
let tasks = Tasks::<Task>::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<dyn Fn()>,
|
||||
set_holder: Box<dyn FnOnce(PopupHolder<View>)>,
|
||||
) {
|
||||
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(())
|
||||
}),
|
||||
)));
|
||||
}
|
||||
|
|
@ -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<ComponentButton>,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<dyn Fn()>,
|
||||
set_holder: Box<dyn FnOnce(PopupHolder<View>)>,
|
||||
) {
|
||||
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(())
|
||||
}),
|
||||
)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<PopupHolder<views::game_launcher::View>>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<networking::skymap_catalog::SkymapCatalog, String>),
|
||||
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<PopupHolder<views::download_skymaps::View>>,
|
||||
}
|
||||
|
||||
pub struct View {
|
||||
|
|
@ -29,6 +40,9 @@ pub struct View {
|
|||
parser_state: ParserState,
|
||||
tasks: Tasks<Task>,
|
||||
list_parent: WidgetID,
|
||||
frontend_tasks: FrontendTasks,
|
||||
globals: WguiGlobals,
|
||||
state: Rc<RefCell<State>>,
|
||||
}
|
||||
|
||||
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<Task>, 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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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::<WidgetLabel>(&par.layout.state, "label_title")?;
|
||||
let mut label_author = data.fetch_widget_as::<WidgetLabel>(&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 })
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -41,10 +41,17 @@ impl<TaskType: Clone + 'static> Tasks<TaskType> {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn make_callback(&self, task: TaskType) -> Rc<dyn Fn()> {
|
||||
pub fn make_callback_rc(&self, task: TaskType) -> Rc<dyn Fn()> {
|
||||
let this = self.clone();
|
||||
Rc::new(move || {
|
||||
this.push(task.clone());
|
||||
})
|
||||
}
|
||||
|
||||
pub fn make_callback_box(&self, task: TaskType) -> Box<dyn Fn()> {
|
||||
let this = self.clone();
|
||||
Box::new(move || {
|
||||
this.push(task.clone());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue