diff --git a/src/ui/gui/appwindow.slint b/src/ui/gui/appwindow.slint index 3820bcc..b1c0c50 100644 --- a/src/ui/gui/appwindow.slint +++ b/src/ui/gui/appwindow.slint @@ -53,6 +53,8 @@ export component AppWindow inherits Window { callback netplay_create_session(string, string, string, string, string, string, bool, bool); callback netplay_select_rom(string); callback netplay_join_session(string, string, string, string); + callback netplay_resume_session(string); + callback netplay_save_session; callback netplay_close; callback cheats_select_rom_clicked(string); callback cheats_clear_clicked; @@ -134,6 +136,8 @@ export component AppWindow inherits Window { in-out property netplay_recommended_delay <=> NetplayWaitData.recommended_delay; in-out property netplay_game_pal <=> NetplayWaitData.game_pal; in-out property netplay_connected_peers_count <=> NetplayWaitData.connected_peers_count; + in-out property netplay_is_host <=> NetplayWaitData.is_host; + in-out property netplay_resyncing <=> NetplayWaitData.resyncing; private property popup_message; private property popup_has_close; @@ -275,6 +279,9 @@ export component AppWindow inherits Window { begin_game(a) => { netplay_begin_game(a); } + save_session => { + netplay_save_session(); + } close => { netplay_close(); } @@ -288,6 +295,9 @@ export component AppWindow inherits Window { create_session(a, b, c, d, e, f, g, h) => { netplay_create_session(a,b,c,d,e,f,g,h); } + resume_session(rom_dir) => { + netplay_resume_session(rom_dir); + } close => { netplay_close(); } diff --git a/src/ui/gui/netplay_create.slint b/src/ui/gui/netplay_create.slint index 14df865..3e61aff 100644 --- a/src/ui/gui/netplay_create.slint +++ b/src/ui/gui/netplay_create.slint @@ -16,6 +16,7 @@ export component NetplayCreate inherits Page { title: @tr("Netplay: Create Session"); callback select_rom(string); callback create_session(string, string, string, string, string, string, bool, bool); + callback resume_session(string); callback close; VerticalBox { @@ -113,6 +114,14 @@ export component NetplayCreate inherits Page { } } + Button { + text: @tr("Resume Session…"); + enabled: !NetplayData.pending_session; + clicked => { + resume_session(SettingsData.rom_dir); + } + } + Button { text: @tr("Cancel"); clicked => { diff --git a/src/ui/gui/netplay_page.slint b/src/ui/gui/netplay_page.slint index 678b2cc..364b876 100644 --- a/src/ui/gui/netplay_page.slint +++ b/src/ui/gui/netplay_page.slint @@ -27,6 +27,7 @@ export global NetplayData { in-out property show_wait_session; in-out property show_create_session; in-out property show_join_session; + in-out property resyncing; } export component Netplay inherits Page { diff --git a/src/ui/gui/netplay_wait.slint b/src/ui/gui/netplay_wait.slint index bb7159e..d7f787b 100644 --- a/src/ui/gui/netplay_wait.slint +++ b/src/ui/gui/netplay_wait.slint @@ -21,12 +21,15 @@ export global NetplayWaitData { in-out property recommended_delay; in-out property game_pal; in-out property connected_peers_count; + in-out property is_host; + in-out property resyncing; } export component NetplayWait inherits Page { title: @tr("Netplay: Waiting Room"); callback send_chat_message(string); callback begin_game(int); + callback save_session; callback close; VerticalBox { alignment: start; @@ -143,6 +146,14 @@ export component NetplayWait inherits Page { } } + if NetplayWaitData.is_host: Button { + text: @tr("Save Session"); + enabled: NetplayWaitData.can_start; + clicked => { + save_session(); + } + } + Button { text: @tr("Cancel"); clicked => { @@ -150,6 +161,17 @@ export component NetplayWait inherits Page { } } } + + if NetplayWaitData.resyncing: Rectangle { + background: #ffaa00; + height: 24px; + Text { + text: @tr("Resyncing…"); + horizontal-alignment: center; + vertical-alignment: center; + color: white; + } + } } } } diff --git a/src/ui/netplay.rs b/src/ui/netplay.rs index 2519c4b..e50dbf8 100644 --- a/src/ui/netplay.rs +++ b/src/ui/netplay.rs @@ -271,6 +271,48 @@ fn setup_callbacks( app.on_netplay_feedback_button_clicked(move || { open_uri("https://github.com/gopher64/gopher64/discussions/453"); }); + + // Resume Session: open a file picker for .netplay-state files + let weak_app = app.as_weak(); + app.on_netplay_resume_session(move |rom_dir| { + let weak_app = weak_app.clone(); + tokio::spawn(async move { + if let Some(file) = rfd::AsyncFileDialog::new() + .add_filter("Netplay State", &["netplay-state"]) + .set_directory(rom_dir) + .pick_file() + .await + { + eprintln!("resume session from: {}", file.path().display()); + // TODO: #3 — load the netplay session state from the file and + // re-establish the connection. For now, just show a message. + weak_app + .upgrade_in_event_loop(|handle| { + ui::video::onscreen_message( + "Resume session not yet implemented (#3)", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); + let _ = handle; + }) + .ok(); + } + }); + }); + + // Save Session: host-only, triggers the save flow from #3 + let weak_app = app.as_weak(); + app.on_netplay_save_session(move || { + eprintln!("save session requested"); + weak_app + .upgrade_in_event_loop(|handle| { + ui::video::onscreen_message( + "Save session not yet implemented (#3)", + ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, + ); + let _ = handle; + }) + .ok(); + }); } fn close_connections( @@ -751,6 +793,8 @@ fn setup_wait_window( app.set_netplay_game_name(game_name); app.set_netplay_rom_path(rom_path); app.set_netplay_can_start(true); + // is_host is set when the first ResponseSession arrives and the players + // list is populated (host is players[0], see netplay_wait.slint). let request_update = NetplayLobbyMessage { message_type: MessageType::RequestUpdateSession, @@ -780,6 +824,20 @@ fn setup_wait_window( .collect::>(), ), ))); + // The host is the session creator, always + // registered first in the lobby; the wait + // window's host-gated controls (input delay, + // Start Game, Save Session) all key off + // players[0] == player_name. + handle.set_netplay_is_host( + handle.get_netplay_player_name() + == handle + .get_netplay_players() + .iter() + .next() + .map(|s| s.to_string()) + .unwrap_or_default(), + ); }) .unwrap(); }