#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; 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; 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 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; } 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); // Legacy keyboard API InputDriver.Keyboard[0].SetKey(key, (uint)code, true); // Raise KeyDown for new keyboard API mKeyDownArgs.Key = key; KeyDown(this, mKeyDownArgs); // Raise KeyPress for new keyboard API if (!Char.IsControl(mKeyPressArgs.KeyChar)) { OnKeyPress(mKeyPressArgs); } return OSStatus.NoError; case KeyboardEventKind.RawKeyUp: Keymap.TryGetValue(code, out key); // Legacy keyboard API InputDriver.Keyboard[0].SetKey(key, (uint)code, false); // Raise KeyUp for new keyboard API mKeyUpArgs.Key = key; KeyUp(this, mKeyUpArgs); 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 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 } }