Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion maxima-lib/src/core/service_layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,7 @@ service_layer_type!(Asset, {
});

service_layer_type!(GameHub, {
background_video: ServiceAsset,
background_video: Option<ServiceAsset>,
hero_background: ServiceImageRendition,
});

Expand Down
1 change: 1 addition & 0 deletions maxima-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ strum_macros = "0.25.3"
sys-locale = "0.3.1"
humansize = { version = "2.0.0", features = ["no_alloc"] }
fuzzy-matcher = "*"
ffmpeg-next = "7.0.2"

[target.'cfg(windows)'.dependencies]
is_elevated = "0.1.2"
Expand Down
3 changes: 2 additions & 1 deletion maxima-ui/res/locale/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
"settings_view": {
"interface" : {
"header": "Interface",
"language": "Language"
"language": "Language",
"videos": "Background Videos"
},
"game_installation" : {
"header": "Game Installation",
Expand Down
84 changes: 70 additions & 14 deletions maxima-ui/src/bridge/get_games.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
use anyhow::{Ok, Result, bail};
use egui::Context;
use log::{debug, info};
use maxima::{core::{service_layer::{ServiceGame, ServiceGameImagesRequestBuilder, ServiceLayerClient, SERVICE_REQUEST_GAMEIMAGES}, LockedMaxima}, util::native::maxima_dir};
use maxima::{core::{service_layer::{ServiceGame, ServiceGameHubCollection, ServiceGameImagesRequestBuilder, ServiceHeroBackgroundImageRequestBuilder, ServiceLayerClient, SERVICE_REQUEST_GAMEIMAGES, SERVICE_REQUEST_GETHEROBACKGROUNDIMAGE}, LockedMaxima}, util::native::maxima_dir};
use std::{fs, sync::mpsc::Sender};

use crate::{
bridge_thread::{InteractThreadGameListResponse, MaximaLibResponse}, ui_image::UIImageCacheLoaderCommand, GameDetailsWrapper, GameInfo
};

fn get_preferred_bg_hero(heroes: &Option<ServiceGameHubCollection>) -> Option<String> {
if heroes.is_none() {
return None
}
let bg = heroes.as_ref().unwrap().items().get(0);

if bg.is_none() {
return None;
}
let bg = bg.as_ref().unwrap().hero_background();

if let Some(img) = bg.aspect_10x3_image() {
return Some(img.path().clone());
}

if let Some(img) = bg.aspect_2x1_image() {
return Some(img.path().clone());
}

if let Some(img) = bg.aspect_16x9_image() {
return Some(img.path().clone());
}

None
}

async fn get_preferred_hero_image(images: &Option<ServiceGame>) -> Option<String> {
if images.is_none() {
return None;
Expand Down Expand Up @@ -56,22 +82,24 @@ fn get_logo_image(images: &Option<ServiceGame>) -> Option<String> {

async fn handle_images(slug: String, locale: String, has_hero: bool, has_logo: bool, channel: Sender<UIImageCacheLoaderCommand>, service_layer: ServiceLayerClient) -> Result<()> {
debug!("handling image downloads for {}", &slug);
let images = service_layer
.request(SERVICE_REQUEST_GAMEIMAGES, ServiceGameImagesRequestBuilder::default()
.should_fetch_context_image(!has_logo)
.should_fetch_backdrop_images(!has_hero)
.game_slug(slug.clone())
.locale(locale.clone())
.build()?).await?;
let g_images = if has_hero && has_logo { None } else {
service_layer
.request(SERVICE_REQUEST_GAMEIMAGES, ServiceGameImagesRequestBuilder::default()
.should_fetch_context_image(!has_logo)
.should_fetch_backdrop_images(!has_hero)
.game_slug(slug.clone())
.locale(locale.clone())
.build()?).await?
};

if !has_hero {
if let Some(hero) = get_preferred_hero_image(&images).await {
if let Some(hero) = get_preferred_hero_image(&g_images).await {
channel.send(UIImageCacheLoaderCommand::ProvideRemote(crate::ui_image::UIImageType::Hero(slug.clone()), hero)).unwrap()
}
}

if !has_logo {
if let Some(logo) = get_logo_image(&images) {
if let Some(logo) = get_logo_image(&g_images) {
channel.send(UIImageCacheLoaderCommand::ProvideRemote(crate::ui_image::UIImageType::Logo(slug), logo))?
} else {
channel.send(UIImageCacheLoaderCommand::Stub(crate::ui_image::UIImageType::Logo(slug)))?
Expand Down Expand Up @@ -106,17 +134,33 @@ pub async fn get_games_request(
}

for game in owned_games {
info!("processing {}", &game.base_offer().slug());
let slug = game.base_offer().slug().clone();
info!("processing {}", &slug);

// it'd be better if we could do this in the task but by then the info is long gone
let h_images: Option<ServiceGameHubCollection> = {
service_layer.request(SERVICE_REQUEST_GETHEROBACKGROUNDIMAGE,
ServiceHeroBackgroundImageRequestBuilder::default()
.game_slug(slug.clone())
.locale(locale.clone())
.build()?).await?
};

let game_info = GameInfo {
slug: game.base_offer().slug().to_string(),
slug: slug.clone(),
offer: game.base_offer().offer().offer_id().to_string(),
name: game.name(),
details: GameDetailsWrapper::Unloaded,
bg_video: if let Some(imgs) = &h_images {
if let Some(vid) = imgs.items().get(0).unwrap().background_video() {
vid.url().clone()
} else { None }
} else { None },
dlc: game.extra_offers().clone(),
installed: game.base_offer().installed().await,
has_cloud_saves: game.base_offer().offer().has_cloud_save(),
};
let slug = game_info.slug.clone();

let settings = crate::GameSettings {
//TODO: eventually support cloud saves, the option is here for that but for now, keep it disabled in ui!
cloud_saves: true,
Expand All @@ -129,6 +173,11 @@ pub async fn get_games_request(
});
channel.send(res)?;

let background_hero = maxima_dir()
.unwrap()
.join("cache/ui/images/")
.join(&slug)
.join("background.jpg");
let game_hero = maxima_dir()
.unwrap()
.join("cache/ui/images/")
Expand All @@ -141,8 +190,9 @@ pub async fn get_games_request(
.join("logo.png");
let has_hero = fs::metadata(&game_hero).is_ok();
let has_logo = fs::metadata(&game_logo).is_ok();
let has_background = fs::metadata(&background_hero).is_ok();

if !has_hero || !has_logo {
if !has_hero || !has_logo{
//we're like 20 tasks deep i swear but this shit's gonna be real fast, trust
let slug_send = slug.clone();
let locale_send = locale.clone();
Expand All @@ -152,6 +202,12 @@ pub async fn get_games_request(
tokio::task::yield_now().await;

}

if !has_background {
if let Some(background_image) = get_preferred_bg_hero(&h_images) {
channel1.send(UIImageCacheLoaderCommand::ProvideRemote(crate::ui_image::UIImageType::Background(slug.clone()), background_image)).unwrap()
}
}

egui::Context::request_repaint(&ctx);
}
Expand Down
2 changes: 1 addition & 1 deletion maxima-ui/src/bridge_thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
panic, path::PathBuf, sync::mpsc::{Receiver, Sender}, time::{Duration, SystemTime}
};

use maxima::{content::manager::{ContentManager, QueuedGameBuilder}, core::{dip::{DiPManifest, DIP_RELATIVE_PATH}, service_layer::ServicePlayer, LockedMaxima, Maxima, MaximaOptionsBuilder}, util::registry::{check_registry_validity, set_up_registry}};
use maxima::{content::manager::{ContentManager, QueuedGameBuilder}, core::{dip::{DiPManifest, DIP_RELATIVE_PATH}, service_layer::{ServicePlayer, ServiceGameHubCollection}, LockedMaxima, Maxima, MaximaOptionsBuilder}, util::registry::{check_registry_validity, set_up_registry}};
use crate::{
bridge::{
game_details::game_details_request,
Expand Down
73 changes: 66 additions & 7 deletions maxima-ui/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![feature(slice_pattern)]
extern crate ffmpeg_next as ffmpeg;
use anyhow::bail;
use clap::{arg, command, Parser};

Expand All @@ -7,6 +8,7 @@ use egui::style::{ScrollStyle, Spacing};
use egui::Style;
use log::{error, warn};
use maxima::core::library::OwnedOffer;
use renderers::media_player::{Player, State};
use strum_macros::EnumIter;
use views::downloads_view::{downloads_view, QueuedDownload};
use views::undefinied_view::coming_soon_view;
Expand Down Expand Up @@ -82,6 +84,12 @@ struct Args {
#[tokio::main]
async fn main() {
init_logger();
let ffmpeg_success = if let Err(err) = ffmpeg::init() {
log::error!("Unable to initialize ffmpeg {err:?}");
false
} else {
true
};
let mut args = Args::parse();

if !cfg!(debug_assertions) {
Expand Down Expand Up @@ -121,7 +129,7 @@ async fn main() {
"Maxima",
native_options,
Box::new(move |cc| {
let app = MaximaEguiApp::new(cc, args);
let app = MaximaEguiApp::new(cc, args, ffmpeg_success);
// Run initialization code that needs access to the UI here, but DO NOT run any long-runtime functions here,
// as it's before the UI is shown
if args.no_login {
Expand Down Expand Up @@ -219,6 +227,7 @@ pub struct GameInfo {
name: String,
/// Game info
details: GameDetailsWrapper,
bg_video: Option<String>, // not the best place to put it
dlc: Vec<OwnedOffer>,
installed: bool,
has_cloud_saves: bool,
Expand Down Expand Up @@ -286,6 +295,8 @@ pub struct MaximaEguiApp {
app_bg_renderer: Option<AppBgRenderer>,
/// Image cache
img_cache: UIImageCache,
/// Renderer for the app's background videos
app_bg_media_player: Option<Player>,
/// Translations
locale: TranslationManager,
/// If a core thread has crashed and made the UI unstable
Expand Down Expand Up @@ -317,7 +328,8 @@ pub enum FrontendLanguage {
pub struct FrontendSettings {
default_install_folder: String,
language: FrontendLanguage,
game_settings: HashMap<String, GameSettings>
game_settings: HashMap<String, GameSettings>,
videos: bool,
}

impl FrontendSettings {
Expand All @@ -326,6 +338,7 @@ impl FrontendSettings {
default_install_folder: String::new(),
language: FrontendLanguage::SystemDefault,
game_settings: HashMap::new(),
videos: true,
}
}
}
Expand All @@ -335,7 +348,7 @@ const F9B233: Color32 = Color32::from_rgb(249, 178, 51);
const WIDGET_HOVER: Color32 = Color32::from_rgb(255, 188, 61);

impl MaximaEguiApp {
fn new(cc: &eframe::CreationContext<'_>, args: Args) -> Self {
fn new(cc: &eframe::CreationContext<'_>, args: Args, ffmpeg_success: bool) -> Self {
let style: Style = Style {
spacing: Spacing {
scroll: ScrollStyle {
Expand Down Expand Up @@ -456,6 +469,9 @@ impl MaximaEguiApp {
game_view_bg_renderer: GameViewBgRenderer::new(cc),
app_bg_renderer: AppBgRenderer::new(cc),
img_cache,
app_bg_media_player: if settings.videos && ffmpeg_success {
Some(Player::new(&cc.egui_ctx))
} else { None },
locale: TranslationManager::new(&settings.language),
critical_bg_thread_crashed: false,
backend: BridgeThread::new(&cc.egui_ctx, remote_provider_channel), //please don't fucking break
Expand Down Expand Up @@ -631,21 +647,64 @@ impl eframe::App for MaximaEguiApp {
let has_game_img = self.backend_state == BackendStallState::BingChilling && self.games.len() > 0;
let gaming = self.page_view == PageType::Games && has_game_img;
let how_game: f32 = ctx.animate_bool(egui::Id::new("MainAppBackgroundGamePageFadeBool"), gaming);

if has_game_img
{
if self.game_sel.is_empty() && self.games.len() > 0 {
if let Some(key) = self.games.keys().next() {
self.game_sel = key.clone()
}
}
//TODO: background
match &self.img_cache.get(ui_image::UIImageType::Hero(self.games[&self.game_sel].slug.clone())) {
Some(tex) => render.draw(ui, fullrect, tex.size_vec2(), tex.id(), how_game),
None => { render.draw(ui, fullrect, fullrect.size(), TextureId::Managed(1), 0.0); },
let game = &self.games[&self.game_sel];

let player_state: Option<(TextureId, Vec2)> = {
match &mut self.app_bg_media_player {
Some(player) => {
if let Some(video) = &game.bg_video {
player.start(&video);
match player.state() {
State::Paused => {
if gaming {
player.unpause();
}
Some((player.texture(), player.size()))
}
State::Playing => {
if !gaming {
player.pause();
}
if player.ready_to_show() {
Some((player.texture(), player.size()))
} else {
None
}
},
State::EndOfFile | State::Stopped => {
None
}
}
}
else {
player.stop();
None
}
},
None => None

}
};

if let Some((tex, size)) = player_state {
render.draw(ui, fullrect, size, tex, how_game);
} else if let Some(img) = &self.img_cache.get(ui_image::UIImageType::Background(game.slug.clone())) {
render.draw(ui, fullrect, img.size_vec2(), img.id(), how_game);
} else {
render.draw(ui, fullrect, fullrect.size(), TextureId::Managed(1), 0.0);
}
} else {
render.draw(ui, fullrect, fullrect.size(), TextureId::Managed(1), 0.0);
}

}
let app_rect = ui.available_rect_before_wrap().clone();
match self.backend_state {
Expand Down
Loading