mirror of
https://github.com/Ryujinx/Opentk.git
synced 2024-12-29 06:35:41 +00:00
a85cecdc59
Added IDisposable interface to all classes holding native data that must be freed. OpenTK.Toolkit.Init() now returns an IDisposable instance that can be used to cleanup all native data held by OpenTK. This is useful when re-initializing OpenTK (possibly in a new AppDomain), as is the case in the Example browser.
1106 lines
36 KiB
C#
1106 lines
36 KiB
C#
#region License
|
|
//
|
|
// The Open Toolkit Library License
|
|
//
|
|
// Copyright (c) 2006 - 2010 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
|
|
|
|
// Created by Erik Ylvisaker on 3/17/08.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
#if !MINIMAL
|
|
using System.Drawing;
|
|
#endif
|
|
using System.Text;
|
|
|
|
namespace OpenTK.Platform.MacOS
|
|
{
|
|
using Carbon;
|
|
using Graphics;
|
|
|
|
class CarbonGLNative : INativeWindow
|
|
{
|
|
#region Fields
|
|
|
|
CarbonWindowInfo window;
|
|
CarbonInput mInputDriver;
|
|
|
|
static MacOSKeyMap Keymap = new MacOSKeyMap();
|
|
|
|
IntPtr uppHandler;
|
|
|
|
string title = "OpenTK Window";
|
|
Rectangle bounds, clientRectangle;
|
|
Rectangle windowedBounds;
|
|
bool mIsDisposed = false;
|
|
bool mExists = true;
|
|
DisplayDevice mDisplayDevice;
|
|
|
|
WindowAttributes mWindowAttrib;
|
|
WindowClass mWindowClass;
|
|
WindowPositionMethod mPositionMethod = WindowPositionMethod.CenterOnMainScreen;
|
|
int mTitlebarHeight;
|
|
private WindowBorder windowBorder = WindowBorder.Resizable;
|
|
private WindowState windowState = WindowState.Normal;
|
|
|
|
static Dictionary<IntPtr, WeakReference> mWindows =
|
|
new Dictionary<IntPtr, WeakReference>(new IntPtrEqualityComparer());
|
|
|
|
KeyPressEventArgs mKeyPressArgs = new KeyPressEventArgs((char)0);
|
|
|
|
bool mMouseIn = false;
|
|
bool mIsActive = false;
|
|
|
|
Icon mIcon;
|
|
|
|
// Used to accumulate mouse motion when the cursor is hidden.
|
|
float mouse_rel_x;
|
|
float mouse_rel_y;
|
|
|
|
#endregion
|
|
|
|
#region AGL Device Hack
|
|
|
|
static internal Dictionary<IntPtr, WeakReference> WindowRefMap
|
|
{
|
|
get { return mWindows; }
|
|
}
|
|
internal DisplayDevice TargetDisplayDevice
|
|
{
|
|
get { return mDisplayDevice; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
static CarbonGLNative()
|
|
{
|
|
Application.Initialize();
|
|
}
|
|
|
|
CarbonGLNative() : this(WindowClass.Document,
|
|
WindowAttributes.StandardDocument | WindowAttributes.StandardHandler |
|
|
WindowAttributes.InWindowMenu | WindowAttributes.LiveResize)
|
|
{
|
|
}
|
|
|
|
CarbonGLNative(WindowClass @class, WindowAttributes attrib)
|
|
{
|
|
mWindowClass = @class;
|
|
mWindowAttrib = attrib;
|
|
}
|
|
|
|
public CarbonGLNative(int x, int y, int width, int height, string title,
|
|
GraphicsMode mode, GameWindowFlags options, DisplayDevice device)
|
|
{
|
|
CreateNativeWindow(WindowClass.Document,
|
|
WindowAttributes.StandardDocument | WindowAttributes.StandardHandler |
|
|
WindowAttributes.InWindowMenu | WindowAttributes.LiveResize,
|
|
new Rect((short)x, (short)y, (short)width, (short)height));
|
|
|
|
mDisplayDevice = device;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (mIsDisposed)
|
|
return;
|
|
|
|
Debug.Print("Disposing of CarbonGLNative window.");
|
|
|
|
CursorVisible = true;
|
|
API.DisposeWindow(window.Handle);
|
|
mIsDisposed = true;
|
|
mExists = false;
|
|
|
|
CG.SetLocalEventsSuppressionInterval(0.25);
|
|
|
|
if (disposing)
|
|
{
|
|
mWindows.Remove(window.Handle);
|
|
|
|
window.Dispose();
|
|
window = null;
|
|
}
|
|
|
|
DisposeUPP();
|
|
|
|
Disposed(this, EventArgs.Empty);
|
|
}
|
|
|
|
~CarbonGLNative()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Members
|
|
|
|
void DisposeUPP()
|
|
{
|
|
if (uppHandler != IntPtr.Zero)
|
|
{
|
|
API.RemoveEventHandler(uppHandler);
|
|
API.DisposeEventHandlerUPP(uppHandler);
|
|
}
|
|
|
|
uppHandler = IntPtr.Zero;
|
|
}
|
|
|
|
void CreateNativeWindow(WindowClass @class, WindowAttributes attrib, Rect r)
|
|
{
|
|
Debug.Print("Creating window...");
|
|
Debug.Indent();
|
|
|
|
IntPtr windowRef = API.CreateNewWindow(@class, attrib, r);
|
|
API.SetWindowTitle(windowRef, title);
|
|
|
|
window = new CarbonWindowInfo(windowRef, true, false);
|
|
|
|
SetLocation(r.X, r.Y);
|
|
SetSize(r.Width, r.Height);
|
|
|
|
Debug.Unindent();
|
|
Debug.Print("Created window.");
|
|
|
|
mWindows.Add(windowRef, new WeakReference(this));
|
|
|
|
LoadSize();
|
|
|
|
Rect titleSize = API.GetWindowBounds(window.Handle, WindowRegionCode.TitleBarRegion);
|
|
mTitlebarHeight = titleSize.Height;
|
|
|
|
Debug.Print("Titlebar size: {0}", titleSize);
|
|
|
|
ConnectEvents();
|
|
|
|
System.Diagnostics.Debug.WriteLine("Attached window events.");
|
|
}
|
|
|
|
void ConnectEvents()
|
|
{
|
|
mInputDriver = new CarbonInput();
|
|
|
|
EventTypeSpec[] eventTypes = new EventTypeSpec[]
|
|
{
|
|
new EventTypeSpec(EventClass.Window, WindowEventKind.WindowClose),
|
|
new EventTypeSpec(EventClass.Window, WindowEventKind.WindowClosed),
|
|
new EventTypeSpec(EventClass.Window, WindowEventKind.WindowBoundsChanged),
|
|
new EventTypeSpec(EventClass.Window, WindowEventKind.WindowActivate),
|
|
new EventTypeSpec(EventClass.Window, WindowEventKind.WindowDeactivate),
|
|
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseDown),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseUp),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseMoved),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseDragged),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseEntered),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.MouseExited),
|
|
//new EventTypeSpec(EventClass.Mouse, MouseEventKind.WheelMoved),
|
|
|
|
//new EventTypeSpec(EventClass.Keyboard, KeyboardEventKind.RawKeyDown),
|
|
//new EventTypeSpec(EventClass.Keyboard, KeyboardEventKind.RawKeyRepeat),
|
|
//new EventTypeSpec(EventClass.Keyboard, KeyboardEventKind.RawKeyUp),
|
|
//new EventTypeSpec(EventClass.Keyboard, KeyboardEventKind.RawKeyModifiersChanged),
|
|
};
|
|
|
|
MacOSEventHandler handler = EventHandler;
|
|
uppHandler = API.NewEventHandlerUPP(handler);
|
|
|
|
API.InstallWindowEventHandler(window.Handle, uppHandler, eventTypes,
|
|
window.Handle, IntPtr.Zero);
|
|
|
|
Application.WindowEventHandler = this;
|
|
}
|
|
|
|
void Activate()
|
|
{
|
|
API.SelectWindow(window.Handle);
|
|
}
|
|
|
|
void Show()
|
|
{
|
|
IntPtr parent = IntPtr.Zero;
|
|
|
|
API.ShowWindow(window.Handle);
|
|
API.RepositionWindow(window.Handle, parent, WindowPositionMethod);
|
|
API.SelectWindow(window.Handle);
|
|
}
|
|
|
|
void Hide()
|
|
{
|
|
API.HideWindow(window.Handle);
|
|
}
|
|
|
|
internal void SetFullscreen(AglContext context)
|
|
{
|
|
windowedBounds = bounds;
|
|
|
|
int width, height;
|
|
|
|
context.SetFullScreen(window, out width, out height);
|
|
|
|
Debug.Print("Prev Size: {0}, {1}", Width, Height);
|
|
clientRectangle.Size = new Size(width, height);
|
|
Debug.Print("New Size: {0}, {1}", Width, Height);
|
|
|
|
// TODO: if we go full screen we need to make this use the device specified.
|
|
bounds = mDisplayDevice.Bounds;
|
|
|
|
|
|
windowState = WindowState.Fullscreen;
|
|
}
|
|
|
|
internal void UnsetFullscreen(AglContext context)
|
|
{
|
|
context.UnsetFullScreen(window);
|
|
|
|
Debug.Print("Telling Carbon to reset window state to " + windowState.ToString());
|
|
SetCarbonWindowState();
|
|
|
|
SetSize((short)windowedBounds.Width, (short)windowedBounds.Height);
|
|
}
|
|
|
|
bool IsDisposed
|
|
{
|
|
get { return mIsDisposed; }
|
|
}
|
|
|
|
WindowPositionMethod WindowPositionMethod
|
|
{
|
|
get { return mPositionMethod; }
|
|
set { mPositionMethod = value; }
|
|
}
|
|
|
|
internal OSStatus DispatchEvent(IntPtr inCaller, IntPtr inEvent, EventInfo evt, IntPtr userData)
|
|
{
|
|
switch (evt.EventClass)
|
|
{
|
|
case EventClass.Window:
|
|
return ProcessWindowEvent(inCaller, inEvent, evt, userData);
|
|
|
|
case EventClass.Mouse:
|
|
return ProcessMouseEvent(inCaller, inEvent, evt, userData);
|
|
|
|
case EventClass.Keyboard:
|
|
return ProcessKeyboardEvent(inCaller, inEvent, evt, userData);
|
|
|
|
default:
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
}
|
|
|
|
protected static OSStatus EventHandler(IntPtr inCaller, IntPtr inEvent, IntPtr userData)
|
|
{
|
|
if (mWindows.ContainsKey(userData) == false)
|
|
{
|
|
// Bail out if the window passed in is not actually our window.
|
|
// I think this happens if using winforms with a GameWindow sometimes.
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
|
|
WeakReference reference = mWindows[userData];
|
|
if (reference.IsAlive == false)
|
|
{
|
|
// Bail out if the CarbonGLNative window has been garbage collected.
|
|
mWindows.Remove(userData);
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
|
|
CarbonGLNative window = (CarbonGLNative)reference.Target;
|
|
if (window == null)
|
|
{
|
|
Debug.WriteLine("Window for event not found.");
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
|
|
EventInfo evt = new EventInfo(inEvent);
|
|
return window.DispatchEvent(inCaller, inEvent, evt, userData);
|
|
}
|
|
|
|
private OSStatus ProcessKeyboardEvent(IntPtr inCaller, IntPtr inEvent, EventInfo evt, IntPtr userData)
|
|
{
|
|
System.Diagnostics.Debug.Assert(evt.EventClass == EventClass.Keyboard);
|
|
MacOSKeyCode code = (MacOSKeyCode)0;
|
|
char charCode = '\0';
|
|
|
|
switch (evt.KeyboardEventKind)
|
|
{
|
|
case KeyboardEventKind.RawKeyDown:
|
|
case KeyboardEventKind.RawKeyRepeat:
|
|
case KeyboardEventKind.RawKeyUp:
|
|
GetCharCodes(inEvent, out code, out charCode);
|
|
mKeyPressArgs.KeyChar = charCode;
|
|
break;
|
|
}
|
|
|
|
OpenTK.Input.Key key;
|
|
switch (evt.KeyboardEventKind)
|
|
{
|
|
case KeyboardEventKind.RawKeyRepeat:
|
|
if (InputDriver.Keyboard[0].KeyRepeat)
|
|
goto case KeyboardEventKind.RawKeyDown;
|
|
break;
|
|
|
|
case KeyboardEventKind.RawKeyDown:
|
|
Keymap.TryGetValue(code, out key);
|
|
InputDriver.Keyboard[0].SetKey(key, (uint)code, true);
|
|
OnKeyPress(mKeyPressArgs);
|
|
return OSStatus.NoError;
|
|
|
|
case KeyboardEventKind.RawKeyUp:
|
|
Keymap.TryGetValue(code, out key);
|
|
InputDriver.Keyboard[0].SetKey(key, (uint)code, false);
|
|
return OSStatus.NoError;
|
|
|
|
case KeyboardEventKind.RawKeyModifiersChanged:
|
|
ProcessModifierKey(inEvent);
|
|
return OSStatus.NoError;
|
|
}
|
|
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
|
|
private OSStatus ProcessWindowEvent(IntPtr inCaller, IntPtr inEvent, EventInfo evt, IntPtr userData)
|
|
{
|
|
System.Diagnostics.Debug.Assert(evt.EventClass == EventClass.Window);
|
|
|
|
switch (evt.WindowEventKind)
|
|
{
|
|
case WindowEventKind.WindowClose:
|
|
CancelEventArgs cancel = new CancelEventArgs();
|
|
OnClosing(cancel);
|
|
|
|
if (cancel.Cancel)
|
|
return OSStatus.NoError;
|
|
else
|
|
return OSStatus.EventNotHandled;
|
|
|
|
case WindowEventKind.WindowClosed:
|
|
mExists = false;
|
|
OnClosed();
|
|
|
|
return OSStatus.NoError;
|
|
|
|
case WindowEventKind.WindowBoundsChanged:
|
|
int thisWidth = Width;
|
|
int thisHeight = Height;
|
|
int thisX = X;
|
|
int thisY = Y;
|
|
|
|
LoadSize();
|
|
|
|
if (thisX != X || thisY != Y)
|
|
Move(this, EventArgs.Empty);
|
|
|
|
if (thisWidth != Width || thisHeight != Height)
|
|
Resize(this, EventArgs.Empty);
|
|
|
|
return OSStatus.EventNotHandled;
|
|
|
|
case WindowEventKind.WindowActivate:
|
|
OnActivate();
|
|
return OSStatus.EventNotHandled;
|
|
|
|
case WindowEventKind.WindowDeactivate:
|
|
OnDeactivate();
|
|
return OSStatus.EventNotHandled;
|
|
default:
|
|
|
|
Debug.Print("{0}", evt);
|
|
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
}
|
|
|
|
protected OSStatus ProcessMouseEvent(IntPtr inCaller, IntPtr inEvent, EventInfo evt, IntPtr userData)
|
|
{
|
|
System.Diagnostics.Debug.Assert(evt.EventClass == EventClass.Mouse);
|
|
MouseButton button = MouseButton.Primary;
|
|
HIPoint pt = new HIPoint();
|
|
HIPoint screenLoc = new HIPoint();
|
|
|
|
IntPtr thisEventWindow;
|
|
API.GetEventWindowRef(inEvent, out thisEventWindow);
|
|
|
|
OSStatus err = API.GetEventMouseLocation(inEvent, out screenLoc);
|
|
if (this.windowState == WindowState.Fullscreen)
|
|
{
|
|
pt = screenLoc;
|
|
}
|
|
else if (CursorVisible)
|
|
{
|
|
err = API.GetEventWindowMouseLocation(inEvent, out pt);
|
|
pt.Y -= mTitlebarHeight;
|
|
}
|
|
else
|
|
{
|
|
err = API.GetEventMouseDelta(inEvent, out pt);
|
|
pt.X += mouse_rel_x;
|
|
pt.Y += mouse_rel_y;
|
|
pt = ConfineMouseToWindow(thisEventWindow, pt);
|
|
ResetMouseToWindowCenter();
|
|
mouse_rel_x = pt.X;
|
|
mouse_rel_y = pt.Y;
|
|
}
|
|
|
|
if (err != OSStatus.NoError && err != OSStatus.EventParameterNotFound)
|
|
{
|
|
// this error comes up from the application event handler.
|
|
throw new MacOSException(err);
|
|
}
|
|
|
|
Point mousePosInClient = new Point((int)pt.X, (int)pt.Y);
|
|
CheckEnterLeaveEvents(thisEventWindow, mousePosInClient);
|
|
|
|
switch (evt.MouseEventKind)
|
|
{
|
|
case MouseEventKind.MouseDown:
|
|
case MouseEventKind.MouseUp:
|
|
button = API.GetEventMouseButton(inEvent);
|
|
bool pressed = evt.MouseEventKind == MouseEventKind.MouseDown;
|
|
|
|
switch (button)
|
|
{
|
|
case MouseButton.Primary:
|
|
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Left] = pressed;
|
|
break;
|
|
|
|
case MouseButton.Secondary:
|
|
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Right] = pressed;
|
|
break;
|
|
|
|
case MouseButton.Tertiary:
|
|
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Middle] = pressed;
|
|
break;
|
|
}
|
|
return OSStatus.NoError;
|
|
|
|
case MouseEventKind.WheelMoved: // older, integer resolution only
|
|
{
|
|
// this is really an int, we use a float to avoid clipping the wheel value
|
|
float delta = API.GetEventMouseWheelDelta (inEvent);
|
|
InputDriver.Mouse[0].WheelPrecise += delta;
|
|
}
|
|
return OSStatus.NoError;
|
|
|
|
case MouseEventKind.WheelScroll: // newer, more precise X and Y scroll
|
|
{
|
|
API.ScrollDelta delta = API.GetEventWheelScroll(inEvent);
|
|
InputDriver.Mouse[0].WheelPrecise += delta.deltaY;
|
|
}
|
|
return OSStatus.NoError;
|
|
|
|
case MouseEventKind.MouseMoved:
|
|
case MouseEventKind.MouseDragged:
|
|
if (this.windowState == WindowState.Fullscreen)
|
|
{
|
|
if (mousePosInClient.X != InputDriver.Mouse[0].X || mousePosInClient.Y != InputDriver.Mouse[0].Y)
|
|
{
|
|
InputDriver.Mouse[0].Position = mousePosInClient;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ignore clicks in the title bar
|
|
if (pt.Y < 0)
|
|
return OSStatus.EventNotHandled;
|
|
|
|
if (mousePosInClient.X != InputDriver.Mouse[0].X || mousePosInClient.Y != InputDriver.Mouse[0].Y)
|
|
{
|
|
InputDriver.Mouse[0].Position = mousePosInClient;
|
|
}
|
|
}
|
|
return OSStatus.EventNotHandled;
|
|
|
|
default:
|
|
Debug.Print("{0}", evt);
|
|
return OSStatus.EventNotHandled;
|
|
}
|
|
}
|
|
|
|
void ResetMouseToWindowCenter()
|
|
{
|
|
OpenTK.Input.Mouse.SetPosition(
|
|
(Bounds.Left + Bounds.Right) / 2,
|
|
(Bounds.Top + Bounds.Bottom) / 2);
|
|
}
|
|
|
|
private void CheckEnterLeaveEvents(IntPtr eventWindowRef, Point pt)
|
|
{
|
|
if (window == null)
|
|
return;
|
|
|
|
bool thisIn = eventWindowRef == window.Handle;
|
|
|
|
if (pt.Y < 0)
|
|
thisIn = false;
|
|
|
|
if (thisIn != mMouseIn)
|
|
{
|
|
mMouseIn = thisIn;
|
|
|
|
if (mMouseIn)
|
|
OnMouseEnter();
|
|
else
|
|
OnMouseLeave();
|
|
}
|
|
}
|
|
|
|
// Point in client (window) coordinates
|
|
private HIPoint ConfineMouseToWindow(IntPtr window, HIPoint client)
|
|
{
|
|
if (client.X < 0)
|
|
client.X = 0;
|
|
if (client.X >= Width)
|
|
client.X = Width - 1;
|
|
if (client.Y < 0)
|
|
client.Y = 0;
|
|
if (client.Y >= Height)
|
|
client.Y = Height - 1;
|
|
|
|
return client;
|
|
}
|
|
|
|
private static void GetCharCodes(IntPtr inEvent, out MacOSKeyCode code, out char charCode)
|
|
{
|
|
code = API.GetEventKeyboardKeyCode(inEvent);
|
|
charCode = API.GetEventKeyboardChar(inEvent);
|
|
}
|
|
|
|
private void ProcessModifierKey(IntPtr inEvent)
|
|
{
|
|
MacOSKeyModifiers modifiers = API.GetEventKeyModifiers(inEvent);
|
|
|
|
bool caps = (modifiers & MacOSKeyModifiers.CapsLock) != 0 ? true : false;
|
|
bool control = (modifiers & MacOSKeyModifiers.Control) != 0 ? true : false;
|
|
bool command = (modifiers & MacOSKeyModifiers.Command) != 0 ? true : false;
|
|
bool option = (modifiers & MacOSKeyModifiers.Option) != 0 ? true : false;
|
|
bool shift = (modifiers & MacOSKeyModifiers.Shift) != 0 ? true : false;
|
|
|
|
Debug.Print("Modifiers Changed: {0}", modifiers);
|
|
|
|
Input.KeyboardDevice keyboard = InputDriver.Keyboard[0];
|
|
|
|
if (keyboard[OpenTK.Input.Key.AltLeft] ^ option)
|
|
keyboard.SetKey(OpenTK.Input.Key.AltLeft, (uint)MacOSKeyCode.OptionAlt, option);
|
|
|
|
if (keyboard[OpenTK.Input.Key.ShiftLeft] ^ shift)
|
|
keyboard.SetKey(OpenTK.Input.Key.ShiftLeft, (uint)MacOSKeyCode.Shift, shift);
|
|
|
|
if (keyboard[OpenTK.Input.Key.WinLeft] ^ command)
|
|
keyboard.SetKey(OpenTK.Input.Key.WinLeft, (uint)MacOSKeyCode.Command, command);
|
|
|
|
if (keyboard[OpenTK.Input.Key.ControlLeft] ^ control)
|
|
keyboard.SetKey(OpenTK.Input.Key.ControlLeft, (uint)MacOSKeyCode.Control, control);
|
|
|
|
if (keyboard[OpenTK.Input.Key.CapsLock] ^ caps)
|
|
keyboard.SetKey(OpenTK.Input.Key.CapsLock, (uint)MacOSKeyCode.CapsLock, caps);
|
|
|
|
}
|
|
|
|
Rect GetRegion()
|
|
{
|
|
Rect retval = API.GetWindowBounds(window.Handle, WindowRegionCode.ContentRegion);
|
|
|
|
return retval;
|
|
}
|
|
|
|
void SetLocation(short x, short y)
|
|
{
|
|
if (windowState == WindowState.Fullscreen)
|
|
return;
|
|
|
|
API.MoveWindow(window.Handle, x, y, false);
|
|
}
|
|
|
|
void SetSize(short width, short height)
|
|
{
|
|
if (WindowState == WindowState.Fullscreen)
|
|
return;
|
|
|
|
// The bounds of the window should be the size specified, but
|
|
// API.SizeWindow sets the content region size. So
|
|
// we reduce the size to get the correct bounds.
|
|
width -= (short)(bounds.Width - clientRectangle.Width);
|
|
height -= (short)(bounds.Height - clientRectangle.Height);
|
|
|
|
API.SizeWindow(window.Handle, width, height, true);
|
|
}
|
|
|
|
void SetClientSize(short width, short height)
|
|
{
|
|
if (WindowState == WindowState.Fullscreen)
|
|
return;
|
|
|
|
API.SizeWindow(window.Handle, width, height, true);
|
|
}
|
|
|
|
private void LoadSize()
|
|
{
|
|
if (WindowState == WindowState.Fullscreen)
|
|
return;
|
|
|
|
Rect r = API.GetWindowBounds(window.Handle, WindowRegionCode.StructureRegion);
|
|
bounds = new Rectangle(r.X, r.Y, r.Width, r.Height);
|
|
|
|
r = API.GetWindowBounds(window.Handle, WindowRegionCode.GlobalPortRegion);
|
|
clientRectangle = new Rectangle(0, 0, r.Width, r.Height);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region INativeWindow Members
|
|
|
|
public void ProcessEvents()
|
|
{
|
|
Application.ProcessEvents();
|
|
}
|
|
|
|
public Point PointToClient(Point point)
|
|
{
|
|
Rect r = Carbon.API.GetWindowBounds(window.Handle, WindowRegionCode.ContentRegion);
|
|
return new Point(point.X - r.X, point.Y - r.Y);
|
|
}
|
|
|
|
public Point PointToScreen(Point point)
|
|
{
|
|
Rect r = Carbon.API.GetWindowBounds(window.Handle, WindowRegionCode.ContentRegion);
|
|
return new Point(point.X + r.X, point.Y + r.Y);
|
|
}
|
|
|
|
public bool Exists
|
|
{
|
|
get { return mExists; }
|
|
}
|
|
|
|
public IWindowInfo WindowInfo
|
|
{
|
|
get { return window; }
|
|
}
|
|
|
|
public bool IsIdle
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public OpenTK.Input.IInputDriver InputDriver
|
|
{
|
|
get { return mInputDriver; }
|
|
}
|
|
|
|
public Icon Icon
|
|
{
|
|
get { return mIcon; }
|
|
set
|
|
{
|
|
if (value != Icon)
|
|
{
|
|
SetIcon(value);
|
|
mIcon = value;
|
|
IconChanged(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetIcon(Icon icon)
|
|
{
|
|
// The code for this function was adapted from Mono's
|
|
// XplatUICarbon implementation, written by Geoff Norton
|
|
// http://anonsvn.mono-project.com/viewvc/trunk/mcs/class/Managed.Windows.Forms/System.Windows.Forms/XplatUICarbon.cs?view=markup&pathrev=136932
|
|
if (icon == null)
|
|
{
|
|
API.RestoreApplicationDockTileImage();
|
|
}
|
|
|
|
else
|
|
{
|
|
Bitmap bitmap;
|
|
int size;
|
|
IntPtr[] data;
|
|
int index;
|
|
|
|
bitmap = new Bitmap(128, 128);
|
|
#if MINIMAL
|
|
using (OpenTK.Minimal.Graphics g = OpenTK.Minimal.Graphics.FromImage(bitmap))
|
|
#else
|
|
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap))
|
|
#endif
|
|
{
|
|
g.DrawImage(icon.ToBitmap(), 0, 0, 128, 128);
|
|
}
|
|
index = 0;
|
|
size = bitmap.Width * bitmap.Height;
|
|
data = new IntPtr[size];
|
|
|
|
for (int y = 0; y < bitmap.Height; y++)
|
|
{
|
|
for (int x = 0; x < bitmap.Width; x++)
|
|
{
|
|
int pixel = bitmap.GetPixel(x, y).ToArgb();
|
|
if (BitConverter.IsLittleEndian)
|
|
{
|
|
byte a = (byte)((pixel >> 24) & 0xFF);
|
|
byte r = (byte)((pixel >> 16) & 0xFF);
|
|
byte g = (byte)((pixel >> 8) & 0xFF);
|
|
byte b = (byte)(pixel & 0xFF);
|
|
data[index++] = (IntPtr)(a + (r << 8) + (g << 16) + (b << 24));
|
|
}
|
|
|
|
else
|
|
{
|
|
data[index++] = (IntPtr)pixel;
|
|
}
|
|
}
|
|
}
|
|
|
|
IntPtr provider = API.CGDataProviderCreateWithData(IntPtr.Zero, data, size * 4, IntPtr.Zero);
|
|
IntPtr image = API.CGImageCreate(128, 128, 8, 32, 4 * 128, API.CGColorSpaceCreateDeviceRGB(), 4, provider, IntPtr.Zero, 0,
|
|
0);
|
|
API.SetApplicationDockTileImage(image);
|
|
}
|
|
}
|
|
|
|
public string Title
|
|
{
|
|
get { return title; }
|
|
set
|
|
{
|
|
if (value != Title)
|
|
{
|
|
API.SetWindowTitle(window.Handle, value);
|
|
title = value;
|
|
TitleChanged(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Visible
|
|
{
|
|
get { return API.IsWindowVisible(window.Handle); }
|
|
set
|
|
{
|
|
if (value != Visible)
|
|
{
|
|
if (value)
|
|
Show();
|
|
else
|
|
Hide();
|
|
|
|
VisibleChanged(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Focused
|
|
{
|
|
get { return this.mIsActive; }
|
|
}
|
|
|
|
public Rectangle Bounds
|
|
{
|
|
|
|
get { return bounds; }
|
|
set
|
|
{
|
|
Location = value.Location;
|
|
Size = value.Size;
|
|
}
|
|
}
|
|
|
|
public Point Location
|
|
{
|
|
get { return Bounds.Location; }
|
|
set { SetLocation((short)value.X, (short)value.Y); }
|
|
}
|
|
|
|
public Size Size
|
|
{
|
|
get { return bounds.Size; }
|
|
set { SetSize((short)value.Width, (short)value.Height); }
|
|
}
|
|
|
|
public int Width
|
|
{
|
|
get { return ClientRectangle.Width; }
|
|
set { SetClientSize((short)value, (short)Height); }
|
|
}
|
|
|
|
public int Height
|
|
{
|
|
get { return ClientRectangle.Height; }
|
|
set { SetClientSize((short)Width, (short)value); }
|
|
}
|
|
|
|
public int X
|
|
{
|
|
get { return ClientRectangle.X; }
|
|
set { Location = new Point(value, Y); }
|
|
}
|
|
|
|
public int Y
|
|
{
|
|
get { return ClientRectangle.Y; }
|
|
set { Location = new Point(X, value); }
|
|
}
|
|
|
|
public Rectangle ClientRectangle
|
|
{
|
|
get { return clientRectangle; }
|
|
// just set the size, and ignore the location value.
|
|
// this is the behavior of the Windows WinGLNative.
|
|
set { ClientSize = value.Size; }
|
|
}
|
|
|
|
public Size ClientSize
|
|
{
|
|
get { return clientRectangle.Size; }
|
|
set
|
|
{
|
|
API.SizeWindow(window.Handle, (short)value.Width, (short)value.Height, true);
|
|
LoadSize();
|
|
Resize(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
public bool CursorVisible
|
|
{
|
|
get { return CG.CursorIsVisible(); }
|
|
set
|
|
{
|
|
if (value)
|
|
{
|
|
CG.DisplayShowCursor(IntPtr.Zero);
|
|
CG.AssociateMouseAndMouseCursorPosition(true);
|
|
}
|
|
else
|
|
{
|
|
CG.DisplayHideCursor(IntPtr.Zero);
|
|
ResetMouseToWindowCenter();
|
|
CG.AssociateMouseAndMouseCursorPosition(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
CancelEventArgs e = new CancelEventArgs();
|
|
OnClosing(e);
|
|
|
|
if (e.Cancel)
|
|
return;
|
|
|
|
OnClosed();
|
|
}
|
|
|
|
public WindowState WindowState
|
|
{
|
|
get
|
|
{
|
|
if (windowState == WindowState.Fullscreen)
|
|
return WindowState.Fullscreen;
|
|
|
|
if (Carbon.API.IsWindowCollapsed(window.Handle))
|
|
return WindowState.Minimized;
|
|
|
|
if (Carbon.API.IsWindowInStandardState(window.Handle))
|
|
{
|
|
return WindowState.Maximized;
|
|
}
|
|
|
|
return WindowState.Normal;
|
|
}
|
|
set
|
|
{
|
|
if (value == WindowState)
|
|
return;
|
|
|
|
Debug.Print("Switching window state from {0} to {1}", WindowState, value);
|
|
WindowState oldState = WindowState;
|
|
|
|
windowState = value;
|
|
|
|
if (oldState == WindowState.Fullscreen)
|
|
{
|
|
window.GoWindowedHack = true;
|
|
|
|
// when returning from full screen, wait until the context is updated
|
|
// to actually do the work.
|
|
return;
|
|
}
|
|
|
|
if (oldState == WindowState.Minimized)
|
|
{
|
|
API.CollapseWindow(window.Handle, false);
|
|
}
|
|
|
|
SetCarbonWindowState();
|
|
}
|
|
}
|
|
|
|
private void SetCarbonWindowState()
|
|
{
|
|
CarbonPoint idealSize;
|
|
|
|
switch (windowState)
|
|
{
|
|
case WindowState.Fullscreen:
|
|
window.GoFullScreenHack = true;
|
|
|
|
break;
|
|
|
|
case WindowState.Maximized:
|
|
// hack because mac os has no concept of maximized. Instead windows are "zoomed"
|
|
// meaning they are maximized up to their reported ideal size. So we report a
|
|
// large ideal size.
|
|
idealSize = new CarbonPoint(9000, 9000);
|
|
API.ZoomWindowIdeal(window.Handle, WindowPartCode.inZoomOut, ref idealSize);
|
|
break;
|
|
|
|
case WindowState.Normal:
|
|
if (WindowState == WindowState.Maximized)
|
|
{
|
|
idealSize = new CarbonPoint();
|
|
API.ZoomWindowIdeal(window.Handle, WindowPartCode.inZoomIn, ref idealSize);
|
|
}
|
|
|
|
break;
|
|
|
|
case WindowState.Minimized:
|
|
API.CollapseWindow(window.Handle, true);
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
WindowStateChanged(this, EventArgs.Empty);
|
|
LoadSize();
|
|
Resize(this, EventArgs.Empty);
|
|
}
|
|
|
|
public WindowBorder WindowBorder
|
|
{
|
|
get { return windowBorder; }
|
|
set
|
|
{
|
|
if (windowBorder == value)
|
|
return;
|
|
|
|
windowBorder = value;
|
|
|
|
if (windowBorder == WindowBorder.Resizable)
|
|
{
|
|
API.ChangeWindowAttributes(window.Handle, WindowAttributes.Resizable | WindowAttributes.FullZoom, WindowAttributes.NoAttributes);
|
|
}
|
|
|
|
else if (windowBorder == WindowBorder.Fixed)
|
|
{
|
|
API.ChangeWindowAttributes(window.Handle, WindowAttributes.NoAttributes, WindowAttributes.Resizable | WindowAttributes.FullZoom);
|
|
}
|
|
|
|
WindowBorderChanged(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
#region --- Event wrappers ---
|
|
|
|
private void OnKeyPress(KeyPressEventArgs keyPressArgs)
|
|
{
|
|
KeyPress(this, keyPressArgs);
|
|
}
|
|
|
|
|
|
private void OnWindowStateChanged()
|
|
{
|
|
WindowStateChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
protected virtual void OnClosing(CancelEventArgs e)
|
|
{
|
|
Closing(this, e);
|
|
}
|
|
|
|
protected virtual void OnClosed()
|
|
{
|
|
Closed(this, EventArgs.Empty);
|
|
}
|
|
|
|
|
|
private void OnMouseLeave()
|
|
{
|
|
MouseLeave(this, EventArgs.Empty);
|
|
}
|
|
|
|
private void OnMouseEnter()
|
|
{
|
|
MouseEnter(this, EventArgs.Empty);
|
|
}
|
|
|
|
private void OnActivate()
|
|
{
|
|
mIsActive = true;
|
|
FocusedChanged(this, EventArgs.Empty);
|
|
}
|
|
private void OnDeactivate()
|
|
{
|
|
mIsActive = false;
|
|
FocusedChanged(this, EventArgs.Empty);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public event EventHandler<EventArgs> Move = delegate { };
|
|
public event EventHandler<EventArgs> Resize = delegate { };
|
|
public event EventHandler<CancelEventArgs> Closing = delegate { };
|
|
public event EventHandler<EventArgs> Closed = delegate { };
|
|
public event EventHandler<EventArgs> Disposed = delegate { };
|
|
public event EventHandler<EventArgs> IconChanged = delegate { };
|
|
public event EventHandler<EventArgs> TitleChanged = delegate { };
|
|
public event EventHandler<EventArgs> VisibleChanged = delegate { };
|
|
public event EventHandler<EventArgs> FocusedChanged = delegate { };
|
|
public event EventHandler<EventArgs> WindowBorderChanged = delegate { };
|
|
public event EventHandler<EventArgs> WindowStateChanged = delegate { };
|
|
public event EventHandler<OpenTK.Input.KeyboardKeyEventArgs> KeyDown = delegate { };
|
|
public event EventHandler<KeyPressEventArgs> KeyPress = delegate { };
|
|
public event EventHandler<OpenTK.Input.KeyboardKeyEventArgs> KeyUp = delegate { };
|
|
public event EventHandler<EventArgs> MouseEnter = delegate { };
|
|
public event EventHandler<EventArgs> MouseLeave = delegate { };
|
|
|
|
#endregion
|
|
}
|
|
}
|