mame/src/osd/windows/window.c
Aaron Giles d8715ab4ac Note: I have done some testing, but there are probably more bugs
lurking. If you run into anything odd, please let me know.

Added new module uiinput.c which manages input for the user interface.
The OSD is responsible for pushing mouse events and character events
to this interface in order to support mouse movement and text-based
input (currently only used for the select game menu). Added support
for navigating through the menus using the mouse. 
[Nathan Woods, Aaron Giles]

Redesigned the UI menus so that they can maintain a richer state. Now
the menus can be generated once and reused, rather than requiring them
to be regenerated on each frame. All menus also share a comment eventing
system and navigation through them is managed centrally. Rewrote all the 
menus to use the new system, apart from the cheat menus, which are now 
disabled. Reorganized the video menu to make it easier to understand.
[Aaron Giles]
2008-07-12 20:18:25 +00:00

1833 lines
50 KiB
C

//============================================================
//
// window.c - Win32 window handling
//
// Copyright Nicola Salmoria and the MAME Team.
// Visit http://mamedev.org for licensing and usage restrictions.
//
//============================================================
#define LOG_THREADS 0
#define LOG_TEMP_PAUSE 0
// Needed for RAW Input
#define WM_INPUT 0x00FF
// standard windows headers
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
// standard C headers
#include <math.h>
#include <process.h>
// MAME headers
#include "osdepend.h"
#include "driver.h"
#include "deprecat.h"
#include "uiinput.h"
// MAMEOS headers
#include "winmain.h"
#include "window.h"
#include "video.h"
#include "input.h"
#include "debugwin.h"
#include "strconv.h"
#include "config.h"
#include "winutf8.h"
#ifdef MESS
#include "menu.h"
#endif /* MESS */
extern int drawnone_init(win_draw_callbacks *callbacks);
extern int drawgdi_init(win_draw_callbacks *callbacks);
extern int drawdd_init(win_draw_callbacks *callbacks);
extern int drawd3d_init(win_draw_callbacks *callbacks);
//============================================================
// PARAMETERS
//============================================================
// window styles
#define WINDOW_STYLE WS_OVERLAPPEDWINDOW
#define WINDOW_STYLE_EX 0
// debugger window styles
#define DEBUG_WINDOW_STYLE WS_OVERLAPPED
#define DEBUG_WINDOW_STYLE_EX 0
// full screen window styles
#define FULLSCREEN_STYLE WS_POPUP
#define FULLSCREEN_STYLE_EX WS_EX_TOPMOST
// minimum window dimension
#define MIN_WINDOW_DIM 200
// custom window messages
#define WM_USER_FINISH_CREATE_WINDOW (WM_USER + 0)
#define WM_USER_SELF_TERMINATE (WM_USER + 1)
#define WM_USER_REDRAW (WM_USER + 2)
#define WM_USER_SET_FULLSCREEN (WM_USER + 3)
#define WM_USER_SET_MAXSIZE (WM_USER + 4)
#define WM_USER_SET_MINSIZE (WM_USER + 5)
#define WM_USER_UI_TEMP_PAUSE (WM_USER + 6)
#define WM_USER_EXEC_FUNC (WM_USER + 7)
//============================================================
// GLOBAL VARIABLES
//============================================================
win_window_info *win_window_list;
static win_window_info **last_window_ptr;
static DWORD main_threadid;
// actual physical resolution
static int win_physical_width;
static int win_physical_height;
//============================================================
// LOCAL VARIABLES
//============================================================
// event handling
static DWORD last_event_check;
// debugger
static int in_background;
static int ui_temp_pause;
static int ui_temp_was_paused;
static int multithreading_enabled;
static HANDLE window_thread;
static DWORD window_threadid;
static DWORD last_update_time;
static win_draw_callbacks draw;
static HANDLE ui_pause_event;
static HANDLE window_thread_ready_event;
//============================================================
// PROTOTYPES
//============================================================
static void winwindow_exit(running_machine *machine);
static void winwindow_video_window_destroy(win_window_info *window);
static void draw_video_contents(win_window_info *window, HDC dc, int update);
static unsigned __stdcall thread_entry(void *param);
static int complete_create(running_machine *machine, win_window_info *window);
static void create_window_class(void);
static void set_starting_view(running_machine *machine, int index, win_window_info *window, const char *view);
static void constrain_to_aspect_ratio(win_window_info *window, RECT *rect, int adjustment);
static void get_min_bounds(win_window_info *window, RECT *bounds, int constrain);
static void get_max_bounds(win_window_info *window, RECT *bounds, int constrain);
static void update_minmax_state(win_window_info *window);
static void minimize_window(win_window_info *window);
static void maximize_window(win_window_info *window);
static void adjust_window_position_after_major_change(win_window_info *window);
static void set_fullscreen(win_window_info *window, int fullscreen);
// temporary hacks
#if LOG_THREADS
struct _mtlog
{
osd_ticks_t timestamp;
const char *event;
};
static struct _mtlog mtlog[100000];
static volatile LONG mtlogindex;
void mtlog_add(const char *event)
{
int index = InterlockedIncrement((LONG *) &mtlogindex) - 1;
if (index < ARRAY_LENGTH(mtlog))
{
mtlog[index].timestamp = osd_ticks();
mtlog[index].event = event;
}
}
static void mtlog_dump(void)
{
osd_ticks_t cps = osd_ticks_per_second();
osd_ticks_t last = mtlog[0].timestamp * 1000000 / cps;
int i;
FILE *f = fopen("mt.log", "w");
for (i = 0; i < mtlogindex; i++)
{
osd_ticks_t curr = mtlog[i].timestamp * 1000000 / cps;
fprintf(f, "%20I64d %10I64d %s\n", curr, curr - last, mtlog[i].event);
last = curr;
}
fclose(f);
}
#else
void mtlog_add(const char *event) { }
#endif
//============================================================
// winwindow_init
// (main thread)
//============================================================
void winwindow_init(running_machine *machine)
{
size_t temp;
// determine if we are using multithreading or not
multithreading_enabled = options_get_bool(mame_options(), WINOPTION_MULTITHREADING);
// get the main thread ID before anything else
main_threadid = GetCurrentThreadId();
// ensure we get called on the way out
add_exit_callback(machine, winwindow_exit);
// set up window class and register it
create_window_class();
// create an event to signal UI pausing
ui_pause_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!ui_pause_event)
fatalerror("Failed to create pause event");
// if multithreading, create a thread to run the windows
if (multithreading_enabled)
{
// create an event to signal when the window thread is ready
window_thread_ready_event = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!window_thread_ready_event)
fatalerror("Failed to create window thread ready event");
// create a thread to run the windows from
temp = _beginthreadex(NULL, 0, thread_entry, NULL, 0, (unsigned *)&window_threadid);
window_thread = (HANDLE)temp;
if (window_thread == NULL)
fatalerror("Failed to create window thread");
// set the thread priority equal to the main MAME thread
SetThreadPriority(window_thread, GetThreadPriority(GetCurrentThread()));
}
// otherwise, treat the window thread as the main thread
else
{
window_thread = GetCurrentThread();
window_threadid = main_threadid;
}
// initialize the drawers
if (video_config.mode == VIDEO_MODE_D3D)
{
if (drawd3d_init(&draw))
video_config.mode = VIDEO_MODE_GDI;
}
if (video_config.mode == VIDEO_MODE_DDRAW)
{
if (drawdd_init(&draw))
video_config.mode = VIDEO_MODE_GDI;
}
if (video_config.mode == VIDEO_MODE_GDI)
drawgdi_init(&draw);
if (video_config.mode == VIDEO_MODE_NONE)
drawnone_init(&draw);
// set up the window list
last_window_ptr = &win_window_list;
}
//============================================================
// winwindow_exit
// (main thread)
//============================================================
static void winwindow_exit(running_machine *machine)
{
assert(GetCurrentThreadId() == main_threadid);
// free all the windows
while (win_window_list != NULL)
{
win_window_info *temp = win_window_list;
win_window_list = temp->next;
winwindow_video_window_destroy(temp);
}
// kill the drawers
(*draw.exit)();
// if we're multithreaded, clean up the window thread
if (multithreading_enabled)
{
PostThreadMessage(window_threadid, WM_USER_SELF_TERMINATE, 0, 0);
WaitForSingleObject(window_thread, INFINITE);
#if (LOG_THREADS)
mtlog_dump();
#endif
}
// kill the UI pause event
if (ui_pause_event)
CloseHandle(ui_pause_event);
// kill the window thread ready event
if (window_thread_ready_event)
CloseHandle(window_thread_ready_event);
// if we hid the cursor during the emulation, show it
while (ShowCursor(TRUE) < 0) ;
}
//============================================================
// winwindow_process_events_periodic
// (main thread)
//============================================================
void winwindow_process_events_periodic(running_machine *machine)
{
DWORD currticks = GetTickCount();
assert(GetCurrentThreadId() == main_threadid);
// update once every 1/8th of a second
if (currticks - last_event_check < 1000 / 8)
return;
winwindow_process_events(machine, TRUE);
}
//============================================================
// is_mame_window
//============================================================
static BOOL is_mame_window(HWND hwnd)
{
win_window_info *window;
for (window = win_window_list; window != NULL; window = window->next)
if (window->hwnd == hwnd)
return TRUE;
return FALSE;
}
//============================================================
// winwindow_process_events
// (main thread)
//============================================================
void winwindow_process_events(running_machine *machine, int ingame)
{
MSG message;
assert(GetCurrentThreadId() == main_threadid);
// if we're running, disable some parts of the debugger
if (ingame && (machine->debug_flags & DEBUG_FLAG_ENABLED) != 0)
debugwin_update_during_game();
// remember the last time we did this
last_event_check = GetTickCount();
do
{
// if we are paused, lets wait for a message
if (ui_temp_pause > 0)
WaitMessage();
// loop over all messages in the queue
while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
{
int dispatch = TRUE;
if (message.hwnd == NULL || is_mame_window(message.hwnd))
{
switch (message.message)
{
// ignore keyboard messages
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
dispatch = FALSE;
break;
// forward mouse button downs to the input system
case WM_LBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(0, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_RBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(1, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_MBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(2, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_XBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(3, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
// forward mouse button ups to the input system
case WM_LBUTTONUP:
dispatch = !wininput_handle_mouse_button(0, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_RBUTTONUP:
dispatch = !wininput_handle_mouse_button(1, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_MBUTTONUP:
dispatch = !wininput_handle_mouse_button(2, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_XBUTTONUP:
dispatch = !wininput_handle_mouse_button(3, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
}
}
// dispatch if necessary
if (dispatch)
winwindow_dispatch_message(machine, &message);
}
} while (ui_temp_pause > 0);
// update the cursor state after processing events
winwindow_update_cursor_state(machine);
}
//============================================================
// winwindow_dispatch_message
// (main thread)
//============================================================
void winwindow_dispatch_message(running_machine *machine, MSG *message)
{
assert(GetCurrentThreadId() == main_threadid);
// dispatch our special communication messages
switch (message->message)
{
// special case for quit
case WM_QUIT:
mame_schedule_exit(machine);
break;
// temporary pause from the window thread
case WM_USER_UI_TEMP_PAUSE:
winwindow_ui_pause_from_main_thread(machine, message->wParam);
break;
// execute arbitrary function
case WM_USER_EXEC_FUNC:
{
void (*func)(void *) = (void (*)(void *)) message->wParam;
void *param = (void *) message->lParam;
func(param);
}
break;
// everything else dispatches normally
default:
TranslateMessage(message);
DispatchMessage(message);
break;
}
}
//============================================================
// winwindow_toggle_full_screen
// (main thread)
//============================================================
void winwindow_toggle_full_screen(void)
{
win_window_info *window;
assert(GetCurrentThreadId() == main_threadid);
// if we are in debug mode, never go full screen
if (options_get_bool(mame_options(), OPTION_DEBUG))
return;
// toggle the window mode
video_config.windowed = !video_config.windowed;
// iterate over windows and toggle their fullscreen state
for (window = win_window_list; window != NULL; window = window->next)
SendMessage(window->hwnd, WM_USER_SET_FULLSCREEN, !video_config.windowed, 0);
SetForegroundWindow(win_window_list->hwnd);
}
//============================================================
// winwindow_has_focus
// (main or window thread)
//============================================================
BOOL winwindow_has_focus(void)
{
HWND focuswnd = GetFocus();
win_window_info *window;
// see if one of the video windows has focus
for (window = win_window_list; window != NULL; window = window->next)
if (focuswnd == window->hwnd)
return TRUE;
return FALSE;
}
//============================================================
// winwindow_update_cursor_state
// (main thread)
//============================================================
void winwindow_update_cursor_state(running_machine *machine)
{
static POINT saved_cursor_pos = { -1, -1 };
assert(GetCurrentThreadId() == main_threadid);
// if we should hide the mouse, then do it
// rules are:
// 1. we must have focus before hiding the cursor
// 2. we also hide the cursor in full screen mode and when the window doesn't have a menu
// 3. we also hide the cursor in windowed mode if we're not paused and
// the input system requests it
if (winwindow_has_focus() && ((!video_config.windowed && !win_has_menu(win_window_list)) || (!mame_is_paused(machine) && wininput_should_hide_mouse())))
{
win_window_info *window = win_window_list;
RECT bounds;
// hide cursor
while (ShowCursor(FALSE) >= -1) ;
ShowCursor(TRUE);
// store the cursor position
GetCursorPos(&saved_cursor_pos);
// clip cursor to game video window
GetClientRect(window->hwnd, &bounds);
ClientToScreen(window->hwnd, &((POINT *)&bounds)[0]);
ClientToScreen(window->hwnd, &((POINT *)&bounds)[1]);
ClipCursor(&bounds);
}
else
{
// show cursor
while (ShowCursor(TRUE) < 1) ;
ShowCursor(FALSE);
// allow cursor to move freely
ClipCursor(NULL);
if (saved_cursor_pos.x != -1 || saved_cursor_pos.y != -1)
{
SetCursorPos(saved_cursor_pos.x, saved_cursor_pos.y);
saved_cursor_pos.x = saved_cursor_pos.y = -1;
}
}
}
//============================================================
// winwindow_video_window_create
// (main thread)
//============================================================
void winwindow_video_window_create(running_machine *machine, int index, win_monitor_info *monitor, const win_window_config *config)
{
win_window_info *window, *win;
char option[20];
assert(GetCurrentThreadId() == main_threadid);
// allocate a new window object
window = malloc_or_die(sizeof(*window));
memset(window, 0, sizeof(*window));
window->maxwidth = config->width;
window->maxheight = config->height;
window->refresh = config->refresh;
window->monitor = monitor;
window->fullscreen = !video_config.windowed;
// see if we are safe for fullscreen
window->fullscreen_safe = TRUE;
for (win = win_window_list; win != NULL; win = win->next)
if (win->monitor == monitor)
window->fullscreen_safe = FALSE;
// add us to the list
*last_window_ptr = window;
last_window_ptr = &window->next;
// create a lock that we can use to skip blitting
window->render_lock = osd_lock_alloc();
// load the layout
window->target = render_target_alloc(NULL, 0);
if (window->target == NULL)
fatalerror("Error creating render target for window %d", index);
// set the specific view
sprintf(option, "view%d", index);
set_starting_view(machine, index, window, options_get_string(mame_options(), option));
// remember the current values in case they change
window->targetview = render_target_get_view(window->target);
window->targetorient = render_target_get_orientation(window->target);
window->targetlayerconfig = render_target_get_layer_config(window->target);
// make the window title
if (video_config.numscreens == 1)
sprintf(window->title, APPNAME ": %s [%s]", machine->gamedrv->description, machine->gamedrv->name);
else
sprintf(window->title, APPNAME ": %s [%s] - Screen %d", machine->gamedrv->description, machine->gamedrv->name, index);
// set the initial maximized state
window->startmaximized = options_get_bool(mame_options(), WINOPTION_MAXIMIZE);
// finish the window creation on the window thread
if (multithreading_enabled)
{
// wait until the window thread is ready to respond to events
WaitForSingleObject(window_thread_ready_event, INFINITE);
PostThreadMessage(window_threadid, WM_USER_FINISH_CREATE_WINDOW, 0, (LPARAM)window);
while (window->init_state == 0)
Sleep(1);
}
else
window->init_state = complete_create(machine, window) ? -1 : 1;
// handle error conditions
if (window->init_state == -1)
fatalerror("Unable to complete window creation");
}
//============================================================
// winwindow_video_window_destroy
// (main thread)
//============================================================
static void winwindow_video_window_destroy(win_window_info *window)
{
win_window_info **prevptr;
assert(GetCurrentThreadId() == main_threadid);
// remove us from the list
for (prevptr = &win_window_list; *prevptr != NULL; prevptr = &(*prevptr)->next)
if (*prevptr == window)
{
*prevptr = window->next;
break;
}
// destroy the window
if (window->hwnd != NULL)
SendMessage(window->hwnd, WM_USER_SELF_TERMINATE, 0, 0);
// free the render target
if (window->target != NULL)
render_target_free(window->target);
// free the lock
osd_lock_free(window->render_lock);
// free the window itself
free(window);
}
//============================================================
// winwindow_video_window_update
// (main thread)
//============================================================
void winwindow_video_window_update(win_window_info *window)
{
int targetview, targetorient, targetlayerconfig;
assert(GetCurrentThreadId() == main_threadid);
mtlog_add("winwindow_video_window_update: begin");
// see if the target has changed significantly in window mode
targetview = render_target_get_view(window->target);
targetorient = render_target_get_orientation(window->target);
targetlayerconfig = render_target_get_layer_config(window->target);
if (targetview != window->targetview || targetorient != window->targetorient || targetlayerconfig != window->targetlayerconfig)
{
window->targetview = targetview;
window->targetorient = targetorient;
window->targetlayerconfig = targetlayerconfig;
// in window mode, reminimize/maximize
if (!window->fullscreen)
{
if (window->isminimized)
SendMessage(window->hwnd, WM_USER_SET_MINSIZE, 0, 0);
if (window->ismaximized)
SendMessage(window->hwnd, WM_USER_SET_MAXSIZE, 0, 0);
}
}
// if we're visible and running and not in the middle of a resize, draw
if (window->hwnd != NULL && window->target != NULL)
{
int got_lock = TRUE;
mtlog_add("winwindow_video_window_update: try lock");
// only block if we're throttled
if (video_get_throttle() || timeGetTime() - last_update_time > 250)
osd_lock_acquire(window->render_lock);
else
got_lock = osd_lock_try(window->render_lock);
// only render if we were able to get the lock
if (got_lock)
{
const render_primitive_list *primlist;
mtlog_add("winwindow_video_window_update: got lock");
// don't hold the lock; we just used it to see if rendering was still happening
osd_lock_release(window->render_lock);
// ensure the target bounds are up-to-date, and then get the primitives
primlist = (*draw.window_get_primitives)(window);
// post a redraw request with the primitive list as a parameter
last_update_time = timeGetTime();
mtlog_add("winwindow_video_window_update: PostMessage start");
if (multithreading_enabled)
PostMessage(window->hwnd, WM_USER_REDRAW, 0, (LPARAM)primlist);
else
SendMessage(window->hwnd, WM_USER_REDRAW, 0, (LPARAM)primlist);
mtlog_add("winwindow_video_window_update: PostMessage end");
}
}
mtlog_add("winwindow_video_window_update: end");
}
//============================================================
// winwindow_video_window_monitor
// (window thread)
//============================================================
win_monitor_info *winwindow_video_window_monitor(win_window_info *window, const RECT *proposed)
{
win_monitor_info *monitor;
// in window mode, find the nearest
if (!window->fullscreen)
{
if (proposed != NULL)
monitor = winvideo_monitor_from_handle(MonitorFromRect(proposed, MONITOR_DEFAULTTONEAREST));
else
monitor = winvideo_monitor_from_handle(MonitorFromWindow(window->hwnd, MONITOR_DEFAULTTONEAREST));
}
// in full screen, just use the configured monitor
else
monitor = window->monitor;
// make sure we're up-to-date
winvideo_monitor_refresh(monitor);
return monitor;
}
//============================================================
// create_window_class
// (main thread)
//============================================================
static void create_window_class(void)
{
static int classes_created = FALSE;
assert(GetCurrentThreadId() == main_threadid);
if (!classes_created)
{
WNDCLASS wc = { 0 };
// initialize the description of the window class
wc.lpszClassName = TEXT("MAME");
wc.hInstance = GetModuleHandle(NULL);
#ifdef MESS
wc.lpfnWndProc = win_mess_window_proc;
#else
wc.lpfnWndProc = winwindow_video_window_proc;
#endif
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
// register the class; fail if we can't
if (!RegisterClass(&wc))
fatalerror("Failed to create window class");
classes_created = TRUE;
}
}
//============================================================
// set_starting_view
// (main thread)
//============================================================
static void set_starting_view(running_machine *machine, int index, win_window_info *window, const char *view)
{
const char *defview = options_get_string(mame_options(), WINOPTION_VIEW);
int viewindex;
assert(GetCurrentThreadId() == main_threadid);
// choose non-auto over auto
if (strcmp(view, "auto") == 0 && strcmp(defview, "auto") != 0)
view = defview;
// query the video system to help us pick a view
viewindex = video_get_view_for_target(machine, window->target, view, index, video_config.numscreens);
// set the view
render_target_set_view(window->target, viewindex);
}
//============================================================
// winwindow_ui_pause_from_main_thread
// (main thread)
//============================================================
void winwindow_ui_pause_from_main_thread(running_machine *machine, int pause)
{
int old_temp_pause = ui_temp_pause;
assert(GetCurrentThreadId() == main_threadid);
// if we're pausing, increment the pause counter
if (pause)
{
// if we're the first to pause, we have to actually initiate it
if (ui_temp_pause++ == 0)
{
// only call mame_pause if we weren't already paused due to some external reason
ui_temp_was_paused = mame_is_paused(machine);
if (!ui_temp_was_paused)
mame_pause(machine, TRUE);
SetEvent(ui_pause_event);
}
}
// if we're resuming, decrement the pause counter
else
{
// if we're the last to resume, unpause MAME
if (--ui_temp_pause == 0)
{
// but only do it if we were the ones who initiated it
if (!ui_temp_was_paused)
mame_pause(machine, FALSE);
ResetEvent(ui_pause_event);
}
}
if (LOG_TEMP_PAUSE)
logerror("winwindow_ui_pause_from_main_thread(): %d --> %d\n", old_temp_pause, ui_temp_pause);
}
//============================================================
// winwindow_ui_pause_from_window_thread
// (window thread)
//============================================================
void winwindow_ui_pause_from_window_thread(running_machine *machine, int pause)
{
assert(GetCurrentThreadId() == window_threadid);
// if we're multithreaded, we have to request a pause on the main thread
if (multithreading_enabled)
{
// request a pause from the main thread
PostThreadMessage(main_threadid, WM_USER_UI_TEMP_PAUSE, pause, 0);
// if we're pausing, block until it happens
if (pause)
WaitForSingleObject(ui_pause_event, INFINITE);
}
// otherwise, we just do it directly
else
winwindow_ui_pause_from_main_thread(machine, pause);
}
//============================================================
// winwindow_ui_exec_on_main_thread
// (window thread)
//============================================================
void winwindow_ui_exec_on_main_thread(void (*func)(void *), void *param)
{
assert(GetCurrentThreadId() == window_threadid);
// if we're multithreaded, we have to request a pause on the main thread
if (multithreading_enabled)
{
// request a pause from the main thread
PostThreadMessage(main_threadid, WM_USER_EXEC_FUNC, (WPARAM) func, (LPARAM) param);
}
// otherwise, we just do it directly
else
(*func)(param);
}
//============================================================
// winwindow_ui_is_paused
//============================================================
int winwindow_ui_is_paused(running_machine *machine)
{
return mame_is_paused(machine) && ui_temp_was_paused;
}
//============================================================
// wnd_extra_width
// (window thread)
//============================================================
INLINE int wnd_extra_width(win_window_info *window)
{
RECT temprect = { 100, 100, 200, 200 };
if (window->fullscreen)
return 0;
AdjustWindowRectEx(&temprect, WINDOW_STYLE, win_has_menu(window), WINDOW_STYLE_EX);
return rect_width(&temprect) - 100;
}
//============================================================
// wnd_extra_height
// (window thread)
//============================================================
INLINE int wnd_extra_height(win_window_info *window)
{
RECT temprect = { 100, 100, 200, 200 };
if (window->fullscreen)
return 0;
AdjustWindowRectEx(&temprect, WINDOW_STYLE, win_has_menu(window), WINDOW_STYLE_EX);
return rect_height(&temprect) - 100;
}
//============================================================
// thread_entry
// (window thread)
//============================================================
static unsigned __stdcall thread_entry(void *param)
{
MSG message;
// make a bogus user call to make us a message thread
PeekMessage(&message, NULL, 0, 0, PM_NOREMOVE);
// attach our input to the main thread
AttachThreadInput(main_threadid, window_threadid, TRUE);
// signal to the main thread that we are ready to receive events
SetEvent(window_thread_ready_event);
// run the message pump
while (GetMessage(&message, NULL, 0, 0))
{
int dispatch = TRUE;
if ((message.hwnd == NULL) || is_mame_window(message.hwnd))
{
switch (message.message)
{
// ignore input messages here
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
dispatch = FALSE;
break;
// forward mouse button downs to the input system
case WM_LBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(0, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_RBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(1, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_MBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(2, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_XBUTTONDOWN:
dispatch = !wininput_handle_mouse_button(3, TRUE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
// forward mouse button ups to the input system
case WM_LBUTTONUP:
dispatch = !wininput_handle_mouse_button(0, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_RBUTTONUP:
dispatch = !wininput_handle_mouse_button(1, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_MBUTTONUP:
dispatch = !wininput_handle_mouse_button(2, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
case WM_XBUTTONUP:
dispatch = !wininput_handle_mouse_button(3, FALSE, GET_X_LPARAM(message.lParam), GET_Y_LPARAM(message.lParam));
break;
// a terminate message to the thread posts a quit
case WM_USER_SELF_TERMINATE:
PostQuitMessage(0);
dispatch = FALSE;
break;
// handle the "complete create" message
case WM_USER_FINISH_CREATE_WINDOW:
{
win_window_info *window = (win_window_info *)message.lParam;
window->init_state = complete_create(Machine, window) ? -1 : 1;
dispatch = FALSE;
break;
}
}
}
// dispatch if necessary
if (dispatch)
{
TranslateMessage(&message);
DispatchMessage(&message);
}
}
return 0;
}
//============================================================
// complete_create
// (window thread)
//============================================================
static int complete_create(running_machine *machine, win_window_info *window)
{
RECT monitorbounds, client;
int tempwidth, tempheight;
HMENU menu = NULL;
HDC dc;
assert(GetCurrentThreadId() == window_threadid);
// get the monitor bounds
monitorbounds = window->monitor->info.rcMonitor;
// create the window menu if needed
#if HAS_WINDOW_MENU
if (win_create_menu(machine, &menu))
return 1;
#endif
// create the window, but don't show it yet
window->hwnd = win_create_window_ex_utf8(
window->fullscreen ? FULLSCREEN_STYLE_EX : WINDOW_STYLE_EX,
"MAME",
window->title,
window->fullscreen ? FULLSCREEN_STYLE : WINDOW_STYLE,
monitorbounds.left + 20, monitorbounds.top + 20,
monitorbounds.left + 100, monitorbounds.top + 100,
NULL,//(win_window_list != NULL) ? win_window_list->hwnd : NULL,
menu,
GetModuleHandle(NULL),
NULL);
if (window->hwnd == NULL)
return 1;
// set a pointer back to us
SetWindowLongPtr(window->hwnd, GWLP_USERDATA, (LONG_PTR)window);
// skip the positioning stuff for -video none */
if (video_config.mode == VIDEO_MODE_NONE)
return 0;
// adjust the window position to the initial width/height
tempwidth = (window->maxwidth != 0) ? window->maxwidth : 640;
tempheight = (window->maxheight != 0) ? window->maxheight : 480;
SetWindowPos(window->hwnd, NULL, monitorbounds.left + 20, monitorbounds.top + 20,
monitorbounds.left + tempwidth + wnd_extra_width(window),
monitorbounds.top + tempheight + wnd_extra_height(window),
SWP_NOZORDER);
// maximum or minimize as appropriate
if (window->startmaximized)
maximize_window(window);
else
minimize_window(window);
adjust_window_position_after_major_change(window);
// show the window
if (!window->fullscreen || window->fullscreen_safe)
{
// finish off by trying to initialize DirectX; if we fail, ignore it
if ((*draw.window_init)(window))
return 1;
ShowWindow(window->hwnd, SW_SHOW);
}
// clear the window
dc = GetDC(window->hwnd);
GetClientRect(window->hwnd, &client);
FillRect(dc, &client, (HBRUSH)GetStockObject(BLACK_BRUSH));
ReleaseDC(window->hwnd, dc);
return 0;
}
//============================================================
// winwindow_video_window_proc
// (window thread)
//============================================================
LRESULT CALLBACK winwindow_video_window_proc(HWND wnd, UINT message, WPARAM wparam, LPARAM lparam)
{
LONG_PTR ptr = GetWindowLongPtr(wnd, GWLP_USERDATA);
win_window_info *window = (win_window_info *)ptr;
// we may get called before SetWindowLongPtr is called
if (window != NULL)
{
assert(GetCurrentThreadId() == window_threadid);
update_minmax_state(window);
}
// handle a few messages
switch (message)
{
// paint: redraw the last bitmap
case WM_PAINT:
{
PAINTSTRUCT pstruct;
HDC hdc = BeginPaint(wnd, &pstruct);
draw_video_contents(window, hdc, TRUE);
if (win_has_menu(window))
DrawMenuBar(window->hwnd);
EndPaint(wnd, &pstruct);
break;
}
// non-client paint: punt if full screen
case WM_NCPAINT:
if (!window->fullscreen || HAS_WINDOW_MENU)
return DefWindowProc(wnd, message, wparam, lparam);
break;
// input: handle the raw input
case WM_INPUT:
wininput_handle_raw((HRAWINPUT)lparam);
break;
// syskeys - ignore
case WM_SYSKEYUP:
case WM_SYSKEYDOWN:
break;
// input events
case WM_MOUSEMOVE:
ui_input_push_mouse_move_event(Machine, window->target, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
break;
case WM_MOUSELEAVE:
ui_input_push_mouse_leave_event(Machine, window->target);
break;
case WM_LBUTTONDOWN:
{
DWORD ticks = GetTickCount();
ui_input_push_mouse_click_event(Machine, window->target, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
// check for a double-click
if (ticks - window->lastclicktime < GetDoubleClickTime() &&
GET_X_LPARAM(lparam) >= window->lastclickx - 4 && GET_X_LPARAM(lparam) <= window->lastclickx + 4 &&
GET_Y_LPARAM(lparam) >= window->lastclicky - 4 && GET_Y_LPARAM(lparam) <= window->lastclicky + 4)
{
window->lastclicktime = 0;
ui_input_push_mouse_double_click_event(Machine, window->target, GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
}
else
{
window->lastclicktime = ticks;
window->lastclickx = GET_X_LPARAM(lparam);
window->lastclicky = GET_Y_LPARAM(lparam);
}
break;
}
case WM_CHAR:
ui_input_push_char_event(Machine, window->target, (unicode_char) wparam);
break;
// pause the system when we start a menu or resize
case WM_ENTERSIZEMOVE:
window->resize_state = RESIZE_STATE_RESIZING;
case WM_ENTERMENULOOP:
winwindow_ui_pause_from_window_thread(Machine, TRUE);
break;
// unpause the system when we stop a menu or resize and force a redraw
case WM_EXITSIZEMOVE:
window->resize_state = RESIZE_STATE_PENDING;
case WM_EXITMENULOOP:
winwindow_ui_pause_from_window_thread(Machine, FALSE);
InvalidateRect(wnd, NULL, FALSE);
break;
// get min/max info: set the minimum window size
case WM_GETMINMAXINFO:
{
MINMAXINFO *minmax = (MINMAXINFO *)lparam;
minmax->ptMinTrackSize.x = MIN_WINDOW_DIM;
minmax->ptMinTrackSize.y = MIN_WINDOW_DIM;
break;
}
// sizing: constrain to the aspect ratio unless control key is held down
case WM_SIZING:
{
RECT *rect = (RECT *)lparam;
if (video_config.keepaspect && !(GetAsyncKeyState(VK_CONTROL) & 0x8000))
constrain_to_aspect_ratio(window, rect, wparam);
InvalidateRect(wnd, NULL, FALSE);
break;
}
// syscommands: catch win_start_maximized
case WM_SYSCOMMAND:
{
// prevent screensaver or monitor power events
if (wparam == SC_MONITORPOWER || wparam == SC_SCREENSAVE)
return 1;
// most SYSCOMMANDs require us to invalidate the window
InvalidateRect(wnd, NULL, FALSE);
// handle maximize
if ((wparam & 0xfff0) == SC_MAXIMIZE)
{
update_minmax_state(window);
if (window->ismaximized)
minimize_window(window);
else
maximize_window(window);
break;
}
return DefWindowProc(wnd, message, wparam, lparam);
}
// track whether we are in the foreground
case WM_ACTIVATEAPP:
in_background = !wparam;
break;
// close: cause MAME to exit
case WM_CLOSE:
if (multithreading_enabled)
PostThreadMessage(main_threadid, WM_QUIT, 0, 0);
else
mame_schedule_exit(Machine);
break;
// destroy: clean up all attached rendering bits and NULL out our hwnd
case WM_DESTROY:
(*draw.window_destroy)(window);
window->hwnd = NULL;
return DefWindowProc(wnd, message, wparam, lparam);
// self redraw: draw ourself in a non-painty way
case WM_USER_REDRAW:
{
HDC hdc = GetDC(wnd);
mtlog_add("winwindow_video_window_proc: WM_USER_REDRAW begin");
window->primlist = (const render_primitive_list *)lparam;
draw_video_contents(window, hdc, FALSE);
mtlog_add("winwindow_video_window_proc: WM_USER_REDRAW end");
ReleaseDC(wnd, hdc);
break;
}
// self destruct
case WM_USER_SELF_TERMINATE:
DestroyWindow(window->hwnd);
break;
// fullscreen set
case WM_USER_SET_FULLSCREEN:
set_fullscreen(window, wparam);
break;
// minimum size set
case WM_USER_SET_MINSIZE:
minimize_window(window);
break;
// maximum size set
case WM_USER_SET_MAXSIZE:
maximize_window(window);
break;
// set focus: if we're not the primary window, switch back
// commented out ATM because this prevents us from resizing secondary windows
// case WM_SETFOCUS:
// if (window != win_window_list && win_window_list != NULL)
// SetFocus(win_window_list->hwnd);
// break;
// everything else: defaults
default:
return DefWindowProc(wnd, message, wparam, lparam);
}
return 0;
}
//============================================================
// draw_video_contents
// (window thread)
//============================================================
static void draw_video_contents(win_window_info *window, HDC dc, int update)
{
assert(GetCurrentThreadId() == window_threadid);
mtlog_add("draw_video_contents: begin");
mtlog_add("draw_video_contents: render lock acquire");
osd_lock_acquire(window->render_lock);
mtlog_add("draw_video_contents: render lock acquired");
// if we're iconic, don't bother
if (window->hwnd != NULL && !IsIconic(window->hwnd))
{
// if no bitmap, just fill
if (window->primlist == NULL)
{
RECT fill;
GetClientRect(window->hwnd, &fill);
FillRect(dc, &fill, (HBRUSH)GetStockObject(BLACK_BRUSH));
}
// otherwise, render with our drawing system
else
{
(*draw.window_draw)(window, dc, update);
mtlog_add("draw_video_contents: drawing finished");
}
}
osd_lock_release(window->render_lock);
mtlog_add("draw_video_contents: render lock released");
mtlog_add("draw_video_contents: end");
}
//============================================================
// constrain_to_aspect_ratio
// (window thread)
//============================================================
static void constrain_to_aspect_ratio(win_window_info *window, RECT *rect, int adjustment)
{
win_monitor_info *monitor = winwindow_video_window_monitor(window, rect);
INT32 extrawidth = wnd_extra_width(window);
INT32 extraheight = wnd_extra_height(window);
INT32 propwidth, propheight;
INT32 minwidth, minheight;
INT32 maxwidth, maxheight;
INT32 viswidth, visheight;
INT32 adjwidth, adjheight;
float pixel_aspect;
assert(GetCurrentThreadId() == window_threadid);
// get the pixel aspect ratio for the target monitor
pixel_aspect = winvideo_monitor_get_aspect(monitor);
// determine the proposed width/height
propwidth = rect_width(rect) - extrawidth;
propheight = rect_height(rect) - extraheight;
// based on which edge we are adjusting, take either the width, height, or both as gospel
// and scale to fit using that as our parameter
switch (adjustment)
{
case WMSZ_BOTTOM:
case WMSZ_TOP:
render_target_compute_visible_area(window->target, 10000, propheight, pixel_aspect, render_target_get_orientation(window->target), &propwidth, &propheight);
break;
case WMSZ_LEFT:
case WMSZ_RIGHT:
render_target_compute_visible_area(window->target, propwidth, 10000, pixel_aspect, render_target_get_orientation(window->target), &propwidth, &propheight);
break;
default:
render_target_compute_visible_area(window->target, propwidth, propheight, pixel_aspect, render_target_get_orientation(window->target), &propwidth, &propheight);
break;
}
// get the minimum width/height for the current layout
render_target_get_minimum_size(window->target, &minwidth, &minheight);
// clamp against the absolute minimum
propwidth = MAX(propwidth, MIN_WINDOW_DIM);
propheight = MAX(propheight, MIN_WINDOW_DIM);
// clamp against the minimum width and height
propwidth = MAX(propwidth, minwidth);
propheight = MAX(propheight, minheight);
// clamp against the maximum (fit on one screen for full screen mode)
if (window->fullscreen)
{
maxwidth = rect_width(&monitor->info.rcMonitor) - extrawidth;
maxheight = rect_height(&monitor->info.rcMonitor) - extraheight;
}
else
{
maxwidth = rect_width(&monitor->info.rcWork) - extrawidth;
maxheight = rect_height(&monitor->info.rcWork) - extraheight;
// further clamp to the maximum width/height in the window
if (window->maxwidth != 0)
maxwidth = MIN(maxwidth, window->maxwidth + extrawidth);
if (window->maxheight != 0)
maxheight = MIN(maxheight, window->maxheight + extraheight);
}
// clamp to the maximum
propwidth = MIN(propwidth, maxwidth);
propheight = MIN(propheight, maxheight);
// compute the visible area based on the proposed rectangle
render_target_compute_visible_area(window->target, propwidth, propheight, pixel_aspect, render_target_get_orientation(window->target), &viswidth, &visheight);
// compute the adjustments we need to make
adjwidth = (viswidth + extrawidth) - rect_width(rect);
adjheight = (visheight + extraheight) - rect_height(rect);
// based on which corner we're adjusting, constrain in different ways
switch (adjustment)
{
case WMSZ_BOTTOM:
case WMSZ_BOTTOMRIGHT:
case WMSZ_RIGHT:
rect->right += adjwidth;
rect->bottom += adjheight;
break;
case WMSZ_BOTTOMLEFT:
rect->left -= adjwidth;
rect->bottom += adjheight;
break;
case WMSZ_LEFT:
case WMSZ_TOPLEFT:
case WMSZ_TOP:
rect->left -= adjwidth;
rect->top -= adjheight;
break;
case WMSZ_TOPRIGHT:
rect->right += adjwidth;
rect->top -= adjheight;
break;
}
}
//============================================================
// get_min_bounds
// (window thread)
//============================================================
static void get_min_bounds(win_window_info *window, RECT *bounds, int constrain)
{
INT32 minwidth, minheight;
assert(GetCurrentThreadId() == window_threadid);
// get the minimum target size
render_target_get_minimum_size(window->target, &minwidth, &minheight);
// expand to our minimum dimensions
if (minwidth < MIN_WINDOW_DIM)
minwidth = MIN_WINDOW_DIM;
if (minheight < MIN_WINDOW_DIM)
minheight = MIN_WINDOW_DIM;
// account for extra window stuff
minwidth += wnd_extra_width(window);
minheight += wnd_extra_height(window);
// if we want it constrained, figure out which one is larger
if (constrain)
{
RECT test1, test2;
// first constrain with no height limit
test1.top = test1.left = 0;
test1.right = minwidth;
test1.bottom = 10000;
constrain_to_aspect_ratio(window, &test1, WMSZ_BOTTOMRIGHT);
// then constrain with no width limit
test2.top = test2.left = 0;
test2.right = 10000;
test2.bottom = minheight;
constrain_to_aspect_ratio(window, &test2, WMSZ_BOTTOMRIGHT);
// pick the larger
if (rect_width(&test1) > rect_width(&test2))
{
minwidth = rect_width(&test1);
minheight = rect_height(&test1);
}
else
{
minwidth = rect_width(&test2);
minheight = rect_height(&test2);
}
}
// get the window rect
GetWindowRect(window->hwnd, bounds);
// now adjust
bounds->right = bounds->left + minwidth;
bounds->bottom = bounds->top + minheight;
}
//============================================================
// get_max_bounds
// (window thread)
//============================================================
static void get_max_bounds(win_window_info *window, RECT *bounds, int constrain)
{
RECT maximum;
assert(GetCurrentThreadId() == window_threadid);
// compute the maximum client area
winvideo_monitor_refresh(window->monitor);
maximum = window->monitor->info.rcWork;
// clamp to the window's max
if (window->maxwidth != 0)
{
int temp = window->maxwidth + wnd_extra_width(window);
if (temp < rect_width(&maximum))
maximum.right = maximum.left + temp;
}
if (window->maxheight != 0)
{
int temp = window->maxheight + wnd_extra_height(window);
if (temp < rect_height(&maximum))
maximum.bottom = maximum.top + temp;
}
// constrain to fit
if (constrain)
constrain_to_aspect_ratio(window, &maximum, WMSZ_BOTTOMRIGHT);
else
{
maximum.right -= wnd_extra_width(window);
maximum.bottom -= wnd_extra_height(window);
}
// center within the work area
bounds->left = window->monitor->info.rcWork.left + (rect_width(&window->monitor->info.rcWork) - rect_width(&maximum)) / 2;
bounds->top = window->monitor->info.rcWork.top + (rect_height(&window->monitor->info.rcWork) - rect_height(&maximum)) / 2;
bounds->right = bounds->left + rect_width(&maximum);
bounds->bottom = bounds->top + rect_height(&maximum);
}
//============================================================
// update_minmax_state
// (window thread)
//============================================================
static void update_minmax_state(win_window_info *window)
{
assert(GetCurrentThreadId() == window_threadid);
if (!window->fullscreen)
{
RECT bounds, minbounds, maxbounds;
// compare the maximum bounds versus the current bounds
get_min_bounds(window, &minbounds, video_config.keepaspect);
get_max_bounds(window, &maxbounds, video_config.keepaspect);
GetWindowRect(window->hwnd, &bounds);
// if either the width or height matches, we were maximized
window->isminimized = (rect_width(&bounds) == rect_width(&minbounds) ||
rect_height(&bounds) == rect_height(&minbounds));
window->ismaximized = (rect_width(&bounds) == rect_width(&maxbounds) ||
rect_height(&bounds) == rect_height(&maxbounds));
}
else
{
window->isminimized = FALSE;
window->ismaximized = TRUE;
}
}
//============================================================
// minimize_window
// (window thread)
//============================================================
static void minimize_window(win_window_info *window)
{
RECT newsize;
assert(GetCurrentThreadId() == window_threadid);
get_min_bounds(window, &newsize, video_config.keepaspect);
SetWindowPos(window->hwnd, NULL, newsize.left, newsize.top, rect_width(&newsize), rect_height(&newsize), SWP_NOZORDER);
}
//============================================================
// maximize_window
// (window thread)
//============================================================
static void maximize_window(win_window_info *window)
{
RECT newsize;
assert(GetCurrentThreadId() == window_threadid);
get_max_bounds(window, &newsize, video_config.keepaspect);
SetWindowPos(window->hwnd, NULL, newsize.left, newsize.top, rect_width(&newsize), rect_height(&newsize), SWP_NOZORDER);
}
//============================================================
// adjust_window_position_after_major_change
// (window thread)
//============================================================
static void adjust_window_position_after_major_change(win_window_info *window)
{
RECT oldrect, newrect;
assert(GetCurrentThreadId() == window_threadid);
// get the current size
GetWindowRect(window->hwnd, &oldrect);
// adjust the window size so the client area is what we want
if (!window->fullscreen)
{
// constrain the existing size to the aspect ratio
newrect = oldrect;
if (video_config.keepaspect)
constrain_to_aspect_ratio(window, &newrect, WMSZ_BOTTOMRIGHT);
}
// in full screen, make sure it covers the primary display
else
{
win_monitor_info *monitor = winwindow_video_window_monitor(window, NULL);
newrect = monitor->info.rcMonitor;
}
// adjust the position if different
if (oldrect.left != newrect.left || oldrect.top != newrect.top ||
oldrect.right != newrect.right || oldrect.bottom != newrect.bottom)
SetWindowPos(window->hwnd, window->fullscreen ? HWND_TOPMOST : HWND_TOP,
newrect.left, newrect.top,
rect_width(&newrect), rect_height(&newrect), 0);
// take note of physical window size (used for lightgun coordinate calculation)
if (window == win_window_list)
{
win_physical_width = rect_width(&newrect);
win_physical_height = rect_height(&newrect);
logerror("Physical width %d, height %d\n",win_physical_width,win_physical_height);
}
}
//============================================================
// set_fullscreen
// (window thread)
//============================================================
static void set_fullscreen(win_window_info *window, int fullscreen)
{
assert(GetCurrentThreadId() == window_threadid);
// if we're in the right state, punt
if (window->fullscreen == fullscreen)
return;
window->fullscreen = fullscreen;
// kill off the drawers
(*draw.window_destroy)(window);
// hide ourself
ShowWindow(window->hwnd, SW_HIDE);
// configure the window if non-fullscreen
if (!fullscreen)
{
// adjust the style
SetWindowLong(window->hwnd, GWL_STYLE, WINDOW_STYLE);
SetWindowLong(window->hwnd, GWL_EXSTYLE, WINDOW_STYLE_EX);
SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
// force to the bottom, then back on top
SetWindowPos(window->hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
SetWindowPos(window->hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
// if we have previous non-fullscreen bounds, use those
if (window->non_fullscreen_bounds.right != window->non_fullscreen_bounds.left)
{
SetWindowPos(window->hwnd, HWND_TOP, window->non_fullscreen_bounds.left, window->non_fullscreen_bounds.top,
rect_width(&window->non_fullscreen_bounds), rect_height(&window->non_fullscreen_bounds),
SWP_NOZORDER);
}
// otherwise, set a small size and maximize from there
else
{
SetWindowPos(window->hwnd, HWND_TOP, 0, 0, MIN_WINDOW_DIM, MIN_WINDOW_DIM, SWP_NOZORDER);
maximize_window(window);
}
}
// configure the window if fullscreen
else
{
// save the bounds
GetWindowRect(window->hwnd, &window->non_fullscreen_bounds);
// adjust the style
SetWindowLong(window->hwnd, GWL_STYLE, FULLSCREEN_STYLE);
SetWindowLong(window->hwnd, GWL_EXSTYLE, FULLSCREEN_STYLE_EX);
SetWindowPos(window->hwnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
// set topmost
SetWindowPos(window->hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
// adjust the window to compensate for the change
adjust_window_position_after_major_change(window);
// show ourself
if (!window->fullscreen || window->fullscreen_safe)
{
if (video_config.mode != VIDEO_MODE_NONE)
ShowWindow(window->hwnd, SW_SHOW);
if ((*draw.window_init)(window))
exit(1);
}
// ensure we're still adjusted correctly
adjust_window_position_after_major_change(window);
}