diff --git a/dash-frontend/assets/gui/tab/home.xml b/dash-frontend/assets/gui/tab/home.xml
index 0f3d0c38..1a66cabe 100644
--- a/dash-frontend/assets/gui/tab/home.xml
+++ b/dash-frontend/assets/gui/tab/home.xml
@@ -7,12 +7,17 @@
justify_content="center"
align_items="center"
flex_grow="1"
- gap="24">
+ gap="8"
+ >
+
diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json
index 7399497d..a8f282a2 100644
--- a/dash-frontend/assets/lang/de.json
+++ b/dash-frontend/assets/lang/de.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "Wir sind ein kleines Team aus 2 Entwicklern, die WayVR in ihrer Freizeit bauen. Wenn dir die Software gefällt, unterstütze uns bitte – jede Spende hilft uns dabei, weiterzumachen.",
"THANK_YOU_WAYVR": "Danke vom WayVR-Team!",
"BUTTON_TOOLTIP": "Via Open Collective spenden",
- "CURRENT_SUPPORTERS": "Aktuelle Unterstützer"
+ "CURRENT_SUPPORTERS": "Aktuelle Unterstützer",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "Unterstützt von {USER}|Besonderer Dank an {USER}|Gesponsert von {USER}|Ermöglicht durch {USER}|Getragen von Community-Mitgliedern wie {USER}|Mit Hilfe von {USER}|Aufrechterhalten durch {USER}|Ein Beitrag von {USER}|Finanziert durch {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "Kaffee spendiert von {USER}|Angetrieben von {USER}|Ein großes Shoutout an {USER}!|{USER} hält uns am Laufen|Mit {USER} wachsen wir weiter|{USER} hält die Server stabil|In voller Dankbarkeit gegenüber {USER}|Power-up durch {USER}|Unterstützt von Freunden wie {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Unterstützt von Legenden wie {USER}|Koffein-Zufuhr gesichert durch {USER}|Eine echte Legende: {USER}|Unser Held des Tages: {USER}|Ein absoluter Superstar: {USER}|Bezahlt gerade die Stromrechnung: {USER}|Der Retter unserer Nerven: {USER}|Der Ruhepol im Chaos: {USER}|Kaffee-zu-Code-Konverter: {USER}|Hält die Production stabil: {USER}|Die eine und einzige(r): {USER}|Bezwinger des dmabuf-Buffers: {USER}"
},
"DOWNLOAD": "Herunterladen",
"DOWNLOADER": "Downloader",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "Voreinstellung speichern und an die App-Schublade anheften",
"REMOVE_FROM_FAVOURITES": "Aus angehefteten Anwendungen entfernen",
"EMPTY": "Leer"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/assets/lang/en.csv b/dash-frontend/assets/lang/en.csv
index 0be7d081..4f6f43cf 100644
--- a/dash-frontend/assets/lang/en.csv
+++ b/dash-frontend/assets/lang/en.csv
@@ -272,6 +272,9 @@ DONATE.DESCRIPTION,"We're a small team of 2 developers building WayVR in our fre
DONATE.THANK_YOU_WAYVR,Thank you from the WayVR Team!,Thank you message displayed on the donate tab.
DONATE.BUTTON_TOOLTIP,Donate via Open Collective,Tooltip on the donate button on the donate tab.
DONATE.CURRENT_SUPPORTERS,Current Supporters,Section header on the donate tab listing supporters.
+DONATE.BROUGHT_TO_YOU_VARIANTS_OFTEN,Supported by {USER}|Special thanks to {USER}|Sponsored by {USER}|Backed by {USER}|Driven by community members like {USER}|Made possible by {USER}|Sustained by {USER}|Contributed by {USER}|Funded by {USER},This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it.
+DONATE.BROUGHT_TO_YOU_VARIANTS_RARE,Coffee provided by {USER}|Fuelled by {USER}|Shoutout to {USER}!|Keeping us going: {USER}|Helping us grow: {USER}|Keeping the servers running: {USER}|With gratitude to {USER}|Powered by {USER}|Supported by friends like {USER},This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it (rare versions).
+DONATE.BROUGHT_TO_YOU_VARIANTS_VERY_RARE,Supported by legends like {USER}|Caffeine supplied by {USER}|A true legend: {USER}|Our hero of the day: {USER}|An absolute superstar: {USER}|Paying for the electricity bill: {USER}|The savior of our sanity: {USER}|Mental stability sponsored by: {USER}|Caffeine-to-code converter provided by {USER}|Keeping the production stable: {USER}|The one and only: {USER}|Passing dmabuf buffers: {USER},This text will show up a random person from donations page - line below this string will display the supporter user. There are many variants of it (rare versions).
DOWNLOAD,Download,Button in the Whisper model download dialog to confirm downloading a model.
DOWNLOADER,Downloader,Window title for the file download popup.
WELCOME.ALL_SET,You're all set!,Main heading on the final welcome/tutorial page.
diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json
index c25800e7..892cff8b 100644
--- a/dash-frontend/assets/lang/en.json
+++ b/dash-frontend/assets/lang/en.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "We're a small team of 2 developers building WayVR in our free time. If you enjoy using this software, please consider supporting us - every donation helps us keep going.",
"THANK_YOU_WAYVR": "Thank you from the WayVR Team!",
"BUTTON_TOOLTIP": "Donate via Open Collective",
- "CURRENT_SUPPORTERS": "Current Supporters"
+ "CURRENT_SUPPORTERS": "Current Supporters",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "Supported by {USER}|Special thanks to {USER}|Sponsored by {USER}|Backed by {USER}|Driven by community members like {USER}|Made possible by {USER}|Sustained by {USER}|Contributed by {USER}|Funded by {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "Coffee provided by {USER}|Fuelled by {USER}|Shoutout to {USER}!|Keeping us going: {USER}|Helping us grow: {USER}|Keeping the servers running: {USER}|With gratitude to {USER}|Powered by {USER}|Supported by friends like {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Supported by legends like {USER}|Caffeine supplied by {USER}|A true legend: {USER}|Our hero of the day: {USER}|An absolute superstar: {USER}|Paying for the electricity bill: {USER}|The savior of our sanity: {USER}|Leading the way: {USER}|Caffeine-to-code converter provided by {USER}|Keeping the production stable: {USER}|The one and only: {USER}|Passing dmabuf buffers: {USER}"
},
"DOWNLOAD": "Download",
"DOWNLOADER": "Downloader",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "Save preset and pin it to the app drawer",
"REMOVE_FROM_FAVOURITES": "Remove from pinned apps",
"EMPTY": "Empty"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json
index c9b5b492..6fea379a 100644
--- a/dash-frontend/assets/lang/es.json
+++ b/dash-frontend/assets/lang/es.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "Somos un pequeño equipo de 2 desarrolladores creando WayVR en nuestro tiempo libre. Si disfrutas usando este software, considera apoyarnos; cada donación nos ayuda a seguir adelante.",
"THANK_YOU_WAYVR": "¡Gracias del equipo de WayVR!",
"BUTTON_TOOLTIP": "Dona a través de Open Collective",
- "CURRENT_SUPPORTERS": "Colaboradores actuales"
+ "CURRENT_SUPPORTERS": "Colaboradores actuales",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "Apoyado por {USER}|Gracias a {USER}|Con el apoyo de {USER}|Patrocinado por {USER}|Un aporte especial de {USER}|Impulsado por {USER}|Haciendo esto posible: {USER}|Sostenible gracias a {USER}|Un agradecimiento a {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "Un gran impulso de {USER}|Todo es gracias a {USER}|Con todo el cariño de {USER}|El motor de hoy: {USER}|Sumando valor con {USER}|Haciendo crecer el proyecto: {USER}|De corazón, gracias a {USER}|Con el respaldo de {USER}|La chispa del día: {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "¡Un nivel leyenda: {USER}!|Energía pura gracias a {USER}|Nuestra estrella del día: {USER}|El MVP de hoy: {USER}|Rescatando el día: {USER}|Sosteniendo la aplicación: {USER}|El combustible secreto: {USER}|Manteniendo la cordura: {USER}|Conversor de café en código: {USER}|Salvando la producción: {USER}|¡Simplemente increíble: {USER}!|El corazón de todo: {USER}"
},
"DOWNLOAD": "Descargar",
"DOWNLOADER": "Descargador",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "Guardar ajuste y fijarlo al cajón de aplicaciones",
"REMOVE_FROM_FAVOURITES": "Quitar de aplicaciones fijadas",
"EMPTY": "Vacío"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json
index 242d1ccb..4fffe6f5 100644
--- a/dash-frontend/assets/lang/it.json
+++ b/dash-frontend/assets/lang/it.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "Siamo un piccolo team di 2 sviluppatori che realizza WayVR nel tempo libero. Se ti piace usare questo software, considera di supportarci: ogni donazione ci aiuta a continuare.",
"THANK_YOU_WAYVR": "Grazie dal team di WayVR!",
"BUTTON_TOOLTIP": "Donazione tramite Open Collective",
- "CURRENT_SUPPORTERS": "Supporter attuali"
+ "CURRENT_SUPPORTERS": "Supporter attuali",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "Supportato da {USER}|Grazie speciali a {USER}|Sponsorizzato da {USER}|Contributo di {USER}|Sostenuto da membri come {USER}|Reso possibile da {USER}|Sostenuto da {USER}|Grazie a {USER}|Alimentato da {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "Caffè offerto da {USER}|Energia pura grazie a {USER}|Un grande applauso a {USER}!|Ci tiene in piedi: {USER}|Ci aiuta a crescere: {USER}|Mantiene i server accesi: {USER}|Con immensa gratitudine a {USER}|Spinto dalla forza di {USER}|Supportato da amici come {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Supportato da leggende come {USER}|Caffeina fornita da {USER}|Una vera leggenda: {USER}|Il nostro eroe del giorno: {USER}|Una superstar assoluta: {USER}|Paga la bolletta della luce: {USER}|Il motore della nostra creatività: {USER}|Il navigatore del nostro percorso: {USER}|Convertitore caffeina-codice fornito da {USER}|Mantiene la produzione stabile: {USER}|L'unico e inimitabile: {USER}|Maestro dei buffer dmabuf: {USER}"
},
"DOWNLOAD": "Scarica",
"DOWNLOADER": "Download",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "Salva il preset e fissalo al cassetto delle app",
"REMOVE_FROM_FAVOURITES": "Rimuovi dalle app fissate",
"EMPTY": "Vuoto"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json
index 321a2979..4aa9e508 100644
--- a/dash-frontend/assets/lang/ja.json
+++ b/dash-frontend/assets/lang/ja.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "WayVRは、2人の開発者が空き時間を利用して作っている小規模なプロジェクトです。もしこのソフトを気に入っていただけたら、ぜひサポートをお願いします。皆様の寄付が開発の大きな励みになります。",
"THANK_YOU_WAYVR": "WayVRチームより、感謝を込めて!",
"BUTTON_TOOLTIP": "Open Collectiveで寄付する",
- "CURRENT_SUPPORTERS": "現在のサポーター"
+ "CURRENT_SUPPORTERS": "現在のサポーター",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "{USER}さんにサポートされています|{USER}さんに感謝!|{USER}さんのご支援で成り立っています|{USER}さんが支えてくれています|{USER}さんの後押しのおかげです|{USER}さんが後援しています|{USER}さんのご協力に感謝|{USER}さんが応援してくれています|本日のサポーターは{USER}さんです",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "{USER}さんの優しさに感謝|{USER}さんが背中を押してくれました|{USER}さんの心強いサポート|{USER}さんが彩りを添えてくれました|{USER}さんのおかげで前進しています|{USER}さんの温かいご支援|{USER}さんが笑顔を届けてくれました|{USER}さんが道を照らしてくれました|{USER}さんに心から感謝して",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "今日のMVPは{USER}さん!|{USER}さんが神降臨!|{USER}さんが限界突破させてくれた!|{USER}さんのパワーで爆走中!|救世主、現る:{USER}さん!|{USER}さんが文明を支えています|{USER}さんの情熱が燃料です!|本日のスーパーヒーロー:{USER}さん!|{USER}さんがバグをなぎ倒してくれた!|{USER}さんの応援でテンションMAX!|全宇宙から{USER}さんに拍手を!"
},
"DOWNLOAD": "ダウンロード",
"DOWNLOADER": "ダウンローダー",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "プリセットを保存し、アプリドロワーに固定する",
"REMOVE_FROM_FAVOURITES": "固定済みアプリから外す",
"EMPTY": "なし"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json
index 8463f18e..a0c382b6 100644
--- a/dash-frontend/assets/lang/pl.json
+++ b/dash-frontend/assets/lang/pl.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "Jesteśmy małym zespołem dwóch programistów, którzy tworzą WayVR w wolnym czasie. Jeśli lubisz korzystać z tego programu, rozważ wsparcie nas finansowo – każda darowizna pomaga nam działać dalej.",
"THANK_YOU_WAYVR": "Dzięki od zespołu WayVR!",
"BUTTON_TOOLTIP": "Wesprzyj projekt przez Open Collective",
- "CURRENT_SUPPORTERS": "Obecni darczyńcy"
+ "CURRENT_SUPPORTERS": "Obecni darczyńcy",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "Wspierane przez {USER}|Dzięki dla {USER}|Sponsor: {USER}|Z pomocą {USER}|Wkład od {USER}|Umożliwione przez {USER}|Dzięki wsparciu {USER}|Patron: {USER}|Działamy dzięki {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "Kawa od {USER}|Energia dostarczona przez {USER}|Wielkie brawa dla {USER}!|Dzięki {USER} idziemy do przodu|Razem z {USER} rośniemy w siłę|Serwery napędzane przez {USER}|Z ogromną wdzięcznością dla {USER}|Napędzane przez {USER}|Wsparcie od przyjaciół takich jak {USER}",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "Wspierane przez legendę: {USER}|Dostawa kofeiny od {USER}|Prawdziwa legenda: {USER}|Gwiazda dnia: {USER}|Absolutna supergwiazda: {USER}|Opłaca rachunki za prąd: {USER}|Ludzie, którzy czynią to możliwe: {USER}|Sponsor spokoju ducha: {USER}|Konwerter kawy na kod: {USER}|Strażnik stabilności produkcji: {USER}|Jedyny w swoim rodzaju: {USER}|Przesyła bufory dmabuf: {USER}"
},
"DOWNLOAD": "Pobierz",
"DOWNLOADER": "Menedżer pobierania",
diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json
index b95e5eeb..94862c4d 100644
--- a/dash-frontend/assets/lang/zh_CN.json
+++ b/dash-frontend/assets/lang/zh_CN.json
@@ -271,7 +271,10 @@
"DESCRIPTION": "我们是由两名开发者组成的小团队,在业余时间构建 WayVR。如果你喜欢这款软件,请考虑支持我们 —— 每一份捐赠都能帮助我们坚持下去。",
"THANK_YOU_WAYVR": "WayVR 团队向您致谢!",
"BUTTON_TOOLTIP": "通过 Open Collective 捐赠",
- "CURRENT_SUPPORTERS": "当前支持者"
+ "CURRENT_SUPPORTERS": "当前支持者",
+ "BROUGHT_TO_YOU_VARIANTS_OFTEN": "由 {USER} 赞助|感谢 {USER} 的支持|由 {USER} 助力|来自 {USER} 的贡献|{USER} 让这一切成为可能|由 {USER} 维持运行|特别感谢 {USER}|{USER} 给予了支持|由 {USER} 驱动",
+ "BROUGHT_TO_YOU_VARIANTS_RARE": "感谢 {USER} 的温暖支持|{USER} 为我们注入了能量|由 {USER} 助力成长|感谢 {USER} 的无私奉献|{USER} 让今天变得更美好|今日的动力源泉:{USER}|由 {USER} 悉心支持|感恩 {USER} 的陪伴|{USER} 是今天的闪光点",
+ "BROUGHT_TO_YOU_VARIANTS_VERY_RARE": "今日之神:{USER}|全场 MVP:{USER}|{USER} 拯救了世界|给 {USER} 递上一朵小红花|由 {USER} 承包了今天的快乐|{USER} 承包了今日的电费|代码之光:{USER}|{USER} 拯救了我们的发际线|简直是超级英雄:{USER}|{USER} 让生产环境稳如老狗|唯一且无可替代的:{USER}|{USER} 是今天的顶梁柱"
},
"DOWNLOAD": "下载",
"DOWNLOADER": "下载管理器",
@@ -343,4 +346,4 @@
"ADD_TO_FAVOURITES_HINT": "保存预设并将其固定到应用抽屉",
"REMOVE_FROM_FAVOURITES": "从固定应用中移除",
"EMPTY": "暂无"
-}
\ No newline at end of file
+}
diff --git a/dash-frontend/src/tab/donate.rs b/dash-frontend/src/tab/donate.rs
index 4ab8c0b2..e934fd8b 100644
--- a/dash-frontend/src/tab/donate.rs
+++ b/dash-frontend/src/tab/donate.rs
@@ -4,19 +4,23 @@ use wgui::{
assets::AssetPath,
components::button::ComponentButton,
globals::WguiGlobals,
- layout::WidgetID,
- parser::{Fetchable, ParseDocumentParams, ParserState},
+ layout::{Layout, WidgetID},
+ parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
+ taffy::{self, style_helpers::length},
task::Tasks,
+ widget::div::WidgetDiv,
};
use crate::{
frontend::{Frontend, FrontendTask},
tab::{Tab, TabType},
+ util::cached_fetcher,
};
-#[derive(Clone)]
#[allow(clippy::enum_variant_names)]
-enum Task {}
+enum Task {
+ SetSupporters(cached_fetcher::Supporters),
+}
pub struct TabDonate {
#[allow(dead_code)]
@@ -24,6 +28,8 @@ pub struct TabDonate {
marker: PhantomData,
#[allow(dead_code)]
tasks: Tasks,
+
+ id_current_supporters: WidgetID,
}
impl Tab for TabDonate {
@@ -31,10 +37,14 @@ impl Tab for TabDonate {
TabType::Donate
}
- fn update(&mut self, _frontend: &mut Frontend, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
- // for task in self.tasks.drain() {
- // match task {}
- // }
+ fn update(&mut self, frontend: &mut Frontend, _time_ms: u32, _user_data: &mut T) -> anyhow::Result<()> {
+ while !self.tasks.is_empty() {
+ for task in self.tasks.drain() {
+ match task {
+ Task::SetSupporters(supporters) => self.set_supporters(&mut frontend.layout, supporters)?,
+ }
+ }
+ }
Ok(())
}
@@ -48,9 +58,76 @@ fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
}
}
+async fn request_supporters(tasks: Tasks) {
+ if let Some(supporters) = cached_fetcher::request_supporters().await {
+ tasks.push(Task::SetSupporters(supporters))
+ }
+}
+
+fn tier_pretty_print(tier: &str) -> String {
+ format!("{} Tier", wlx_common::locale::capitalize_string(tier))
+}
+
+fn tier_color(tier: &str) -> &'static str {
+ match tier {
+ "platinum" => "#aaffff",
+ "gold" => "#ffffaa",
+ "silver" => "#cccccc",
+ "bronze" => "#ffaa66",
+ _ => "on_background",
+ }
+}
+
impl TabDonate {
+ fn set_supporters(&mut self, layout: &mut Layout, supporters: cached_fetcher::Supporters) -> anyhow::Result<()> {
+ let globals = layout.state.globals.clone();
+ layout.remove_children(self.id_current_supporters);
+
+ let mut current_tier = "";
+ let mut tier_parent = WidgetID::default();
+
+ for supporter in &supporters.supporters {
+ if supporter.tier != current_tier {
+ current_tier = &supporter.tier;
+
+ let mut params = TemplateParams::new();
+ params.insert_str("text", tier_pretty_print(&supporter.tier));
+ params.insert("color", tier_color(&supporter.tier));
+ self.state.realize_template(
+ &doc_params(&globals),
+ "TierCell",
+ layout,
+ self.id_current_supporters,
+ params,
+ )?;
+
+ tier_parent = layout
+ .add_child(
+ self.id_current_supporters,
+ WidgetDiv::create(),
+ taffy::Style {
+ gap: length(8.0_f32),
+ flex_wrap: taffy::FlexWrap::Wrap,
+ ..Default::default()
+ },
+ )?
+ .0
+ .id;
+ }
+
+ let mut params = TemplateParams::new();
+ params.insert("username", &supporter.username);
+ self
+ .state
+ .realize_template(&doc_params(&globals), "SupporterCell", layout, tier_parent, params)?;
+ }
+
+ Ok(())
+ }
+
pub fn new(frontend: &mut Frontend, parent_id: WidgetID, _data: &mut T) -> anyhow::Result {
let state = wgui::parser::parse_from_assets(&doc_params(&frontend.globals), &mut frontend.layout, parent_id)?;
+ let id_current_supporters = state.get_widget_id("current_supporters")?;
frontend.tasks.handle_button(
&state.fetch_component_as::("btn_donate")?,
@@ -59,10 +136,13 @@ impl TabDonate {
let tasks = Tasks::::new();
+ frontend.executor.spawn(request_supporters(tasks.clone())).detach();
+
Ok(Self {
state,
marker: PhantomData,
tasks,
+ id_current_supporters,
})
}
}
diff --git a/dash-frontend/src/tab/home.rs b/dash-frontend/src/tab/home.rs
index b6b6a03e..abc9f0e0 100644
--- a/dash-frontend/src/tab/home.rs
+++ b/dash-frontend/src/tab/home.rs
@@ -1,11 +1,13 @@
-use std::marker::PhantomData;
+use std::{marker::PhantomData, rc::Rc};
use wgui::{
+ animation::{Animation, AnimationEasing},
assets::AssetPath,
- components::button::ComponentButton,
+ color::{WguiColor, WguiColorName, WguiNamedColor},
+ components::{ComponentTrait, button::ComponentButton},
event::CallbackDataCommon,
i18n::Translation,
- layout::{Widget, WidgetID},
+ layout::{LayoutTask, LayoutTasks, Widget, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};
@@ -14,6 +16,7 @@ use wlx_common::config::GeneralConfig;
use crate::{
frontend::{Frontend, FrontendTask},
tab::{Tab, TabType},
+ util::cached_fetcher,
various,
};
@@ -29,14 +32,91 @@ impl Tab for TabHome {
}
}
-fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, config: &GeneralConfig) {
- let mut username = various::get_username();
- // first character as uppercase
- if let Some(first) = username.chars().next() {
- let first = first.to_uppercase().to_string();
- username.replace_range(0..1, &first);
+fn get_supporter_anim(
+ btn: Rc,
+ tasks: LayoutTasks,
+ supporters: Rc,
+ prev_supporter_name: String,
+) -> Animation {
+ let username = loop {
+ let random_supporter = &supporters.supporters[rand::random_range(0..supporters.supporters.len() - 1)];
+ let username = random_supporter.username.clone();
+ if username != prev_supporter_name {
+ break username;
+ }
+ };
+
+ const FADE_SPEED: f32 = 10.0;
+
+ Animation::new_ex(
+ btn.base().get_id(),
+ 1234,
+ 480,
+ AnimationEasing::Linear,
+ Box::new(move |common, data| {
+ let opacity_in = f32::clamp(data.pos * FADE_SPEED, 0.0, 1.0);
+ let opacity_out = f32::clamp((1.0 - data.pos) * FADE_SPEED, 0.0, 1.0);
+ let opacity = f32::min(opacity_in, opacity_out);
+
+ btn.set_text_color(
+ common,
+ WguiColor::Named(WguiNamedColor::with_alpha(WguiColorName::OnPrimary, opacity)),
+ );
+
+ // first iter
+ if data.pos == 0.0 {
+ let translation_key = match rand::random::() {
+ 0..=15 => "DONATE.BROUGHT_TO_YOU_VARIANTS_VERY_RARE",
+ 16..=70 => "DONATE.BROUGHT_TO_YOU_VARIANTS_RARE",
+ 71..=u8::MAX => "DONATE.BROUGHT_TO_YOU_VARIANTS_OFTEN",
+ };
+
+ let translated = common.globals().i18n_builtin.translate(translation_key);
+ let variants = translated.split('|').collect::>();
+ let variant = variants[rand::random_range(0..variants.len() - 1)];
+
+ btn.set_text(
+ common,
+ Translation::from_raw_text_string(variant.replace("{USER}", &username)),
+ );
+ }
+
+ // last iter
+ if data.pos == 1.0 {
+ // infinitely looped animation, re-trigger it.
+ tasks.push(LayoutTask::PlayAnimation(get_supporter_anim(
+ btn.clone(),
+ tasks.clone(),
+ supporters.clone(),
+ username.clone(), /* prev supporter name */
+ )))
+ }
+ }),
+ )
+}
+
+async fn config_supporters(btn: Rc, tasks: LayoutTasks) {
+ let Some(supporters) = cached_fetcher::request_supporters().await else {
+ return;
+ };
+
+ if supporters.supporters.len() <= 2 {
+ return; // Welp.
}
+ let supporters = Rc::new(supporters);
+
+ tasks.push(LayoutTask::PlayAnimation(get_supporter_anim(
+ btn,
+ tasks.clone(),
+ supporters,
+ String::new(),
+ )));
+}
+
+fn configure_label_hello(common: &mut CallbackDataCommon, label_hello: Widget, config: &GeneralConfig) {
+ let username = wlx_common::locale::capitalize_string(&various::get_username());
+
let translated = if !config.hide_username {
common.i18n().translate_and_replace("HELLO_USER", ("{USER}", &username))
} else {
@@ -66,6 +146,8 @@ impl TabHome {
frontend.interface.general_config(data),
);
+ let btn_supporter = state.fetch_component_as::("btn_supporter")?;
+
let btn_apps = state.fetch_component_as::("btn_apps")?;
let btn_games = state.fetch_component_as::("btn_games")?;
let btn_monado = state.fetch_component_as::("btn_monado")?;
@@ -74,14 +156,20 @@ impl TabHome {
let btn_donate = state.fetch_component_as::("btn_donate")?;
let btn_website = state.fetch_component_as::("btn_website")?;
- let tasks = &mut frontend.tasks;
- tasks.handle_button(&btn_apps, FrontendTask::SetTab(TabType::Apps));
- tasks.handle_button(&btn_games, FrontendTask::SetTab(TabType::Games));
- tasks.handle_button(&btn_monado, FrontendTask::SetTab(TabType::Monado));
- tasks.handle_button(&btn_settings, FrontendTask::SetTab(TabType::Settings));
- tasks.handle_button(&btn_welcome_screen, FrontendTask::SetTab(TabType::Welcome));
- tasks.handle_button(&btn_donate, FrontendTask::SetTab(TabType::Donate));
- tasks.handle_button(&btn_website, FrontendTask::OpenURL("https://wayvr.org".into()));
+ let f_tasks = &mut frontend.tasks;
+ f_tasks.handle_button(&btn_apps, FrontendTask::SetTab(TabType::Apps));
+ f_tasks.handle_button(&btn_games, FrontendTask::SetTab(TabType::Games));
+ f_tasks.handle_button(&btn_monado, FrontendTask::SetTab(TabType::Monado));
+ f_tasks.handle_button(&btn_settings, FrontendTask::SetTab(TabType::Settings));
+ f_tasks.handle_button(&btn_welcome_screen, FrontendTask::SetTab(TabType::Welcome));
+ f_tasks.handle_button(&btn_donate, FrontendTask::SetTab(TabType::Donate));
+ f_tasks.handle_button(&btn_supporter, FrontendTask::SetTab(TabType::Donate));
+ f_tasks.handle_button(&btn_website, FrontendTask::OpenURL("https://wayvr.org".into()));
+
+ frontend
+ .executor
+ .spawn(config_supporters(btn_supporter, frontend.layout.tasks.clone()))
+ .detach();
Ok(Self {
state,
diff --git a/dash-frontend/src/tab/mod.rs b/dash-frontend/src/tab/mod.rs
index 881084de..af9c6b1e 100644
--- a/dash-frontend/src/tab/mod.rs
+++ b/dash-frontend/src/tab/mod.rs
@@ -24,7 +24,7 @@ pub enum TabType {
impl TabType {
pub fn get_preferred_padding(&self) -> f32 {
match self {
- TabType::Welcome | TabType::Donate => 0.0,
+ TabType::Welcome => 0.0,
_ => 16.0,
}
}
diff --git a/dash-frontend/src/util/cached_fetcher.rs b/dash-frontend/src/util/cached_fetcher.rs
index 54d55d31..e04fcba8 100644
--- a/dash-frontend/src/util/cached_fetcher.rs
+++ b/dash-frontend/src/util/cached_fetcher.rs
@@ -1,14 +1,73 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+
use crate::util::{networking::http_client, steam_utils::AppID};
use anyhow::Context;
-use serde::Deserialize;
+use serde::{Deserialize, Serialize};
use wlx_common::cache_dir;
+fn get_unix_timestamp() -> u64 {
+ SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Supporter {
+ pub username: String,
+ pub date: String,
+ pub tier: String,
+ pub contribution_count: u32,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct Supporters {
+ pub time_window_days: u32,
+ pub supporters: Vec,
+}
+
+#[derive(Serialize, Deserialize)]
+struct SupportersFile {
+ fetch_timestamp: u64,
+ supporters: Option, // empty in case of server/network failure
+}
+
+pub async fn request_supporters() -> Option {
+ let cache_file_path = "supporters_v1.json";
+ const CACHE_DURATION_SECS: u64 = 7200;
+ let current_timestamp = get_unix_timestamp();
+
+ if let Some(data) = cache_dir::get_data(cache_file_path).await
+ && let Ok(string) = str::from_utf8(&data)
+ && let Ok(file) = serde_json::from_str::(string)
+ && file.fetch_timestamp + CACHE_DURATION_SECS > current_timestamp
+ {
+ return file.supporters;
+ }
+
+ // perform request
+ let mut supporters_file = SupportersFile {
+ fetch_timestamp: current_timestamp,
+ supporters: None,
+ };
+
+ let url = "https://wayvr.org/files/supporters_v1.json";
+ if let Ok(res) = http_client::get_simple(url).await
+ && let Ok(supporters) = res.into_json::()
+ {
+ supporters_file.supporters = Some(supporters);
+ }
+
+ let json = serde_json::to_string_pretty(&supporters_file).unwrap() /* safe */;
+
+ cache_dir::set_data(cache_file_path, json.as_bytes()).await.ok()?;
+
+ supporters_file.supporters
+}
+
pub struct CoverArt {
// can be empty in case if data couldn't be fetched (use a fallback image then)
pub compressed_image_data: Vec,
}
-pub async fn request_image(app_id: AppID) -> anyhow::Result {
+pub async fn request_cover_art(app_id: AppID) -> anyhow::Result {
let cache_file_path = format!("cover_arts/{}.bin", app_id);
// check if file already exists in cache directory
diff --git a/dash-frontend/src/util/openxr_bindings.rs b/dash-frontend/src/util/openxr_bindings.rs
index bb4e420e..ec14b45c 100644
--- a/dash-frontend/src/util/openxr_bindings.rs
+++ b/dash-frontend/src/util/openxr_bindings.rs
@@ -46,14 +46,7 @@ impl BindingsDropdown for XrInputSubpathKind {
self
.get_str("Translation")
.map(Translation::from_translation_key)
- .unwrap_or_else(|| {
- let mut chars = self.as_ref().chars();
- let capitalized = match chars.next() {
- None => String::new(),
- Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
- };
- Translation::from_raw_text(&capitalized)
- })
+ .unwrap_or_else(|| Translation::from_raw_text(&wlx_common::locale::capitalize_string(self.as_ref())))
}
fn action_str(&self, action: &str, side: XrInputSide) -> Rc {
let value = self.as_ref();
diff --git a/dash-frontend/src/views/game_cover.rs b/dash-frontend/src/views/game_cover.rs
index f9a68793..7ab5659e 100644
--- a/dash-frontend/src/views/game_cover.rs
+++ b/dash-frontend/src/views/game_cover.rs
@@ -62,7 +62,7 @@ const GAME_COVER_SIZE_Y: f32 = 210.0;
impl View {
async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box) {
- let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
+ let cover_art = match cached_fetcher::request_cover_art(manifest.app_id.clone()).await {
Ok(cover_art) => cover_art,
Err(e) => {
log::error!("request_cover_image failed: {:?}", e);
@@ -75,7 +75,7 @@ impl View {
fn mount_image(&self, layout: &mut Layout, glyph: &CustomGlyphData) -> anyhow::Result<()> {
let image = WidgetImage::create(WidgetImageParams {
- round: WLength::Units(10.0),
+ round: WLength::Units(6.0),
glyph_data: Some(glyph.clone()),
..Default::default()
});
@@ -173,7 +173,7 @@ impl View {
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0).into()),
border_color: Some(BORDER_COLOR_DEFAULT.into()),
hover_border_color: Some(BORDER_COLOR_HOVERED.into()),
- round: WLength::Units(12.0),
+ round: WLength::Units(8.0),
border: 2.0,
tooltip: Some(TooltipInfo {
side: TooltipSide::Bottom,
@@ -209,11 +209,11 @@ impl View {
},
)?;
- let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
+ let rect_gradient = |color: drawing::Color, color2: drawing::Color, round: f32| {
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
color: color.into(),
color2: color2.into(),
- round: WLength::Units(12.0),
+ round: WLength::Units(round),
gradient: GradientMode::Vertical,
..Default::default()
})
@@ -234,7 +234,8 @@ impl View {
widget_button.id,
rect_gradient(
drawing::Color::new(1.0, 1.0, 1.0, 0.2),
- drawing::Color::new(1.0, 1.0, 1.0, 0.02),
+ drawing::Color::new(1.0, 1.0, 1.0, 0.01),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.05),
)?;
@@ -248,6 +249,7 @@ impl View {
rect_gradient(
drawing::Color::new(1.0, 1.0, 1.0, 0.15),
drawing::Color::new(1.0, 1.0, 1.0, 0.0),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.5),
)?;
@@ -258,6 +260,7 @@ impl View {
rect_gradient(
drawing::Color::new(0.0, 0.0, 0.0, 0.0),
drawing::Color::new(0.0, 0.0, 0.0, 0.25),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::END, 0.5),
)?;
@@ -268,6 +271,7 @@ impl View {
rect_gradient(
drawing::Color::new(0.0, 0.0, 0.0, 0.1),
drawing::Color::new(0.0, 0.0, 0.0, 0.9),
+ 8.0,
),
rect_gradient_style(taffy::AlignSelf::END, 0.05),
)?;
diff --git a/wgui/src/color.rs b/wgui/src/color.rs
index 70f8ddcd..b5f90513 100644
--- a/wgui/src/color.rs
+++ b/wgui/src/color.rs
@@ -3,7 +3,7 @@ use strum::{EnumCount, EnumString};
// Primary: button color
// OnPrimary: text color placed on the Primary-colored button
-#[derive(Debug, Copy, Clone, EnumCount, EnumString)]
+#[derive(Debug, Copy, Clone, PartialEq, EnumCount, EnumString)]
#[repr(usize)]
#[strum(serialize_all = "snake_case")]
pub enum WguiColorName {
@@ -26,7 +26,7 @@ pub enum WguiColorName {
Highlight,
}
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, PartialEq, Debug)]
pub struct WguiNamedColor {
name: WguiColorName,
rgb_multiplier: f32,
@@ -43,7 +43,7 @@ pub enum ParentColor {
Ignore,
}
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, PartialEq)]
pub enum WguiColor {
Raw(drawing::Color),
Named(WguiNamedColor),
@@ -203,6 +203,15 @@ impl Default for WguiColor {
}
impl WguiNamedColor {
+ pub const fn with_alpha(name: WguiColorName, alpha: f32) -> WguiNamedColor {
+ WguiNamedColor {
+ name,
+ rgb_multiplier: 1.0,
+ rgb_addition: 0.0,
+ alpha,
+ }
+ }
+
pub fn resolve(&self, palette: &WguiColorPalette) -> drawing::Color {
let mut color = palette[self.name];
if self.alpha != 1.0 {
diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs
index ce9f9c73..17f6a546 100644
--- a/wgui/src/components/button.rs
+++ b/wgui/src/components/button.rs
@@ -211,6 +211,14 @@ impl ComponentButton {
label.set_text(common, text);
}
+ pub fn set_text_color(&self, common: &mut CallbackDataCommon, color: WguiColor) {
+ let state = self.state.borrow();
+ let Some(mut label) = common.state.widgets.get_as::(state.id_label) else {
+ return;
+ };
+ label.set_color(common, color, true);
+ }
+
pub fn set_color(&self, common: &mut CallbackDataCommon, color: WguiColor) {
let gradient_intensity = common.state.theme.gradient_intensity;
diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs
index a36f5525..aa8e07a9 100644
--- a/wgui/src/layout.rs
+++ b/wgui/src/layout.rs
@@ -6,7 +6,7 @@ use std::{
};
use crate::{
- animation::Animations,
+ animation::{Animation, Animations},
components::{self, Component, ComponentWeak, DestroyData, FocusChangeData, RefreshData},
drawing::{
self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack,
@@ -153,6 +153,7 @@ pub enum LayoutTask {
SetWidgetVisible(WidgetID, bool), // if true, sets Display to Flex; None, otherwise
ModifyLayoutState(LayoutModifyStateFunc),
PlaySound(WguiSoundType),
+ PlayAnimation(Animation),
Dispatch(LayoutDispatchFunc),
SetFocus(ComponentWeak),
RefreshPalette,
@@ -653,7 +654,10 @@ impl Layout {
}
}
- fn try_recompute_layout(&mut self, size: Vec2) -> anyhow::Result<()> {
+ fn try_recompute_layout(&mut self, mut size: Vec2) -> anyhow::Result<()> {
+ size.x = size.x.round();
+ size.y = size.y.round();
+
if !self.state.tree.dirty(self.tree_root_node)? && self.prev_size == size {
// Nothing to do
return Ok(());
@@ -749,6 +753,9 @@ impl Layout {
let mut tasks = self.tasks.drain();
while let Some(task) = tasks.pop_front() {
match task {
+ LayoutTask::PlayAnimation(animation) => {
+ self.animations.add(animation);
+ }
LayoutTask::RefreshPalette => {
let root = self.tree_root_widget;
LayoutState::refresh_palette_recur(&mut self.common(), root);
diff --git a/wgui/src/palette.rs b/wgui/src/palette.rs
index 2bc054a6..43f1558e 100644
--- a/wgui/src/palette.rs
+++ b/wgui/src/palette.rs
@@ -128,7 +128,7 @@ static DEFAULT: &WguiColorPalette = &WguiColorPalette {
hex("#ffebf5"), // OnDanger
hex("#002e43"), // Background
hex("#e4f5f6"), // OnBackground
- hex("#0c5170"), // OackgroundVariant
+ hex("#0c5170"), // OnBackgroundVariant
hex("#b5cacc"), // OnBackgroundVariant
hex("#00131c"), // BackgroundContrast
hex("#e4edf6"), // OnBackgroundContrast
diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs
index 43474aca..9fa6f759 100644
--- a/wgui/src/widget/label.rs
+++ b/wgui/src/widget/label.rs
@@ -122,6 +122,12 @@ impl WidgetLabel {
}
pub fn set_color(&mut self, common: &mut CallbackDataCommon, color: WguiColor, apply_to_existing_text: bool) {
+ if let Some(current_color) = &self.params.style.color
+ && *current_color == color
+ {
+ // not changed
+ return;
+ }
self.params.style.color = Some(color);
if apply_to_existing_text {
self.update_attrs(&common.globals().palette);
diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs
index 868a1023..4608bace 100644
--- a/wgui/src/widget/mod.rs
+++ b/wgui/src/widget/mod.rs
@@ -440,12 +440,15 @@ impl WidgetState {
&& (scrolling_cur.y - scrolling_target.y).abs() < epsilon
{
*scrolling_cur = *scrolling_target;
+ perform_next_tick = false;
}
}
if perform_next_tick {
alterables.mark_tick(this_widget_id);
alterables.mark_redraw();
+ } else {
+ self.data.scrolling_cur_prev = self.data.scrolling_cur;
}
}
diff --git a/wlx-common/src/locale.rs b/wlx-common/src/locale.rs
index 337b6a39..d87e87bc 100644
--- a/wlx-common/src/locale.rs
+++ b/wlx-common/src/locale.rs
@@ -89,3 +89,11 @@ impl WayVRLangProvider {
Self::default()
}
}
+
+pub fn capitalize_string(s: &str) -> String {
+ let mut chars = s.chars();
+ match chars.next() {
+ None => String::new(),
+ Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
+ }
+}