App pinning and unpinning, preserve settings (done)

This commit is contained in:
Aleksander 2026-07-07 16:29:35 +02:00
parent 81fbc65b51
commit 5cb52ef9db
16 changed files with 194 additions and 78 deletions

View File

@ -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="M15 14v-2h8v2zm-4 7l-3.175-2.85q-1.8-1.625-3.088-2.9t-2.125-2.4t-1.225-2.175T1 8.475q0-2.35 1.575-3.912T6.5 3q1.3 0 2.475.538T11 5.075q.85-1 2.025-1.537T15.5 3q2.125 0 3.563 1.288T20.85 7.3q-.45-.175-.9-.262t-.875-.088q-2.525 0-4.3 1.763T13 13q0 1.3.525 2.463T15 17.45q-.475.425-1.237 1.088T12.45 19.7z"/></svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -1,5 +1,5 @@
<layout> <layout>
<template name="Separator"> <template name="Separator">
<rectangle width="100%" height="1" color="#FFFFFF77" /> <rectangle width="100%" min_height="2" color="#FFFFFF10" />
</template> </template>
</layout> </layout>

View File

@ -2,9 +2,10 @@
<include src="../theme.xml" /> <include src="../theme.xml" />
<include src="../t_group_box.xml" /> <include src="../t_group_box.xml" />
<!-- tooltip, src, src_ext, name -->
<template name="AppEntry"> <template name="AppEntry">
<Button <Button
id="button" width="116" max_width="140" min_height="100" flex_grow="1" id="button" tooltip_side="bottom" tooltip="${tooltip}" width="116" max_width="140" min_height="100" flex_grow="1"
flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="4" flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="4"
color="#3385FF10" color="#3385FF10"
> >

View File

@ -1,8 +1,8 @@
<layout> <layout>
<template name="Subtext"> <template name="Subtext">
<div flex_direction="row" gap="8"> <div flex_direction="row" gap="8" max_width="100%">
<label weight="bold" text="${title}" /> <label weight="bold" text="${title}" wrap="1" />
<label id="${label_id}" /> <label id="${label_id}" wrap="1" />
</div> </div>
</template> </template>
@ -15,12 +15,10 @@
<elements> <elements>
<div flex_direction="row" gap="16" flex_grow="1"> <div flex_direction="row" gap="16" flex_grow="1">
<rectangle macro="group_box" id="icon_parent" padding="16" color="#0033aa66" color2="#00000022" gradient="vertical" justify_content="center"> <rectangle macro="group_box" id="icon_parent" padding="16" color="#0033aa66" color2="#00000022" gradient="vertical" justify_content="center"/>
</rectangle>
<div flex_direction="column" gap="8" flex_grow="1"> <div flex_direction="column" gap="8" flex_grow="1">
<label id="label_title" weight="bold" size="32" overflow="hidden" /> <label id="label_title" weight="bold" size="32" overflow="hidden" />
<Subtext label_id="label_exec" overflow="hidden" /> <Subtext label_id="label_exec"/>
<Separator /> <Separator />
<RadioGroup id="radio_compositor" flex_direction="row" gap="16"> <RadioGroup id="radio_compositor" flex_direction="row" gap="16">
<RadioBox translation="APP_LAUNCHER.MODE.NATIVE" value="Native" checked="1" /> <RadioBox translation="APP_LAUNCHER.MODE.NATIVE" value="Native" checked="1" />
@ -30,14 +28,14 @@
<label translation="APP_LAUNCHER.RES_TITLE" /> <label translation="APP_LAUNCHER.RES_TITLE" />
<RadioGroup id="radio_res" flex_direction="row" gap="16"> <RadioGroup id="radio_res" flex_direction="row" gap="16">
<RadioBox text="1440p" value="Res1440" /> <RadioBox text="1440p" value="Res1440" />
<RadioBox text="1080p" value="Res1080" checked="1" /> <RadioBox text="1080p" value="Res1080"/>
<RadioBox text="720p" value="Res720" /> <RadioBox text="720p" value="Res720" />
<RadioBox text="480p" value="Res480" /> <RadioBox text="480p" value="Res480" />
</RadioGroup> </RadioGroup>
<Separator /> <Separator />
<label translation="APP_LAUNCHER.ASPECT_TITLE" /> <label translation="APP_LAUNCHER.ASPECT_TITLE" />
<RadioGroup id="radio_orientation" flex_direction="row" gap="16"> <RadioGroup id="radio_orientation" flex_direction="row" gap="16">
<RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip_str="16:9" checked="1" /> <RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip_str="16:9"/>
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip_str="3:2" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip_str="3:2" />
<RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip_str="1:1" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip_str="1:1" />
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip_str="2:3" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip_str="2:3" />
@ -52,12 +50,16 @@
</RadioGroup --> </RadioGroup -->
<Separator /> <Separator />
<CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" align_self="baseline"/> <CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" align_self="baseline"/>
<div flex_direction="row" justify_content="end" gap="8" margin_top="8"> <div flex_direction="row" align_items="end" justify_content="center" gap="8" margin_top="8">
<Button id="btn_pin" color="#ce4477" padding_left="8" padding_right="8" round="8" min_height="40"> <Button id="btn_unpin" color="#997777" padding_left="8" padding_right="8" round="8" min_height="40">
<sprite src_builtin="dashboard/heart_plus.svg" width="32" height="32" /> <sprite src_builtin="dashboard/heart_minus.svg" width="32" height="32" />
<label text="Add to favourites" weight="bold" size="17" shadow="#00000099" /> <label translation="REMOVE_FROM_FAVOURITES" weight="bold" size="17" shadow="#00000099" />
</Button> </Button>
<Button id="btn_launch" color="#44ce22" padding_left="8" padding_right="8" rround="8" min_height="40"> <Button id="btn_pin" color="#ce4477" padding_left="8" padding_right="8" round="8" min_height="40" tooltip="ADD_TO_FAVOURITES_HINT">
<sprite src_builtin="dashboard/heart_plus.svg" width="32" height="32" />
<label translation="ADD_TO_FAVOURITES" weight="bold" size="17" shadow="#00000099" />
</Button>
<Button id="btn_launch" color="#44ce22" padding_left="8" padding_right="8" round="8" min_width="200" min_height="40">
<sprite src_builtin="dashboard/play.svg" width="32" height="32" /> <sprite src_builtin="dashboard/play.svg" width="32" height="32" />
<label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" shadow="#00000099" /> <label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" shadow="#00000099" />
</Button> </Button>

View File

@ -182,7 +182,9 @@
"REMOVE_UNUSED_MODELS": "Sie schalten die Sprache-zu-Text-Funktion aus.\nMöchten Sie die heruntergeladenen Whisper-Modelle entfernen, um Speicherplatz freizugeben?" "REMOVE_UNUSED_MODELS": "Sie schalten die Sprache-zu-Text-Funktion aus.\nMöchten Sie die heruntergeladenen Whisper-Modelle entfernen, um Speicherplatz freizugeben?"
}, },
"WHISPER_MODEL": "Sprach-zu-Text-Modell", "WHISPER_MODEL": "Sprach-zu-Text-Modell",
"WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern genauere\nErgebnisse, verbrauchen jedoch mehr VRAM." "WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern genauere\nErgebnisse, verbrauchen jedoch mehr VRAM.",
"DEFAULT_OVERLAY_SCALE": "Standardgröße des Overlays",
"DEFAULT_OVERLAY_SCALE_HELP": "Neue Overlays werden in dieser Skalierung erstellt."
}, },
"HELLO": "Hallo!", "HELLO": "Hallo!",
"AUDIO": { "AUDIO": {
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Arbeits-Set" "WORKING_SET": "Arbeits-Set"
}, },
"DOWNLOAD": "Herunterladen" "DOWNLOAD": "Herunterladen",
"SAVED_TO_FAVOURITES": "Zu Favoriten hinzugefügt",
"ADD_TO_FAVOURITES": "Zu Favoriten hinzufügen",
"ADD_TO_FAVOURITES_HINT": "Voreinstellung speichern und an den App-Drawer anheften",
"REMOVE_FROM_FAVOURITES": "Aus Favoriten entfernen",
"EMPTY": "Leer",
"PINNED_APPS": "Angepinnte Anwendungen"
} }

View File

@ -300,6 +300,9 @@
"VERSION": "Version", "VERSION": "Version",
"WIDTH": "Width", "WIDTH": "Width",
"SAVED_TO_FAVOURITES": "Saved to favourites", "SAVED_TO_FAVOURITES": "Saved to favourites",
"ADD_TO_FAVOURITES": "Add to favourites",
"ADD_TO_FAVOURITES_HINT": "Save preset and pin it to the app drawer",
"REMOVE_FROM_FAVOURITES": "Remove from favourites",
"EMPTY": "Empty", "EMPTY": "Empty",
"PINNED_APPS": "Pinned applications" "PINNED_APPS": "Pinned applications"
} }

View File

@ -182,7 +182,9 @@
"REMOVE_UNUSED_MODELS": "Estás desactivando la función de voz a texto.\n¿Te gustaría eliminar los modelos de Whisper descargados para liberar espacio?" "REMOVE_UNUSED_MODELS": "Estás desactivando la función de voz a texto.\n¿Te gustaría eliminar los modelos de Whisper descargados para liberar espacio?"
}, },
"WHISPER_MODEL": "Modelo de voz a texto", "WHISPER_MODEL": "Modelo de voz a texto",
"WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes producen\nresultados más precisos, pero consumen más VRAM." "WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes producen\nresultados más precisos, pero consumen más VRAM.",
"DEFAULT_OVERLAY_SCALE": "Tamaño de la superposición predeterminado",
"DEFAULT_OVERLAY_SCALE_HELP": "Genera nuevas capas de superposición con esta escala."
}, },
"HELLO": "¡Hola!", "HELLO": "¡Hola!",
"AUDIO": { "AUDIO": {
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Set de trabajo" "WORKING_SET": "Set de trabajo"
}, },
"DOWNLOAD": "Descargar" "DOWNLOAD": "Descargar",
"SAVED_TO_FAVOURITES": "Guardado en favoritos",
"ADD_TO_FAVOURITES": "Añadir a favoritos",
"ADD_TO_FAVOURITES_HINT": "Guarda el ajuste preestablecido y ánclalo al cajón de aplicaciones",
"REMOVE_FROM_FAVOURITES": "Eliminar de favoritos",
"EMPTY": "Vacío",
"PINNED_APPS": "Aplicaciones ancladas"
} }

View File

@ -203,7 +203,9 @@
"REMOVE_UNUSED_MODELS": "Stai disattivando la funzione di sintesi vocale.\nDesideri rimuovere i modelli Whisper scaricati per liberare spazio?" "REMOVE_UNUSED_MODELS": "Stai disattivando la funzione di sintesi vocale.\nDesideri rimuovere i modelli Whisper scaricati per liberare spazio?"
}, },
"WHISPER_MODEL": "Modello di sintesi vocale (speech-to-text)", "WHISPER_MODEL": "Modello di sintesi vocale (speech-to-text)",
"WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nI modelli più grandi producono risultati\npiù accurati, ma consumano più VRAM." "WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nI modelli più grandi producono risultati\npiù accurati, ma consumano più VRAM.",
"DEFAULT_OVERLAY_SCALE": "Dimensioni overlay predefinite",
"DEFAULT_OVERLAY_SCALE_HELP": "Genera nuovi overlay con questa scala."
}, },
"APPLICATION_LAUNCHER": "Lanciatore applicazioni", "APPLICATION_LAUNCHER": "Lanciatore applicazioni",
"APPLICATION_STARTED": "Applicazione avviata", "APPLICATION_STARTED": "Applicazione avviata",
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Set di lavoro" "WORKING_SET": "Set di lavoro"
}, },
"DOWNLOAD": "Scarica" "DOWNLOAD": "Scarica",
"SAVED_TO_FAVOURITES": "Salvato nei preferiti",
"ADD_TO_FAVOURITES": "Aggiungi ai preferiti",
"ADD_TO_FAVOURITES_HINT": "Salva il preset e fissalo al cassetto delle app",
"REMOVE_FROM_FAVOURITES": "Rimuovi dai preferiti",
"EMPTY": "Vuoto",
"PINNED_APPS": "Applicazioni fissate"
} }

View File

@ -182,7 +182,9 @@
"REMOVE_UNUSED_MODELS": "文字起こし機能をオフにします。\n空き容量を増やすために、ダウンロード済みのモデルファイルを削除しますか" "REMOVE_UNUSED_MODELS": "文字起こし機能をオフにします。\n空き容量を増やすために、ダウンロード済みのモデルファイルを削除しますか"
}, },
"WHISPER_MODEL": "文字起こしモデル", "WHISPER_MODEL": "文字起こしモデル",
"WHISPER_MODEL_HELP": "使用するWhisperモデル。\nモデルが大きいほど精度は向上しますが、\nVRAMの消費量が増加します。" "WHISPER_MODEL_HELP": "使用するWhisperモデル。\nモデルが大きいほど精度は向上しますが、\nVRAMの消費量が増加します。",
"DEFAULT_OVERLAY_SCALE": "デフォルトのオーバーレイサイズ",
"DEFAULT_OVERLAY_SCALE_HELP": "新しいオーバーレイをこのスケールで生成します。"
}, },
"HELLO": "こんにちは!", "HELLO": "こんにちは!",
"AUDIO": { "AUDIO": {
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "ワーキングセット" "WORKING_SET": "ワーキングセット"
}, },
"DOWNLOAD": "ダウンロード" "DOWNLOAD": "ダウンロード",
} "SAVED_TO_FAVOURITES": "お気に入りに保存しました",
"ADD_TO_FAVOURITES": "お気に入りに追加",
"ADD_TO_FAVOURITES_HINT": "プリセットを保存してアプリドロワーにピン留めします",
"REMOVE_FROM_FAVOURITES": "お気に入りから削除",
"EMPTY": "空",
"PINNED_APPS": "ピン留めされたアプリケーション"
}

View File

@ -177,7 +177,9 @@
"REMOVE_UNUSED_MODELS": "Wyłączasz funkcję zamiany mowy na tekst.\nCzy chcesz usunąć pobrane modele Whisper, aby zwolnić miejsce?" "REMOVE_UNUSED_MODELS": "Wyłączasz funkcję zamiany mowy na tekst.\nCzy chcesz usunąć pobrane modele Whisper, aby zwolnić miejsce?"
}, },
"WHISPER_MODEL": "Model mowy na tekst", "WHISPER_MODEL": "Model mowy na tekst",
"WHISPER_MODEL_HELP": "Model Whisper do wykorzystania.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM." "WHISPER_MODEL_HELP": "Model Whisper do wykorzystania.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM.",
"DEFAULT_OVERLAY_SCALE": "Domyślny rozmiar nakładki",
"DEFAULT_OVERLAY_SCALE_HELP": "Twórz nowe nakładki w tej skali."
}, },
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
"APPLICATIONS": "Aplikacje", "APPLICATIONS": "Aplikacje",
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Aktywny zestaw" "WORKING_SET": "Aktywny zestaw"
}, },
"DOWNLOAD": "Pobierz" "DOWNLOAD": "Pobierz",
"SAVED_TO_FAVOURITES": "Dodano do ulubionych",
"ADD_TO_FAVOURITES": "Dodaj do ulubionych",
"ADD_TO_FAVOURITES_HINT": "Zapisz ustawienie i przypnij je do szuflady aplikacji",
"REMOVE_FROM_FAVOURITES": "Usuń z ulubionych",
"EMPTY": "Puste",
"PINNED_APPS": "Przypięte aplikacje"
} }

View File

@ -203,7 +203,9 @@
"REMOVE_UNUSED_MODELS": "您正在关闭语音转文本功能。\n是否删除已下载的 Whisper 模型以释放空间?" "REMOVE_UNUSED_MODELS": "您正在关闭语音转文本功能。\n是否删除已下载的 Whisper 模型以释放空间?"
}, },
"WHISPER_MODEL": "语音转文本模型", "WHISPER_MODEL": "语音转文本模型",
"WHISPER_MODEL_HELP": "要使用的 Whisper 模型。\n模型越大结果越准确\n但消耗的显存 (VRAM) 越多。" "WHISPER_MODEL_HELP": "要使用的 Whisper 模型。\n模型越大结果越准确\n但消耗的显存 (VRAM) 越多。",
"DEFAULT_OVERLAY_SCALE": "默认叠加层大小",
"DEFAULT_OVERLAY_SCALE_HELP": "以该比例生成新的叠加层。"
}, },
"APPLICATION_LAUNCHER": "应用启动器", "APPLICATION_LAUNCHER": "应用启动器",
"APPLICATION_STARTED": "应用已启动", "APPLICATION_STARTED": "应用已启动",
@ -296,5 +298,11 @@
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "工作集" "WORKING_SET": "工作集"
}, },
"DOWNLOAD": "下载" "DOWNLOAD": "下载",
"SAVED_TO_FAVOURITES": "已保存至收藏夹",
"ADD_TO_FAVOURITES": "添加到收藏",
"ADD_TO_FAVOURITES_HINT": "保存预设并将其固定到应用抽屉",
"REMOVE_FROM_FAVOURITES": "从收藏中移除",
"EMPTY": "空",
"PINNED_APPS": "已固定应用程序"
} }

View File

@ -8,7 +8,7 @@ use wgui::{
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams}, parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
task::Tasks, task::Tasks,
}; };
use wlx_common::desktop_finder::DesktopEntry; use wlx_common::{config::PinnedApp, desktop_finder::DesktopEntry};
use crate::{ use crate::{
frontend::{Frontend, FrontendTasks}, frontend::{Frontend, FrontendTasks},
@ -68,10 +68,11 @@ fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [DesktopEntry]) -> Op
impl<T> TabApps<T> { impl<T> TabApps<T> {
fn refresh_pinned_apps(&self, state: &mut State, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> { fn refresh_pinned_apps(&self, state: &mut State, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
frontend.layout.remove_children(self.pinned_apps_parent); frontend.layout.remove_children(self.pinned_apps_parent);
let globals = frontend.globals.clone();
let mut stale_entries = Vec::<Rc<str>>::new(); let mut stale_entries = Vec::<Rc<str>>::new();
let mut pinned_desktop_entries = Vec::<&DesktopEntry>::new(); let mut pinned_desktop_entries = Vec::<(PinnedApp, &DesktopEntry)>::new();
// collect pinned desktop entries // collect pinned desktop entries
{ {
@ -82,7 +83,7 @@ impl<T> TabApps<T> {
continue; continue;
}; };
pinned_desktop_entries.push(desktop_entry); pinned_desktop_entries.push((pinned_app.clone(), desktop_entry));
} }
// cleanup: // cleanup:
// remove non-existent app ids from pinned apps // remove non-existent app ids from pinned apps
@ -105,14 +106,31 @@ impl<T> TabApps<T> {
} }
// mount pinned desktop entries // mount pinned desktop entries
for desktop_entry in pinned_desktop_entries { for (pinned_app, desktop_entry) in pinned_desktop_entries {
mount_entry( let tooltip_string = format!(
"{}\n{}\n{}",
pinned_app.compositor_mode.as_ref(),
pinned_app.orientation_mode.as_ref(),
pinned_app.res_mode.as_ref()
);
let button = mount_entry(
frontend, frontend,
&mut state.parser_state, &mut state.parser_state,
&doc_params(frontend.globals.clone()), &doc_params(frontend.globals.clone()),
self.pinned_apps_parent, self.pinned_apps_parent,
desktop_entry, desktop_entry,
Some(tooltip_string),
)?; )?;
button.on_click(on_app_click(
frontend.tasks.clone(),
self.tasks.clone(),
globals.clone(),
desktop_entry.clone(),
self.state.clone(),
Some(pinned_app.clone()),
));
} }
Ok(()) Ok(())
@ -134,6 +152,7 @@ fn on_app_click(
globals: WguiGlobals, globals: WguiGlobals,
entry: DesktopEntry, entry: DesktopEntry,
state: Rc<RefCell<State>>, state: Rc<RefCell<State>>,
pinned_app: Option<PinnedApp>,
) -> ButtonClickCallback { ) -> ButtonClickCallback {
Rc::new(move |_common, _evt| { Rc::new(move |_common, _evt| {
views::app_launcher::mount_popup( views::app_launcher::mount_popup(
@ -142,6 +161,7 @@ fn on_app_click(
entry.clone(), entry.clone(),
state.borrow_mut().view_launcher.clone(), state.borrow_mut().view_launcher.clone(),
tasks.make_callback_box(Task::RefreshPinnedApps), tasks.make_callback_box(Task::RefreshPinnedApps),
pinned_app.clone(),
); );
Ok(()) Ok(())
}) })
@ -317,10 +337,15 @@ fn mount_entry<T>(
doc_params: &ParseDocumentParams, doc_params: &ParseDocumentParams,
id_parent: WidgetID, id_parent: WidgetID,
entry: &DesktopEntry, entry: &DesktopEntry,
tooltip: Option<String>,
) -> anyhow::Result<Rc<ComponentButton>> { ) -> anyhow::Result<Rc<ComponentButton>> {
{ {
let mut params = TemplateParams::new(); let mut params = TemplateParams::new();
if let Some(tooltip) = tooltip {
params.insert_str("tooltip", tooltip);
};
// entry icon // entry icon
params.insert_rc( params.insert_rc(
"src_ext", "src_ext",
@ -383,6 +408,7 @@ impl AppList {
&doc_params(globals.clone()), &doc_params(globals.clone()),
self.list_parent.id, self.list_parent.id,
&entry, &entry,
None,
)?; )?;
button.on_click(on_app_click( button.on_click(on_app_click(
@ -391,6 +417,7 @@ impl AppList {
globals.clone(), globals.clone(),
entry.clone(), entry.clone(),
rc_state.clone(), rc_state.clone(),
None,
)); ));
} else { } else {
break; break;

View File

@ -3,7 +3,9 @@ use strum::VariantNames;
use wayvr_ipc::packet_client::{PositionMode, WvrProcessLaunchParams}; use wayvr_ipc::packet_client::{PositionMode, WvrProcessLaunchParams};
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
components::{button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup}, components::{
ComponentTrait, button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup,
},
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
@ -31,6 +33,7 @@ enum Task {
SetAutoStart(bool), SetAutoStart(bool),
Launch, Launch,
PinApp, PinApp,
UnpinApp,
} }
struct LaunchParams<'a, T> { struct LaunchParams<'a, T> {
@ -67,10 +70,12 @@ pub struct View {
res_mode: AppResMode, res_mode: AppResMode,
orientation_mode: AppOrientationMode, orientation_mode: AppOrientationMode,
pinned_app: Option<PinnedApp>,
auto_start: bool, auto_start: bool,
on_close_request: Option<Box<dyn FnOnce()>>, on_close_request: Option<Box<dyn FnOnce()>>,
on_app_pinned: Option<Box<dyn Fn()>>, on_app_pins_changed: Option<Box<dyn Fn()>>,
} }
pub struct Params<'a> { pub struct Params<'a> {
@ -81,7 +86,8 @@ pub struct Params<'a> {
pub config: &'a GeneralConfig, pub config: &'a GeneralConfig,
pub frontend_tasks: &'a FrontendTasks, pub frontend_tasks: &'a FrontendTasks,
pub on_close_request: Box<dyn FnOnce()>, pub on_close_request: Box<dyn FnOnce()>,
pub on_app_pinned: Box<dyn Fn()>, pub on_app_pins_changed: Box<dyn Fn()>,
pub pinned_app: Option<PinnedApp>,
} }
impl ViewTrait for View { impl ViewTrait for View {
@ -108,6 +114,7 @@ impl View {
let btn_launch = state.fetch_component_as::<ComponentButton>("btn_launch")?; let btn_launch = state.fetch_component_as::<ComponentButton>("btn_launch")?;
let btn_pin = state.fetch_component_as::<ComponentButton>("btn_pin")?; let btn_pin = state.fetch_component_as::<ComponentButton>("btn_pin")?;
let btn_unpin = state.fetch_component_as::<ComponentButton>("btn_unpin")?;
{ {
let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?; let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?;
@ -121,7 +128,16 @@ impl View {
let tasks = Tasks::new(); let tasks = Tasks::new();
tasks.handle_button(&btn_launch, Task::Launch); tasks.handle_button(&btn_launch, Task::Launch);
tasks.handle_button(&btn_pin, Task::PinApp);
if params.pinned_app.is_some() {
// "Unpin app"
tasks.handle_button(&btn_unpin, Task::UnpinApp);
params.layout.remove_widget(btn_pin.base().get_id());
} else {
// "Pin app"
tasks.handle_button(&btn_pin, Task::PinApp);
params.layout.remove_widget(btn_unpin.base().get_id());
}
let id_icon_parent = state.get_widget_id("icon_parent")?; let id_icon_parent = state.get_widget_id("icon_parent")?;
@ -138,28 +154,35 @@ impl View {
)?; )?;
} }
let compositor_mode = if params.config.xwayland_by_default { let compositor_mode = match &params.pinned_app {
AppCompositorMode::Cage Some(pinned_app) => pinned_app.compositor_mode,
} else { None => {
AppCompositorMode::Native if params.config.xwayland_by_default {
AppCompositorMode::Cage
} else {
AppCompositorMode::Native
}
}
}; };
radio_compositor.set_value_simple(compositor_mode.as_ref())?;
tasks.push(Task::SetCompositor(compositor_mode));
let res_mode = AppResMode::Res1080;
// TODO: configurable defaults ? // TODO: configurable defaults ?
//radio_res.set_value(res_mode.as_ref())?; let mut res_mode = AppResMode::Res1080;
//tasks.push(Task::SetRes(res_mode)); let mut orientation_mode = AppOrientationMode::Wide;
let orientation_mode = AppOrientationMode::Wide;
// TODO: configurable defaults ?
//radio_orientation.set_value(orientation_mode.as_ref())?;
//tasks.push(Task::SetOrientation(orientation_mode));
let pos_mode = AppPosMode::Anchored; let pos_mode = AppPosMode::Anchored;
// TODO: configurable defaults ?
//radio_pos.set_value(pos_mode.as_ref())?; if let Some(pinned_app) = &params.pinned_app {
//tasks.push(Task::SetPos(pos_mode)); res_mode = pinned_app.res_mode;
orientation_mode = pinned_app.orientation_mode;
}
// update radios
{
let mut common = params.layout.common();
// TODO: pos_mode is disabled as for now
radio_compositor.set_value(&mut common, compositor_mode.as_ref())?;
radio_res.set_value(&mut common, res_mode.as_ref())?;
radio_orientation.set_value(&mut common, orientation_mode.as_ref())?;
}
let auto_start = false; let auto_start = false;
@ -269,7 +292,8 @@ impl View {
frontend_tasks: params.frontend_tasks.clone(), frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals.clone(), globals: params.globals.clone(),
on_close_request: Some(params.on_close_request), on_close_request: Some(params.on_close_request),
on_app_pinned: Some(params.on_app_pinned), on_app_pins_changed: Some(params.on_app_pins_changed),
pinned_app: params.pinned_app,
}) })
} }
@ -290,6 +314,10 @@ impl View {
self.action_pin_app(interface.general_config(data)); self.action_pin_app(interface.general_config(data));
interface.config_changed(data, Default::default()); interface.config_changed(data, Default::default());
} }
Task::UnpinApp => {
self.action_unpin_app(interface.general_config(data));
interface.config_changed(data, Default::default());
}
} }
} }
} }
@ -303,6 +331,20 @@ impl View {
} }
} }
fn action_unpin_app(&mut self, config: &mut GeneralConfig) {
let Some(pinned_app) = &self.pinned_app else {
unreachable!();
};
self.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Save));
config.pinned_apps.retain(|p| p != pinned_app);
if let Some(c) = &self.on_app_pins_changed {
c();
}
self.close();
}
fn action_pin_app(&mut self, config: &mut GeneralConfig) { fn action_pin_app(&mut self, config: &mut GeneralConfig) {
self self
.frontend_tasks .frontend_tasks
@ -319,7 +361,7 @@ impl View {
res_mode: self.res_mode, res_mode: self.res_mode,
}); });
if let Some(c) = &self.on_app_pinned { if let Some(c) = &self.on_app_pins_changed {
c(); c();
} }
@ -450,7 +492,8 @@ pub fn mount_popup(
globals: WguiGlobals, globals: WguiGlobals,
entry: DesktopEntry, entry: DesktopEntry,
popup: PopupHolder<View>, popup: PopupHolder<View>,
on_app_pinned: Box<dyn Fn()>, on_app_pins_changed: Box<dyn Fn()>,
pinned_app: Option<PinnedApp>,
) { ) {
frontend_tasks frontend_tasks
.clone() .clone()
@ -466,7 +509,8 @@ pub fn mount_popup(
frontend_tasks: &frontend_tasks, frontend_tasks: &frontend_tasks,
config: data.config, config: data.config,
on_close_request, on_close_request,
on_app_pinned, on_app_pins_changed,
pinned_app,
})?; })?;
popup.set_view(data.handle, view, None); popup.set_view(data.handle, view, None);

View File

@ -86,17 +86,6 @@ impl ComponentRadioGroup {
self.state.borrow().selected.as_ref().and_then(|b| b.get_value()) self.state.borrow().selected.as_ref().and_then(|b| b.get_value())
} }
pub fn set_value_simple(&self, value: &str) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut();
for radio_box in &state.radio_boxes {
if radio_box.get_value().is_some_and(|box_val| &*box_val == value) {
state.selected = Some(radio_box.clone());
return Ok(());
}
}
anyhow::bail!("No RadioBox found with value '{value}'")
}
pub fn set_value(&self, common: &mut CallbackDataCommon, value: &str) -> anyhow::Result<()> { pub fn set_value(&self, common: &mut CallbackDataCommon, value: &str) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
let mut selected = None; let mut selected = None;

View File

@ -32,10 +32,11 @@ pub struct SamplePlayer {
fn audio_thread(receiver: Receiver<AudioThreadMessage>) { fn audio_thread(receiver: Receiver<AudioThreadMessage>) {
log::debug!("audio_thread starting"); log::debug!("audio_thread starting");
let Ok(stream) = rodio::DeviceSinkBuilder::open_default_sink() else { let Ok(mut stream) = rodio::DeviceSinkBuilder::open_default_sink() else {
log::error!("Failed to open audio stream. Audio will not work."); log::error!("Failed to open audio stream. Audio will not work.");
return; return;
}; };
stream.log_on_drop(false);
while let Ok(msg) = receiver.recv() { while let Ok(msg) = receiver.recv() {
match msg { match msg {

View File

@ -151,13 +151,13 @@ pub struct SerializedWindowSet {
pub hidden_overlays: SerializedWindowStates, pub hidden_overlays: SerializedWindowStates,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
pub enum AppPosMode { pub enum AppPosMode {
Floating, Floating,
Anchored, Anchored,
Static, Static,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
pub enum AppOrientationMode { pub enum AppOrientationMode {
Wide, Wide,
SemiWide, SemiWide,
@ -166,13 +166,13 @@ pub enum AppOrientationMode {
Tall, Tall,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
pub enum AppCompositorMode { pub enum AppCompositorMode {
Cage, Cage,
Native, Native,
} }
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
pub enum AppResMode { pub enum AppResMode {
Res1440, Res1440,
Res1080, Res1080,
@ -180,7 +180,7 @@ pub enum AppResMode {
Res480, Res480,
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct PinnedApp { pub struct PinnedApp {
pub app_id: Rc<str>, // desktop entry app id, for example "libreoffice-draw" pub app_id: Rc<str>, // desktop entry app id, for example "libreoffice-draw"
pub compositor_mode: AppCompositorMode, pub compositor_mode: AppCompositorMode,