update controllers when clicking on controller config (#841)

* update controllers when clicking on controller config

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more

* more
This commit is contained in:
Logan McNaughton
2026-05-06 09:44:39 +02:00
committed by GitHub
parent 945d456f9c
commit 172d10d7cb
9 changed files with 175 additions and 166 deletions
+1 -1
View File
@@ -207,7 +207,7 @@ pub fn get_rom_contents(file_path: &std::path::Path) -> Option<Vec<u8>> {
pub struct Device {
#[serde(skip)]
pub netplay: Option<netplay::Netplay>,
#[serde(skip, default = "ui::Ui::default")]
#[serde(skip, default = "ui::Ui::new")]
pub ui: ui::Ui,
pub byte_swap: usize,
pub save_state: bool,
+22 -21
View File
@@ -249,10 +249,10 @@ async fn main() -> std::io::Result<()> {
ui::usb::close(shutdown_tx);
}
} else if std::env::args().count() > 1 {
let mut ui = ui::Ui::new();
let mut config = ui::config::Config::new();
if args.clear_input_bindings {
ui::input::clear_bindings(&mut ui);
ui::input::clear_bindings(&mut config);
return Ok(());
}
if let Some(port) = args.port
@@ -260,34 +260,35 @@ async fn main() -> std::io::Result<()> {
{
return Err(Error::other("Port must be between 1 and 4"));
}
if args.list_controllers {
let controllers = ui::input::get_controller_names(&ui);
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {i}: {controller}");
}
return Ok(());
}
if let Some(profile) = args.configure_input_profile {
ui::input::configure_input_profile(
&mut ui,
&mut config,
profile,
args.use_dinput,
args.deadzone.unwrap_or(ui::input::DEADZONE_DEFAULT),
);
return Ok(());
}
if let Some(assign_controller) = args.assign_controller {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::assign_controller(&mut ui, assign_controller, port);
}
if let Some(profile) = args.bind_input_profile {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::bind_input_profile(&mut ui, profile, port);
if args.list_controllers {
let controllers = ui::input::get_controller_names();
for (i, controller) in controllers.iter().enumerate() {
println!("Controller {i}: {controller}");
}
} else {
if let Some(assign_controller) = args.assign_controller {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::assign_controller(&mut config, assign_controller - 1, port);
}
if let Some(profile) = args.bind_input_profile {
let Some(port) = args.port else {
return Err(Error::other("Must specify port number"));
};
ui::input::bind_input_profile(&mut config, profile, port);
}
}
unsafe { sdl3_sys::init::SDL_Quit() };
} else {
#[cfg(feature = "gui")]
{
+1 -35
View File
@@ -38,7 +38,6 @@ pub struct Audio {
}
pub struct Input {
pub joysticks: Vec<sdl3_sys::joystick::SDL_JoystickID>,
pub keyboard_state: *const bool,
pub controllers: [input::Controllers; 4],
}
@@ -77,7 +76,6 @@ pub struct Ui {
pub config: config::Config,
pub game_id: String,
pub game_hash: String,
pub with_sdl: bool,
pub audio: Audio,
pub input: Input,
pub storage: Storage,
@@ -85,17 +83,6 @@ pub struct Ui {
pub usb: Usb,
}
impl Drop for Ui {
fn drop(&mut self) {
if self.with_sdl {
unsafe {
sdl3_ttf_sys::ttf::TTF_Quit();
sdl3_sys::init::SDL_Quit();
}
}
}
}
pub fn sdl_init(flag: sdl3_sys::init::SDL_InitFlags) {
unsafe {
if sdl3_sys::init::SDL_WasInit(flag) == 0 && !sdl3_sys::init::SDL_InitSubSystem(flag) {
@@ -141,7 +128,7 @@ pub fn get_dirs() -> Dirs {
}
impl Ui {
fn construct_ui(joysticks: Vec<sdl3_sys::everything::SDL_JoystickID>, with_sdl: bool) -> Ui {
pub fn new() -> Ui {
let dirs = get_dirs();
let (fps_tx, fps_rx) = tokio::sync::mpsc::channel(1000);
@@ -179,7 +166,6 @@ impl Ui {
},
],
keyboard_state: std::ptr::null_mut(),
joysticks,
},
storage: Storage {
save_state_slot: 0,
@@ -241,26 +227,6 @@ impl Ui {
cart_rx: None,
},
dirs,
with_sdl,
}
}
pub fn default() -> Ui {
Self::construct_ui(vec![], false)
}
pub fn new() -> Ui {
sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
let mut num_joysticks = 0;
let joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
if joysticks.is_null() {
panic!("Could not get joystick list");
}
let mut joystick_vec = vec![];
for i in 0..num_joysticks {
joystick_vec.push(unsafe { *joysticks.add(i as usize) });
}
unsafe { sdl3_sys::stdinc::SDL_free(joysticks as *mut std::ffi::c_void) }
Self::construct_ui(joystick_vec, true)
}
}
+84 -76
View File
@@ -53,18 +53,13 @@ fn check_latest_version(weak: slint::Weak<AppWindow>) {
});
}
fn run_with_path(
weak: slint::Weak<AppWindow>,
path: std::path::PathBuf,
controller_paths: &[Option<String>],
) {
let controller_paths = controller_paths.to_owned();
fn run_with_path(weak: slint::Weak<AppWindow>, path: std::path::PathBuf) {
let weak2 = weak.clone();
weak.upgrade_in_event_loop(move |handle| {
if handle.get_game_running() {
return;
}
save_settings(&handle, &controller_paths);
save_settings(&handle);
run_rom(
path,
@@ -87,23 +82,18 @@ fn run_with_path(
.unwrap();
}
fn file_dropped(app: &AppWindow, controller_paths: &[Option<String>]) {
fn file_dropped(app: &AppWindow) {
let weak = app.as_weak();
let controller_paths = controller_paths.to_owned();
app.window()
.on_winit_window_event(move |_winit_window, event| {
if let slint::winit_030::winit::event::WindowEvent::DroppedFile(path) = event {
run_with_path(weak.clone(), path.to_path_buf(), &controller_paths);
run_with_path(weak.clone(), path.to_path_buf());
}
slint::winit_030::EventResult::Propagate
});
}
fn local_game_window(
app: &AppWindow,
config: &ui::config::Config,
controller_paths: &[Option<String>],
) {
fn local_game_window(app: &AppWindow, config: &ui::config::Config) {
let dirs = ui::get_dirs();
app.set_recent_roms(slint::ModelRc::from(std::rc::Rc::new(
@@ -128,26 +118,18 @@ fn local_game_window(
)));
let weak = app.as_weak();
let owned_controller_paths = controller_paths.to_owned();
app.on_open_rom_button_clicked(move || {
let controller_paths = owned_controller_paths.clone();
weak.upgrade_in_event_loop(move |handle| {
save_settings(&handle, &controller_paths);
save_settings(&handle);
open_rom(&handle)
})
.unwrap();
});
let weak = app.as_weak();
let owned_controller_paths = controller_paths.to_owned();
app.on_recent_rom_button_clicked(move |rom| {
let controller_paths = owned_controller_paths.clone();
weak.upgrade_in_event_loop(move |handle| {
run_with_path(
handle.as_weak(),
std::path::PathBuf::from(rom.to_string()),
&controller_paths,
);
run_with_path(handle.as_weak(), std::path::PathBuf::from(rom.to_string()));
})
.unwrap();
});
@@ -158,7 +140,7 @@ fn local_game_window(
eprintln!("Error opening saves folder: {}", e);
}
});
file_dropped(app, controller_paths);
file_dropped(app);
}
fn input_profiles(config: &ui::config::Config) -> Vec<String> {
@@ -195,7 +177,6 @@ fn settings_window(app: &AppWindow, config: &ui::config::Config) {
fn update_input_profiles(weak: &slint::Weak<AppWindow>, config: &ui::config::Config) {
let profiles = input_profiles(config);
let config_bindings = config.input.input_profile_binding.clone();
let weak2 = weak.clone();
weak.upgrade_in_event_loop(move |handle| {
let profile_bindings = slint::VecModel::default();
for (i, input_profile_binding) in handle.get_selected_profile_binding().iter().enumerate() {
@@ -221,16 +202,6 @@ fn update_input_profiles(weak: &slint::Weak<AppWindow>, config: &ui::config::Con
handle
.set_selected_profile_binding(slint::ModelRc::from(std::rc::Rc::new(profile_bindings)));
// this is a workaround to make the input profile combobox update
handle.set_blank_profiles(true);
slint::Timer::single_shot(std::time::Duration::from_millis(200), move || {
weak2
.upgrade_in_event_loop(move |handle| {
handle.set_blank_profiles(false);
})
.unwrap();
});
})
.unwrap();
}
@@ -247,12 +218,7 @@ fn clear_gb_paths(weak: &slint::Weak<AppWindow>, player: i32) {
.unwrap();
}
fn controller_window(
app: &AppWindow,
config: &ui::config::Config,
controller_names: &[String],
controller_paths: &[Option<String>],
) {
fn controller_window(app: &AppWindow, config: &ui::config::Config) {
app.set_emulate_vru(config.input.emulate_vru);
app.set_controller_enabled(slint::ModelRc::from(std::rc::Rc::new(
@@ -287,28 +253,66 @@ fn controller_window(
update_input_profiles(&app.as_weak(), config);
app.set_controller_names(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
controller_names
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
app.set_controller_changed(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(vec![false, false, false, false]),
)));
let controller_changed = slint::VecModel::default();
let selected_controllers = slint::VecModel::default();
for selected in config.input.controller_assignment.iter() {
let selected_index = controller_paths
.iter()
.position(|path| selected == path)
.unwrap_or(0) as i32;
selected_controllers.push(selected_index);
controller_changed.push(false);
}
app.set_selected_controller(slint::ModelRc::from(std::rc::Rc::new(selected_controllers)));
let config_controller_assignment = config.input.controller_assignment.clone();
let weak_app = app.as_weak();
app.on_controller_window_created(move || {
let controller_assignment = config_controller_assignment.clone();
weak_app
.upgrade_in_event_loop(move |handle| {
let mut current_selected_paths = vec![None; 4];
for (i, selected_controller) in handle.get_selected_controller().iter().enumerate()
{
current_selected_paths[i] = handle
.get_controller_paths()
.row_data(selected_controller as usize);
}
app.set_controller_changed(slint::ModelRc::from(std::rc::Rc::new(controller_changed)));
let controller_names = ui::input::get_controller_names();
handle.set_controller_names(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
controller_names
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
let controller_paths = ui::input::get_controller_paths();
handle.set_controller_paths(slint::ModelRc::from(std::rc::Rc::new(
slint::VecModel::from(
controller_paths
.iter()
.map(|x| x.into())
.collect::<Vec<slint::SharedString>>(),
),
)));
let selected_controllers = slint::VecModel::default();
for i in 0..4 {
let assigned_path =
if let Some(current_selected_path) = &current_selected_paths[i] {
current_selected_path.to_string()
} else if let Some(config_assigned_path) = &controller_assignment[i] {
config_assigned_path.to_string()
} else {
String::new()
};
let selected_index = controller_paths
.iter()
.position(|controller_path| assigned_path == *controller_path)
.unwrap_or(0) as i32;
selected_controllers.push(selected_index);
}
handle.set_selected_controller(slint::ModelRc::from(std::rc::Rc::new(
selected_controllers,
)));
})
.unwrap();
});
let weak_app = app.as_weak();
app.on_input_profile_button_clicked(move || {
@@ -411,7 +415,7 @@ fn controller_window(
});
}
pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
pub fn save_settings(app: &AppWindow) {
let mut config = ui::config::Config::new();
config.video.integer_scaling = app.get_integer_scaling();
config.video.fullscreen = app.get_fullscreen();
@@ -443,8 +447,16 @@ pub fn save_settings(app: &AppWindow, controller_paths: &[Option<String>]) {
for (i, selected_controller) in app.get_selected_controller().iter().enumerate() {
if app.get_controller_changed().row_data(i).unwrap_or(false) {
config.input.controller_assignment[i] =
controller_paths[selected_controller as usize].clone();
let controller_path = app
.get_controller_paths()
.row_data(selected_controller as usize)
.unwrap()
.to_string();
if controller_path.is_empty() {
config.input.controller_assignment[i] = None;
} else {
config.input.controller_assignment[i] = Some(controller_path);
}
}
}
}
@@ -493,21 +505,17 @@ pub fn app_window() {
let app = AppWindow::new().unwrap();
about_window(&app);
ui::retroachievements::ra_window(&app);
let mut controller_paths;
{
let game_ui = ui::Ui::new();
let mut controller_names = ui::input::get_controller_names(&game_ui);
controller_names.insert(0, "None".into());
controller_paths = ui::input::get_controller_paths(&game_ui);
controller_paths.insert(0, None);
settings_window(&app, &game_ui.config);
controller_window(&app, &game_ui.config, &controller_names, &controller_paths);
local_game_window(&app, &game_ui.config, &controller_paths);
let config = ui::config::Config::new();
settings_window(&app, &config);
controller_window(&app, &config);
local_game_window(&app, &config);
}
ui::netplay::netplay_window(&app, &controller_paths);
ui::netplay::netplay_window(&app);
ui::cheats::cheats_window(&app);
app.run().unwrap();
save_settings(&app, &controller_paths);
save_settings(&app);
unsafe { sdl3_sys::init::SDL_Quit() };
}
pub fn run_rom(
+5 -1
View File
@@ -41,6 +41,7 @@ export component AppWindow inherits Window {
callback ra_games_clicked;
callback ra_show_profile_clicked;
callback ra_toggled(bool,bool,bool,bool);
callback controller_window_created;
in property version <=> AboutData.version;
in property has_update <=> AboutData.has_update;
in-out property ra_username <=> RAData.ra_username;
@@ -72,10 +73,10 @@ export component AppWindow inherits Window {
in-out property selected_profile_binding <=> ControllerData.selected_profile_binding;
in-out property input_profiles <=> ControllerData.input_profiles;
in-out property controller_names <=> ControllerData.controller_names;
in-out property controller_paths <=> ControllerData.controller_paths;
in-out property selected_controller <=> ControllerData.selected_controller;
in-out property game_running <=> State.game_running;
in-out property recent_roms <=> State.recent_roms;
in-out property blank_profiles <=> ControllerData.blank_profiles;
in-out property cheat_game_name <=> CheatsData.cheat_game_name;
in-out property cheat_game_crc <=> CheatsData.cheat_game_crc;
in-out property cheats <=> CheatsData.cheats;
@@ -119,6 +120,9 @@ export component AppWindow inherits Window {
transferpak_toggled(player,enabled) => {
transferpak_toggled(player,enabled);
}
controller_window_created => {
controller_window_created();
}
}
if(side-bar.current-item == 2): Netplay {
create_session_button_clicked => {
+7 -2
View File
@@ -14,16 +14,21 @@ export global ControllerData {
in-out property <[int]> selected_profile_binding: [-1, -1, -1, -1];
in-out property <[string]> input_profiles;
in-out property <[string]> controller_names;
in-out property <[string]> controller_paths;
in-out property <[int]> selected_controller;
in-out property <[bool]> controller_changed;
in-out property <bool> blank_profiles;
}
export component ControllerConfig inherits Page {
callback input_profile_button_clicked;
callback transferpak_toggled(int,bool);
callback controller_window_created;
title: @tr("Controller Configuration");
init => {
controller_window_created();
}
VerticalBox {
alignment: start;
HorizontalBox {
@@ -92,7 +97,7 @@ export component ControllerConfig inherits Page {
}
}
if !ControllerData.blank_profiles: GroupBox {
GroupBox {
title: @tr("Profile");
horizontal-stretch: 0.25;
VerticalLayout {
+50 -23
View File
@@ -341,10 +341,10 @@ fn hotkey_pressed(
pressed
}
pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
pub fn get_controller_names() -> Vec<String> {
let mut controllers: Vec<String> = vec![];
for joystick in game_ui.input.joysticks.iter() {
for joystick in get_joysticks().iter() {
let name = unsafe { sdl3_sys::joystick::SDL_GetJoystickNameForID(*joystick) };
controllers.push(if name.is_null() {
"Unknown controller".to_string()
@@ -352,22 +352,24 @@ pub fn get_controller_names(game_ui: &ui::Ui) -> Vec<String> {
unsafe { std::ffi::CStr::from_ptr(name).to_str().unwrap() }.to_string()
});
}
controllers.insert(0, "None".into());
controllers
}
#[cfg(feature = "gui")]
pub fn get_controller_paths(game_ui: &ui::Ui) -> Vec<Option<String>> {
let mut controller_paths: Vec<Option<String>> = vec![];
pub fn get_controller_paths() -> Vec<String> {
let mut controller_paths: Vec<String> = vec![];
for joystick in game_ui.input.joysticks.iter() {
for joystick in get_joysticks().iter() {
let path = unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(*joystick) };
controller_paths.push(if path.is_null() {
None
String::new()
} else {
Some(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }.to_string())
unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }.to_string()
});
}
controller_paths.insert(0, String::new());
controller_paths
}
@@ -553,13 +555,15 @@ pub fn get(ui: &mut ui::Ui, channel: usize) -> InputData {
}
}
pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
if controller < ui.input.joysticks.len() as i32 {
let path = unsafe {
sdl3_sys::joystick::SDL_GetJoystickPathForID(ui.input.joysticks[controller as usize])
};
pub fn assign_controller(config: &mut ui::config::Config, controller: i32, port: usize) {
let joysticks = get_joysticks();
if controller < 0 {
config.input.controller_assignment[port - 1] = None;
} else if controller < joysticks.len() as i32 {
let path =
unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(joysticks[controller as usize]) };
if !path.is_null() {
ui.config.input.controller_assignment[port - 1] =
config.input.controller_assignment[port - 1] =
Some(unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap().to_string() });
} else {
eprintln!("Invalid controller path for controller {controller}");
@@ -569,18 +573,18 @@ pub fn assign_controller(ui: &mut ui::Ui, controller: i32, port: usize) {
}
}
pub fn bind_input_profile(ui: &mut ui::Ui, profile: String, port: usize) {
if ui.config.input.input_profiles.contains_key(&profile) {
ui.config.input.input_profile_binding[port - 1] = profile;
pub fn bind_input_profile(config: &mut ui::config::Config, profile: String, port: usize) {
if config.input.input_profiles.contains_key(&profile) {
config.input.input_profile_binding[port - 1] = profile;
} else {
eprintln!("Invalid profile name")
}
}
pub fn clear_bindings(ui: &mut ui::Ui) {
pub fn clear_bindings(config: &mut ui::config::Config) {
for i in 0..4 {
ui.config.input.controller_assignment[i] = None;
ui.config.input.input_profile_binding[i] = "default".to_string();
config.input.controller_assignment[i] = None;
config.input.input_profile_binding[i] = "default".to_string();
}
}
@@ -604,10 +608,17 @@ fn close_input_profile_window(
sdl3_ttf_sys::ttf::TTF_DestroyRendererTextEngine(text_engine);
sdl3_sys::render::SDL_DestroyRenderer(renderer);
sdl3_sys::video::SDL_DestroyWindow(window);
sdl3_ttf_sys::ttf::TTF_Quit();
sdl3_sys::init::SDL_Quit();
}
}
pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, deadzone: i32) {
pub fn configure_input_profile(
config: &mut ui::config::Config,
profile: String,
dinput: bool,
deadzone: i32,
) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_VIDEO);
ui::ttf_init();
@@ -622,7 +633,7 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, d
let mut open_joysticks: Vec<*mut sdl3_sys::joystick::SDL_Joystick> = Vec::new();
let mut open_controllers: Vec<*mut sdl3_sys::gamepad::SDL_Gamepad> = Vec::new();
for joystick in ui.input.joysticks.iter() {
for joystick in get_joysticks().iter() {
if !dinput {
let controller = unsafe { sdl3_sys::gamepad::SDL_OpenGamepad(*joystick) };
if !controller.is_null() {
@@ -847,7 +858,7 @@ pub fn configure_input_profile(ui: &mut ui::Ui, profile: String, dinput: bool, d
dinput,
deadzone,
};
ui.config.input.input_profiles.insert(profile, new_profile);
config.input.input_profiles.insert(profile, new_profile);
}
pub fn get_default_profile() -> ui::config::InputProfile {
@@ -1050,6 +1061,22 @@ pub fn get_default_profile() -> ui::config::InputProfile {
}
}
fn get_joysticks() -> Vec<sdl3_sys::joystick::SDL_JoystickID> {
ui::sdl_init(sdl3_sys::init::SDL_INIT_GAMEPAD);
unsafe { sdl3_sys::events::SDL_PumpEvents() };
let mut num_joysticks = 0;
let sdl_joysticks = unsafe { sdl3_sys::joystick::SDL_GetJoysticks(&mut num_joysticks) };
if !sdl_joysticks.is_null() {
let parts =
unsafe { std::slice::from_raw_parts(sdl_joysticks, num_joysticks as usize) }.to_vec();
unsafe { sdl3_sys::stdinc::SDL_free(sdl_joysticks as *mut std::ffi::c_void) };
parts
} else {
eprintln!("Could not get joysticks");
vec![]
}
}
pub fn init(ui: &mut ui::Ui) {
ui.input.keyboard_state =
unsafe { sdl3_sys::keyboard::SDL_GetKeyboardState(std::ptr::null_mut()) };
@@ -1063,7 +1090,7 @@ pub fn init(ui: &mut ui::Ui) {
{
let mut joystick_id = sdl3_sys::everything::SDL_JoystickID(0);
for joystick in ui.input.joysticks.iter() {
for joystick in get_joysticks().iter() {
let path = unsafe { sdl3_sys::joystick::SDL_GetJoystickPathForID(*joystick) };
if !path.is_null()
&& unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }
+3 -7
View File
@@ -1208,17 +1208,15 @@ pub fn setup_join_window(
join_window.show().unwrap();
}
pub fn netplay_window(app: &AppWindow, controller_paths: &[Option<String>]) {
pub fn netplay_window(app: &AppWindow) {
let weak_create = app.as_weak();
let weak_app = app.as_weak();
let controller_paths_create = controller_paths.to_owned();
app.on_create_session_button_clicked(move || {
let controller_paths = controller_paths_create.clone();
let weak_app = weak_app.clone();
weak_create
.upgrade_in_event_loop(move |handle| {
let create_window = NetplayCreate::new().unwrap();
save_settings(&handle, &controller_paths);
save_settings(&handle);
setup_create_window(
&create_window,
ui::GameSettings {
@@ -1242,14 +1240,12 @@ pub fn netplay_window(app: &AppWindow, controller_paths: &[Option<String>]) {
let weak_join = app.as_weak();
let weak_app = app.as_weak();
let controller_paths_join = controller_paths.to_owned();
app.on_join_session_button_clicked(move || {
let controller_paths = controller_paths_join.clone();
let weak_app = weak_app.clone();
weak_join
.upgrade_in_event_loop(move |handle| {
let join_window = NetplayJoin::new().unwrap();
save_settings(&handle, &controller_paths);
save_settings(&handle);
setup_join_window(
&join_window,
handle.get_rom_dir(),
+2
View File
@@ -150,6 +150,8 @@ pub fn close(ui: &ui::Ui) {
unsafe {
rdp_close();
sdl3_sys::video::SDL_DestroyWindow(ui.video.window);
sdl3_ttf_sys::ttf::TTF_Quit();
sdl3_sys::init::SDL_Quit();
}
}