Opentk/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs
the_fiddler c8c652131e Added experimental Icon and IconConverter implementations from Mono.
Modified INativeWindow implementations to not qualify Icon class fully, so we can change the implementation between System.Drawing and OpenTK at will (using the EXPERIMENTAL #define).
2009-11-02 09:33:53 +00:00

960 lines
29 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.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Text;
namespace OpenTK.Platform.MacOS
{
using Carbon;
using Graphics;
class CarbonGLNative : INativeWindow
{
#region Fields
CarbonWindowInfo window;
CarbonInput mInputDriver;
GraphicsContext context;
static MacOSKeyMap Keymap = new MacOSKeyMap();
IntPtr uppHandler;
string title = "OpenTK Window";
Rectangle bounds, windowedBounds, clientRectangle;
bool mIsDisposed = false;
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>();
#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));
}
#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.");
mIsDisposed = true;
if (disposing)
{
mWindows.Remove(window.WindowRef);
window.Dispose();
window = null;
}
DisposeUPP();
}
~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.WindowRef, WindowRegionCode.TitleBarRegion);
mTitlebarHeight = titleSize.Height;
Debug.Print("Titlebar size: {0}", titleSize);
ConnectEvents();
System.Diagnostics.Debug.Print("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.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.WindowRef, uppHandler, eventTypes, window.WindowRef, IntPtr.Zero);
Application.WindowEventHandler = this;
}
void Activate()
{
API.SelectWindow(window.WindowRef);
}
void Show()
{
IntPtr parent = IntPtr.Zero;
API.ShowWindow(window.WindowRef);
API.RepositionWindow(window.WindowRef, parent, WindowPositionMethod);
API.SelectWindow(window.WindowRef);
}
void Hide()
{
API.HideWindow(window.WindowRef);
}
void SetFullscreen()
{
windowedBounds = bounds;
((AglContext)(context as IGraphicsContextInternal).Implementation).SetFullScreen(window);
Debug.Print("Prev Size: {0}, {1}", Width, Height);
bounds = DisplayDevice.Default.Bounds;
Debug.Print("New Size: {0}, {1}", Width, Height);
}
void UnsetFullscreen()
{
((AglContext)(context as IGraphicsContextInternal).Implementation).UnsetFullScreen(window);
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)
{
// bail out if the window passed in is not actually our window.
// I think this happens if using winforms with a GameWindow sometimes.
if (mWindows.ContainsKey(userData) == false)
return OSStatus.EventNotHandled;
WeakReference reference = mWindows[userData];
// bail out if the CarbonGLNative window has been garbage collected.
if (reference.IsAlive == false)
{
mWindows.Remove(userData);
return OSStatus.EventNotHandled;
}
EventInfo evt = new EventInfo(inEvent);
CarbonGLNative window = (CarbonGLNative)reference.Target;
//Debug.Print("Processing {0} event for {1}.", evt, window.window);
if (window == null)
{
Debug.WriteLine("Window for event not found.");
return OSStatus.EventNotHandled;
}
switch (evt.EventClass)
{
case EventClass.Window:
return window.ProcessWindowEvent(inCaller, inEvent, evt, userData);
case EventClass.Mouse:
return window.ProcessMouseEvent(inCaller, inEvent, evt, userData);
case EventClass.Keyboard:
return window.ProcessKeyboardEvent(inCaller, inEvent, evt, userData);
default:
return OSStatus.EventNotHandled;
}
}
private OSStatus ProcessKeyboardEvent(IntPtr inCaller, IntPtr inEvent, EventInfo evt, IntPtr userData)
{
System.Diagnostics.Debug.Assert(evt.EventClass == EventClass.Keyboard);
MacOSKeyCode code;
char charCode;
switch (evt.KeyboardEventKind)
{
case KeyboardEventKind.RawKeyRepeat:
GetCharCodes(inEvent, out code, out charCode);
InputDriver.Keyboard[0].KeyRepeat = true;
goto case KeyboardEventKind.RawKeyDown;
case KeyboardEventKind.RawKeyDown:
GetCharCodes(inEvent, out code, out charCode);
InputDriver.Keyboard[0][Keymap[code]] = true;
return OSStatus.EventNotHandled;
case KeyboardEventKind.RawKeyUp:
GetCharCodes(inEvent, out code, out charCode);
InputDriver.Keyboard[0][Keymap[code]] = false;
return OSStatus.EventNotHandled;
case KeyboardEventKind.RawKeyModifiersChanged:
ProcessModifierKey(inEvent);
return OSStatus.EventNotHandled;
default:
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:
OnClosed();
return OSStatus.NoError;
case WindowEventKind.WindowBoundsChanged:
int thisWidth = Width;
int thisHeight = Height;
LoadSize();
if (thisWidth != Width || thisHeight != Height)
OnResize();
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();
OSStatus err;
if (this.windowState == WindowState.Fullscreen)
{
err = API.GetEventMouseLocation(inEvent, out pt);
}
else
{
err = API.GetEventWindowMouseLocation(inEvent, out pt);
}
if (err != OSStatus.NoError)
{
// this error comes up from the application event handler.
if (err != OSStatus.EventParameterNotFound)
{
throw new MacOSException(err);
}
}
if (this.windowState == WindowState.Fullscreen)
{
InputDriver.Mouse[0].Position = new Point((int)pt.X, (int)pt.Y);
}
else
{
// ignore clicks in the title bar
if (pt.Y < mTitlebarHeight)
return OSStatus.EventNotHandled;
InputDriver.Mouse[0].Position =
new Point((int)pt.X, (int)(pt.Y - mTitlebarHeight));
}
switch (evt.MouseEventKind)
{
case MouseEventKind.MouseDown:
button = API.GetEventMouseButton(inEvent);
switch (button)
{
case MouseButton.Primary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Left] = true;
break;
case MouseButton.Secondary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Right] = true;
break;
case MouseButton.Tertiary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Middle] = true;
break;
}
break;
case MouseEventKind.MouseUp:
switch (button)
{
case MouseButton.Primary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Left] = false;
break;
case MouseButton.Secondary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Right] = false;
break;
case MouseButton.Tertiary:
InputDriver.Mouse[0][OpenTK.Input.MouseButton.Middle] = false;
break;
}
button = API.GetEventMouseButton(inEvent);
break;
case MouseEventKind.MouseMoved:
case MouseEventKind.MouseDragged:
//Debug.Print("MouseMoved: {0}", InputDriver.Mouse[0].Position);
return OSStatus.EventNotHandled;
default:
Debug.Print("{0}", evt);
return OSStatus.EventNotHandled;
}
return OSStatus.EventNotHandled;
}
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[OpenTK.Input.Key.AltLeft] = option;
if (keyboard[OpenTK.Input.Key.ShiftLeft] ^ shift)
keyboard[OpenTK.Input.Key.ShiftLeft] = shift;
if (keyboard[OpenTK.Input.Key.WinLeft] ^ command)
keyboard[OpenTK.Input.Key.WinLeft] = command;
if (keyboard[OpenTK.Input.Key.ControlLeft] ^ control)
keyboard[OpenTK.Input.Key.ControlLeft] = control;
if (keyboard[OpenTK.Input.Key.CapsLock] ^ caps)
keyboard[OpenTK.Input.Key.CapsLock] = caps;
}
Rect GetRegion()
{
Rect retval = API.GetWindowBounds(window.WindowRef, WindowRegionCode.ContentRegion);
return retval;
}
void SetLocation(short x, short y)
{
if (windowState == WindowState.Fullscreen)
return;
API.MoveWindow(window.WindowRef, x, y, false);
}
void SetSize(short width, short height)
{
if (WindowState == WindowState.Fullscreen)
return;
// Todo: why call SizeWindow twice?
API.SizeWindow(window.WindowRef, width, height, true);
bounds.Width = (short)width;
bounds.Height = (short)height;
Rect contentBounds = API.GetWindowBounds(window.WindowRef, WindowRegionCode.ContentRegion);
Rect newSize = new Rect(0, 0,
(short)(2 * width - contentBounds.Width),
(short)(2 * height - contentBounds.Height));
Debug.Print("Content region was: {0}", contentBounds);
Debug.Print("Resizing window to: {0}", newSize);
API.SizeWindow(window.WindowRef, newSize.Width, newSize.Height, true);
contentBounds = API.GetWindowBounds(window.WindowRef, WindowRegionCode.ContentRegion);
Debug.Print("New content region size: {0}", contentBounds);
clientRectangle = contentBounds.ToRectangle();
}
protected void OnResize()
{
LoadSize();
if (context != null && this.windowState != WindowState.Fullscreen)
context.Update(window);
if (Resize != null)
{
Resize(this, EventArgs.Empty);
}
}
private void LoadSize()
{
if (WindowState == WindowState.Fullscreen)
return;
bounds = GetRegion().ToRectangle();
}
protected virtual void OnClosing(CancelEventArgs e)
{
if (Closing != null)
Closing(this, e);
}
protected virtual void OnClosed()
{
if (Closed != null)
Closed(this, EventArgs.Empty);
}
#endregion
#region INativeGLWindow Members
public void CreateWindow(int width, int height, GraphicsMode mode, int major, int minor, GraphicsContextFlags flags, out IGraphicsContext context)
{
Rect r = new Rect(0, 0, (short)width, (short)height);
CreateNativeWindow(mWindowClass, mWindowAttrib, r);
Show();
this.context = new GraphicsContext(mode, window, major, minor, flags);
this.context.MakeCurrent(window);
context = this.context;
}
public void DestroyWindow()
{
Dispose();
}
public void ProcessEvents()
{
Application.ProcessEvents();
}
public Point PointToClient(Point point)
{
IntPtr handle = window.WindowRef;
Rect r = Carbon.API.GetWindowBounds(window.WindowRef, WindowRegionCode.ContentRegion);
Console.WriteLine("Rect: {0}", r);
return new Point(point.X - r.X, point.Y - r.Y);
}
public Point PointToScreen(Point point)
{
throw new NotImplementedException();
}
public bool Exists
{
get { return !mIsDisposed; }
}
public IWindowInfo WindowInfo
{
get { return window; }
}
public bool IsIdle
{
get { return true; }
}
public OpenTK.Input.IInputDriver InputDriver
{
get
{
return mInputDriver;
}
}
public bool Fullscreen
{
get
{
return false;
}
set
{
throw new NotImplementedException();
}
}
#endregion
#region INativeWindow Members
public Icon Icon
{
get { return null; }
set { }
}
public string Title
{
get
{
return title;
}
set
{
API.SetWindowTitle(window.WindowRef, value);
title = value;
}
}
public bool Visible
{
get { return API.IsWindowVisible(window.WindowRef); }
set
{
if (value && Visible == false)
Show();
else
Hide();
}
}
public bool Focused
{
get { throw new NotImplementedException(); }
}
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 Bounds.Width; }
set { Size = new Size(value, Height); }
}
public int Height
{
get { return Bounds.Height; }
set { Size = new Size(Width, value); }
}
public int X
{
get
{
return bounds.X;
}
set
{
Location = new Point(value, Y);
}
}
public int Y
{
get
{
return bounds.Y;
}
set
{
Location = new Point(X, value);
}
}
public Rectangle ClientRectangle
{
get
{
return clientRectangle;
}
set
{
throw new NotImplementedException();
}
}
public Size ClientSize
{
get
{
return clientRectangle.Size;
}
set
{
throw new NotImplementedException();
}
}
public void Close()
{
throw new NotImplementedException();
}
public WindowState WindowState
{
get
{
if (windowState == WindowState.Fullscreen)
return WindowState.Fullscreen;
if (Carbon.API.IsWindowCollapsed(window.WindowRef))
return WindowState.Minimized;
if (Carbon.API.IsWindowInStandardState(window.WindowRef))
{
return WindowState.Maximized;
}
return WindowState.Normal;
}
set
{
if (value == WindowState)
return;
Debug.Print("Switching window state from {0} to {1}", WindowState, value);
if (WindowState == WindowState.Fullscreen)
{
UnsetFullscreen();
}
if (WindowState == WindowState.Minimized)
{
API.CollapseWindow(window.WindowRef, false);
}
CarbonPoint idealSize;
switch (value)
{
case WindowState.Fullscreen:
SetFullscreen();
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.WindowRef, WindowPartCode.inZoomOut, ref idealSize);
break;
case WindowState.Normal:
if (WindowState == WindowState.Maximized)
{
idealSize = new CarbonPoint();
API.ZoomWindowIdeal(window.WindowRef, WindowPartCode.inZoomIn, ref idealSize);
}
break;
case WindowState.Minimized:
API.CollapseWindow(window.WindowRef, true);
break;
}
windowState = value;
if (WindowStateChanged != null)
WindowStateChanged(this, EventArgs.Empty);
OnResize();
}
}
public WindowBorder WindowBorder
{
get
{
return windowBorder;
}
set
{
if (windowBorder != value)
{
windowBorder = value;
if (windowBorder == WindowBorder.Resizable)
{
API.ChangeWindowAttributes(window.WindowRef, WindowAttributes.Resizable | WindowAttributes.FullZoom,
WindowAttributes.NoAttributes);
}
else if (windowBorder == WindowBorder.Fixed)
{
API.ChangeWindowAttributes(window.WindowRef, WindowAttributes.NoAttributes,
WindowAttributes.Resizable | WindowAttributes.FullZoom);
}
if (WindowBorderChanged != null)
WindowBorderChanged(this, EventArgs.Empty);
}
}
}
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<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
}
}