mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-23 03:29:43 +00:00
ui: Support controller peripherals and XMU devices (#1315)
* Added XMU Settings to the Input Screen * Added Peripherals to config * Prevent overwriting existing XMUs * Added blockdev.h to try to fix the MacOS build * Fixed some issues that antangelo pointed out * Moved the peripheralType and param vars into the loop * Moved fatx.h and fatx.c to ui\thirdparty\fatx * Added Validation for Peripheral Settings * Fixed some nits that were pointed out * don't pass NULL into xemu_settings_set_string * Changes following Matt's recommendations * Changes to XMU FilePicker * XMU image auto-bind logic refactor * renamed peripheralType to peripheral_type * removed unnecessary calls to g_strdup_printf and g_free * Cleaned up some comments, removed an unnecessary variable * handle overwrite prompt in Windows * Fixed some code format and style inconsistencies * More formatting fixes * Fixed a few memory leaks * qemu_access: check for Read and Write access * Run clang-format * Remove unused xemu_new_xmu declaration * Fix use after free in rebind code
This commit is contained in:
parent
800eb468a4
commit
03f40b1d8e
@ -25,6 +25,27 @@ input:
|
||||
port2: string
|
||||
port3: string
|
||||
port4: string
|
||||
peripherals:
|
||||
port1:
|
||||
peripheral_type_0: integer
|
||||
peripheral_param_0: string
|
||||
peripheral_type_1: integer
|
||||
peripheral_param_1: string
|
||||
port2:
|
||||
peripheral_type_0: integer
|
||||
peripheral_param_0: string
|
||||
peripheral_type_1: integer
|
||||
peripheral_param_1: string
|
||||
port3:
|
||||
peripheral_type_0: integer
|
||||
peripheral_param_0: string
|
||||
peripheral_type_1: integer
|
||||
peripheral_param_1: string
|
||||
port4:
|
||||
peripheral_type_0: integer
|
||||
peripheral_param_0: string
|
||||
peripheral_type_1: integer
|
||||
peripheral_param_1: string
|
||||
gamecontrollerdb_path: string
|
||||
auto_bind:
|
||||
type: bool
|
||||
|
@ -1,5 +1,6 @@
|
||||
pfiles = [
|
||||
'controller_mask.png',
|
||||
'xmu_mask.png',
|
||||
'logo_sdf.png',
|
||||
'xemu_64x64.png',
|
||||
'abxy.ttf',
|
||||
|
BIN
data/xmu_mask.png
Normal file
BIN
data/xmu_mask.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.7 KiB |
@ -47,7 +47,7 @@ endif
|
||||
xemu_ss.add(when: 'CONFIG_LINUX', if_true: [gtk, files('xemu-os-utils-linux.c')])
|
||||
xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c'))
|
||||
xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m'))
|
||||
xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib)
|
||||
xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib, fatx)
|
||||
|
||||
softmmu_ss.add_all(xemu_ss)
|
||||
|
||||
|
51
ui/thirdparty/fatx/fatx.c
vendored
Normal file
51
ui/thirdparty/fatx/fatx.c
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
#include "fatx.h"
|
||||
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
#define FATX_SIGNATURE 0x58544146
|
||||
|
||||
// This is from libfatx
|
||||
#pragma pack(1)
|
||||
struct fatx_superblock {
|
||||
uint32_t signature;
|
||||
uint32_t volume_id;
|
||||
uint32_t sectors_per_cluster;
|
||||
uint32_t root_cluster;
|
||||
uint16_t unknown1;
|
||||
uint8_t padding[4078];
|
||||
};
|
||||
#pragma pack()
|
||||
|
||||
bool create_fatx_image(const char *filename, unsigned int size)
|
||||
{
|
||||
unsigned int empty_fat = cpu_to_le32(0xfffffff8);
|
||||
unsigned char zero = 0;
|
||||
|
||||
FILE *fp = qemu_fopen(filename, "wb");
|
||||
if (fp != NULL) {
|
||||
struct fatx_superblock superblock;
|
||||
memset(&superblock, 0xff, sizeof(struct fatx_superblock));
|
||||
|
||||
superblock.signature = cpu_to_le32(FATX_SIGNATURE);
|
||||
superblock.sectors_per_cluster = cpu_to_le32(4);
|
||||
superblock.volume_id = (uint32_t)rand();
|
||||
superblock.root_cluster = cpu_to_le32(1);
|
||||
superblock.unknown1 = 0;
|
||||
|
||||
// Write the fatx superblock.
|
||||
fwrite(&superblock, sizeof(superblock), 1, fp);
|
||||
|
||||
// Write the FAT
|
||||
fwrite(&empty_fat, sizeof(empty_fat), 1, fp);
|
||||
|
||||
fseek(fp, size - sizeof(unsigned char), SEEK_SET);
|
||||
fwrite(&zero, sizeof(unsigned char), 1, fp);
|
||||
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
16
ui/thirdparty/fatx/fatx.h
vendored
Normal file
16
ui/thirdparty/fatx/fatx.h
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef FATX_H
|
||||
#define FATX_H
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool create_fatx_image(const char *filename, unsigned int size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
3
ui/thirdparty/meson.build
vendored
3
ui/thirdparty/meson.build
vendored
@ -62,3 +62,6 @@ fpng = declare_dependency(include_directories: 'fpng', link_with: libfpng)
|
||||
|
||||
json = declare_dependency(include_directories: 'json')
|
||||
httplib = declare_dependency(include_directories: 'httplib')
|
||||
|
||||
libfatx = static_library('fatx', sources: 'fatx/fatx.c')
|
||||
fatx = declare_dependency(include_directories: 'fatx', link_with: libfatx)
|
||||
|
@ -285,6 +285,9 @@ const char *noc_file_dialog_open(int flags,
|
||||
ofn.lpstrInitialDir = initialDir;
|
||||
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
|
||||
|
||||
if (flags & NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION)
|
||||
ofn.Flags |= OFN_OVERWRITEPROMPT;
|
||||
|
||||
if (flags & NOC_FILE_DIALOG_OPEN) {
|
||||
ret = GetOpenFileNameW(&ofn);
|
||||
} else {
|
||||
|
258
ui/xemu-input.c
258
ui/xemu-input.c
@ -32,6 +32,8 @@
|
||||
#include "xemu-notifications.h"
|
||||
#include "xemu-settings.h"
|
||||
|
||||
#include "sysemu/blockdev.h"
|
||||
|
||||
// #define DEBUG_INPUT
|
||||
|
||||
#ifdef DEBUG_INPUT
|
||||
@ -93,8 +95,32 @@ static const char **port_index_to_settings_key_map[] = {
|
||||
&g_config.input.bindings.port4,
|
||||
};
|
||||
|
||||
static int *peripheral_types_settings_map[4][2] = {
|
||||
{ &g_config.input.peripherals.port1.peripheral_type_0,
|
||||
&g_config.input.peripherals.port1.peripheral_type_1 },
|
||||
{ &g_config.input.peripherals.port2.peripheral_type_0,
|
||||
&g_config.input.peripherals.port2.peripheral_type_1 },
|
||||
{ &g_config.input.peripherals.port3.peripheral_type_0,
|
||||
&g_config.input.peripherals.port3.peripheral_type_1 },
|
||||
{ &g_config.input.peripherals.port4.peripheral_type_0,
|
||||
&g_config.input.peripherals.port4.peripheral_type_1 }
|
||||
};
|
||||
|
||||
static const char **peripheral_params_settings_map[4][2] = {
|
||||
{ &g_config.input.peripherals.port1.peripheral_param_0,
|
||||
&g_config.input.peripherals.port1.peripheral_param_1 },
|
||||
{ &g_config.input.peripherals.port2.peripheral_param_0,
|
||||
&g_config.input.peripherals.port2.peripheral_param_1 },
|
||||
{ &g_config.input.peripherals.port3.peripheral_param_0,
|
||||
&g_config.input.peripherals.port3.peripheral_param_1 },
|
||||
{ &g_config.input.peripherals.port4.peripheral_param_0,
|
||||
&g_config.input.peripherals.port4.peripheral_param_1 }
|
||||
};
|
||||
|
||||
static int sdl_kbd_scancode_map[25];
|
||||
|
||||
static const int port_map[4] = { 3, 4, 1, 2 };
|
||||
|
||||
void xemu_input_init(void)
|
||||
{
|
||||
if (g_config.input.background_input_capture) {
|
||||
@ -112,6 +138,10 @@ void xemu_input_init(void)
|
||||
new_con->type = INPUT_DEVICE_SDL_KEYBOARD;
|
||||
new_con->name = "Keyboard";
|
||||
new_con->bound = -1;
|
||||
new_con->peripheral_types[0] = PERIPHERAL_NONE;
|
||||
new_con->peripheral_types[1] = PERIPHERAL_NONE;
|
||||
new_con->peripherals[0] = NULL;
|
||||
new_con->peripherals[1] = NULL;
|
||||
|
||||
sdl_kbd_scancode_map[0] = g_config.input.keyboard_controller_scancode_map.a;
|
||||
sdl_kbd_scancode_map[1] = g_config.input.keyboard_controller_scancode_map.b;
|
||||
@ -154,6 +184,7 @@ void xemu_input_init(void)
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||
xemu_queue_notification(buf);
|
||||
xemu_input_rebind_xmu(port);
|
||||
}
|
||||
|
||||
QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry);
|
||||
@ -177,6 +208,24 @@ int xemu_input_get_controller_default_bind_port(ControllerState *state, int star
|
||||
return -1;
|
||||
}
|
||||
|
||||
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
|
||||
int peripheral_type,
|
||||
const char *peripheral_parameter)
|
||||
{
|
||||
int *peripheral_type_ptr =
|
||||
peripheral_types_settings_map[player_index][peripheral_index];
|
||||
const char **peripheral_param_ptr =
|
||||
peripheral_params_settings_map[player_index][peripheral_index];
|
||||
|
||||
assert(peripheral_type_ptr);
|
||||
assert(peripheral_param_ptr);
|
||||
|
||||
*peripheral_type_ptr = peripheral_type;
|
||||
xemu_settings_set_string(
|
||||
peripheral_param_ptr,
|
||||
peripheral_parameter == NULL ? "" : peripheral_parameter);
|
||||
}
|
||||
|
||||
void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
{
|
||||
if (event->type == SDL_CONTROLLERDEVICEADDED) {
|
||||
@ -201,6 +250,10 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick);
|
||||
new_con->sdl_joystick_guid = SDL_JoystickGetGUID(new_con->sdl_joystick);
|
||||
new_con->bound = -1;
|
||||
new_con->peripheral_types[0] = PERIPHERAL_NONE;
|
||||
new_con->peripheral_types[1] = PERIPHERAL_NONE;
|
||||
new_con->peripherals[0] = NULL;
|
||||
new_con->peripherals[1] = NULL;
|
||||
|
||||
char guid_buf[35] = { 0 };
|
||||
SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf, sizeof(guid_buf));
|
||||
@ -254,6 +307,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
|
||||
xemu_queue_notification(buf);
|
||||
xemu_input_rebind_xmu(port);
|
||||
}
|
||||
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
|
||||
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
|
||||
@ -286,6 +340,11 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
|
||||
if (iter->sdl_gamecontroller) {
|
||||
SDL_GameControllerClose(iter->sdl_gamecontroller);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (iter->peripherals[i])
|
||||
g_free(iter->peripherals[i]);
|
||||
}
|
||||
free(iter);
|
||||
|
||||
handled = 1;
|
||||
@ -380,12 +439,9 @@ void xemu_input_update_sdl_controller_state(ControllerState *state)
|
||||
}
|
||||
|
||||
const SDL_GameControllerAxis sdl_axis_map[6] = {
|
||||
SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||
SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
SDL_CONTROLLER_AXIS_LEFTX,
|
||||
SDL_CONTROLLER_AXIS_LEFTY,
|
||||
SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY,
|
||||
SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
};
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
@ -429,6 +485,22 @@ void xemu_input_bind(int index, ControllerState *state, int save)
|
||||
if (bound_controllers[index]) {
|
||||
assert(bound_controllers[index]->device != NULL);
|
||||
Error *err = NULL;
|
||||
|
||||
// Unbind any XMUs
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (bound_controllers[index]->peripherals[i]) {
|
||||
// If this was an XMU, unbind the XMU
|
||||
if (bound_controllers[index]->peripheral_types[i] ==
|
||||
PERIPHERAL_XMU)
|
||||
xemu_input_unbind_xmu(index, i);
|
||||
|
||||
// Free up the XmuState and set the peripheral type to none
|
||||
g_free(bound_controllers[index]->peripherals[i]);
|
||||
bound_controllers[index]->peripherals[i] = NULL;
|
||||
bound_controllers[index]->peripheral_types[i] = PERIPHERAL_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
qdev_unplug((DeviceState *)bound_controllers[index]->device, &err);
|
||||
assert(err == NULL);
|
||||
|
||||
@ -460,7 +532,6 @@ void xemu_input_bind(int index, ControllerState *state, int save)
|
||||
bound_controllers[index] = state;
|
||||
bound_controllers[index]->bound = index;
|
||||
|
||||
const int port_map[4] = {3, 4, 1, 2};
|
||||
char *tmp;
|
||||
|
||||
// Create controller's internal USB hub.
|
||||
@ -506,6 +577,179 @@ void xemu_input_bind(int index, ControllerState *state, int save)
|
||||
}
|
||||
}
|
||||
|
||||
bool xemu_input_bind_xmu(int player_index, int expansion_slot_index,
|
||||
const char *filename, bool is_rebind)
|
||||
{
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
|
||||
|
||||
ControllerState *player = bound_controllers[player_index];
|
||||
enum peripheral_type peripheral_type =
|
||||
player->peripheral_types[expansion_slot_index];
|
||||
if (peripheral_type != PERIPHERAL_XMU)
|
||||
return false;
|
||||
|
||||
XmuState *xmu = (XmuState *)player->peripherals[expansion_slot_index];
|
||||
|
||||
// Unbind existing XMU
|
||||
if (xmu->dev != NULL) {
|
||||
xemu_input_unbind_xmu(player_index, expansion_slot_index);
|
||||
}
|
||||
|
||||
if (filename == NULL)
|
||||
return false;
|
||||
|
||||
// Look for any other XMUs that are using this file, and unbind them
|
||||
for (int player_i = 0; player_i < 4; player_i++) {
|
||||
ControllerState *state = bound_controllers[player_i];
|
||||
if (state != NULL) {
|
||||
for (int peripheral_i = 0; peripheral_i < 2; peripheral_i++) {
|
||||
if (state->peripheral_types[peripheral_i] == PERIPHERAL_XMU) {
|
||||
XmuState *xmu_i =
|
||||
(XmuState *)state->peripherals[peripheral_i];
|
||||
assert(xmu_i);
|
||||
|
||||
if (xmu_i->filename != NULL &&
|
||||
strcmp(xmu_i->filename, filename) == 0) {
|
||||
char *buf =
|
||||
g_strdup_printf("This XMU is already mounted on "
|
||||
"player %d slot %c\r\n",
|
||||
player_i + 1, 'A' + peripheral_i);
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xmu->filename = g_strdup(filename);
|
||||
|
||||
const int xmu_map[2] = { 2, 3 };
|
||||
char *tmp;
|
||||
|
||||
static int id_counter = 0;
|
||||
tmp = g_strdup_printf("xmu_%d", id_counter++);
|
||||
|
||||
// Add the file as a drive
|
||||
QDict *qdict1 = qdict_new();
|
||||
qdict_put_str(qdict1, "id", tmp);
|
||||
qdict_put_str(qdict1, "format", "raw");
|
||||
qdict_put_str(qdict1, "file", filename);
|
||||
|
||||
QemuOpts *drvopts =
|
||||
qemu_opts_from_qdict(qemu_find_opts("drive"), qdict1, &error_abort);
|
||||
|
||||
DriveInfo *dinfo = drive_new(drvopts, 0, &error_abort);
|
||||
assert(dinfo);
|
||||
|
||||
// Create the usb-storage device
|
||||
QDict *qdict2 = qdict_new();
|
||||
|
||||
// Specify device driver
|
||||
qdict_put_str(qdict2, "driver", "usb-storage");
|
||||
|
||||
// Specify device identifier
|
||||
qdict_put_str(qdict2, "drive", tmp);
|
||||
g_free(tmp);
|
||||
|
||||
// Specify index/port
|
||||
tmp = g_strdup_printf("1.%d.%d", port_map[player_index],
|
||||
xmu_map[expansion_slot_index]);
|
||||
qdict_put_str(qdict2, "port", tmp);
|
||||
g_free(tmp);
|
||||
|
||||
// Create the device
|
||||
QemuOpts *opts =
|
||||
qemu_opts_from_qdict(qemu_find_opts("device"), qdict2, &error_abort);
|
||||
|
||||
DeviceState *dev = qdev_device_add(opts, &error_abort);
|
||||
assert(dev);
|
||||
|
||||
xmu->dev = (void *)dev;
|
||||
|
||||
// Unref for eventual cleanup
|
||||
qobject_unref(qdict1);
|
||||
qobject_unref(qdict2);
|
||||
|
||||
if (!is_rebind) {
|
||||
xemu_save_peripheral_settings(player_index, expansion_slot_index,
|
||||
peripheral_type, xmu->filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void xemu_input_unbind_xmu(int player_index, int expansion_slot_index)
|
||||
{
|
||||
assert(player_index >= 0 && player_index < 4);
|
||||
assert(expansion_slot_index >= 0 && expansion_slot_index < 2);
|
||||
|
||||
ControllerState *state = bound_controllers[player_index];
|
||||
if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU)
|
||||
return;
|
||||
|
||||
XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index];
|
||||
if (xmu != NULL) {
|
||||
if (xmu->dev != NULL) {
|
||||
qdev_unplug((DeviceState *)xmu->dev, &error_abort);
|
||||
object_unref(OBJECT(xmu->dev));
|
||||
xmu->dev = NULL;
|
||||
}
|
||||
|
||||
g_free((void *)xmu->filename);
|
||||
xmu->filename = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void xemu_input_rebind_xmu(int port)
|
||||
{
|
||||
// Try to bind peripherals back to controller
|
||||
for (int i = 0; i < 2; i++) {
|
||||
enum peripheral_type peripheral_type =
|
||||
(enum peripheral_type)(*peripheral_types_settings_map[port][i]);
|
||||
|
||||
// If peripheralType is out of range, change the settings for this
|
||||
// controller and peripheral port to default
|
||||
if (peripheral_type < PERIPHERAL_NONE ||
|
||||
peripheral_type >= PERIPHERAL_TYPE_COUNT) {
|
||||
xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL);
|
||||
peripheral_type = PERIPHERAL_NONE;
|
||||
}
|
||||
|
||||
const char *param = *peripheral_params_settings_map[port][i];
|
||||
|
||||
if (peripheral_type == PERIPHERAL_XMU) {
|
||||
if (param != NULL && strlen(param) > 0) {
|
||||
// This is an XMU and needs to be bound to this controller
|
||||
if (qemu_access(param, R_OK | W_OK) == 0) {
|
||||
bound_controllers[port]->peripheral_types[i] =
|
||||
peripheral_type;
|
||||
bound_controllers[port]->peripherals[i] =
|
||||
g_malloc(sizeof(XmuState));
|
||||
memset(bound_controllers[port]->peripherals[i], 0,
|
||||
sizeof(XmuState));
|
||||
bool did_bind = xemu_input_bind_xmu(port, i, param, true);
|
||||
if (did_bind) {
|
||||
char *buf =
|
||||
g_strdup_printf("Connected XMU %s to port %d%c",
|
||||
param, port + 1, 'A' + i);
|
||||
xemu_queue_notification(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
} else {
|
||||
char *buf =
|
||||
g_strdup_printf("Unable to bind XMU at %s to port %d%c",
|
||||
param, port + 1, 'A' + i);
|
||||
xemu_queue_error_message(buf);
|
||||
g_free(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void xemu_input_set_test_mode(int enabled)
|
||||
{
|
||||
test_mode = enabled;
|
||||
|
@ -26,6 +26,8 @@
|
||||
#define XEMU_INPUT_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "qemu/queue.h"
|
||||
|
||||
enum controller_state_buttons_mask {
|
||||
@ -63,6 +65,13 @@ enum controller_input_device_type {
|
||||
INPUT_DEVICE_SDL_GAMECONTROLLER,
|
||||
};
|
||||
|
||||
enum peripheral_type { PERIPHERAL_NONE, PERIPHERAL_XMU, PERIPHERAL_TYPE_COUNT };
|
||||
|
||||
typedef struct XmuState {
|
||||
const char *filename;
|
||||
void *dev;
|
||||
} XmuState;
|
||||
|
||||
typedef struct ControllerState {
|
||||
QTAILQ_ENTRY(ControllerState) entry;
|
||||
|
||||
@ -88,6 +97,9 @@ typedef struct ControllerState {
|
||||
SDL_JoystickID sdl_joystick_id;
|
||||
SDL_JoystickGUID sdl_joystick_guid;
|
||||
|
||||
enum peripheral_type peripheral_types[2];
|
||||
void *peripherals[2];
|
||||
|
||||
int bound; // Which port this input device is bound to
|
||||
void *device; // DeviceState opaque
|
||||
} ControllerState;
|
||||
@ -109,7 +121,14 @@ void xemu_input_update_sdl_controller_state(ControllerState *state);
|
||||
void xemu_input_update_rumble(ControllerState *state);
|
||||
ControllerState *xemu_input_get_bound(int index);
|
||||
void xemu_input_bind(int index, ControllerState *state, int save);
|
||||
bool xemu_input_bind_xmu(int player_index, int peripheral_port_index,
|
||||
const char *filename, bool is_rebind);
|
||||
void xemu_input_rebind_xmu(int port);
|
||||
void xemu_input_unbind_xmu(int player_index, int peripheral_port_index);
|
||||
int xemu_input_get_controller_default_bind_port(ControllerState *state, int start);
|
||||
void xemu_save_peripheral_settings(int player_index, int peripheral_index,
|
||||
int peripheral_type,
|
||||
const char *peripheral_parameter);
|
||||
|
||||
void xemu_input_set_test_mode(int enabled);
|
||||
int xemu_input_get_test_mode(void);
|
||||
|
@ -16,25 +16,24 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
#include "common.hh"
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <fpng.h>
|
||||
#include "ui/xemu-widescreen.h"
|
||||
#include "gl-helpers.hh"
|
||||
#include "stb_image.h"
|
||||
#include "common.hh"
|
||||
#include "data/controller_mask.png.h"
|
||||
#include "data/logo_sdf.png.h"
|
||||
#include "ui/shader/xemu-logo-frag.h"
|
||||
#include "data/xemu_64x64.png.h"
|
||||
#include "data/xmu_mask.png.h"
|
||||
#include "notifications.hh"
|
||||
#include "ui/xemu-widescreen.h"
|
||||
#include "stb_image.h"
|
||||
#include <fpng.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
Fbo *controller_fbo,
|
||||
*logo_fbo;
|
||||
GLuint g_controller_tex,
|
||||
g_logo_tex,
|
||||
g_icon_tex;
|
||||
#include "ui/shader/xemu-logo-frag.h"
|
||||
|
||||
Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
|
||||
GLuint g_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex;
|
||||
|
||||
enum class ShaderType {
|
||||
Blit,
|
||||
@ -186,9 +185,11 @@ static GLuint Shader(GLenum type, const char *src)
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status != GL_TRUE) {
|
||||
glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf);
|
||||
fprintf(stderr, "Shader compilation failed: %s\n\n"
|
||||
fprintf(stderr,
|
||||
"Shader compilation failed: %s\n\n"
|
||||
"[Shader Source]\n"
|
||||
"%s\n", err_buf, src);
|
||||
"%s\n",
|
||||
err_buf, src);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
@ -222,15 +223,15 @@ void main() {
|
||||
GLuint vert = Shader(GL_VERTEX_SHADER, vert_src);
|
||||
assert(vert != 0);
|
||||
|
||||
// const char *image_frag_src = R"(
|
||||
// #version 150 core
|
||||
// uniform sampler2D tex;
|
||||
// in vec2 Texcoord;
|
||||
// out vec4 out_Color;
|
||||
// void main() {
|
||||
// out_Color.rgba = texture(tex, Texcoord);
|
||||
// }
|
||||
// )";
|
||||
// const char *image_frag_src = R"(
|
||||
// #version 150 core
|
||||
// uniform sampler2D tex;
|
||||
// in vec2 Texcoord;
|
||||
// out vec4 out_Color;
|
||||
// void main() {
|
||||
// out_Color.rgba = texture(tex, Texcoord);
|
||||
// }
|
||||
// )";
|
||||
|
||||
const char *image_gamma_frag_src = R"(
|
||||
#version 400 core
|
||||
@ -370,7 +371,7 @@ void RenderDecal(DecalShader *s, float x, float y, float w, float h,
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
|
||||
float tw = tw_i, th = th_i;
|
||||
|
||||
#define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0
|
||||
#define COL(color, c) (float)(((color) >> ((c)*8)) & 0xff) / 255.0
|
||||
if (s->flipy_loc >= 0) {
|
||||
glUniform1i(s->flipy_loc, s->flip);
|
||||
}
|
||||
@ -403,7 +404,7 @@ void RenderDecal(DecalShader *s, float x, float y, float w, float h,
|
||||
if (s->scale_loc >= 0) {
|
||||
glUniform1f(s->scale_loc, s->scale);
|
||||
}
|
||||
#undef COL
|
||||
#undef COL
|
||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
||||
}
|
||||
|
||||
@ -420,6 +421,7 @@ static const struct rect tex_items[] = {
|
||||
{ 67, 48, 28, 28 }, // obj_port_lbl_2
|
||||
{ 67, 20, 28, 28 }, // obj_port_lbl_3
|
||||
{ 95, 76, 28, 28 }, // obj_port_lbl_4
|
||||
{ 0, 0, 512, 512 } // obj_xmu
|
||||
};
|
||||
|
||||
enum tex_item_names {
|
||||
@ -431,6 +433,7 @@ enum tex_item_names {
|
||||
obj_port_lbl_2,
|
||||
obj_port_lbl_3,
|
||||
obj_port_lbl_4,
|
||||
obj_xmu
|
||||
};
|
||||
|
||||
void InitCustomRendering(void)
|
||||
@ -441,6 +444,9 @@ void InitCustomRendering(void)
|
||||
g_decal_shader = NewDecalShader(ShaderType::Mask);
|
||||
controller_fbo = new Fbo(512, 512);
|
||||
|
||||
g_xmu_tex = LoadTextureFromMemory(xmu_mask_data, xmu_mask_size);
|
||||
xmu_fbo = new Fbo(512, 256);
|
||||
|
||||
g_logo_tex = LoadTextureFromMemory(logo_sdf_data, logo_sdf_size);
|
||||
g_logo_shader = NewDecalShader(ShaderType::Logo);
|
||||
logo_fbo = new Fbo(512, 512);
|
||||
@ -646,6 +652,26 @@ void RenderControllerPort(float frame_x, float frame_y, int i,
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void RenderXmu(float frame_x, float frame_y, uint32_t primary_color,
|
||||
uint32_t secondary_color)
|
||||
{
|
||||
glUseProgram(g_decal_shader->prog);
|
||||
glBindVertexArray(g_decal_shader->vao);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, g_xmu_tex);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
glBlendFunc(GL_ONE, GL_ZERO);
|
||||
|
||||
// Render xmu
|
||||
RenderDecal(g_decal_shader, frame_x, frame_y, 256, 256,
|
||||
tex_items[obj_xmu].x, tex_items[obj_xmu].y,
|
||||
tex_items[obj_xmu].w, tex_items[obj_xmu].h, primary_color,
|
||||
secondary_color, 0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void RenderLogo(uint32_t time)
|
||||
{
|
||||
uint32_t color = 0x62ca13ff;
|
||||
@ -801,8 +827,7 @@ void SaveScreenshot(GLuint tex, bool flip)
|
||||
time_t t = time(NULL);
|
||||
struct tm *tmp = localtime(&t);
|
||||
if (tmp) {
|
||||
strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png",
|
||||
tmp);
|
||||
strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png", tmp);
|
||||
} else {
|
||||
strcpy(fname, "xemu.png");
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ public:
|
||||
void Restore();
|
||||
};
|
||||
|
||||
extern Fbo *controller_fbo, *logo_fbo;
|
||||
extern Fbo *controller_fbo, *xmu_fbo, *logo_fbo;
|
||||
extern GLuint g_icon_tex;
|
||||
|
||||
void InitCustomRendering(void);
|
||||
@ -47,6 +47,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
|
||||
uint32_t secondary_color, ControllerState *state);
|
||||
void RenderControllerPort(float frame_x, float frame_y, int i,
|
||||
uint32_t port_color);
|
||||
void RenderXmu(float frame_x, float frame_y, uint32_t primary_color,
|
||||
uint32_t secondary_color);
|
||||
void RenderFramebuffer(GLint tex, int width, int height, bool flip);
|
||||
void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]);
|
||||
bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector<uint8_t> &png, int max_width = 0, int max_height = 0);
|
||||
|
@ -40,6 +40,10 @@
|
||||
#include "../xemu-os-utils.h"
|
||||
#include "../xemu-xbe.h"
|
||||
|
||||
#include "../thirdparty/fatx/fatx.h"
|
||||
|
||||
#define DEFAULT_XMU_SIZE 8388608
|
||||
|
||||
MainMenuScene g_main_menu;
|
||||
|
||||
MainMenuTabView::~MainMenuTabView() {}
|
||||
@ -86,6 +90,9 @@ void MainMenuInputView::Draw()
|
||||
// Dimensions of controller (rendered at origin)
|
||||
float controller_width = 477.0f;
|
||||
float controller_height = 395.0f;
|
||||
// Dimensions of XMU
|
||||
float xmu_x = 0, xmu_x_stride = 256, xmu_y = 0;
|
||||
float xmu_w = 256, xmu_h = 256;
|
||||
|
||||
// Setup rendering to fbo for controller and port images
|
||||
controller_fbo->Target();
|
||||
@ -120,14 +127,14 @@ void MainMenuInputView::Draw()
|
||||
// uses the texture as a unique ID. Push a new ID now to resolve
|
||||
// the conflict.
|
||||
ImGui::PushID(i);
|
||||
float x = b_x+i*b_x_stride;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, is_selected ?
|
||||
color_active :
|
||||
color_inactive);
|
||||
bool activated = ImGui::ImageButton(id,
|
||||
ImVec2(b_w*g_viewport_mgr.m_scale,b_h*g_viewport_mgr.m_scale),
|
||||
ImVec2(x/t_w, (b_y+b_h)/t_h),
|
||||
ImVec2((x+b_w)/t_w, b_y/t_h),
|
||||
float x = b_x + i * b_x_stride;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
is_selected ? color_active : color_inactive);
|
||||
bool activated = ImGui::ImageButton(
|
||||
id,
|
||||
ImVec2(b_w * g_viewport_mgr.m_scale, b_h * g_viewport_mgr.m_scale),
|
||||
ImVec2(x / t_w, (b_y + b_h) / t_h),
|
||||
ImVec2((x + b_w) / t_w, b_y / t_h),
|
||||
port_padding * g_viewport_mgr.m_scale);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
@ -193,6 +200,16 @@ void MainMenuInputView::Draw()
|
||||
}
|
||||
if (ImGui::Selectable(selectable_label, is_selected)) {
|
||||
xemu_input_bind(active, iter, 1);
|
||||
|
||||
// FIXME: We want to bind the XMU here, but we can't because we
|
||||
// just unbound it and we need to wait for Qemu to release the
|
||||
// file
|
||||
|
||||
// If we previously had no controller connected, we can rebind
|
||||
// the XMU
|
||||
if (bound_state == NULL)
|
||||
xemu_input_rebind_xmu(active);
|
||||
|
||||
bound_state = iter;
|
||||
}
|
||||
if (is_selected) {
|
||||
@ -260,6 +277,168 @@ void MainMenuInputView::Draw()
|
||||
ImGui::PopFont();
|
||||
ImGui::SetCursorPos(pos);
|
||||
|
||||
if (bound_state) {
|
||||
SectionTitle("Expansion Slots");
|
||||
// Begin a 2-column layout to render the expansion slots
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
|
||||
g_viewport_mgr.Scale(ImVec2(0, 12)));
|
||||
ImGui::Columns(2, "mixed", false);
|
||||
|
||||
xmu_fbo->Target();
|
||||
id = (ImTextureID)(intptr_t)xmu_fbo->Texture();
|
||||
|
||||
const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0";
|
||||
const char *comboLabels[2] = { "###ExpansionSlotA",
|
||||
"###ExpansionSlotB" };
|
||||
for (int i = 0; i < 2; i++) {
|
||||
// Display a combo box to allow the user to choose the type of
|
||||
// peripheral they want to use
|
||||
enum peripheral_type selected_type =
|
||||
bound_state->peripheral_types[i];
|
||||
const char *peripheral_type_names[2] = { "None", "Memory Unit" };
|
||||
const char *selected_peripheral_type =
|
||||
peripheral_type_names[selected_type];
|
||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||
if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type,
|
||||
ImGuiComboFlags_NoArrowButton)) {
|
||||
// Handle all available peripheral types
|
||||
for (int j = 0; j < 2; j++) {
|
||||
bool is_selected = selected_type == j;
|
||||
ImGui::PushID(j);
|
||||
const char *selectable_label = peripheral_type_names[j];
|
||||
|
||||
if (ImGui::Selectable(selectable_label, is_selected)) {
|
||||
// Free any existing peripheral
|
||||
if (bound_state->peripherals[i] != NULL) {
|
||||
if (bound_state->peripheral_types[i] ==
|
||||
PERIPHERAL_XMU) {
|
||||
// Another peripheral was already bound.
|
||||
// Unplugging
|
||||
xemu_input_unbind_xmu(active, i);
|
||||
}
|
||||
|
||||
// Free the existing state
|
||||
g_free((void *)bound_state->peripherals[i]);
|
||||
bound_state->peripherals[i] = NULL;
|
||||
}
|
||||
|
||||
// Change the peripheral type to the newly selected type
|
||||
bound_state->peripheral_types[i] =
|
||||
(enum peripheral_type)j;
|
||||
|
||||
// Allocate state for the new peripheral
|
||||
if (j == PERIPHERAL_XMU) {
|
||||
bound_state->peripherals[i] =
|
||||
g_malloc(sizeof(XmuState));
|
||||
memset(bound_state->peripherals[i], 0,
|
||||
sizeof(XmuState));
|
||||
}
|
||||
|
||||
xemu_save_peripheral_settings(
|
||||
active, i, bound_state->peripheral_types[i], NULL);
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
DrawComboChevron();
|
||||
|
||||
// Set an X offset to center the image button within the column
|
||||
ImGui::SetCursorPosX(
|
||||
ImGui::GetCursorPosX() +
|
||||
(int)((ImGui::GetColumnWidth() -
|
||||
xmu_w * g_viewport_mgr.m_scale -
|
||||
2 * port_padding * g_viewport_mgr.m_scale) /
|
||||
2));
|
||||
|
||||
selected_type = bound_state->peripheral_types[i];
|
||||
if (selected_type == PERIPHERAL_XMU) {
|
||||
float x = xmu_x + i * xmu_x_stride;
|
||||
float y = xmu_y;
|
||||
|
||||
XmuState *xmu = (XmuState *)bound_state->peripherals[i];
|
||||
if (xmu->filename != NULL && strlen(xmu->filename) > 0) {
|
||||
RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00);
|
||||
|
||||
} else {
|
||||
RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00);
|
||||
}
|
||||
|
||||
ImVec2 xmu_display_size;
|
||||
if (ImGui::GetContentRegionMax().x <
|
||||
xmu_h * g_viewport_mgr.m_scale) {
|
||||
xmu_display_size.x = ImGui::GetContentRegionMax().x / 2;
|
||||
xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w;
|
||||
} else {
|
||||
xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale,
|
||||
xmu_h * g_viewport_mgr.m_scale);
|
||||
}
|
||||
|
||||
ImGui::SetCursorPosX(
|
||||
ImGui::GetCursorPosX() +
|
||||
(int)((ImGui::GetColumnWidth() - xmu_display_size.x) /
|
||||
2.0));
|
||||
|
||||
ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1),
|
||||
ImVec2(0.5f * (i + 1), 0));
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
|
||||
ImGui::SetCursorPos(pos);
|
||||
|
||||
// Button to generate a new XMU
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::Button("New Image", ImVec2(250, 0))) {
|
||||
int flags = NOC_FILE_DIALOG_SAVE |
|
||||
NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION;
|
||||
const char *new_path = PausedFileOpen(
|
||||
flags, img_file_filters, NULL, "xmu.img");
|
||||
|
||||
if (new_path) {
|
||||
if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) {
|
||||
// XMU was created successfully. Bind it
|
||||
xemu_input_bind_xmu(active, i, new_path, false);
|
||||
} else {
|
||||
// Show alert message
|
||||
char *msg = g_strdup_printf(
|
||||
"Unable to create XMU image at %s", new_path);
|
||||
xemu_queue_error_message(msg);
|
||||
g_free(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *xmu_port_path = NULL;
|
||||
if (xmu->filename == NULL)
|
||||
xmu_port_path = g_strdup("");
|
||||
else
|
||||
xmu_port_path = g_strdup(xmu->filename);
|
||||
if (FilePicker("Image", &xmu_port_path, img_file_filters)) {
|
||||
if (strlen(xmu_port_path) == 0) {
|
||||
xemu_input_unbind_xmu(active, i);
|
||||
} else {
|
||||
xemu_input_bind_xmu(active, i, xmu_port_path, false);
|
||||
}
|
||||
}
|
||||
g_free((void *)xmu_port_path);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::NextColumn();
|
||||
}
|
||||
|
||||
xmu_fbo->Restore();
|
||||
|
||||
ImGui::PopStyleVar(); // ItemSpacing
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
SectionTitle("Options");
|
||||
Toggle("Auto-bind controllers", &g_config.input.auto_bind,
|
||||
"Bind newly connected controllers to any open port");
|
||||
@ -625,8 +804,9 @@ void MainMenuNetworkView::DrawNatOptions(bool appearing)
|
||||
void MainMenuNetworkView::DrawUdpOptions(bool appearing)
|
||||
{
|
||||
if (appearing) {
|
||||
strncpy(remote_addr, g_config.net.udp.remote_addr, sizeof(remote_addr)-1);
|
||||
strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr)-1);
|
||||
strncpy(remote_addr, g_config.net.udp.remote_addr,
|
||||
sizeof(remote_addr) - 1);
|
||||
strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr) - 1);
|
||||
}
|
||||
|
||||
float size_ratio = 0.5;
|
||||
@ -663,7 +843,9 @@ MainMenuSnapshotsView::~MainMenuSnapshotsView()
|
||||
g_free(m_search_regex);
|
||||
}
|
||||
|
||||
bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding)
|
||||
bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot,
|
||||
XemuSnapshotData *data,
|
||||
int current_snapshot_binding)
|
||||
{
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
@ -673,18 +855,27 @@ bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSn
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5)));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
|
||||
g_viewport_mgr.Scale(ImVec2(5, 5)));
|
||||
|
||||
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||
|
||||
ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name);
|
||||
ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT));
|
||||
ImVec2 thumbnail_size = g_viewport_mgr.Scale(
|
||||
ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT));
|
||||
ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y);
|
||||
ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y);
|
||||
ImVec2 title_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 date_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 binding_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 button_size(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3));
|
||||
ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x +
|
||||
style.FramePadding.x * 2,
|
||||
thumbnail_pos.y);
|
||||
ImVec2 title_pos(name_pos.x,
|
||||
name_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 date_pos(name_pos.x,
|
||||
title_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 binding_pos(name_pos.x,
|
||||
date_pos.y + ts_title.y + style.FramePadding.x);
|
||||
ImVec2 button_size(-FLT_MIN,
|
||||
fmax(thumbnail_size.y + style.FramePadding.y * 2,
|
||||
ts_title.y + ts_sub.y + style.FramePadding.y * 3));
|
||||
|
||||
bool load = ImGui::Button("###button", button_size);
|
||||
|
||||
@ -699,42 +890,55 @@ bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSn
|
||||
int thumbnail_width, thumbnail_height;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, thumbnail);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH,
|
||||
&thumbnail_width);
|
||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT,
|
||||
&thumbnail_height);
|
||||
|
||||
// Draw black background behind thumbnail
|
||||
ImVec2 thumbnail_min(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y);
|
||||
ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x, thumbnail_min.y + thumbnail_size.y);
|
||||
ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x,
|
||||
thumbnail_min.y + thumbnail_size.y);
|
||||
draw_list->AddRectFilled(thumbnail_min, thumbnail_max, IM_COL32_BLACK);
|
||||
|
||||
// Draw centered thumbnail image
|
||||
int scaled_width, scaled_height;
|
||||
ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height);
|
||||
ImVec2 img_min = ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2,
|
||||
ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x,
|
||||
thumbnail_size.y, &scaled_width, &scaled_height);
|
||||
ImVec2 img_min =
|
||||
ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2,
|
||||
thumbnail_min.y + (thumbnail_size.y - scaled_height) / 2);
|
||||
ImVec2 img_max = ImVec2(img_min.x + scaled_width, img_min.y + scaled_height);
|
||||
ImVec2 img_max =
|
||||
ImVec2(img_min.x + scaled_width, img_min.y + scaled_height);
|
||||
draw_list->AddImage((ImTextureID)(uint64_t)thumbnail, img_min, img_max);
|
||||
|
||||
// Snapshot title
|
||||
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||
draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y), IM_COL32(255, 255, 255, 255), snapshot->name);
|
||||
draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y),
|
||||
IM_COL32(255, 255, 255, 255), snapshot->name);
|
||||
ImGui::PopFont();
|
||||
|
||||
// Snapshot XBE title name
|
||||
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||
const char *title_name = data->xbe_title_name ? data->xbe_title_name : "(Unknown XBE Title Name)";
|
||||
draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name);
|
||||
const char *title_name = data->xbe_title_name ? data->xbe_title_name :
|
||||
"(Unknown XBE Title Name)";
|
||||
draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y),
|
||||
IM_COL32(255, 255, 255, 200), title_name);
|
||||
|
||||
// Snapshot date
|
||||
g_autoptr(GDateTime) date = g_date_time_new_from_unix_local(snapshot->date_sec);
|
||||
g_autoptr(GDateTime) date =
|
||||
g_date_time_new_from_unix_local(snapshot->date_sec);
|
||||
char *date_buf = g_date_time_format(date, "%Y-%m-%d %H:%M:%S");
|
||||
draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), IM_COL32(255, 255, 255, 200), date_buf);
|
||||
draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y),
|
||||
IM_COL32(255, 255, 255, 200), date_buf);
|
||||
g_free(date_buf);
|
||||
|
||||
// Snapshot keyboard binding
|
||||
if (current_snapshot_binding != -1) {
|
||||
char *binding_text = g_strdup_printf("Bound to F%d", current_snapshot_binding + 5);
|
||||
draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), binding_text);
|
||||
char *binding_text =
|
||||
g_strdup_printf("Bound to F%d", current_snapshot_binding + 5);
|
||||
draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y),
|
||||
IM_COL32(255, 255, 255, 200), binding_text);
|
||||
g_free(binding_text);
|
||||
}
|
||||
|
||||
@ -758,7 +962,7 @@ void MainMenuSnapshotsView::ClearSearch()
|
||||
int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data)
|
||||
{
|
||||
GError *gerr = NULL;
|
||||
MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData;
|
||||
MainMenuSnapshotsView *win = (MainMenuSnapshotsView *)data->UserData;
|
||||
|
||||
if (win->m_search_regex) {
|
||||
g_free(win->m_search_regex);
|
||||
@ -770,7 +974,8 @@ int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data)
|
||||
}
|
||||
|
||||
char *buf = g_strdup_printf("(.*)%s(.*)", data->Buf);
|
||||
win->m_search_regex = g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr);
|
||||
win->m_search_regex =
|
||||
g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr);
|
||||
g_free(buf);
|
||||
if (gerr) {
|
||||
win->m_search_regex = NULL;
|
||||
@ -785,14 +990,17 @@ void MainMenuSnapshotsView::Draw()
|
||||
g_snapshot_mgr.Refresh();
|
||||
|
||||
SectionTitle("Snapshots");
|
||||
Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game,
|
||||
"Only display snapshots created while running the currently running XBE");
|
||||
Toggle("Filter by current title",
|
||||
&g_config.general.snapshots.filter_current_game,
|
||||
"Only display snapshots created while running the currently running "
|
||||
"XBE");
|
||||
|
||||
if (g_config.general.snapshots.filter_current_game) {
|
||||
struct xbe *xbe = xemu_get_xbe_info();
|
||||
if (xbe && xbe->cert) {
|
||||
if (xbe->cert->m_titleid != m_current_title_id) {
|
||||
char *title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL);
|
||||
char *title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40,
|
||||
NULL, NULL, NULL);
|
||||
if (title_name) {
|
||||
m_current_title_name = title_name;
|
||||
g_free(title_name);
|
||||
@ -816,7 +1024,8 @@ void MainMenuSnapshotsView::Draw()
|
||||
|
||||
bool snapshot_with_create_name_exists = false;
|
||||
for (int i = 0; i < g_snapshot_mgr.m_snapshots_len; ++i) {
|
||||
if (g_strcmp0(m_search_buf.c_str(), g_snapshot_mgr.m_snapshots[i].name) == 0) {
|
||||
if (g_strcmp0(m_search_buf.c_str(),
|
||||
g_snapshot_mgr.m_snapshots[i].name) == 0) {
|
||||
snapshot_with_create_name_exists = true;
|
||||
break;
|
||||
}
|
||||
@ -828,8 +1037,10 @@ void MainMenuSnapshotsView::Draw()
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 0, 0, 1));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 0, 0, 1));
|
||||
}
|
||||
if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", ImVec2(-FLT_MIN, 0))) {
|
||||
xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL);
|
||||
if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create",
|
||||
ImVec2(-FLT_MIN, 0))) {
|
||||
xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(),
|
||||
NULL);
|
||||
ClearSearch();
|
||||
}
|
||||
if (snapshot_with_create_name_exists) {
|
||||
@ -837,15 +1048,20 @@ void MainMenuSnapshotsView::Draw()
|
||||
}
|
||||
|
||||
if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str());
|
||||
ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. "
|
||||
"This button will overwrite the existing snapshot.",
|
||||
m_search_buf.c_str());
|
||||
}
|
||||
ImGui::PopFont();
|
||||
|
||||
bool at_least_one_snapshot_displayed = false;
|
||||
|
||||
for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) {
|
||||
if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name &&
|
||||
m_current_title_name.size() && strcmp(m_current_title_name.c_str(), g_snapshot_mgr.m_extra_data[i].xbe_title_name)) {
|
||||
if (g_config.general.snapshots.filter_current_game &&
|
||||
g_snapshot_mgr.m_extra_data[i].xbe_title_name &&
|
||||
m_current_title_name.size() &&
|
||||
strcmp(m_current_title_name.c_str(),
|
||||
g_snapshot_mgr.m_extra_data[i].xbe_title_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -853,12 +1069,15 @@ void MainMenuSnapshotsView::Draw()
|
||||
GMatchInfo *match;
|
||||
bool keep_entry = false;
|
||||
|
||||
g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name, (GRegexMatchFlags)0, &match);
|
||||
g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name,
|
||||
(GRegexMatchFlags)0, &match);
|
||||
keep_entry |= g_match_info_matches(match);
|
||||
g_match_info_free(match);
|
||||
|
||||
if (g_snapshot_mgr.m_extra_data[i].xbe_title_name) {
|
||||
g_regex_match(m_search_regex, g_snapshot_mgr.m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match);
|
||||
g_regex_match(m_search_regex,
|
||||
g_snapshot_mgr.m_extra_data[i].xbe_title_name,
|
||||
(GRegexMatchFlags)0, &match);
|
||||
keep_entry |= g_match_info_matches(match);
|
||||
g_free(match);
|
||||
}
|
||||
@ -873,7 +1092,8 @@ void MainMenuSnapshotsView::Draw()
|
||||
|
||||
int current_snapshot_binding = -1;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) {
|
||||
if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]),
|
||||
snapshot->name) == 0) {
|
||||
assert(current_snapshot_binding == -1);
|
||||
current_snapshot_binding = i;
|
||||
}
|
||||
@ -885,7 +1105,8 @@ void MainMenuSnapshotsView::Draw()
|
||||
bool load = BigSnapshotButton(snapshot, data, current_snapshot_binding);
|
||||
|
||||
// FIXME: Provide context menu control annotation
|
||||
if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) {
|
||||
if (ImGui::IsItemHovered() &&
|
||||
ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) {
|
||||
ImGui::SetNextWindowPos(pos);
|
||||
ImGui::OpenPopup("Snapshot Options");
|
||||
}
|
||||
@ -915,12 +1136,14 @@ void MainMenuSnapshotsView::Draw()
|
||||
}
|
||||
ImVec2 dim = ImGui::CalcTextSize(msg);
|
||||
ImVec2 cur = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth()-dim.x)/2);
|
||||
ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth() - dim.x) / 2);
|
||||
ImGui::TextColored(ImVec4(0.94f, 0.94f, 0.94f, 0.70f), "%s", msg);
|
||||
}
|
||||
}
|
||||
|
||||
void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding)
|
||||
void MainMenuSnapshotsView::DrawSnapshotContextMenu(
|
||||
QEMUSnapshotInfo *snapshot, XemuSnapshotData *data,
|
||||
int current_snapshot_binding)
|
||||
{
|
||||
if (!ImGui::BeginPopupContextItem("Snapshot Options")) {
|
||||
return;
|
||||
@ -936,9 +1159,12 @@ void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot,
|
||||
|
||||
if (ImGui::MenuItem(item_name)) {
|
||||
if (current_snapshot_binding >= 0) {
|
||||
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], "");
|
||||
xemu_settings_set_string(g_snapshot_shortcut_index_key_map
|
||||
[current_snapshot_binding],
|
||||
"");
|
||||
}
|
||||
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name);
|
||||
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i],
|
||||
snapshot->name);
|
||||
current_snapshot_binding = i;
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
@ -949,7 +1175,9 @@ void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot,
|
||||
|
||||
if (current_snapshot_binding >= 0) {
|
||||
if (ImGui::MenuItem("Unbind")) {
|
||||
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], "");
|
||||
xemu_settings_set_string(
|
||||
g_snapshot_shortcut_index_key_map[current_snapshot_binding],
|
||||
"");
|
||||
current_snapshot_binding = -1;
|
||||
}
|
||||
}
|
||||
@ -982,11 +1210,13 @@ MainMenuSystemView::MainMenuSystemView() : m_dirty(false)
|
||||
|
||||
void MainMenuSystemView::Draw()
|
||||
{
|
||||
const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0";
|
||||
const char *rom_file_filters =
|
||||
".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0";
|
||||
const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0";
|
||||
|
||||
if (m_dirty) {
|
||||
ImGui::TextColored(ImVec4(1,0,0,1), "Application restart required to apply settings");
|
||||
ImGui::TextColored(ImVec4(1, 0, 0, 1),
|
||||
"Application restart required to apply settings");
|
||||
}
|
||||
|
||||
if ((int)g_config.sys.avpack == CONFIG_SYS_AVPACK_NONE) {
|
||||
@ -997,7 +1227,8 @@ void MainMenuSystemView::Draw()
|
||||
|
||||
if (ChevronCombo(
|
||||
"System Memory", &g_config.sys.mem_limit,
|
||||
"64 MiB (Default)\0""128 MiB\0",
|
||||
"64 MiB (Default)\0"
|
||||
"128 MiB\0",
|
||||
"Increase to 128 MiB for debug or homebrew applications")) {
|
||||
m_dirty = true;
|
||||
}
|
||||
@ -1030,8 +1261,9 @@ void MainMenuSystemView::Draw()
|
||||
}
|
||||
}
|
||||
|
||||
MainMenuAboutView::MainMenuAboutView(): m_config_info_text{NULL}
|
||||
{}
|
||||
MainMenuAboutView::MainMenuAboutView() : m_config_info_text{ NULL }
|
||||
{
|
||||
}
|
||||
|
||||
void MainMenuAboutView::UpdateConfigInfoText()
|
||||
{
|
||||
@ -1039,18 +1271,19 @@ void MainMenuAboutView::UpdateConfigInfoText()
|
||||
g_free(m_config_info_text);
|
||||
}
|
||||
|
||||
gchar *bootrom_checksum = GetFileMD5Checksum(g_config.sys.files.bootrom_path);
|
||||
gchar *bootrom_checksum =
|
||||
GetFileMD5Checksum(g_config.sys.files.bootrom_path);
|
||||
if (!bootrom_checksum) {
|
||||
bootrom_checksum = g_strdup("None");
|
||||
}
|
||||
|
||||
gchar *flash_rom_checksum = GetFileMD5Checksum(g_config.sys.files.flashrom_path);
|
||||
gchar *flash_rom_checksum =
|
||||
GetFileMD5Checksum(g_config.sys.files.flashrom_path);
|
||||
if (!flash_rom_checksum) {
|
||||
flash_rom_checksum = g_strdup("None");
|
||||
}
|
||||
|
||||
m_config_info_text = g_strdup_printf(
|
||||
"MCPX Boot ROM MD5 Hash: %s\n"
|
||||
m_config_info_text = g_strdup_printf("MCPX Boot ROM MD5 Hash: %s\n"
|
||||
"Flash ROM (BIOS) MD5 Hash: %s",
|
||||
bootrom_checksum, flash_rom_checksum);
|
||||
g_free(bootrom_checksum);
|
||||
@ -1061,22 +1294,25 @@ void MainMenuAboutView::Draw()
|
||||
{
|
||||
static const char *build_info_text = NULL;
|
||||
if (build_info_text == NULL) {
|
||||
build_info_text = g_strdup_printf(
|
||||
"Version: %s\nBranch: %s\nCommit: %s\nDate: %s",
|
||||
build_info_text =
|
||||
g_strdup_printf("Version: %s\nBranch: %s\nCommit: "
|
||||
"%s\nDate: %s",
|
||||
xemu_version, xemu_branch, xemu_commit, xemu_date);
|
||||
}
|
||||
|
||||
static const char *sys_info_text = NULL;
|
||||
if (sys_info_text == NULL) {
|
||||
const char *gl_shader_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
const char *gl_version = (const char*)glGetString(GL_VERSION);
|
||||
const char *gl_renderer = (const char*)glGetString(GL_RENDERER);
|
||||
const char *gl_vendor = (const char*)glGetString(GL_VENDOR);
|
||||
const char *gl_shader_version =
|
||||
(const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
|
||||
const char *gl_version = (const char *)glGetString(GL_VERSION);
|
||||
const char *gl_renderer = (const char *)glGetString(GL_RENDERER);
|
||||
const char *gl_vendor = (const char *)glGetString(GL_VENDOR);
|
||||
sys_info_text = g_strdup_printf(
|
||||
"CPU: %s\nOS Platform: %s\nOS Version: %s\nManufacturer: %s\n"
|
||||
"CPU: %s\nOS Platform: %s\nOS Version: "
|
||||
"%s\nManufacturer: %s\n"
|
||||
"GPU Model: %s\nDriver: %s\nShader: %s",
|
||||
xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(), gl_vendor,
|
||||
gl_renderer, gl_version, gl_shader_version);
|
||||
xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(),
|
||||
gl_vendor, gl_renderer, gl_version, gl_shader_version);
|
||||
}
|
||||
|
||||
if (m_config_info_text == NULL) {
|
||||
@ -1121,7 +1357,7 @@ void MainMenuAboutView::Draw()
|
||||
}
|
||||
|
||||
MainMenuTabButton::MainMenuTabButton(std::string text, std::string icon)
|
||||
: m_icon(icon), m_text(text)
|
||||
: m_icon(icon), m_text(text)
|
||||
{
|
||||
}
|
||||
|
||||
@ -1134,8 +1370,10 @@ bool MainMenuTabButton::Draw(bool selected)
|
||||
IM_COL32(0, 0, 0, 0);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, col);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, selected ? col : IM_COL32(32, 32, 32, 255));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, selected ? col : IM_COL32(32, 32, 32, 255));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
selected ? col : IM_COL32(32, 32, 32, 255));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive,
|
||||
selected ? col : IM_COL32(32, 32, 32, 255));
|
||||
int p = ImGui::GetTextLineHeight() * 0.5;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(p, p));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
|
||||
@ -1154,8 +1392,7 @@ bool MainMenuTabButton::Draw(bool selected)
|
||||
}
|
||||
|
||||
MainMenuScene::MainMenuScene()
|
||||
: m_animation(0.12, 0.12),
|
||||
m_general_button("General", ICON_FA_GEARS),
|
||||
: m_animation(0.12, 0.12), m_general_button("General", ICON_FA_GEARS),
|
||||
m_input_button("Input", ICON_FA_GAMEPAD),
|
||||
m_display_button("Display", ICON_FA_TV),
|
||||
m_audio_button("Audio", ICON_FA_VOLUME_HIGH),
|
||||
@ -1249,11 +1486,12 @@ void MainMenuScene::HandleInput()
|
||||
bool focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows |
|
||||
ImGuiFocusedFlags_NoPopupHierarchy);
|
||||
|
||||
// XXX: Ensure we have focus for two frames. If a user cancels a popup window, we do not want to cancel main
|
||||
// XXX: Ensure we have focus for two frames. If a user cancels a popup
|
||||
// window, we do not want to cancel main
|
||||
// window as well.
|
||||
if (nofocus || (focus && m_had_focus_last_frame &&
|
||||
(ImGui::IsKeyDown(ImGuiKey_GamepadFaceRight)
|
||||
|| ImGui::IsKeyDown(ImGuiKey_Escape)))) {
|
||||
(ImGui::IsKeyDown(ImGuiKey_GamepadFaceRight) ||
|
||||
ImGui::IsKeyDown(ImGuiKey_Escape)))) {
|
||||
Hide();
|
||||
return;
|
||||
}
|
||||
@ -1313,9 +1551,10 @@ bool MainMenuScene::Draw()
|
||||
float nav_width = width * 0.3;
|
||||
float content_width = width - nav_width;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26,26,26,255));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26, 26, 26, 255));
|
||||
|
||||
ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true, ImGuiWindowFlags_NavFlattened);
|
||||
ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true,
|
||||
ImGuiWindowFlags_NavFlattened);
|
||||
|
||||
bool move_focus_to_tab = false;
|
||||
if (m_current_view_index != m_next_view_index) {
|
||||
@ -1349,7 +1588,8 @@ bool MainMenuScene::Draw()
|
||||
int s = ImGui::GetTextLineHeight() * 0.75;
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(s, s));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(s, s));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6*g_viewport_mgr.m_scale);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,
|
||||
6 * g_viewport_mgr.m_scale);
|
||||
|
||||
ImGui::PushID(m_current_view_index);
|
||||
ImGui::BeginChild("###MainWindowContent", ImVec2(content_width, -1),
|
||||
@ -1364,7 +1604,9 @@ bool MainMenuScene::Draw()
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128));
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - style.FramePadding.x * 2.0f - ImGui::GetTextLineHeight());
|
||||
ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x -
|
||||
style.FramePadding.x * 2.0f -
|
||||
ImGui::GetTextLineHeight());
|
||||
if (ImGui::Button(ICON_FA_XMARK)) {
|
||||
Hide();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user