using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; using Beefy.gfx; using Beefy.events; using Beefy.sys; using Beefy.geom; namespace Beefy.widgets { public delegate void MouseLeftWindowHandler(BFWindow window); public delegate void WindowLostFocusHandler(BFWindow window, BFWindow newFocus); public delegate bool WindowCloseQueryHandler(BFWindow window); public delegate void WindowClosedHandler(BFWindow window); public delegate void WindowMovedHandler(BFWindow window); public delegate void MouseWheelHandler(MouseEvent mouseEvent); public delegate void KeyDownHandler(KeyDownEvent keyboardEvent); //public delegate void CloseTemporaryHandler(WidgetWindow window); public class WidgetWindow : BFWindow { public Event mOnMouseLeftWindow ~ _.Dispose(); public Event mOnWindowLostFocus ~ _.Dispose(); public Event mOnMouseDown ~ _.Dispose(); public Event mOnWindowCloseQuery ~ _.Dispose(); public Event mOnWindowClosed ~ _.Dispose(); public Event mOnWindowMoved ~ _.Dispose(); public Event mOnMouseWheel ~ _.Dispose(); public Event mOnMenuItemSelected ~ _.Dispose(); public Event mOnWindowKeyDown ~ _.Dispose(); public Event> mOnHitTest ~ _.Dispose(); public static Event sOnMouseLeftWindow ~ _.Dispose(); public static Event sOnWindowLostFocus ~ _.Dispose(); public static Event sOnMouseDown ~ _.Dispose(); public static Event sOnWindowClosed ~ _.Dispose(); public static Event sOnWindowMoved ~ _.Dispose(); public static Event sOnMouseWheel ~ _.Dispose(); public static Event sOnMenuItemSelected ~ _.Dispose(); public static Event sOnKeyDown ~ _.Dispose(); //public Event sOnCloseTemporaries ~ _.Dispose(); public bool mIsMainWindow; public MouseFlag mMouseFlags; public Widget mRootWidget; public Widget mCaptureWidget; public Widget mOverWidget; public Widget mFocusWidget; public int32 mClientMouseX; public int32 mClientMouseY; public float mMouseX; public float mMouseY; public bool mIsMouseMoving; public bool mHasMouseInside; public bool mHasProxyMouseInside; public bool mIsKeyDownHandled; public int32 mContentClientWidth; public int32 mContentClientHeight; public static WidgetWindow sMouseInsideWindow; public static int32 sLastClosedTick = -1; public Matrix mScaleMatrix = Matrix.IdentityMatrix; public Matrix mInvScaleMatrix = Matrix.IdentityMatrix; public bool[] mIsKeyDown = new bool[(int32)KeyCode.COUNT] ~ delete _; public this(BFWindow parent, String title, int x, int y, int width, int height, BFWindow.Flags windowFlags, Widget rootWidget) : //TODO: Do BFWindow init outside constructor.. see where it be hanging base(parent, title, x, y, width, height, windowFlags) { if (mWindowFlags.HasFlag(Flags.ClientSized)) { mContentClientWidth = (int32)width; mContentClientHeight = (int32)height; } if (!mWindowFlags.HasFlag(BFWindowBase.Flags.NoActivate)) GotFocus(); SetRootWidget(rootWidget); //Debug.WriteLine("WidgetWindow.this {0} {1} {2}", this, mTitle, mRootWidget); //Debug.WriteLine("WidgetWindow this"); } public ~this() { if (sMouseInsideWindow == this) sMouseInsideWindow = null; delete mRootWidget; //Debug.WriteLine("WidgetWindow ~this"); } public void SetRootWidget(Widget rootWidget) { if (mRootWidget != null) mRootWidget.[Friend]RemovedFromWindow(); mRootWidget = rootWidget; if (mRootWidget != null) { mRootWidget.mWidgetWindow = this; mRootWidget.InitChildren(); mRootWidget.AddedToParent(); RehupSize(); } } public KeyFlags GetKeyFlags() { KeyFlags keyFlags = default; if (IsKeyDown(KeyCode.Shift)) keyFlags |= KeyFlags.Shift; if (IsKeyDown(KeyCode.Control)) keyFlags |= KeyFlags.Ctrl; if (IsKeyDown(KeyCode.Menu)) keyFlags |= KeyFlags.Alt; return keyFlags; } public bool IsKeyDown(KeyCode keyCode) { return mIsKeyDown[(int32)keyCode]; } public override void Draw(Graphics g) { if (mRootWidget == null) return; base.Draw(g); mRootWidget.DrawAll(g); } public override void Update() { if (mOverWidget != null) Debug.Assert(mHasMouseInside); if (mRootWidget == null) return; base.Update(); RehupMouse(false); mRootWidget.UpdateAll(); } public override int32 CloseQuery() { bool hadFalse = false; for (var handler in mOnWindowCloseQuery) { if (!handler(this)) { hadFalse = true; break; } } if (hadFalse) return 0; return base.CloseQuery(); } public override void Closed() { sLastClosedTick = BFApp.sApp.mUpdateCnt; if (!mHasClosed) { //Debug.WriteLine("WidgetWindow.Closed {0} {1} {2}", this, mTitle, mRootWidget); if (mRootWidget != null) { mRootWidget.[Friend]RemovedFromWindow(); mRootWidget.RemovedFromParent(null, this); } //Debug.WriteLine("Window closing: {0}", this); mOnWindowClosed(this); sOnWindowClosed(this); base.Closed(); } } public override void PreDraw(Graphics g) { base.PreDraw(g); g.mMatrix.Set(mScaleMatrix); g.mMatrixStack[g.mMatrixStackIdx] = g.mMatrix; } public override void RehupSize() { base.RehupSize(); if (mWindowFlags.HasFlag(Flags.ScaleContent)) { float scaleX = mClientWidth / (float)mContentClientWidth; float scaleY = mClientHeight / (float)mContentClientHeight; if (scaleX > scaleY) { float scale = scaleY; mScaleMatrix.a = scale; mScaleMatrix.b = 0; mScaleMatrix.c = 0; mScaleMatrix.d = scale; mScaleMatrix.tx = (int32)(mClientWidth - (scale * mContentClientWidth)) / 2; mScaleMatrix.ty = 0; } else { float scale = scaleX; mScaleMatrix.a = scale; mScaleMatrix.b = 0; mScaleMatrix.c = 0; mScaleMatrix.d = scale; mScaleMatrix.tx = 0; mScaleMatrix.ty = (int32)(mClientHeight - (scale * mContentClientHeight)) / 2; } mInvScaleMatrix.Set(mScaleMatrix); mInvScaleMatrix.Invert(); mRootWidget.Resize(0, 0, mContentClientWidth, mContentClientHeight); } else { mInvScaleMatrix = Matrix.IdentityMatrix; mScaleMatrix = Matrix.IdentityMatrix; mContentClientWidth = mClientWidth; mContentClientHeight = mClientHeight; mRootWidget.Resize(0, 0, mClientWidth, mClientHeight); } } public override void Moved() { base.Moved(); mOnWindowMoved(this); sOnWindowMoved(this); } public void SetFocus(Widget widget) { /*if (widget != null) Debug.WriteLine("SetFocus: {0} {1}", widget.GetType(), widget.mWidgetWindow.mTitle);*/ if (mFocusWidget == widget) return; Widget oldFocusWidget = mFocusWidget; mFocusWidget = null; if (oldFocusWidget != null) { Debug.Assert(oldFocusWidget.mWidgetWindow == this); if (oldFocusWidget.mHasFocus) oldFocusWidget.LostFocus(); else Debug.Assert(!mHasFocus); } if (mFocusWidget != null) return; // Already got a new focus after LostFocus callbacks mFocusWidget = widget; if ((mFocusWidget != null) && (mHasFocus)) mFocusWidget.GotFocus(); } public override void GotFocus() { if (mHasFocus) return; mIsDirty = true; //Debug.WriteLine("GotFocus {0}", mIsMainWindow); for (var window in sWindowDictionary.Values) { // Don't let more than one window think it has the focus var widgetWindow = window as WidgetWindow; if ((widgetWindow != null) && (widgetWindow != this) && (widgetWindow.mHasFocus)) { widgetWindow.LostFocus(this); break; } } //Debug.WriteLine("GotFocus {0} {1}", mTitle, mIsMainWindow); base.GotFocus(); if (mFocusWidget != null) { //Debug.Assert(!mFocusWidget.mHasFocus); if (!mFocusWidget.mHasFocus) mFocusWidget.GotFocus(); } } public override void LostFocus(BFWindow newFocus) { //Debug.WriteLine("LostFocus {0}", mIsMainWindow); if (!mHasFocus) return; mIsDirty = true; for (int32 i = 0; i < (int32)mIsKeyDown.Count; i++) if (mIsKeyDown[i]) KeyUp(i); // We could have a mousedown in one window while another window has focus still��� // So this check isn't valid. /*for (int btnIdx = 0; btnIdx < 3; btnIdx++) { if (((int)mMouseFlags & (1 << btnIdx)) != 0) { MouseUp(mClientMouseX, mClientMouseY, btnIdx); } }*/ base.LostFocus(newFocus); if (mCaptureWidget != null) mCaptureWidget = null; if (mFocusWidget != null) { Debug.Assert(mFocusWidget.mWidgetWindow == this); var prevFocus = mFocusWidget; prevFocus.LostFocus(); } mOnWindowLostFocus(this, newFocus); sOnWindowLostFocus(this, newFocus); //Debug.Assert(!mHasFocus); } public override void KeyChar(char32 c) { var fakeFocusWindow = GetFakeFocusWindow(); if (fakeFocusWindow != null) { fakeFocusWindow.KeyChar(c); return; } mIsDirty = true; if ((mHasFocus) && (mFocusWidget != null) && (!mIsKeyDownHandled)) { let keyEvent = scope KeyCharEvent(); keyEvent.mChar = c; keyEvent.mSender = mFocusWidget; mFocusWidget.KeyChar(keyEvent); } } BFWindow GetFakeFocusWindow() { /*for (int windowIdx = BFApp.sApp.mWindows.Count - 1; windowIdx >= 0; windowIdx--) { var checkWindow = BFApp.sApp.mWindows[windowIdx]; if (checkWindow == this) return null; if ((checkWindow.mWindowFlags.HasFlag(.FakeFocus)) && (checkWindow.mHasFocus)) return checkWindow; }*/ return null; } public override bool KeyDown(int32 keyCode, int32 isRepeat) { var fakeFocusWindow = GetFakeFocusWindow(); if (fakeFocusWindow != null) return fakeFocusWindow.KeyDown(keyCode, isRepeat); mIsDirty = true; mIsKeyDownHandled = false; if (keyCode < mIsKeyDown.Count) mIsKeyDown[keyCode] = true; KeyDownEvent e = scope KeyDownEvent(); e.mSender = this; e.mKeyFlags = GetKeyFlags(); e.mKeyCode = (KeyCode)keyCode; e.mIsRepeat = isRepeat != 0; if ((sOnKeyDown.HasListeners) || (mOnWindowKeyDown.HasListeners)) { if (sOnKeyDown.HasListeners) sOnKeyDown(e); if (mOnWindowKeyDown.HasListeners) mOnWindowKeyDown(e); } if ((mFocusWidget != null) && (!e.mHandled)) { e.mSender = mFocusWidget; mFocusWidget.KeyDown(e); } if (e.mHandled) mIsKeyDownHandled = true; return e.mHandled; } public override void KeyUp(int32 keyCode) { mIsDirty = true; if (keyCode < mIsKeyDown.Count) mIsKeyDown[keyCode] = false; if (mFocusWidget != null) mFocusWidget.KeyUp((KeyCode)keyCode); var fakeFocusWindow = GetFakeFocusWindow(); if (fakeFocusWindow != null) fakeFocusWindow.KeyUp(keyCode); } public override HitTestResult HitTest(int32 x, int32 y) { if (mOnHitTest.HasListeners) { var result = mOnHitTest(x, y); if (result != HitTestResult.NotHandled) { if ((result == .Transparent) || (result == .Caption)) { if (mHasMouseInside) MouseLeave(); } return result; } } if (mWindowFlags.HasFlag(Flags.Resizable)) { if ((x - mX > mWindowWidth - 24) && (y - mY > mWindowHeight - 24)) return HitTestResult.BottomRight; } return HitTestResult.NotHandled; } public void RehupMouse(bool force) { if (!mHasMouseInside) return; Widget aWidget = mRootWidget.FindWidgetByCoords(mMouseX, mMouseY); if (mCaptureWidget != null) { bool didSomething = false; if (mCaptureWidget == aWidget) { if (!mCaptureWidget.mMouseOver) { mCaptureWidget.MouseEnter(); didSomething = true; } mOverWidget = aWidget; } else { if (mCaptureWidget.mMouseOver) { mCaptureWidget.MouseLeave(); didSomething = true; } mOverWidget = null; } if ((didSomething) || (force)) { float childX; float childY; mCaptureWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); mCaptureWidget.MouseMove(childX, childY); } } else { if (mOverWidget != aWidget) { if (mOverWidget != null) { mOverWidget.MouseLeave(); if (!IsMouseCaptured()) { // Clear out mousedowns if we don't have capture anymore for (int32 btn < 4) { let checkFlag = (MouseFlag)(1 << btn); if (mMouseFlags.HasFlag(checkFlag)) { mMouseFlags &= ~checkFlag; float childX; float childY; mOverWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); mOverWidget.MouseUp(childX, childY, btn); } } } } mOverWidget = aWidget; if (aWidget != null) { aWidget.MouseEnter(); } //Debug.WriteLine("Set OverWidget {0} in {1}", mOverWidget, this); } else if (!force) return; if (aWidget != null) { float childX; float childY; aWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); aWidget.MouseMove(childX, childY); } } } void CheckOtherMouseCaptures() { if (mCaptureWidget == null) { /*foreach (WidgetWindow window in sWindowDictionary.Values) { if (window.mCaptureWidget != null) { //int a = 0; } }*/ } } void SetMouseInside() { if (sMouseInsideWindow == this) { //Debug.WriteLine("Mouse already inside"); return; } //Debug.WriteLine("SetMouseInside 1 {0} prev={1}", this, sMouseInsideWindow); var prevMouseInsideWindow = sMouseInsideWindow; sMouseInsideWindow = null; if (prevMouseInsideWindow != null) prevMouseInsideWindow.MouseLeave(); if (sMouseInsideWindow != null) { //Debug.WriteLine("SetMouseInside {0} aborted", this); return; } sMouseInsideWindow = this; mHasMouseInside = true; //Debug.WriteLine("SetMouseInside 2 {0}", this); } public override void MouseMove(int32 inX, int32 inY) { //Debug.WriteLine("MouseMove {0} {1},{2}", this, inX, inY); mClientMouseX = inX; mClientMouseY = inY; float x; float y; TranslateMouseCoords(inX, inY, out x, out y); SetMouseInside(); CheckOtherMouseCaptures(); if ((mMouseX == x) && (mMouseY == y)) { RehupMouse(false); return; } mIsMouseMoving = true; mMouseX = x; mMouseY = y; RehupMouse(true); mIsMouseMoving = false; } public override void MouseProxyMove(int32 x, int32 y) { // This is just from dragging from another so we don't acknowledge the moves directly //SetMouseInside(); mHasProxyMouseInside = true; mMouseX = x; mMouseY = y; CheckOtherMouseCaptures(); } void TranslateMouseCoords(int32 x, int32 y, out float outX, out float outY) { var pt = mInvScaleMatrix.Multiply(Point(x, y)); outX = pt.x; outY = pt.y; } public override void MouseDown(int32 inX, int32 inY, int32 btn, int32 btnCount) { mMouseFlags |= (MouseFlag)(1 << btn); if ((!mHasFocus) && (mParent == null)) { //INVESTIGATE: This shouldn't be required normally // Fixes some focus issues while debugging? GotFocus(); } float x; float y; TranslateMouseCoords(inX, inY, out x, out y); MouseMove(inX, inY); if ((mOnMouseDown.HasListeners) || (sOnMouseDown.HasListeners)) { MouseEvent anEvent = scope MouseEvent(); anEvent.mSender = this; anEvent.mX = x; anEvent.mY = y; mOnMouseDown(anEvent); sOnMouseDown(anEvent); if (anEvent.mHandled) return; } Widget aWidget = mCaptureWidget ?? mOverWidget; if (aWidget != null) { Debug.Assert(aWidget.mWidgetWindow == this); float childX; float childY; aWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); aWidget.MouseDown(childX, childY, btn, btnCount); } } public override void MouseUp(int32 inX, int32 inY, int32 btn) { //Debug.WriteLine("MouseUp {0} {1} {2}", mIsMainWindow, this, mMouseFlags); if (((int32)mMouseFlags & (1 << btn)) == 0) return; MouseMove(inX, inY); mMouseFlags &= (MouseFlag)(~(1 << btn)); Widget aWidget = mCaptureWidget ?? mOverWidget; if (aWidget != null) { float childX; float childY; aWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); aWidget.MouseUp(childX, childY, btn); } } public override void MouseWheel(int32 inX, int32 inY, int32 delta) { float x; float y; TranslateMouseCoords(inX, inY, out x, out y); MouseMove(inX, inY); if (sOnMouseWheel.HasListeners) { MouseEvent anEvent = scope MouseEvent(); anEvent.mX = x; anEvent.mY = y; anEvent.mWheelDelta = delta; anEvent.mSender = this; sOnMouseWheel(anEvent); if (anEvent.mHandled) return; } if (mOnMouseWheel.HasListeners) { MouseEvent anEvent = scope MouseEvent(); anEvent.mX = x; anEvent.mY = y; anEvent.mWheelDelta = delta; anEvent.mSender = this; mOnMouseWheel(anEvent); if (anEvent.mHandled) return; } Widget aWidget = mOverWidget; if (aWidget != null) { float childX; float childY; aWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); aWidget.MouseWheel(childX, childY, delta); } } public override void MouseLeave() { if (sMouseInsideWindow == this) { sMouseInsideWindow = null; } mHasProxyMouseInside = false; mHasMouseInside = false; if (mOverWidget != null) { mOverWidget.MouseLeave(); mOverWidget = null; } mOnMouseLeftWindow(this); sOnMouseLeftWindow(this); } public override void MenuItemSelected(SysMenu sysMenu) { mOnMenuItemSelected(sysMenu); sOnMenuItemSelected(sysMenu); base.MenuItemSelected(sysMenu); } public void TransferMouse(WidgetWindow newMouseWindow) { if (mWindowFlags.HasFlag(.FakeFocus)) return; if (mMouseFlags == default) return; newMouseWindow.mMouseFlags = mMouseFlags; newMouseWindow.CaptureMouse(); mMouseFlags = default; } public void SetContentSize(int width, int height) { mContentClientWidth = (.)width; mContentClientHeight = (.)height; RehupSize(); } } }