diff --git a/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs b/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs new file mode 100644 index 00000000..c1334e2e --- /dev/null +++ b/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs @@ -0,0 +1,1143 @@ +#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; +using System.Drawing; +using System.Text; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace OpenTK.Platform.MacOS +{ + using Carbon; +#if ANDROID || IPHONE || MINIMAL + using Minimal; + using Graphics = OpenTK.Minimal.Graphics; +#else + using Graphics = System.Drawing.Graphics; +#endif + + class CarbonGLNative : INativeWindow + { + #region Fields + + CarbonWindowInfo window; + CarbonInput mInputDriver; + + IntPtr uppHandler; + + string title = "OpenTK Window"; + Rectangle bounds, clientRectangle; + Rectangle windowedBounds; + bool mIsDisposed = false; + bool mExists = true; + DisplayDevice mDisplayDevice; + + WindowPositionMethod mPositionMethod = WindowPositionMethod.CenterOnMainScreen; + int mTitlebarHeight; + private WindowBorder windowBorder = WindowBorder.Resizable; + private WindowState windowState = WindowState.Normal; + + static Dictionary mWindows = + new Dictionary(new IntPtrEqualityComparer()); + + KeyPressEventArgs mKeyPressArgs = new KeyPressEventArgs((char)0); + OpenTK.Input.KeyboardKeyEventArgs mKeyDownArgs = new OpenTK.Input.KeyboardKeyEventArgs(); + OpenTK.Input.KeyboardKeyEventArgs mKeyUpArgs = new OpenTK.Input.KeyboardKeyEventArgs(); + + 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 WindowRefMap + { + get { return mWindows; } + } + internal DisplayDevice TargetDisplayDevice + { + get { return mDisplayDevice; } + } + + #endregion + + #region Constructors + + static CarbonGLNative() + { + Application.Initialize(); + } + + 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; + } + + Key key; + switch (evt.KeyboardEventKind) + { + case KeyboardEventKind.RawKeyRepeat: + if (InputDriver.Keyboard[0].KeyRepeat) + goto case KeyboardEventKind.RawKeyDown; + break; + + case KeyboardEventKind.RawKeyDown: + ProcessKeyDown(code); + return OSStatus.NoError; + + case KeyboardEventKind.RawKeyUp: + ProcessKeyUp(code); + 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); + } + + void ProcessKeyDown(MacOSKeyCode code) + { + Key key = MacOSKeyMap.GetKey(code); + + // Legacy keyboard API + KeyboardDevice keyboard = InputDriver.Keyboard[0]; + keyboard.SetKey(key, (uint)code, true); + + // Raise KeyDown for new keyboard API + mKeyDownArgs.Key = key; + mKeyDownArgs.Modifiers = keyboard.GetModifiers(); + + KeyDown(this, mKeyDownArgs); + + // Raise KeyPress for new keyboard API + if (!Char.IsControl(mKeyPressArgs.KeyChar)) + { + OnKeyPress(mKeyPressArgs); + } + } + + void ProcessKeyUp(MacOSKeyCode code) + { + Key key = MacOSKeyMap.GetKey(code); + + // Legacy keyboard API + KeyboardDevice keyboard = InputDriver.Keyboard[0]; + keyboard.SetKey(key, (uint)code, false); + + // Raise KeyUp for new keyboard API + mKeyUpArgs.Key = key; + mKeyDownArgs.Modifiers = keyboard.GetModifiers(); + + KeyUp(this, mKeyUpArgs); + } + + void ProcessKey(MacOSKeyCode code, bool pressed) + { + if (pressed) + { + ProcessKeyDown(code); + } + else + { + ProcessKeyUp(code); + } + } + + 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; + + Input.KeyboardDevice keyboard = InputDriver.Keyboard[0]; + + if (keyboard[OpenTK.Input.Key.AltLeft] ^ option) + { + ProcessKey(MacOSKeyCode.OptionAlt, option); + } + + if (keyboard[OpenTK.Input.Key.ShiftLeft] ^ shift) + { + ProcessKey(MacOSKeyCode.Shift, shift); + } + + if (keyboard[OpenTK.Input.Key.WinLeft] ^ command) + { + ProcessKey(MacOSKeyCode.Command, command); + } + + if (keyboard[OpenTK.Input.Key.ControlLeft] ^ control) + { + ProcessKey(MacOSKeyCode.Control, control); + } + + if (keyboard[OpenTK.Input.Key.CapsLock] ^ caps) + { + ProcessKey(MacOSKeyCode.CapsLock, caps); + } + } + + Rect GetClientSize() + { + Rect retval = API.GetWindowBounds(window.Handle, WindowRegionCode.ContentRegion); + return retval; + } + + void SetClientSize(short width, short height) + { + if (WindowState == WindowState.Fullscreen) + return; + + Rect new_bounds = new Rect(Bounds.X, Bounds.Y, width, height); + API.SetWindowBounds(window.Handle, WindowRegionCode.ContentRegion, ref new_bounds); + LoadSize(); + } + + void SetLocation(short x, short y) + { + if (windowState == WindowState.Fullscreen) + return; + + Rect new_bounds = new Rect(x, y, Bounds.Width, Bounds.Height); + API.SetWindowBounds(window.Handle, WindowRegionCode.StructureRegion, ref new_bounds); + LoadSize(); + } + + void SetSize(short width, short height) + { + if (WindowState == WindowState.Fullscreen) + return; + + Rect new_bounds = new Rect(Bounds.X, Bounds.Y, width, height); + API.SetWindowBounds(window.Handle, WindowRegionCode.StructureRegion, ref new_bounds); + LoadSize(); + } + + 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.ContentRegion); + 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); + using (Graphics g = Graphics.FromImage(bitmap)) + { + 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 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; } +// 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 Move = delegate { }; + public event EventHandler Resize = delegate { }; + public event EventHandler Closing = delegate { }; + public event EventHandler Closed = delegate { }; + public event EventHandler Disposed = delegate { }; + public event EventHandler IconChanged = delegate { }; + public event EventHandler TitleChanged = delegate { }; + public event EventHandler VisibleChanged = delegate { }; + public event EventHandler FocusedChanged = delegate { }; + public event EventHandler WindowBorderChanged = delegate { }; + public event EventHandler WindowStateChanged = delegate { }; + public event EventHandler KeyDown = delegate { }; + public event EventHandler KeyPress = delegate { }; + public event EventHandler KeyUp = delegate { }; + public event EventHandler MouseEnter = delegate { }; + public event EventHandler MouseLeave = delegate { }; + + #endregion + } +} diff --git a/Source/OpenTK/Platform/MacOS/MacOSKeyMap.cs b/Source/OpenTK/Platform/MacOS/MacOSKeyMap.cs index c33f542a..35f19467 100644 --- a/Source/OpenTK/Platform/MacOS/MacOSKeyMap.cs +++ b/Source/OpenTK/Platform/MacOS/MacOSKeyMap.cs @@ -36,127 +36,229 @@ namespace OpenTK.Platform.MacOS using Carbon; using Input; - class MacOSKeyMap : Dictionary + class MacOSKeyMap { - public MacOSKeyMap() + public static Key GetKey(MacOSKeyCode code) { // comments indicate members of the Key enum that are missing - - Add(MacOSKeyCode.A, Key.A); - Add(MacOSKeyCode.OptionAlt, Key.AltLeft); - // AltRight - Add(MacOSKeyCode.B, Key.B); - - Add(MacOSKeyCode.Backslash, Key.BackSlash); - Add(MacOSKeyCode.Backspace, Key.BackSpace); - Add(MacOSKeyCode.BracketLeft, Key.BracketLeft); - Add(MacOSKeyCode.BracketRight, Key.BracketRight); - Add(MacOSKeyCode.C, Key.C); - // Capslock - // Clear - Add(MacOSKeyCode.Comma, Key.Comma); - Add(MacOSKeyCode.Control, Key.ControlLeft); - // ControlRight - Add(MacOSKeyCode.D, Key.D); - Add(MacOSKeyCode.Del, Key.Delete); - Add(MacOSKeyCode.Down, Key.Down); - Add(MacOSKeyCode.E, Key.E); - Add(MacOSKeyCode.End, Key.End); - Add(MacOSKeyCode.Enter, Key.Enter); - Add(MacOSKeyCode.Return, Key.Enter); - Add(MacOSKeyCode.Esc, Key.Escape); - Add(MacOSKeyCode.F, Key.F); - Add(MacOSKeyCode.F1, Key.F1); - Add(MacOSKeyCode.F2, Key.F2); - Add(MacOSKeyCode.F3, Key.F3); - Add(MacOSKeyCode.F4, Key.F4); - Add(MacOSKeyCode.F5, Key.F5); - Add(MacOSKeyCode.F6, Key.F6); - Add(MacOSKeyCode.F7, Key.F7); - Add(MacOSKeyCode.F8, Key.F8); - Add(MacOSKeyCode.F9, Key.F9); - Add(MacOSKeyCode.F10, Key.F10); - Add(MacOSKeyCode.F11, Key.F11); - Add(MacOSKeyCode.F12, Key.F12); - Add(MacOSKeyCode.F13, Key.F13); - Add(MacOSKeyCode.F14, Key.F14); - Add(MacOSKeyCode.F15, Key.F15); - // F16-F35 - Add(MacOSKeyCode.G, Key.G); - Add(MacOSKeyCode.H, Key.H); - Add(MacOSKeyCode.Home, Key.Home); - Add(MacOSKeyCode.I, Key.I); - Add(MacOSKeyCode.Insert, Key.Insert); - Add(MacOSKeyCode.J, Key.J); - Add(MacOSKeyCode.K, Key.K); - Add(MacOSKeyCode.KeyPad_0, Key.Keypad0); - Add(MacOSKeyCode.KeyPad_1, Key.Keypad1); - Add(MacOSKeyCode.KeyPad_2, Key.Keypad2); - Add(MacOSKeyCode.KeyPad_3, Key.Keypad3); - Add(MacOSKeyCode.KeyPad_4, Key.Keypad4); - Add(MacOSKeyCode.KeyPad_5, Key.Keypad5); - Add(MacOSKeyCode.KeyPad_6, Key.Keypad6); - Add(MacOSKeyCode.KeyPad_7, Key.Keypad7); - Add(MacOSKeyCode.KeyPad_8, Key.Keypad8); - Add(MacOSKeyCode.KeyPad_9, Key.Keypad9); - Add(MacOSKeyCode.KeyPad_Add, Key.KeypadAdd); - Add(MacOSKeyCode.KeyPad_Decimal, Key.KeypadDecimal); - Add(MacOSKeyCode.KeyPad_Divide, Key.KeypadDivide); - Add(MacOSKeyCode.KeyPad_Enter, Key.KeypadEnter); - Add(MacOSKeyCode.KeyPad_Multiply, Key.KeypadMultiply); - Add(MacOSKeyCode.KeyPad_Subtract, Key.KeypadSubtract); - //Add(MacOSKeyCode.KeyPad_Equal); - Add(MacOSKeyCode.L, Key.L); - Add(MacOSKeyCode.Left, Key.Left); - Add(MacOSKeyCode.M, Key.M); - //Key.MaxKeys - Add(MacOSKeyCode.Menu, Key.Menu); - Add(MacOSKeyCode.Minus, Key.Minus); - Add(MacOSKeyCode.N, Key.N); - Add(MacOSKeyCode.Key_0, Key.Number0); - Add(MacOSKeyCode.Key_1, Key.Number1); - Add(MacOSKeyCode.Key_2, Key.Number2); - Add(MacOSKeyCode.Key_3, Key.Number3); - Add(MacOSKeyCode.Key_4, Key.Number4); - Add(MacOSKeyCode.Key_5, Key.Number5); - Add(MacOSKeyCode.Key_6, Key.Number6); - Add(MacOSKeyCode.Key_7, Key.Number7); - Add(MacOSKeyCode.Key_8, Key.Number8); - Add(MacOSKeyCode.Key_9, Key.Number9); - // Numlock - Add(MacOSKeyCode.O, Key.O); - Add(MacOSKeyCode.P, Key.P); - Add(MacOSKeyCode.Pagedown, Key.PageDown); - Add(MacOSKeyCode.Pageup, Key.PageUp); - // Pause - Add(MacOSKeyCode.Period, Key.Period); - Add(MacOSKeyCode.Equals, Key.Plus); - // PrintScreen - Add(MacOSKeyCode.Q, Key.Q); - Add(MacOSKeyCode.Quote, Key.Quote); - Add(MacOSKeyCode.R, Key.R); - Add(MacOSKeyCode.Right, Key.Right); - Add(MacOSKeyCode.S, Key.S); - // ScrollLock - Add(MacOSKeyCode.Semicolon, Key.Semicolon); - Add(MacOSKeyCode.Shift, Key.ShiftLeft); - //Key.ShiftRight - Add(MacOSKeyCode.Slash, Key.Slash); - // Key.Sleep - Add(MacOSKeyCode.Space, Key.Space); - Add(MacOSKeyCode.T, Key.T); - Add(MacOSKeyCode.Tab, Key.Tab); - Add(MacOSKeyCode.Tilde, Key.Tilde); - Add(MacOSKeyCode.U, Key.U); - Add(MacOSKeyCode.Up, Key.Up); - Add(MacOSKeyCode.V, Key.V); - Add(MacOSKeyCode.W, Key.W); - Add(MacOSKeyCode.Command, Key.WinLeft); - // WinKeyRight - Add(MacOSKeyCode.X, Key.X); - Add(MacOSKeyCode.Y, Key.Y); - Add(MacOSKeyCode.Z, Key.Z); - + switch (code) + { + case MacOSKeyCode.A: + return Key.A; + case MacOSKeyCode.OptionAlt: + return Key.AltLeft; + // AltRight + case MacOSKeyCode.B: + return Key.B; + case MacOSKeyCode.Backslash: + return Key.BackSlash; + case MacOSKeyCode.Backspace: + return Key.BackSpace; + case MacOSKeyCode.BracketLeft: + return Key.BracketLeft; + case MacOSKeyCode.BracketRight: + return Key.BracketRight; + case MacOSKeyCode.C: + return Key.C; + // Capslock + // Clear + case MacOSKeyCode.Comma: + return Key.Comma; + case MacOSKeyCode.Control: + return Key.ControlLeft; + // ControlRight + case MacOSKeyCode.D: + return Key.D; + case MacOSKeyCode.Del: + return Key.Delete; + case MacOSKeyCode.Down: + return Key.Down; + case MacOSKeyCode.E: + return Key.E; + case MacOSKeyCode.End: + return Key.End; + case MacOSKeyCode.Enter: + return Key.Enter; + case MacOSKeyCode.Return: + return Key.Enter; + case MacOSKeyCode.Esc: + return Key.Escape; + case MacOSKeyCode.F: + return Key.F; + case MacOSKeyCode.F1: + return Key.F1; + case MacOSKeyCode.F2: + return Key.F2; + case MacOSKeyCode.F3: + return Key.F3; + case MacOSKeyCode.F4: + return Key.F4; + case MacOSKeyCode.F5: + return Key.F5; + case MacOSKeyCode.F6: + return Key.F6; + case MacOSKeyCode.F7: + return Key.F7; + case MacOSKeyCode.F8: + return Key.F8; + case MacOSKeyCode.F9: + return Key.F9; + case MacOSKeyCode.F10: + return Key.F10; + case MacOSKeyCode.F11: + return Key.F11; + case MacOSKeyCode.F12: + return Key.F12; + case MacOSKeyCode.F13: + return Key.F13; + case MacOSKeyCode.F14: + return Key.F14; + case MacOSKeyCode.F15: + return Key.F15; + // F16-F35 + case MacOSKeyCode.G: + return Key.G; + case MacOSKeyCode.H: + return Key.H; + case MacOSKeyCode.Home: + return Key.Home; + case MacOSKeyCode.I: + return Key.I; + case MacOSKeyCode.Insert: + return Key.Insert; + case MacOSKeyCode.J: + return Key.J; + case MacOSKeyCode.K: + return Key.K; + case MacOSKeyCode.KeyPad_0: + return Key.Keypad0; + case MacOSKeyCode.KeyPad_1: + return Key.Keypad1; + case MacOSKeyCode.KeyPad_2: + return Key.Keypad2; + case MacOSKeyCode.KeyPad_3: + return Key.Keypad3; + case MacOSKeyCode.KeyPad_4: + return Key.Keypad4; + case MacOSKeyCode.KeyPad_5: + return Key.Keypad5; + case MacOSKeyCode.KeyPad_6: + return Key.Keypad6; + case MacOSKeyCode.KeyPad_7: + return Key.Keypad7; + case MacOSKeyCode.KeyPad_8: + return Key.Keypad8; + case MacOSKeyCode.KeyPad_9: + return Key.Keypad9; + case MacOSKeyCode.KeyPad_Add: + return Key.KeypadAdd; + case MacOSKeyCode.KeyPad_Decimal: + return Key.KeypadDecimal; + case MacOSKeyCode.KeyPad_Divide: + return Key.KeypadDivide; + case MacOSKeyCode.KeyPad_Enter: + return Key.KeypadEnter; + case MacOSKeyCode.KeyPad_Multiply: + return Key.KeypadMultiply; + case MacOSKeyCode.KeyPad_Subtract: + return Key.KeypadSubtract; + //case MacOSKeyCode.KeyPad_Equal; + case MacOSKeyCode.L: + return Key.L; + case MacOSKeyCode.Left: + return Key.Left; + case MacOSKeyCode.M: + return Key.M; + //Key.MaxKeys + case MacOSKeyCode.Menu: + return Key.Menu; + case MacOSKeyCode.Minus: + return Key.Minus; + case MacOSKeyCode.N: + return Key.N; + case MacOSKeyCode.Key_0: + return Key.Number0; + case MacOSKeyCode.Key_1: + return Key.Number1; + case MacOSKeyCode.Key_2: + return Key.Number2; + case MacOSKeyCode.Key_3: + return Key.Number3; + case MacOSKeyCode.Key_4: + return Key.Number4; + case MacOSKeyCode.Key_5: + return Key.Number5; + case MacOSKeyCode.Key_6: + return Key.Number6; + case MacOSKeyCode.Key_7: + return Key.Number7; + case MacOSKeyCode.Key_8: + return Key.Number8; + case MacOSKeyCode.Key_9: + return Key.Number9; + // Numlock + case MacOSKeyCode.O: + return Key.O; + case MacOSKeyCode.P: + return Key.P; + case MacOSKeyCode.Pagedown: + return Key.PageDown; + case MacOSKeyCode.Pageup: + return Key.PageUp; + // Pause + case MacOSKeyCode.Period: + return Key.Period; + case MacOSKeyCode.Equals: + return Key.Plus; + // PrintScreen + case MacOSKeyCode.Q: + return Key.Q; + case MacOSKeyCode.Quote: + return Key.Quote; + case MacOSKeyCode.R: + return Key.R; + case MacOSKeyCode.Right: + return Key.Right; + case MacOSKeyCode.S: + return Key.S; + // ScrollLock + case MacOSKeyCode.Semicolon: + return Key.Semicolon; + case MacOSKeyCode.Shift: + return Key.ShiftLeft; + //Key.ShiftRight + case MacOSKeyCode.Slash: + return Key.Slash; + // Key.Sleep + case MacOSKeyCode.Space: + return Key.Space; + case MacOSKeyCode.T: + return Key.T; + case MacOSKeyCode.Tab: + return Key.Tab; + case MacOSKeyCode.Tilde: + return Key.Tilde; + case MacOSKeyCode.U: + return Key.U; + case MacOSKeyCode.Up: + return Key.Up; + case MacOSKeyCode.V: + return Key.V; + case MacOSKeyCode.W: + return Key.W; + case MacOSKeyCode.Command: + return Key.WinLeft; + // WinKeyRight + case MacOSKeyCode.X: + return Key.X; + case MacOSKeyCode.Y: + return Key.Y; + case MacOSKeyCode.Z: + return Key.Z; + + default: + return Key.Unknown; + } } } }