From edb6960d4fee607b770a42807b129a581007c6af Mon Sep 17 00:00:00 2001 From: Logan McNaughton <848146+loganmc10@users.noreply.github.com> Date: Sun, 19 Apr 2026 15:15:56 -0600 Subject: [PATCH] Make Slint optional (#778) * make slint optional * more * more --- .github/workflows/build.yml | 3 + .github/workflows/lint.yml | 1 + Cargo.toml | 8 ++- build.rs | 28 ++++++--- src/cheats.rs | 20 ++++-- src/device.rs | 2 +- src/device/controller/vru.rs | 7 ++- src/main.rs | 14 +++-- src/retroachievements.rs | 118 +++-------------------------------- src/ui.rs | 14 +++++ src/ui/cheats.rs | 12 +--- src/ui/gui.rs | 14 +---- src/ui/input.rs | 1 + src/ui/netplay.rs | 18 +++--- src/ui/retroachievements.rs | 114 +++++++++++++++++++++++++++++++++ src/ui/vru.rs | 2 +- 16 files changed, 210 insertions(+), 166 deletions(-) create mode 100644 src/ui/retroachievements.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1a2384..8163103 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,6 +64,7 @@ jobs: matrix.llvm }}.tar.xz - name: Build run: | + cargo build --no-default-features -v --target=${{ matrix.target }} cargo build --release -v --target=${{ matrix.target }} mkdir output cp target/${{ matrix.target }}/release/gopher64 output/gopher64-${{ matrix.arch }} @@ -113,6 +114,7 @@ jobs: }}.exe - name: Build run: | + cargo build --no-default-features -v --target=${{ matrix.target }} cargo build --release -v --target=${{ matrix.target }} mkdir output cp target/${{ matrix.target }}/release/gopher64.exe output/gopher64-${{ matrix.arch }}.exe @@ -182,6 +184,7 @@ jobs: run: | cargo install cargo-bundle brew install molten-vk + cargo build --no-default-features -v --target=${{ matrix.target }} cargo bundle --release --target=${{ matrix.target }} --format osx install_name_tool -change /opt/homebrew/opt/freetype/lib/libfreetype.6.dylib \ @executable_path/../Frameworks/libfreetype.6.dylib target/${{ matrix.target }}/release/bundle/osx/Gopher64.app/Contents/MacOS/gopher64 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cfe8745..49ad495 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -49,3 +49,4 @@ jobs: - name: Lint run: | cargo clippy -- -Dwarnings + cargo clippy --no-default-features -- -Dwarnings diff --git a/Cargo.toml b/Cargo.toml index dc45a1e..fa65035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,9 @@ version = "1.1.17" edition = "2024" rust-version = "1.95.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["gui"] +gui = ["dep:slint", "dep:slint-build"] [dependencies] dirs = "6.0" @@ -14,7 +16,7 @@ chrono = "0.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde-big-array = "0.5" -slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia"] } +slint = { version = "1.16", default-features = false, features = ["compat-1-2", "std", "backend-winit", "renderer-skia"], optional = true } open = "5.3" sha2 = "0.11" sdl3-sys = { version = "0.6", features = ["build-from-source-static"] } @@ -38,7 +40,7 @@ reqwest = { version = "0.13", features = ["json", "query"] } [build-dependencies] winresource = "0.1" bindgen = "0.72" -slint-build = "1.12" +slint-build = { version = "1.12", optional = true } cc = { version = "1.2", features = ["parallel"] } [profile.release] diff --git a/build.rs b/build.rs index ce72218..c92a360 100644 --- a/build.rs +++ b/build.rs @@ -3,8 +3,11 @@ fn main() { println!("cargo::rerun-if-changed=retroachievements"); println!("cargo::rerun-if-changed=src/compat"); - let slint_config = slint_build::CompilerConfiguration::new(); - slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap(); + #[cfg(feature = "gui")] + { + let slint_config = slint_build::CompilerConfiguration::new(); + slint_build::compile_with_config("src/ui/gui/appwindow.slint", slint_config).unwrap(); + } let mut simd_build = cc::Build::new(); let mut volk_build = cc::Build::new(); @@ -143,6 +146,9 @@ fn main() { println!("cargo:rustc-link-search=native={}", runtime_dir); println!("cargo:rustc-link-lib=static=clang_rt.osx"); } + if cfg!(not(feature = "gui")) && os == "linux" { + println!("cargo:rustc-link-lib=dylib=freetype"); + } volk_build.flag("-flto=thin"); rdp_build.flag("-flto=thin"); @@ -155,7 +161,7 @@ fn main() { let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); - let retroachievements_bindings = bindgen::Builder::default() + let mut retroachievements_builder = bindgen::Builder::default() .header("retroachievements/retroachievements.h") .allowlist_function("ra_init_client") .allowlist_function("ra_welcome") @@ -166,15 +172,21 @@ fn main() { .allowlist_function("ra_do_frame") .allowlist_function("ra_do_idle") .allowlist_function("ra_http_callback") - .allowlist_function("ra_logout_user") .allowlist_function("ra_login_user") .allowlist_function("ra_login_token_user") - .allowlist_function("ra_is_user_logged_in") - .allowlist_function("ra_get_username") - .allowlist_function("ra_get_token") .allowlist_function("ra_state_size") .allowlist_function("ra_save_state") - .allowlist_function("ra_load_state") + .allowlist_function("ra_load_state"); + + if cfg!(feature = "gui") { + retroachievements_builder = retroachievements_builder + .allowlist_function("ra_logout_user") + .allowlist_function("ra_is_user_logged_in") + .allowlist_function("ra_get_username") + .allowlist_function("ra_get_token"); + } + + let retroachievements_bindings = retroachievements_builder .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .generate() .expect("Unable to generate bindings"); diff --git a/src/cheats.rs b/src/cheats.rs index 55648ac..8c60dc3 100644 --- a/src/cheats.rs +++ b/src/cheats.rs @@ -15,16 +15,24 @@ pub struct Cheats { pub enabled: bool, } +#[derive(serde::Serialize, serde::Deserialize, Clone)] +pub struct CheatData { + pub note: String, + pub data: Vec, + pub options: Option>, +} + +pub type Cheat = std::collections::BTreeMap>; + pub fn init( device: &mut device::Device, cheat_settings: std::collections::HashMap>, ) { - let cheats = - serde_json::from_slice::(include_bytes!("../data/cheats.json")) - .unwrap() - .get(&ui::storage::get_game_crc(&device.cart.rom)) - .cloned() - .unwrap_or_default(); + let cheats = serde_json::from_slice::(include_bytes!("../data/cheats.json")) + .unwrap() + .get(&ui::storage::get_game_crc(&device.cart.rom)) + .cloned() + .unwrap_or_default(); let re = regex::Regex::new(r"\?+").unwrap(); for cheat_setting in cheat_settings.iter() { diff --git a/src/device.rs b/src/device.rs index 26cf8a8..9a2c07a 100644 --- a/src/device.rs +++ b/src/device.rs @@ -44,7 +44,7 @@ pub mod tlb; pub mod unmapped; pub mod vi; -pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::gui::GameSettings) { +pub fn run_game(device: &mut Device, rom_contents: &[u8], game_settings: ui::GameSettings) { device.cpu.overclock = game_settings.overclock; if game_settings.disable_expansion_pak { device.rdram.size = 0x400000; diff --git a/src/device/controller/vru.rs b/src/device/controller/vru.rs index 68aa98e..f7dfa95 100644 --- a/src/device/controller/vru.rs +++ b/src/device/controller/vru.rs @@ -1,4 +1,6 @@ -use crate::{device, ui}; +use crate::device; +#[cfg(feature = "gui")] +use crate::ui; use std::collections::HashMap; const JCMD_VRU_READ: u8 = 0x09; @@ -179,7 +181,10 @@ pub fn process(device: &mut device::Device, channel: usize) { device.pif.ram[device.pif.channels[channel].rx_buf.unwrap()] = 0; } JCMD_VRU_READ => { + #[cfg(feature = "gui")] let index = ui::vru::prompt_for_match(&device.vru.words, device.vi.frame_time); + #[cfg(not(feature = "gui"))] + let index = 0x7FFF; let num_results = if index == 0x7FFF { 0 } else { 1 }; let data: HashMap = HashMap::from([ (0, 0x8000), diff --git a/src/main.rs b/src/main.rs index 8d6fc06..ccc95cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,11 +11,10 @@ mod savestates; mod ui; use clap::Parser; use std::io::Error; -use ui::gui; /// N64 emulator #[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] +#[command(author, version, about, long_about = None, arg_required_else_help = if cfg!(feature = "gui") { false } else { true })] struct Args { game: Option, #[arg(short, long)] @@ -214,7 +213,7 @@ async fn main() -> std::io::Result<()> { device::run_game( &mut device, &rom_contents, - ui::gui::GameSettings { + ui::GameSettings { overclock, disable_expansion_pak, cheats, @@ -282,9 +281,12 @@ async fn main() -> std::io::Result<()> { ui::input::bind_input_profile(&mut ui, profile, port); } } else { - retroachievements::init_client(false, false, false); - gui::app_window(); - retroachievements::shutdown_client(); + #[cfg(feature = "gui")] + { + retroachievements::init_client(false, false, false); + ui::gui::app_window(); + retroachievements::shutdown_client(); + } } Ok(()) diff --git a/src/retroachievements.rs b/src/retroachievements.rs index 764067d..e02d541 100644 --- a/src/retroachievements.rs +++ b/src/retroachievements.rs @@ -1,7 +1,6 @@ include!(concat!(env!("OUT_DIR"), "/retroachievements_bindings.rs")); use crate::ui; -use slint::ComponentHandle; static WEB_CLIENT: std::sync::LazyLock = std::sync::LazyLock::new(|| { reqwest::Client::builder() @@ -144,115 +143,6 @@ pub extern "C" fn rust_server_call( }); } -fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver) { - app.set_ra_current_user_message("Logging in...".into()); - let weak_app = app.as_weak(); - tokio::spawn(async move { - rx.await.unwrap(); - weak_app - .upgrade_in_event_loop(move |handle| { - if is_user_logged_in() { - handle.set_ra_current_user_message( - format!("Logged in as {}", handle.get_ra_username()).into(), - ); - handle.set_ra_show_profile(true); - } else { - handle.set_ra_current_user_message("Login failed".into()); - handle.set_ra_show_profile(false); - } - handle.set_ra_logging_in(false); - }) - .unwrap(); - }); -} - -pub fn ra_window(app: &ui::gui::AppWindow) { - let mut token = String::new(); - if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json")) - && let Ok(result) = serde_json::from_slice::(ra_config.as_ref()) - { - app.set_ra_username(result.username.into()); - app.set_ra_enabled(result.enabled); - app.set_ra_hardcore(result.hardcore); - app.set_ra_challenge(result.challenge); - app.set_ra_leaderboard(result.leaderboard); - token = result.token; - } else { - app.set_ra_hardcore(true); - } - - if app.get_ra_enabled() && !app.get_ra_username().is_empty() { - let (tx, rx) = tokio::sync::oneshot::channel::(); - login_token_user(app.get_ra_username().to_string(), token, tx); - set_current_user_message(app, rx); - } else { - app.set_ra_current_user_message("Not currently logged in".into()); - } - - let weak_app2 = app.as_weak(); - app.on_ra_button_clicked(move |password| { - weak_app2 - .upgrade_in_event_loop(move |handle| { - let (tx, rx) = tokio::sync::oneshot::channel::(); - login_user( - handle.get_ra_username().to_string(), - password.to_string(), - tx, - ); - - set_current_user_message(&handle, rx); - }) - .unwrap(); - }); - - app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| { - let file_path = ui::get_dirs().config_dir.join("retroachievements.json"); - let raconfig = if let Ok(ra_config) = std::fs::read(&file_path) - && let Ok(result) = serde_json::from_slice::(ra_config.as_ref()) - { - if !enabled { - unsafe { ra_logout_user() }; - RAConfig { - username: String::new(), - token: String::new(), - enabled, - hardcore, - challenge, - leaderboard, - } - } else { - RAConfig { - username: result.username, - token: result.token, - enabled, - hardcore, - challenge, - leaderboard, - } - } - } else { - RAConfig { - username: String::new(), - token: String::new(), - enabled, - hardcore, - challenge, - leaderboard, - } - }; - let f = std::fs::File::create(&file_path).unwrap(); - serde_json::to_writer_pretty(f, &raconfig).unwrap(); - }); - - app.on_ra_games_clicked(move || { - open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap(); - }); - - app.on_ra_show_profile_clicked(move |username| { - open::that_detached(format!("https://retroachievements.org/user/{}", username)).unwrap(); - }); -} - pub async fn load_game(rom: &[u8], rom_size: usize) { let (tx, rx) = tokio::sync::oneshot::channel::(); unsafe { @@ -299,6 +189,11 @@ pub fn login_user(username: String, password: String, tx: tokio::sync::oneshot:: }; } +#[cfg(feature = "gui")] +pub fn logout_user() { + unsafe { ra_logout_user() }; +} + pub fn login_token_user(username: String, token: String, tx: tokio::sync::oneshot::Sender) { unsafe { let tx_ptr = Box::into_raw(Box::new(tx)) as *mut std::ffi::c_void; @@ -308,10 +203,12 @@ pub fn login_token_user(username: String, token: String, tx: tokio::sync::onesho }; } +#[cfg(feature = "gui")] pub fn is_user_logged_in() -> bool { unsafe { ra_is_user_logged_in() } } +#[cfg(feature = "gui")] pub fn get_username() -> &'static str { let c_username = unsafe { ra_get_username() }; if c_username.is_null() { @@ -323,6 +220,7 @@ pub fn get_username() -> &'static str { } } +#[cfg(feature = "gui")] pub fn get_token() -> &'static str { let c_token = unsafe { ra_get_token() }; if c_token.is_null() { diff --git a/src/ui.rs b/src/ui.rs index 4b95594..c7e924a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,12 +1,18 @@ pub mod audio; +#[cfg(feature = "gui")] pub mod cheats; pub mod config; +#[cfg(feature = "gui")] pub mod gui; pub mod input; +#[cfg(feature = "gui")] pub mod netplay; +#[cfg(feature = "gui")] +pub mod retroachievements; pub mod storage; pub mod usb; pub mod video; +#[cfg(feature = "gui")] pub mod vru; pub struct Dirs { @@ -47,6 +53,14 @@ pub struct Usb { pub cart_rx: Option>, } +#[derive(Clone)] +pub struct GameSettings { + pub overclock: bool, + pub disable_expansion_pak: bool, + pub cheats: std::collections::HashMap>, + pub load_savestate_slot: Option, +} + pub struct Ui { pub dirs: Dirs, pub config: config::Config, diff --git a/src/ui/cheats.rs b/src/ui/cheats.rs index 6b4f4c1..fa47cbc 100644 --- a/src/ui/cheats.rs +++ b/src/ui/cheats.rs @@ -1,18 +1,10 @@ +use crate::cheats; use crate::device; use crate::ui; use crate::ui::gui::AppWindow; use crate::ui::gui::ErrorDialog; use slint::ComponentHandle; -#[derive(serde::Serialize, serde::Deserialize, Clone)] -pub struct CheatData { - note: String, - pub data: Vec, - pub options: Option>, -} - -pub type Cheats = std::collections::BTreeMap>; - pub fn cheats_window(app: &AppWindow) { let weak = app.as_weak(); app.on_cheats_select_rom_clicked(move |rom_dir| { @@ -38,7 +30,7 @@ pub fn cheats_window(app: &AppWindow) { handle.set_cheat_game_name(game_name.into()); }) .unwrap(); - let cheats: Cheats = + let cheats: cheats::Cheat = serde_json::from_slice(include_bytes!("../../data/cheats.json")).unwrap(); if let Some(cheat) = cheats.get(&game_crc) { let cheat = cheat.clone(); diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 5476923..80c945a 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -18,14 +18,6 @@ pub struct NetplayDevice { pub player_number: u8, } -#[derive(Clone)] -pub struct GameSettings { - pub overclock: bool, - pub disable_expansion_pak: bool, - pub cheats: std::collections::HashMap>, - pub load_savestate_slot: Option, -} - #[derive(Clone)] pub struct RASettings { pub enabled: bool, @@ -358,7 +350,7 @@ fn about_window(app: &AppWindow) { pub fn app_window() { let app = AppWindow::new().unwrap(); about_window(&app); - retroachievements::ra_window(&app); + ui::retroachievements::ra_window(&app); let mut controller_paths; { let game_ui = ui::Ui::new(); @@ -378,7 +370,7 @@ pub fn app_window() { pub fn run_rom( file_path: std::path::PathBuf, - game_settings: GameSettings, + game_settings: ui::GameSettings, netplay: Option, ra_settings: RASettings, weak: slint::Weak, @@ -473,7 +465,7 @@ fn open_rom(app: &AppWindow) { if let Some(file) = select_rom.await { run_rom( file.path().to_path_buf(), - GameSettings { + ui::GameSettings { overclock, disable_expansion_pak, cheats: std::collections::HashMap::new(), // will be filled in later diff --git a/src/ui/input.rs b/src/ui/input.rs index 7687167..af59d65 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -384,6 +384,7 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec { controllers } +#[cfg(feature = "gui")] pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec> { let mut controller_paths: Vec> = vec![]; diff --git a/src/ui/netplay.rs b/src/ui/netplay.rs index a9b00d5..9b61c0a 100644 --- a/src/ui/netplay.rs +++ b/src/ui/netplay.rs @@ -1,8 +1,8 @@ use crate::device; use crate::ui; use crate::ui::gui::{ - AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, GameSettings, NetplayCreate, - NetplayDevice, NetplayJoin, NetplayWait, RASettings, run_rom, save_settings, + AppWindow, CustomNetplayServer, DispatcherDialog, ErrorDialog, NetplayCreate, NetplayDevice, + NetplayJoin, NetplayWait, RASettings, run_rom, save_settings, }; use futures::{SinkExt, StreamExt}; use sha2::{Digest, Sha256}; @@ -247,7 +247,7 @@ fn show_custom_url_dialog(weak: slint::Weak, server_url: slint::S pub fn setup_create_window( create_window: &NetplayCreate, - game_settings: GameSettings, + game_settings: ui::GameSettings, ra_settings: RASettings, rom_dir: slint::SharedString, weak_app: slint::Weak, @@ -613,7 +613,7 @@ fn create_session( game_hash: String, game_cheats: String, password: String, - game_settings: GameSettings, + game_settings: ui::GameSettings, ra_settings: RASettings, weak_app: slint::Weak, weak: slint::Weak, @@ -703,7 +703,7 @@ fn create_session( handle.get_rom_path(), message.player_name.as_ref().unwrap().into(), session.port.unwrap(), - GameSettings { + ui::GameSettings { overclock: overclock.parse().unwrap(), disable_expansion_pak: disable_expansion_pak.parse().unwrap(), cheats: serde_json::from_str(cheats).unwrap(), @@ -826,7 +826,7 @@ fn join_session( handle.get_rom_path(), message.player_name.as_ref().unwrap().into(), session.port.unwrap(), - GameSettings { + ui::GameSettings { overclock: overclock.parse().unwrap(), disable_expansion_pak: disable_expansion_pak.parse().unwrap(), cheats: serde_json::from_str(cheats).unwrap(), @@ -872,7 +872,7 @@ fn setup_wait_window( rom_path: slint::SharedString, player_name: slint::SharedString, port: i32, - game_settings: GameSettings, + game_settings: ui::GameSettings, ra_settings: RASettings, peer_addr: slint::SharedString, weak_app: slint::Weak, @@ -1090,7 +1090,7 @@ fn setup_wait_window( run_rom( handle.get_rom_path().as_str().into(), - GameSettings { + ui::GameSettings { overclock: game_settings.overclock, disable_expansion_pak: game_settings.disable_expansion_pak, cheats: game_settings.cheats, @@ -1214,7 +1214,7 @@ pub fn netplay_window(app: &AppWindow, controller_paths: &[Option]) { save_settings(&handle, &controller_paths); setup_create_window( &create_window, - GameSettings { + ui::GameSettings { overclock: handle.get_overclock_n64_cpu(), disable_expansion_pak: handle.get_disable_expansion_pak(), cheats: std::collections::HashMap::new(), // not used here diff --git a/src/ui/retroachievements.rs b/src/ui/retroachievements.rs new file mode 100644 index 0000000..f888038 --- /dev/null +++ b/src/ui/retroachievements.rs @@ -0,0 +1,114 @@ +use crate::retroachievements; +use crate::ui; +use slint::ComponentHandle; + +pub fn ra_window(app: &ui::gui::AppWindow) { + let mut token = String::new(); + if let Ok(ra_config) = std::fs::read(ui::get_dirs().config_dir.join("retroachievements.json")) + && let Ok(result) = + serde_json::from_slice::(ra_config.as_ref()) + { + app.set_ra_username(result.username.into()); + app.set_ra_enabled(result.enabled); + app.set_ra_hardcore(result.hardcore); + app.set_ra_challenge(result.challenge); + app.set_ra_leaderboard(result.leaderboard); + token = result.token; + } else { + app.set_ra_hardcore(true); + } + + if app.get_ra_enabled() && !app.get_ra_username().is_empty() { + let (tx, rx) = tokio::sync::oneshot::channel::(); + retroachievements::login_token_user(app.get_ra_username().to_string(), token, tx); + set_current_user_message(app, rx); + } else { + app.set_ra_current_user_message("Not currently logged in".into()); + } + + let weak_app2 = app.as_weak(); + app.on_ra_button_clicked(move |password| { + weak_app2 + .upgrade_in_event_loop(move |handle| { + let (tx, rx) = tokio::sync::oneshot::channel::(); + retroachievements::login_user( + handle.get_ra_username().to_string(), + password.to_string(), + tx, + ); + + set_current_user_message(&handle, rx); + }) + .unwrap(); + }); + + app.on_ra_toggled(move |enabled, hardcore, challenge, leaderboard| { + let file_path = ui::get_dirs().config_dir.join("retroachievements.json"); + let raconfig = if let Ok(ra_config) = std::fs::read(&file_path) + && let Ok(result) = + serde_json::from_slice::(ra_config.as_ref()) + { + if !enabled { + retroachievements::logout_user(); + retroachievements::RAConfig { + username: String::new(), + token: String::new(), + enabled, + hardcore, + challenge, + leaderboard, + } + } else { + retroachievements::RAConfig { + username: result.username, + token: result.token, + enabled, + hardcore, + challenge, + leaderboard, + } + } + } else { + retroachievements::RAConfig { + username: String::new(), + token: String::new(), + enabled, + hardcore, + challenge, + leaderboard, + } + }; + let f = std::fs::File::create(&file_path).unwrap(); + serde_json::to_writer_pretty(f, &raconfig).unwrap(); + }); + + app.on_ra_games_clicked(move || { + open::that_detached("https://retroachievements.org/system/2-nintendo-64/games").unwrap(); + }); + + app.on_ra_show_profile_clicked(move |username| { + open::that_detached(format!("https://retroachievements.org/user/{}", username)).unwrap(); + }); +} + +fn set_current_user_message(app: &ui::gui::AppWindow, rx: tokio::sync::oneshot::Receiver) { + app.set_ra_current_user_message("Logging in...".into()); + let weak_app = app.as_weak(); + tokio::spawn(async move { + rx.await.unwrap(); + weak_app + .upgrade_in_event_loop(move |handle| { + if retroachievements::is_user_logged_in() { + handle.set_ra_current_user_message( + format!("Logged in as {}", handle.get_ra_username()).into(), + ); + handle.set_ra_show_profile(true); + } else { + handle.set_ra_current_user_message("Login failed".into()); + handle.set_ra_show_profile(false); + } + handle.set_ra_logging_in(false); + }) + .unwrap(); + }); +} diff --git a/src/ui/vru.rs b/src/ui/vru.rs index 6382a2d..aaea147 100644 --- a/src/ui/vru.rs +++ b/src/ui/vru.rs @@ -1,5 +1,5 @@ -use crate::gui; use crate::retroachievements; +use crate::ui::gui; use slint::ComponentHandle; pub fn prompt_for_match(words: &[String], frame_time: f64) -> u16 {