wayvr/dash-frontend/src/tab/apps.rs

361 lines
7.5 KiB
Rust

use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
marker::PhantomData,
rc::Rc,
};
use wgui::{
assets::AssetPath,
components::button::{ButtonClickCallback, ComponentButton},
globals::WguiGlobals,
i18n::Translation,
layout::{WidgetID, WidgetPair},
parser::{Fetchable, ParseDocumentParams, ParserState},
task::Tasks,
};
use wlx_common::desktop_finder::DesktopEntry;
use crate::{
frontend::{Frontend, FrontendTask, FrontendTasks},
tab::{Tab, TabType},
util::popup_manager::{MountPopupParams, PopupHandle},
views::{self, app_launcher},
};
enum Task {
CloseLauncher,
}
struct State {
view_launcher: Option<(PopupHandle, views::app_launcher::View)>,
}
pub struct TabApps<T> {
#[allow(dead_code)]
parser_state: ParserState,
state: Rc<RefCell<State>>,
app_list: AppList,
tasks: Tasks<Task>,
marker: PhantomData<T>,
}
impl<T> Tab<T> for TabApps<T> {
fn get_type(&self) -> TabType {
TabType::Apps
}
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut();
for task in self.tasks.drain() {
match task {
Task::CloseLauncher => state.view_launcher = None,
}
}
self
.app_list
.tick(frontend, &self.state, &self.tasks, &mut self.parser_state)?;
if let Some((_, launcher)) = &mut state.view_launcher {
launcher.update(&mut frontend.interface, data)?;
}
Ok(())
}
}
struct AppList {
//data: Vec<ParserData>,
entries_to_mount: VecDeque<DesktopEntry>,
list_parent: WidgetPair,
prev_category_name: String,
}
// called after the user clicks any desktop entry
fn on_app_click(
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
entry: DesktopEntry,
state: Rc<RefCell<State>>,
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
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(())
})
},
}));
Ok(())
})
}
fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
ParseDocumentParams {
globals,
path: AssetPath::BuiltIn("gui/tab/apps.xml"),
extra: Default::default(),
}
}
impl<T> TabApps<T> {
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
let globals = frontend.layout.state.globals.clone();
let tasks = Tasks::new();
let state = Rc::new(RefCell::new(State { view_launcher: None }));
let parser_state = wgui::parser::parse_from_assets(&doc_params(globals.clone()), &mut frontend.layout, parent_id)?;
let app_list_parent = parser_state.fetch_widget(&frontend.layout.state, "app_list_parent")?;
let mut entries_sorted: Vec<_> = frontend
.interface
.desktop_finder(data)
.find_entries()
.into_values()
.collect();
entries_sorted.sort_by(|a, b| {
let cat_name_a = get_category_name(a);
let cat_name_b = get_category_name(b);
cat_name_a.cmp(cat_name_b)
});
let app_list = AppList {
entries_to_mount: entries_sorted.drain(..).collect(),
list_parent: app_list_parent,
prev_category_name: String::new(),
};
Ok(Self {
app_list,
parser_state,
state,
tasks,
marker: PhantomData,
})
}
}
enum Scores {
Empty,
Unknown,
XFooBar, // X-something
Xfce,
Gnome,
Kde,
Gtk,
Qt,
Settings,
Application,
System,
Utility,
FileTools,
Filesystem,
FileManager,
Graphics,
Office,
Game,
VR, // best score (of course!)
}
fn get_category_name_score(name: &str) -> u8 {
if name.starts_with("X-") {
return Scores::XFooBar as u8;
}
match name {
"" => {
return Scores::Empty as u8;
}
"VR" => {
return Scores::VR as u8;
}
"Game" => {
return Scores::Game as u8;
}
"FileManager" => {
return Scores::FileManager as u8;
}
"Utility" => {
return Scores::Utility as u8;
}
"FileTools" => {
return Scores::FileTools as u8;
}
"Filesystem" => {
return Scores::Filesystem as u8;
}
"System" => {
return Scores::System as u8;
}
"Office" => {
return Scores::Office as u8;
}
"Settings" => {
return Scores::Settings as u8;
}
"Application" => {
return Scores::Application as u8;
}
"GTK" => {
return Scores::Gtk as u8;
}
"Qt" => {
return Scores::Qt as u8;
}
"XFCE" => {
return Scores::Xfce as u8;
}
"GNOME" => {
return Scores::Gnome as u8;
}
"KDE" => {
return Scores::Kde as u8;
}
"Graphics" => {
return Scores::Graphics as u8;
}
_ => {}
}
Scores::Unknown as u8
}
fn get_best_category_name(categories: &[Rc<str>]) -> Option<&Rc<str>> {
let mut best_score: u8 = 0;
let mut best_category: Option<&Rc<str>> = None;
for cat in categories {
let score = get_category_name_score(cat);
if score > best_score {
best_category = Some(cat);
best_score = score;
}
}
best_category
}
fn get_category_name(entry: &DesktopEntry) -> &str {
//log::info!("{:?}", entry.categories);
match get_best_category_name(&entry.categories) {
Some(cat) => cat,
None => "Other",
}
}
impl AppList {
fn mount_entry<T>(
&mut self,
frontend: &mut Frontend<T>,
parser_state: &mut ParserState,
doc_params: &ParseDocumentParams,
entry: &DesktopEntry,
) -> anyhow::Result<Rc<ComponentButton>> {
let category_name = get_category_name(entry);
if category_name != self.prev_category_name {
self.prev_category_name = String::from(category_name);
let mut params = HashMap::<Rc<str>, Rc<str>>::new();
params.insert("text".into(), category_name.into());
parser_state.parse_template(
doc_params,
"CategoryText",
&mut frontend.layout,
self.list_parent.id,
params,
)?;
}
{
let mut params = HashMap::new();
// entry icon
params.insert(
"src_ext".into(),
entry
.icon_path
.as_ref()
.map_or_else(|| "".into(), |icon_path| icon_path.clone()),
);
// entry fallback (question mark) icon
params.insert(
"src".into(),
if entry.icon_path.is_none() {
"dashboard/terminal.svg".into()
} else {
"".into()
},
);
params.insert("name".into(), entry.app_name.clone());
let data = parser_state.parse_template(
doc_params,
"AppEntry",
&mut frontend.layout,
self.list_parent.id,
params,
)?;
data.fetch_component_as::<ComponentButton>("button")
}
}
fn tick<T>(
&mut self,
frontend: &mut Frontend<T>,
state: &Rc<RefCell<State>>,
tasks: &Tasks<Task>,
parser_state: &mut ParserState,
) -> anyhow::Result<()> {
// load 4 entries for a single frame at most
for _ in 0..4 {
if let Some(entry) = self.entries_to_mount.pop_front() {
let globals = frontend.layout.state.globals.clone();
let button = self.mount_entry(frontend, parser_state, &doc_params(globals.clone()), &entry)?;
button.on_click(on_app_click(
frontend.tasks.clone(),
globals.clone(),
entry.clone(),
state.clone(),
tasks.clone(),
));
} else {
break;
}
}
Ok(())
}
}