Opentk/Source/OpenTK/Platform/Windows/WinGLNative.cs
the_fiddler 765f54917e Default window style flags can be constants rather than static readonly fields.
Throw an exception if we fail to register the window class, the previous solution (check for "class already exists" error) was a workaround for a threading issue that has been solved.
Set the small icon when registering the class.
2009-11-04 09:21:41 +00:00

1175 lines
42 KiB
C#

#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using OpenTK.Graphics;
using OpenTK.Input;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
namespace OpenTK.Platform.Windows
{
/// <summary>
/// Drives GameWindow on Windows.
/// This class supports OpenTK, and is not intended for use by OpenTK programs.
/// </summary>
internal sealed class WinGLNative : INativeWindow, IInputDriver
{
#region Fields
readonly static object SyncRoot = new object();
const ExtendedWindowStyle ParentStyleEx = ExtendedWindowStyle.WindowEdge | ExtendedWindowStyle.ApplicationWindow;
const ExtendedWindowStyle ChildStyleEx = 0;
readonly IntPtr Instance = Marshal.GetHINSTANCE(typeof(WinGLNative).Module);
readonly IntPtr ClassName;
readonly WindowProcedure WindowProcedureDelegate;
readonly UIntPtr ModalLoopTimerId;
readonly uint ModalLoopTimerPeriod = 1;
UIntPtr timer_handle;
readonly Functions.TimerProc ModalLoopCallback;
bool class_registered;
bool disposed;
bool exists;
WinWindowInfo window, child_window;
WindowBorder windowBorder = WindowBorder.Resizable;
Nullable<WindowBorder> previous_window_border; // Set when changing to fullscreen state.
Nullable<WindowBorder> deferred_window_border; // Set to avoid changing borders during fullscreen state.
WindowState windowState = WindowState.Normal;
bool borderless_maximized_window_state = false; // Hack to get maximized mode with hidden border (not normally possible).
bool focused;
Rectangle
bounds = new Rectangle(),
client_rectangle = new Rectangle(),
previous_bounds = new Rectangle(); // Used to restore previous size when leaving fullscreen mode.
Icon icon;
const ClassStyle DefaultClassStyle =
ClassStyle.OwnDC | ClassStyle.VRedraw | ClassStyle.HRedraw | ClassStyle.Ime;
readonly IntPtr DefaultWindowProcedure =
Marshal.GetFunctionPointerForDelegate(new WindowProcedure(Functions.DefWindowProc));
// Used for IInputDriver implementation
WinMMJoystick joystick_driver = new WinMMJoystick();
KeyboardDevice keyboard = new KeyboardDevice();
MouseDevice mouse = new MouseDevice();
IList<KeyboardDevice> keyboards = new List<KeyboardDevice>(1);
IList<MouseDevice> mice = new List<MouseDevice>(1);
internal static readonly WinKeyMap KeyMap = new WinKeyMap();
const long ExtendedBit = 1 << 24; // Used to distinguish left and right control, alt and enter keys.
static readonly uint ShiftRightScanCode = Functions.MapVirtualKey(VirtualKeys.RSHIFT, 0); // Used to distinguish left and right shift keys.
KeyPressEventArgs key_press = new KeyPressEventArgs((char)0);
static int window_count;
#endregion
#region Contructors
public WinGLNative(int x, int y, int width, int height, string title, GameWindowFlags options, DisplayDevice device)
{
lock (SyncRoot)
{
++window_count;
ClassName = Marshal.StringToHGlobalAuto(typeof(WinGLNative).Name + window_count.ToString());
ModalLoopTimerId = new UIntPtr((uint)window_count);
}
// This is the main window procedure callback. We need the callback in order to create the window, so
// don't move it below the CreateWindow calls.
WindowProcedureDelegate = WindowProcedure;
// This timer callback is called periodically when the window enters a sizing / moving modal loop.
ModalLoopCallback = delegate(IntPtr handle, WindowMessage msg, UIntPtr eventId, int time)
{
// Todo: find a way to notify the frontend that it should process queued up UpdateFrame/RenderFrame events.
if (Move != null)
Move(this, EventArgs.Empty);
};
// To avoid issues with Ati drivers on Windows 6+ with compositing enabled, the context will not be
// bound to the top-level window, but rather to a child window docked in the parent.
window = new WinWindowInfo(
CreateWindow(x, y, width, height, title, options, device, IntPtr.Zero), null);
child_window = new WinWindowInfo(
CreateWindow(0, 0, ClientSize.Width, ClientSize.Height, title, options, device, window.WindowHandle), window);
exists = true;
keyboard.Description = "Standard Windows keyboard";
keyboard.NumberOfFunctionKeys = 12;
keyboard.NumberOfKeys = 101;
keyboard.NumberOfLeds = 3;
mouse.Description = "Standard Windows mouse";
mouse.NumberOfButtons = 3;
mouse.NumberOfWheels = 1;
keyboards.Add(keyboard);
mice.Add(mouse);
EnableMouseLeaveNotifications();
}
#endregion
#region Private Members
#region WindowProcedure
IntPtr WindowProcedure(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
{
Debug.WriteLine(message.ToString());
switch (message)
{
#region Size / Move / Style events
case WindowMessage.ACTIVATE:
// See http://msdn.microsoft.com/en-us/library/ms646274(VS.85).aspx (WM_ACTIVATE notification):
// wParam: The low-order word specifies whether the window is being activated or deactivated.
bool new_focused_state = Focused;
if (IntPtr.Size == 4)
focused = (wParam.ToInt32() & 0xFFFF) != 0;
else
focused = (wParam.ToInt64() & 0xFFFF) != 0;
if (new_focused_state != Focused && FocusedChanged != null)
FocusedChanged(this, EventArgs.Empty);
return IntPtr.Zero;
case WindowMessage.ENTERMENULOOP:
case WindowMessage.ENTERSIZEMOVE:
// Entering the modal size/move loop: we don't want rendering to
// stop during this time, so we register a timer callback to continue
// processing from time to time.
timer_handle = Functions.SetTimer(handle, ModalLoopTimerId, ModalLoopTimerPeriod, ModalLoopCallback);
if (timer_handle == UIntPtr.Zero)
Debug.Print("[Warning] Failed to set modal loop timer callback ({0}:{1}->{2}).",
GetType().Name, handle, Marshal.GetLastWin32Error());
break;
case WindowMessage.EXITMENULOOP:
case WindowMessage.EXITSIZEMOVE:
// ExitingmModal size/move loop: the timer callback is no longer
// necessary.
if (!Functions.KillTimer(handle, timer_handle))
Debug.Print("[Warning] Failed to kill modal loop timer callback ({0}:{1}->{2}).",
GetType().Name, handle, Marshal.GetLastWin32Error());
timer_handle = UIntPtr.Zero;
break;
case WindowMessage.NCCALCSIZE:
// Need to update the client rectangle, because it has the wrong size on Vista with Aero enabled.
//if (m.WParam == new IntPtr(1))
//{
// unsafe
// {
// NcCalculateSize* nc_calc_size = (NcCalculateSize*)m.LParam;
// //nc_calc_size->NewBounds = nc_calc_size->OldBounds;
// //nc_calc_size->OldBounds = nc_calc_size->NewBounds;
// //client_rectangle = rect.OldClientRectangle;
// }
// m.Result = new IntPtr((int)(NcCalcSizeOptions.ALIGNTOP | NcCalcSizeOptions.ALIGNLEFT/* | NcCalcSizeOptions.REDRAW*/));
//}
break;
case WindowMessage.ERASEBKGND:
return new IntPtr(1);
case WindowMessage.WINDOWPOSCHANGED:
unsafe
{
WindowPosition* pos = (WindowPosition*)lParam;
if (window != null && pos->hwnd == window.WindowHandle)
{
Point new_location = new Point(pos->x, pos->y);
if (Location != new_location)
{
bounds.Location = new_location;
if (Move != null)
Move(this, EventArgs.Empty);
}
Size new_size = new Size(pos->cx, pos->cy);
if (Size != new_size)
{
bounds.Width = pos->cx;
bounds.Height = pos->cy;
Win32Rectangle rect;
Functions.GetClientRect(handle, out rect);
client_rectangle = rect.ToRectangle();
Functions.SetWindowPos(child_window.WindowHandle, IntPtr.Zero, 0, 0, ClientRectangle.Width, ClientRectangle.Height,
SetWindowPosFlags.NOZORDER | SetWindowPosFlags.NOOWNERZORDER |
SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOSENDCHANGING);
if (Resize != null)
Resize(this, EventArgs.Empty);
}
}
}
break;
case WindowMessage.STYLECHANGED:
unsafe
{
if (wParam.ToInt64() == (long)GWL.STYLE)
{
WindowStyle style = ((StyleStruct*)lParam)->New;
if ((style & WindowStyle.Popup) != 0)
windowBorder = WindowBorder.Hidden;
else if ((style & WindowStyle.ThickFrame) != 0)
windowBorder = WindowBorder.Resizable;
else if ((style & ~(WindowStyle.ThickFrame | WindowStyle.MaximizeBox)) != 0)
windowBorder = WindowBorder.Fixed;
}
}
break;
case WindowMessage.SIZE:
SizeMessage state = (SizeMessage)wParam.ToInt64();
WindowState new_state = windowState;
switch (state)
{
case SizeMessage.RESTORED: new_state = borderless_maximized_window_state ?
WindowState.Maximized : WindowState.Normal; break;
case SizeMessage.MINIMIZED: new_state = WindowState.Minimized; break;
case SizeMessage.MAXIMIZED: new_state = WindowBorder == WindowBorder.Hidden ?
WindowState.Fullscreen : WindowState.Maximized;
break;
}
if (new_state != windowState)
{
windowState = new_state;
if (WindowStateChanged != null)
WindowStateChanged(this, EventArgs.Empty);
}
break;
#endregion
#region Input events
case WindowMessage.CHAR:
if (IntPtr.Size == 4)
key_press.KeyChar = (char)wParam.ToInt32();
else
key_press.KeyChar = (char)wParam.ToInt64();
if (KeyPress != null)
KeyPress(this, key_press);
break;
case WindowMessage.MOUSEMOVE:
Point point = new Point(
(int)(lParam.ToInt32() & 0x0000FFFF),
(int)(lParam.ToInt32() & 0xFFFF0000) >> 16);
mouse.Position = point;
{
Win32Rectangle rect;
Functions.GetClientRect(window.WindowHandle, out rect);
if (!rect.ToRectangle().Contains(point))
{
Functions.ReleaseCapture();
if (MouseLeave != null)
MouseLeave(this, EventArgs.Empty);
}
else if (Functions.GetCapture() != window.WindowHandle)
{
Functions.SetFocus(window.WindowHandle);
Functions.SetCapture(window.WindowHandle);
if (MouseEnter != null)
MouseEnter(this, EventArgs.Empty);
}
}
break;
case WindowMessage.MOUSEWHEEL:
// This is due to inconsistent behavior of the WParam value on 64bit arch, whese
// wparam = 0xffffffffff880000 or wparam = 0x00000000ff100000
mouse.Wheel += (int)((long)wParam << 32 >> 48) / 120;
break;
case WindowMessage.LBUTTONDOWN:
mouse[MouseButton.Left] = true;
break;
case WindowMessage.MBUTTONDOWN:
mouse[MouseButton.Middle] = true;
break;
case WindowMessage.RBUTTONDOWN:
mouse[MouseButton.Right] = true;
break;
case WindowMessage.XBUTTONDOWN:
mouse[((wParam.ToInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = true;
break;
case WindowMessage.LBUTTONUP:
mouse[MouseButton.Left] = false;
break;
case WindowMessage.MBUTTONUP:
mouse[MouseButton.Middle] = false;
break;
case WindowMessage.RBUTTONUP:
mouse[MouseButton.Right] = false;
break;
case WindowMessage.XBUTTONUP:
// TODO: Is this correct?
mouse[((wParam.ToInt32() & 0xFFFF0000) >> 16) != (int)MouseKeys.XButton1 ? MouseButton.Button1 : MouseButton.Button2] = false;
break;
// Keyboard events:
case WindowMessage.KEYDOWN:
case WindowMessage.KEYUP:
case WindowMessage.SYSKEYDOWN:
case WindowMessage.SYSKEYUP:
bool pressed =
message == WindowMessage.KEYDOWN ||
message == WindowMessage.SYSKEYDOWN;
// Shift/Control/Alt behave strangely when e.g. ShiftRight is held down and ShiftLeft is pressed
// and released. It looks like neither key is released in this case, or that the wrong key is
// released in the case of Control and Alt.
// To combat this, we are going to release both keys when either is released. Hacky, but should work.
// Win95 does not distinguish left/right key constants (GetAsyncKeyState returns 0).
// In this case, both keys will be reported as pressed.
bool extended = (lParam.ToInt64() & ExtendedBit) != 0;
switch ((VirtualKeys)wParam)
{
case VirtualKeys.SHIFT:
// The behavior of this key is very strange. Unlike Control and Alt, there is no extended bit
// to distinguish between left and right keys. Moreover, pressing both keys and releasing one
// may result in both keys being held down (but not always).
// The only reliably way to solve this was reported by BlueMonkMN at the forums: we should
// check the scancodes. It looks like GLFW does the same thing, so it should be reliable.
// TODO: Not 100% reliable, when both keys are pressed at once.
if (ShiftRightScanCode != 0)
{
unchecked
{
if (((lParam.ToInt64() >> 16) & 0xFF) == ShiftRightScanCode)
keyboard[Input.Key.ShiftRight] = pressed;
else
keyboard[Input.Key.ShiftLeft] = pressed;
}
}
else
{
// Should only fall here on Windows 9x and NT4.0-
keyboard[Input.Key.ShiftLeft] = pressed;
}
return IntPtr.Zero;
case VirtualKeys.CONTROL:
if (extended)
keyboard[Input.Key.ControlRight] = pressed;
else
keyboard[Input.Key.ControlLeft] = pressed;
return IntPtr.Zero;
case VirtualKeys.MENU:
if (extended)
keyboard[Input.Key.AltRight] = pressed;
else
keyboard[Input.Key.AltLeft] = pressed;
return IntPtr.Zero;
case VirtualKeys.RETURN:
if (extended)
keyboard[Key.KeypadEnter] = pressed;
else
keyboard[Key.Enter] = pressed;
return IntPtr.Zero;
default:
if (!WMInput.KeyMap.ContainsKey((VirtualKeys)wParam))
{
Debug.Print("Virtual key {0} ({1}) not mapped.", (VirtualKeys)wParam, (int)lParam);
break;
}
else
{
keyboard[WMInput.KeyMap[(VirtualKeys)wParam]] = pressed;
}
return IntPtr.Zero;
}
break;
case WindowMessage.SYSCHAR:
return IntPtr.Zero;
case WindowMessage.KILLFOCUS:
keyboard.ClearKeys();
break;
#endregion
#region Creation / Destruction events
case WindowMessage.CREATE:
CreateStruct cs = (CreateStruct)Marshal.PtrToStructure(lParam, typeof(CreateStruct));
if (cs.hwndParent == IntPtr.Zero)
{
bounds.X = cs.x;
bounds.Y = cs.y;
bounds.Width = cs.cx;
bounds.Height = cs.cy;
Win32Rectangle rect;
Functions.GetClientRect(handle, out rect);
client_rectangle = rect.ToRectangle();
}
break;
case WindowMessage.CLOSE:
System.ComponentModel.CancelEventArgs e = new System.ComponentModel.CancelEventArgs();
if (Closing != null)
Closing(this, e);
if (!e.Cancel)
{
if (Unload != null)
Unload(this, EventArgs.Empty);
DestroyWindow();
break;
}
return IntPtr.Zero;
case WindowMessage.DESTROY:
exists = false;
Functions.UnregisterClass(ClassName, Instance);
//Marshal.FreeHGlobal(ClassName);
window.Dispose();
child_window.Dispose();
if (Closed != null)
Closed(this, EventArgs.Empty);
break;
#endregion
}
return Functions.DefWindowProc(handle, message, wParam, lParam);
}
#endregion
#region IsIdle
bool IsIdle
{
get
{
MSG message = new MSG();
return !Functions.PeekMessage(ref message, window.WindowHandle, 0, 0, 0);
}
}
#endregion
#region CreateWindow
IntPtr CreateWindow(int x, int y, int width, int height, string title, GameWindowFlags options, DisplayDevice device, IntPtr parentHandle)
{
// Use win32 to create the native window.
// Keep in mind that some construction code runs in the WM_CREATE message handler.
// The style of a parent window is different than that of a child window.
// Note: the child window should always be visible, even if the parent isn't.
WindowStyle style = 0;
ExtendedWindowStyle ex_style = 0;
if (parentHandle == IntPtr.Zero)
{
style |= WindowStyle.OverlappedWindow | WindowStyle.ClipChildren;
ex_style = ParentStyleEx;
}
else
{
style |= WindowStyle.Visible | WindowStyle.Child | WindowStyle.ClipSiblings;
ex_style = ChildStyleEx;
}
// Find out the final window rectangle, after the WM has added its chrome (titlebar, sidebars etc).
Win32Rectangle rect = new Win32Rectangle();
rect.left = x; rect.top = y; rect.right = x + width; rect.bottom = y + height;
Functions.AdjustWindowRectEx(ref rect, style, false, ex_style);
// Create the window class that we will use for this window.
// The current approach is to register a new class for each top-level WinGLWindow we create.
if (!class_registered)
{
ExtendedWindowClass wc = new ExtendedWindowClass();
wc.Size = ExtendedWindowClass.SizeInBytes;
wc.Style = DefaultClassStyle;
wc.Instance = Instance;
wc.WndProc = WindowProcedureDelegate;
wc.ClassName = ClassName;
wc.Icon = Icon != null ? Icon.Handle : IntPtr.Zero;
#warning "This seems to resize one of the 'large' icons, rather than using a small icon directly (multi-icon files). Investigate!"
wc.IconSm = Icon != null ? new Icon(Icon, 16, 16).Handle : IntPtr.Zero;
wc.Cursor = Functions.LoadCursor(CursorName.Arrow);
ushort atom = Functions.RegisterClassEx(ref wc);
if (atom == 0)
throw new PlatformException(String.Format("Failed to register window class. Error: {0}", Marshal.GetLastWin32Error()));
class_registered = true;
}
IntPtr window_name = Marshal.StringToHGlobalAuto(title);
IntPtr handle = Functions.CreateWindowEx(
ex_style, ClassName, window_name, style,
rect.left, rect.top, rect.Width, rect.Height,
parentHandle, IntPtr.Zero, Instance, IntPtr.Zero);
if (handle == IntPtr.Zero)
throw new PlatformException(String.Format("Failed to create window. Error: {0}", Marshal.GetLastWin32Error()));
return handle;
}
#endregion
#region DestroyWindow
/// <summary>
/// Starts the teardown sequence for the current window.
/// </summary>
void DestroyWindow()
{
if (Exists)
{
Debug.Print("Destroying window: {0}", window.ToString());
Functions.DestroyWindow(window.WindowHandle);
exists = false;
}
}
#endregion
void EnableMouseLeaveNotifications()
{
TrackMouseEventStructure tme = new TrackMouseEventStructure();
tme.Size = TrackMouseEventStructure.SizeInBytes;
tme.Flags |= TrackMouseEventFlags.LEAVE;
tme.TrackWindowHandle = child_window.WindowHandle;
tme.HoverTime = -1; // HOVER_DEFAULT
if (!Functions.TrackMouseEvent(ref tme))
Debug.Print("[Error] Failed to enable mouse event tracking. Error: {0}",
Marshal.GetLastWin32Error());
}
#endregion
#region INativeWindow Members
#region Bounds
public Rectangle Bounds
{
get { return bounds; }
set
{
// Note: the bounds variable is updated when the resize/move message arrives.
Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, value.X, value.Y, value.Width, value.Height, 0);
}
}
#endregion
#region Location
public Point Location
{
get { return Bounds.Location; }
set
{
// Note: the bounds variable is updated when the resize/move message arrives.
Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, value.X, value.Y, 0, 0, SetWindowPosFlags.NOSIZE);
}
}
#endregion
#region Size
public Size Size
{
get { return Bounds.Size; }
set
{
// Note: the bounds variable is updated when the resize/move message arrives.
Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, 0, 0, value.Width, value.Height, SetWindowPosFlags.NOMOVE);
}
}
#endregion
#region ClientRectangle
public Rectangle ClientRectangle
{
get
{
if (client_rectangle.Width == 0)
client_rectangle.Width = 1;
if (client_rectangle.Height == 0)
client_rectangle.Height = 1;
return client_rectangle;
}
set
{
Size = value.Size;
}
}
#endregion
#region ClientSize
public Size ClientSize
{
get
{
return ClientRectangle.Size;
}
set
{
WindowStyle style = (WindowStyle)Functions.GetWindowLong(window.WindowHandle, GetWindowLongOffsets.STYLE);
Win32Rectangle rect = Win32Rectangle.From(value);
Functions.AdjustWindowRect(ref rect, style, false);
Size = new Size(rect.Width, rect.Height);
}
}
#endregion
#region Width
public int Width
{
get { return ClientRectangle.Width; }
set { ClientRectangle = new Rectangle(Location, new Size(value, Height)); }
}
#endregion
#region Height
public int Height
{
get { return ClientRectangle.Height; }
set { ClientRectangle = new Rectangle(Location, new Size(Width, value)); }
}
#endregion
#region X
public int X
{
get { return ClientRectangle.X; }
set { ClientRectangle = new Rectangle(new Point(value, Y), Size); }
}
#endregion
#region Y
public int Y
{
get { return ClientRectangle.Y; }
set { ClientRectangle = new Rectangle(new Point(X, value), Size); }
}
#endregion
#region Icon
public Icon Icon
{
get
{
return icon;
}
set
{
icon = value;
if (window.WindowHandle != IntPtr.Zero)
{
Functions.SendMessage(window.WindowHandle, WindowMessage.SETICON, (IntPtr)0, icon == null ? IntPtr.Zero : value.Handle);
Functions.SendMessage(window.WindowHandle, WindowMessage.SETICON, (IntPtr)1, icon == null ? IntPtr.Zero : value.Handle);
}
}
}
#endregion
#region Focused
public bool Focused
{
get { return focused; }
}
#endregion
#region Title
StringBuilder sb_title = new StringBuilder(256);
public string Title
{
get
{
sb_title.Remove(0, sb_title.Length);
if (Functions.GetWindowText(window.WindowHandle, sb_title, sb_title.MaxCapacity) == 0)
Debug.Print("Failed to retrieve window title (window:{0}, reason:{2}).", window.WindowHandle, Marshal.GetLastWin32Error());
return sb_title.ToString();
}
set
{
if (!Functions.SetWindowText(window.WindowHandle, value))
Debug.Print("Failed to change window title (window:{0}, new title:{1}, reason:{2}).", window.WindowHandle, value, Marshal.GetLastWin32Error());
}
}
#endregion
#region Visible
public bool Visible
{
get
{
return Functions.IsWindowVisible(window.WindowHandle);
}
set
{
if (value)
{
Functions.ShowWindow(window.WindowHandle, ShowWindowCommand.SHOWNORMAL);
}
else if (!value)
{
Functions.ShowWindow(window.WindowHandle, ShowWindowCommand.HIDE);
}
}
}
#endregion
#region Exists
public bool Exists { get { return exists; } }
#endregion
#region Close
public void Close()
{
Functions.PostMessage(window.WindowHandle, WindowMessage.CLOSE, IntPtr.Zero, IntPtr.Zero);
}
#endregion
#region public WindowState WindowState
public WindowState WindowState
{
get
{
return windowState;
}
set
{
if (WindowState == value)
return;
ShowWindowCommand command = 0;
bool exiting_fullscreen = false;
borderless_maximized_window_state = false;
switch (value)
{
case WindowState.Normal:
command = ShowWindowCommand.RESTORE;
// If we are leaving fullscreen mode we need to restore the border.
if (WindowState == WindowState.Fullscreen)
exiting_fullscreen = true;
break;
case WindowState.Maximized:
// Note: if we use the MAXIMIZE command and the window border is Hidden (i.e. WS_POPUP),
// we will enter fullscreen mode - we don't want that! As a workaround, we'll resize the window
// manually to cover the whole working area of the current monitor.
// Reset state to avoid strange interactions with fullscreen/minimized windows.
WindowState = WindowState.Normal;
if (WindowBorder == WindowBorder.Hidden)
{
IntPtr current_monitor = Functions.MonitorFromWindow(window.WindowHandle, MonitorFrom.Nearest);
MonitorInfo info = new MonitorInfo();
info.Size = MonitorInfo.SizeInBytes;
Functions.GetMonitorInfo(current_monitor, ref info);
previous_bounds = Bounds;
borderless_maximized_window_state = true;
Bounds = info.Work.ToRectangle();
}
else
{
command = ShowWindowCommand.MAXIMIZE;
}
break;
case WindowState.Minimized:
command = ShowWindowCommand.MINIMIZE;
break;
case WindowState.Fullscreen:
// We achieve fullscreen by hiding the window border and sending the MAXIMIZE command.
// We cannot use the WindowState.Maximized directly, as that will not send the MAXIMIZE
// command for windows with hidden borders.
// Reset state to avoid strange side-effects from maximized/minimized windows.
WindowState = WindowState.Normal;
previous_bounds = Bounds;
previous_window_border = WindowBorder;
WindowBorder = WindowBorder.Hidden;
command = ShowWindowCommand.MAXIMIZE;
break;
}
if (command != 0)
Functions.ShowWindow(window.WindowHandle, command);
// Restore previous window size/location if necessary
if (command == ShowWindowCommand.RESTORE && previous_bounds != Rectangle.Empty)
{
Bounds = previous_bounds;
previous_bounds = Rectangle.Empty;
}
// Restore previous window border or apply pending border change when leaving fullscreen mode.
if (exiting_fullscreen)
{
WindowBorder =
deferred_window_border.HasValue ? deferred_window_border.Value :
previous_window_border.HasValue ? previous_window_border.Value :
WindowBorder;
deferred_window_border = previous_window_border = null;
}
}
}
#endregion
#region public WindowBorder WindowBorder
public WindowBorder WindowBorder
{
get
{
return windowBorder;
}
set
{
// Do not allow border changes during fullscreen mode.
// Defer them for when we leave fullscreen.
if (WindowState == WindowState.Fullscreen)
{
deferred_window_border = value;
return;
}
if (windowBorder == value)
return;
// To ensure maximized/minimized windows work correctly, reset state to normal,
// change the border, then go back to maximized/minimized.
WindowState state = WindowState;
WindowState = WindowState.Normal;
WindowStyle style = WindowStyle.ClipChildren | WindowStyle.ClipSiblings;
switch (value)
{
case WindowBorder.Resizable:
style |= WindowStyle.OverlappedWindow;
break;
case WindowBorder.Fixed:
style |= WindowStyle.OverlappedWindow &
~(WindowStyle.ThickFrame | WindowStyle.MaximizeBox | WindowStyle.SizeBox);
break;
case WindowBorder.Hidden:
style |= WindowStyle.Popup;
break;
}
// Make sure client size doesn't change when changing the border style.
Size client_size = ClientSize;
Win32Rectangle rect = Win32Rectangle.From(client_size);
Functions.AdjustWindowRectEx(ref rect, style, false, ParentStyleEx);
// This avoids leaving garbage on the background window.
Visible = false;
Functions.SetWindowLong(window.WindowHandle, GetWindowLongOffsets.STYLE, (IntPtr)(int)style);
Functions.SetWindowPos(window.WindowHandle, IntPtr.Zero, 0, 0, rect.Width, rect.Height,
SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOZORDER |
SetWindowPosFlags.FRAMECHANGED);
Visible = true;
WindowState = state;
if (WindowBorderChanged != null)
WindowBorderChanged(this, EventArgs.Empty);
}
}
#endregion
#region PointToClient
public Point PointToClient(Point point)
{
if (!Functions.ScreenToClient(window.WindowHandle, ref point))
throw new InvalidOperationException(String.Format(
"Could not convert point {0} from client to screen coordinates. Windows error: {1}",
point.ToString(), Marshal.GetLastWin32Error()));
return point;
}
#endregion
#region PointToScreen
public Point PointToScreen(Point p)
{
throw new NotImplementedException();
}
#endregion
#region Events
public event EventHandler<EventArgs> Idle;
public event EventHandler<EventArgs> Load;
public event EventHandler<EventArgs> Unload;
public event EventHandler<EventArgs> Move;
public event EventHandler<EventArgs> Resize;
public event EventHandler<System.ComponentModel.CancelEventArgs> Closing;
public event EventHandler<EventArgs> Closed;
public event EventHandler<EventArgs> Disposed;
public event EventHandler<EventArgs> IconChanged;
public event EventHandler<EventArgs> TitleChanged;
public event EventHandler<EventArgs> ClientSizeChanged;
public event EventHandler<EventArgs> VisibleChanged;
public event EventHandler<EventArgs> WindowInfoChanged;
public event EventHandler<EventArgs> FocusedChanged;
public event EventHandler<EventArgs> WindowBorderChanged;
public event EventHandler<EventArgs> WindowStateChanged;
public event EventHandler<KeyPressEventArgs> KeyPress;
public event EventHandler<EventArgs> MouseEnter;
public event EventHandler<EventArgs> MouseLeave;
#endregion
#endregion
#region INativeGLWindow Members
#region public void ProcessEvents()
private int ret;
MSG msg;
public void ProcessEvents()
{
while (!IsIdle)
{
ret = Functions.GetMessage(ref msg, window.WindowHandle, 0, 0);
if (ret == -1)
{
throw new PlatformException(String.Format(
"An error happened while processing the message queue. Windows error: {0}",
Marshal.GetLastWin32Error()));
}
Functions.TranslateMessage(ref msg);
Functions.DispatchMessage(ref msg);
}
}
#endregion
#region public IInputDriver InputDriver
public IInputDriver InputDriver
{
get { return this; }
}
#endregion
#region public IWindowInfo WindowInfo
public IWindowInfo WindowInfo
{
get { return child_window; }
}
#endregion
#endregion
#region IInputDriver Members
public void Poll()
{
joystick_driver.Poll();
}
#endregion
#region IKeyboardDriver Members
public IList<KeyboardDevice> Keyboard
{
get { return keyboards; }
}
#endregion
#region IMouseDriver Members
public IList<MouseDevice> Mouse
{
get { return mice; }
}
#endregion
#region IJoystickDriver Members
public IList<JoystickDevice> Joysticks
{
get { return joystick_driver.Joysticks; }
}
#endregion
#region IDisposable Members
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
void Dispose(bool calledManually)
{
if (!disposed)
{
if (calledManually)
{
// Safe to clean managed resources
DestroyWindow();
if (Icon != null)
Icon.Dispose();
}
else
{
Debug.Print("[Warning] INativeWindow leaked ({0}). Did you forget to call INativeWindow.Dispose()?", this);
}
disposed = true;
}
}
~WinGLNative()
{
Dispose(false);
}
#endregion
}
}