From 7611f0bec56112448321229ba6039c384670a6ed Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Mon, 17 Feb 2025 18:47:24 -0500 Subject: [PATCH 1/2] use rotation for drawing layer --- onboard/frontend/ui/MenuCard.cs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/onboard/frontend/ui/MenuCard.cs b/onboard/frontend/ui/MenuCard.cs index 31f0ee7..27b49c7 100644 --- a/onboard/frontend/ui/MenuCard.cs +++ b/onboard/frontend/ui/MenuCard.cs @@ -1,4 +1,6 @@ -using Microsoft.Xna.Framework; +using System; + +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace onboard.ui @@ -13,7 +15,7 @@ public class MenuCard private Texture2D texture; public int listPos; // Tracks the card's current position on the screen - + // Same as rotation variables, but for scale, color private float scale = 1f; private const float scale_amt = 0.05f; @@ -35,7 +37,7 @@ public MenuCard(int initialPos, Texture2D cardTexture, devcade.DevcadeGame game) this.texture = cardTexture; this.game = game; - while(initialPos > 0) + while (initialPos > 0) { rotation -= rotation_amt; scale -= scale_amt; @@ -52,12 +54,13 @@ public MenuCard(int initialPos, Texture2D cardTexture, devcade.DevcadeGame game) } - public void setListPos(int pos) { - this.listPos = pos; + public void setListPos(int pos) + { + this.listPos = pos; this.rotation = 0f; this.scale = 1f; - while(pos > 0) + while (pos > 0) { rotation -= rotation_amt; scale -= scale_amt; @@ -108,18 +111,19 @@ public void DrawSelf(SpriteBatch _spriteBatch, Texture2D cardTexture, int _sHeig { _spriteBatch.Draw( texture ?? cardTexture, - new Vector2(cardX, (int)(_sHeight / 2.0 + (cardTexture.Height * scalingAmount) /2)), + new Vector2(cardX, (int)(_sHeight / 2.0 + (cardTexture.Height * scalingAmount) / 2)), null, new Color(cardOpacity, cardOpacity, cardOpacity, cardOpacity), rotation, new Vector2(0, cardTexture.Height / 2.0f), - (float)(scale * scalingAmount), + (float)(scale * scalingAmount), SpriteEffects.None, - 0f + Math.Abs(rotation) ); } - public void setTexture(Texture2D texture) { + public void setTexture(Texture2D texture) + { this.texture = texture; } } From 0c6aef29ee13e259d47c67089b76384aec297d80 Mon Sep 17 00:00:00 2001 From: aln730 Date: Sun, 3 May 2026 22:14:22 -0400 Subject: [PATCH 2/2] offline mode --- onboard/backend/src/api/mod.rs | 51 +++++++++++++++++++++++++--------- onboard/frontend/ui/Devcade.cs | 10 +++---- onboard/frontend/ui/Menu.cs | 47 ++++++++++++++++--------------- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/onboard/backend/src/api/mod.rs b/onboard/backend/src/api/mod.rs index 5902709..f2a8d11 100644 --- a/onboard/backend/src/api/mod.rs +++ b/onboard/backend/src/api/mod.rs @@ -36,11 +36,17 @@ mod network { use log::{log, Level}; use serde::Deserialize; use std::ops::Deref; + use std::time::Duration; // Construct a static client to be used for all requests. Prevents opening a new connection for // every request. lazy_static! { - static ref CLIENT: reqwest::Client = reqwest::Client::new(); + //Added timeouts to prevent network requests from hanging indefinitely when offline. TBH TIMEOUTS MAY NOT BE THE RIGHT SOLUTUON BUT IT WORKS + static ref CLIENT: reqwest::Client = reqwest::Client::builder() + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(10)) + .build() + .unwrap(); } /** @@ -147,13 +153,22 @@ mod route { * # Errors * This function will return an error if the request fails, or if the JSON cannot be deserialized */ +//Now falls back to game_list_from_fs() if the API is unreachable. pub async fn game_list() -> Result, Error> { - let games: Vec = - network::request_json(format!("{}/{}", api_url(), route::game_list()).as_str()).await?; - Ok(games - .into_iter() - .filter(|game| game.hash.is_some()) - .collect::>()) + match network::request_json::>( + format!("{}/{}", api_url(), route::game_list()).as_str(), + ) + .await + { + Ok(games) => Ok(games + .into_iter() + .filter(|game| game.hash.is_some()) + .collect()), + Err(err) => { + log::warn!("Couldn't fetch game list from API, falling back to filesystem!: {err:?}"); + game_list_from_fs() + } + } } /** @@ -395,15 +410,23 @@ pub async fn download_game(game_id: String) -> Result { } Err(err) => { log::warn!("Couldn't request live info on game! Falling back to local file! {err:?}"); - local_game - .as_ref() - .expect("Game not downloaded and we're offline!") - .clone() + match local_game.as_ref() { + Ok(g) => { + if g.flatpak_app_id.is_some() { + log::info!("Offline and game is already installed, skipping download."); + return Ok(g.clone()); + } + g.clone() + } + Err(_) => { + return Err(anyhow!("Game not downloaded and we're offline!")); + } + } } }; // Is the current hash == the remote hash? if let Ok(local_game) = local_game { - if local_game.hash == game.hash { + if local_game.hash == game.hash && local_game.flatpak_app_id.is_some() { // just to be sure sure return Ok(local_game); } } @@ -526,7 +549,9 @@ pub async fn launch_game(game_id: String) -> Result<(), Error> { tokio::time::sleep(Duration::from_millis(200)).await; - kill_game(game).await?; + if let Err(e) = kill_game(game).await { + log::warn!("kill_game failed (game may have already exited cleanly): {e}"); // not necessary but this fixed exit error for me + } Ok(()) } diff --git a/onboard/frontend/ui/Devcade.cs b/onboard/frontend/ui/Devcade.cs index e5db9be..986d4ba 100644 --- a/onboard/frontend/ui/Devcade.cs +++ b/onboard/frontend/ui/Devcade.cs @@ -381,13 +381,11 @@ protected override void Update(GameTime gameTime) { Client.launchGame( menu.gameSelected().id ).ContinueWith(res => { - if (res.IsCompletedSuccessfully) { - state = MenuState.Input; - } - else { - logger.Error("Failed to launch game: " + res.Exception); - state = MenuState.Input; + _loading = false; + if (!res.IsCompletedSuccessfully || res.Result.type == Response.ResponseType.Err) { + logger.Error("Failed to launch game: " + res.Exception?.Message); } + state = MenuState.Input; }); fadeColor = 0f; diff --git a/onboard/frontend/ui/Menu.cs b/onboard/frontend/ui/Menu.cs index 416ff27..b08f528 100644 --- a/onboard/frontend/ui/Menu.cs +++ b/onboard/frontend/ui/Menu.cs @@ -143,25 +143,31 @@ public void clearGames() { cards?.Clear(); tagLists.Clear(); itemSelected = 0; + currentTag = allTag.name; // reset to default + // Re-add the allTag entry immediately + tagLists.Add(allTag.name, new List()); } public bool reloadGames(GraphicsDevice device, bool clear = true) { - if (clear) - clearGames(); - // Reload the .env file every time the games are reloaded to make sure that the demo mode is up to date + if (clear) clearGames(); Env.load("../.env"); itemSelected = 0; - var errorList = new List { defaultGame }; - - setTags(); - - // Public access to state is definitely a good idea (this whole thing needs a refactor) Devcade.instance.state = Devcade.MenuState.Loading; Devcade.instance._loading = true; - // gameTask is 'never used' but tasks in C# are eager, so it doesn't need to be awaited to run. - Task gameTask = Client.getGameList() + Task gameTask = Client.getTags() + .ContinueWith(t => { + logger.Info("Getting tags from API"); + tags = t.Result.into_result>().unwrap_or(new List()); + tags.Insert(0, allTag); + if (tagLists.Keys.Count == 0) { + foreach (Tag tag in tags) { + tagLists.Add(tag.name, new List()); + } + } + }) + .ContinueWith(_ => Client.getGameList()).Unwrap() .ContinueWith(t => { if (!t.IsCompletedSuccessfully) { logger.Error($"Failed to fetch game list: {t.Exception}"); @@ -200,20 +206,12 @@ public bool reloadGames(GraphicsDevice device, bool clear = true) { } public void setTags() { - if (tags == null || tags.Count == 0) { - logger.Info("Getting tags from API (this should be only once, but maybe every reload of the game list?)"); - tags = Client.getTags().Result.into_result>().unwrap_or(new List()); - tags.Insert(0, allTag); // Make all tag appear at the top of the list - } - + if (tags == null)tags =new List{ allTag}; if (tagLists.Keys.Count != 0) return; - - // tagLists gets cleared every time the games are reloaded?! foreach (Tag tag in tags) { tagLists.Add(tag.name, new List()); } } - public void setCards(GraphicsDevice graphics) { for (int i = 0; i < gameTitles.Count; i++) { devcade.DevcadeGame game = gameTitles[i]; @@ -246,9 +244,10 @@ public void setCards(GraphicsDevice graphics) { // Add the reference to the card to the proper lists within the tag dictionary foreach(devcade.Tag tag in game.tags) { - tagLists[tag.name].Add(newCard); - } - + if (tagLists.ContainsKey(tag.name)) { + tagLists[tag.name].Add(newCard); + } + } tagLists[allTag.name].Add(newCard); } @@ -279,6 +278,7 @@ public devcade.DevcadeGame gameSelected() { // MAKE FONTS, TEXTURES, AND DIMS FIELDS WITHIN TAGS MENU public void initializeTagsMenu(Texture2D cardTexture, SpriteFont font) { + if (tags == null) tags = new List { allTag }; // just to be sure sure, yk tagsMenu = new TagsMenu(tags.ToArray(), cardTexture, font, new Vector2(_sWidth, _sHeight), scalingAmount); } @@ -561,7 +561,8 @@ public void writeString(SpriteBatch _spriteBatch, SpriteFont font, string str, V ); } - public void drawCards(SpriteBatch _spriteBatch, Texture2D cardTexture, SpriteFont font) { +public void drawCards(SpriteBatch _spriteBatch, Texture2D cardTexture, SpriteFont font) { + if (!tagLists.ContainsKey(currentTag)) return; //tryna make refresh work // I still have no idea why the layerDepth does not work\ foreach (MenuCard card in tagLists[currentTag].Where(card => Math.Abs(card.listPos) == 4)) {