From 186c2125fac8c5d8823107bf8383ed928b7905c9 Mon Sep 17 00:00:00 2001 From: Brian Fiete Date: Fri, 19 Jul 2024 10:31:33 +0200 Subject: [PATCH] Initial console support --- BeefLibs/Beefy2D/src/events/KeyboardEvent.bf | 12 +- BeefLibs/Beefy2D/src/gfx/Font.bf | 2 + BeefLibs/Beefy2D/src/widgets/EditWidget.bf | 4 +- BeefLibs/Beefy2D/src/widgets/ListView.bf | 2 +- BeefLibs/Beefy2D/src/widgets/Widget.bf | 34 +- BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf | 35 +- .../corlib/src/Diagnostics/SpawnedProcess.bf | 10 + BeefLibs/corlib/src/Platform.bf | 4 + BeefLibs/corlib/src/Windows.bf | 12 + BeefySysLib/platform/PlatformInterface.h | 1 + BeefySysLib/platform/posix/PosixCommon.cpp | 5 + BeefySysLib/platform/win/Platform.cpp | 5 + IDE/src/Commands.bf | 2 + IDE/src/IDEApp.bf | 45 +- IDE/src/ui/AboutDialog.bf | 2 +- IDE/src/ui/ConsolePanel.bf | 1031 +++++++++++++++++ IDE/src/ui/Panel.bf | 15 + IDE/src/ui/ProjectPanel.bf | 4 +- IDE/src/ui/PropertiesDialog.bf | 4 +- IDE/src/ui/TerminalPanel.bf | 24 + IDE/src/util/ConsoleProvider.bf | 20 + 21 files changed, 1244 insertions(+), 29 deletions(-) create mode 100644 IDE/src/ui/ConsolePanel.bf create mode 100644 IDE/src/ui/TerminalPanel.bf create mode 100644 IDE/src/util/ConsoleProvider.bf diff --git a/BeefLibs/Beefy2D/src/events/KeyboardEvent.bf b/BeefLibs/Beefy2D/src/events/KeyboardEvent.bf index bd8fd29e..130abffb 100644 --- a/BeefLibs/Beefy2D/src/events/KeyboardEvent.bf +++ b/BeefLibs/Beefy2D/src/events/KeyboardEvent.bf @@ -7,10 +7,14 @@ namespace Beefy.widgets { public enum KeyFlags { - None = 0, - Alt = 1, - Ctrl = 2, - Shift = 4 + case None = 0, + Alt = 1, + Ctrl = 2, + Shift = 4, + CapsLock = 8, + NumLock = 0x10; + + public KeyFlags HeldKeys => this & ~(CapsLock | NumLock); } } diff --git a/BeefLibs/Beefy2D/src/gfx/Font.bf b/BeefLibs/Beefy2D/src/gfx/Font.bf index 1e66d435..d5fe081c 100644 --- a/BeefLibs/Beefy2D/src/gfx/Font.bf +++ b/BeefLibs/Beefy2D/src/gfx/Font.bf @@ -691,6 +691,8 @@ namespace Beefy.gfx public float GetWidth(char32 theChar) { CharData charData = GetCharData(theChar); + if (charData == null) + return 0; return charData.mXAdvance; } diff --git a/BeefLibs/Beefy2D/src/widgets/EditWidget.bf b/BeefLibs/Beefy2D/src/widgets/EditWidget.bf index 6bf931a3..16dc3213 100644 --- a/BeefLibs/Beefy2D/src/widgets/EditWidget.bf +++ b/BeefLibs/Beefy2D/src/widgets/EditWidget.bf @@ -2304,7 +2304,7 @@ namespace Beefy.widgets int prevCursorPos; bool gotCursorPos = TryGetCursorTextPos(out prevCursorPos); - if (mWidgetWindow.GetKeyFlags() == .Ctrl) + if (mWidgetWindow.GetKeyFlags(true) == .Ctrl) { switch (keyCode) { @@ -2330,7 +2330,7 @@ namespace Beefy.widgets } } - if (mWidgetWindow.GetKeyFlags() == .Ctrl | .Shift) + if (mWidgetWindow.GetKeyFlags(true) == .Ctrl | .Shift) { switch (keyCode) { diff --git a/BeefLibs/Beefy2D/src/widgets/ListView.bf b/BeefLibs/Beefy2D/src/widgets/ListView.bf index caf41ffa..ee75eb89 100644 --- a/BeefLibs/Beefy2D/src/widgets/ListView.bf +++ b/BeefLibs/Beefy2D/src/widgets/ListView.bf @@ -910,7 +910,7 @@ namespace Beefy.widgets switch (keyCode) { case (KeyCode)'A': - if ((mAllowMultiSelect) && (mWidgetWindow.GetKeyFlags() == KeyFlags.Ctrl)) + if ((mAllowMultiSelect) && (mWidgetWindow.GetKeyFlags(true) == KeyFlags.Ctrl)) { mRoot.WithItems(scope (listViewItem) => { diff --git a/BeefLibs/Beefy2D/src/widgets/Widget.bf b/BeefLibs/Beefy2D/src/widgets/Widget.bf index 173fdb8c..46bb27d1 100644 --- a/BeefLibs/Beefy2D/src/widgets/Widget.bf +++ b/BeefLibs/Beefy2D/src/widgets/Widget.bf @@ -747,18 +747,32 @@ namespace Beefy.widgets } } + public virtual void MouseWheel(MouseEvent evt) + { + if (!evt.mHandled) + { + MouseWheel(evt.mX, evt.mY, evt.mWheelDeltaX, evt.mWheelDeltaY); + + MarkDirty(); + + if (mParent != null) + { + MouseEvent parentEvt = scope .(); + parentEvt.mWheelDeltaX = evt.mWheelDeltaX; + parentEvt.mWheelDeltaY = evt.mWheelDeltaY; + parentEvt.mSender = evt.mSender; + + // Keep passing it up until some is interested in using it... + SelfToParentTranslate(evt.mX, evt.mY, out parentEvt.mX, out parentEvt.mY); + + mParent.MouseWheel(parentEvt); + } + } + } + public virtual void MouseWheel(float x, float y, float deltaX, float deltaY) { - MarkDirty(); - - if (mParent != null) - { - // Keep passing it up until some is interested in using it... - float aX; - float aY; - SelfToParentTranslate(x, y, out aX, out aY); - mParent.MouseWheel(aX, aY, deltaX, deltaY); - } + } public virtual void MouseUp(float x, float y, int32 btn) diff --git a/BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf b/BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf index ccd48a77..5a4a2c24 100644 --- a/BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf +++ b/BeefLibs/Beefy2D/src/widgets/WidgetWindow.bf @@ -17,6 +17,7 @@ namespace Beefy.widgets public delegate void WindowMovedHandler(BFWindow window); public delegate void MouseWheelHandler(MouseEvent mouseEvent); public delegate void KeyDownHandler(KeyDownEvent keyboardEvent); + public delegate void KeyUpHandler(KeyCode keyCode); //public delegate void CloseTemporaryHandler(WidgetWindow window); public delegate void DragDropFileHandler(StringView filePath); @@ -33,6 +34,7 @@ namespace Beefy.widgets public Event mOnMouseWheel ~ _.Dispose(); public Event mOnMenuItemSelected ~ _.Dispose(); public Event mOnWindowKeyDown ~ _.Dispose(); + public Event mOnWindowKeyUp ~ _.Dispose(); public Event mOnHitTest ~ _.Dispose(); public Event mOnDragDropFile ~ _.Dispose(); @@ -122,7 +124,12 @@ namespace Beefy.widgets } } - public KeyFlags GetKeyFlags() +#if BF_PLATFORM_WINDOWS + [CLink, CallingConvention(.Stdcall)] + static extern int16 GetKeyState(int nVirtKey); +#endif + + public KeyFlags GetKeyFlags(bool onlyHeldKeys) { KeyFlags keyFlags = default; if (IsKeyDown(KeyCode.Shift)) @@ -131,6 +138,17 @@ namespace Beefy.widgets keyFlags |= KeyFlags.Ctrl; if (IsKeyDown(KeyCode.Menu)) keyFlags |= KeyFlags.Alt; + +#if BF_PLATFORM_WINDOWS + if (!onlyHeldKeys) + { + if (GetKeyState((.)KeyCode.CapsLock) != 0) + keyFlags |= .CapsLock; + if (GetKeyState((.)KeyCode.Numlock) != 0) + keyFlags |= .NumLock; + } +#endif + return keyFlags; } @@ -414,7 +432,7 @@ namespace Beefy.widgets KeyDownEvent e = scope KeyDownEvent(); e.mSender = this; - e.mKeyFlags = GetKeyFlags(); + e.mKeyFlags = GetKeyFlags(false); e.mKeyCode = (KeyCode)keyCode; e.mIsRepeat = isRepeat != 0; @@ -450,6 +468,8 @@ namespace Beefy.widgets var fakeFocusWindow = GetFakeFocusWindow(); if (fakeFocusWindow != null) fakeFocusWindow.KeyUp(keyCode); + + mOnWindowKeyUp((.)keyCode); } public override HitTestResult HitTest(int32 x, int32 y) @@ -650,7 +670,7 @@ namespace Beefy.widgets let oldFlags = mMouseFlags; if (mMouseFlags == 0) - mMouseDownKeyFlags = GetKeyFlags(); + mMouseDownKeyFlags = GetKeyFlags(true); mMouseFlags |= (MouseFlag)(1 << btn); if ((!mHasFocus) && (mParent == null)) @@ -802,7 +822,14 @@ namespace Beefy.widgets float childX; float childY; aWidget.RootToSelfTranslate(mMouseX, mMouseY, out childX, out childY); - aWidget.MouseWheel(childX, childY, deltaX, deltaY); + + MouseEvent anEvent = scope MouseEvent(); + anEvent.mX = childX; + anEvent.mY = childY; + anEvent.mWheelDeltaX = deltaX; + anEvent.mWheelDeltaY = deltaY; + anEvent.mSender = this; + aWidget.MouseWheel(anEvent); } } diff --git a/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf b/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf index b5da6953..1fcb5015 100644 --- a/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf +++ b/BeefLibs/corlib/src/Diagnostics/SpawnedProcess.bf @@ -35,6 +35,16 @@ namespace System.Diagnostics } } + public int ProcessId + { + get + { + if (mSpawn == null) + return -1; + return Platform.BfpSpawn_GetProcessId(mSpawn); + } + } + public this() { mSpawn = null; diff --git a/BeefLibs/corlib/src/Platform.bf b/BeefLibs/corlib/src/Platform.bf index eddafe9d..87e788bc 100644 --- a/BeefLibs/corlib/src/Platform.bf +++ b/BeefLibs/corlib/src/Platform.bf @@ -243,6 +243,8 @@ namespace System public static void BfpProcess_GetProcessName(BfpProcess* process, char8* outName, int32* inOutNameSize, BfpProcessResult* outResult) => Runtime.NotImplemented(); public static int32 BfpProcess_GetProcessId(BfpProcess* process) => Runtime.NotImplemented(); + + public static int BfpSpawn_GetProcessId(BfpSpawn* spawn) => Runtime.NotImplemented();; #endif public enum BfpSpawnFlags : int32 @@ -286,6 +288,8 @@ namespace System public static extern bool BfpSpawn_WaitFor(BfpSpawn* spawn, int waitMS, int* outExitCode, BfpSpawnResult* outResult); [CallingConvention(.Stdcall), CLink] public static extern void BfpSpawn_GetStdHandles(BfpSpawn* spawn, BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr); + [CallingConvention(.Stdcall), CLink] + public static extern int BfpSpawn_GetProcessId(BfpSpawn* spawn); [CallingConvention(.Stdcall), CLink] public static extern int BfpProcess_GetCurrentId(); diff --git a/BeefLibs/corlib/src/Windows.bf b/BeefLibs/corlib/src/Windows.bf index a0fe1b44..83ac3741 100644 --- a/BeefLibs/corlib/src/Windows.bf +++ b/BeefLibs/corlib/src/Windows.bf @@ -101,6 +101,12 @@ namespace System public function int WndProc(HWnd hWnd, int32 msg, int wParam, int lParam); public delegate IntBool EnumThreadWindowsCallback(HWnd hWnd, void* extraParameter); + public struct Rect : this(int32 left, int32 top, int32 right, int32 bottom) + { + public int32 Width => right - left; + public int32 Height => bottom - top; + } + [CRepr] public struct OpenFileName { @@ -1737,6 +1743,9 @@ namespace System public static extern int SetWindowLongPtrW(int hWnd, int32 nIndex, int value); #endif + [Import("user32.lib"), CLink, CallingConvention(.Stdcall)] + public static extern IntBool SetWindowPos(HWnd hWnd, HWnd hWndAfter, int x, int y, int cx, int cy, int flags); + [Import("user32.lib"), CLink, CallingConvention(.Stdcall)] public static extern IntBool PostMessageW(HWnd hWnd, int32 msg, int wParam, int lParam); @@ -1773,6 +1782,9 @@ namespace System [Import("user32.lib"), CLink, CallingConvention(.Stdcall)] public static extern int32 GetWindowTextA(HWnd hWnd, char8* ptr, int32 length); + [Import("user32.lib"), CLink, CallingConvention(.Stdcall)] + public static extern IntBool AdjustWindowRectEx(ref Rect rect, uint32 style, IntBool menu, uint32 exStyle); + [CLink, CallingConvention(.Stdcall)] public static extern int32 GetWindowThreadProcessId(HWnd handle, out int32 processId); diff --git a/BeefySysLib/platform/PlatformInterface.h b/BeefySysLib/platform/PlatformInterface.h index c470544c..cdafc966 100644 --- a/BeefySysLib/platform/PlatformInterface.h +++ b/BeefySysLib/platform/PlatformInterface.h @@ -217,6 +217,7 @@ BFP_EXPORT void BFP_CALLTYPE BfpSpawn_Release(BfpSpawn* spawn); BFP_EXPORT void BFP_CALLTYPE BfpSpawn_Kill(BfpSpawn* spawn, int exitCode, BfpKillFlags killFlags, BfpSpawnResult* outResult); BFP_EXPORT bool BFP_CALLTYPE BfpSpawn_WaitFor(BfpSpawn* spawn, int waitMS, int* outExitCode, BfpSpawnResult* outResult); BFP_EXPORT void BFP_CALLTYPE BfpSpawn_GetStdHandles(BfpSpawn* spawn, BfpFile** outStdIn, BfpFile** outStdOut, BfpFile** outStdErr); // Caller must release the files +BFP_EXPORT int BFP_CALLTYPE BfpSpawn_GetProcessId(BfpSpawn* spawn); enum BfpThreadCreateFlags { diff --git a/BeefySysLib/platform/posix/PosixCommon.cpp b/BeefySysLib/platform/posix/PosixCommon.cpp index ad7e564c..ae49def1 100644 --- a/BeefySysLib/platform/posix/PosixCommon.cpp +++ b/BeefySysLib/platform/posix/PosixCommon.cpp @@ -1241,6 +1241,11 @@ BFP_EXPORT void BFP_CALLTYPE BfpSpawn_GetStdHandles(BfpSpawn* spawn, BfpFile** o } } +BFP_EXPORT int BFP_CALLTYPE BfpSpawn_GetProcessId(BfpSpawn* spawn); +{ + return spawn->mPid; +} + bool BfpSpawn_WaitFor(BfpSpawn* spawn, int waitMS, int* outExitCode, BfpSpawnResult* outResult) { OUTRESULT(BfpSpawnResult_Ok); diff --git a/BeefySysLib/platform/win/Platform.cpp b/BeefySysLib/platform/win/Platform.cpp index dbda54d2..27bb6bb5 100644 --- a/BeefySysLib/platform/win/Platform.cpp +++ b/BeefySysLib/platform/win/Platform.cpp @@ -2098,6 +2098,11 @@ BFP_EXPORT void BFP_CALLTYPE BfpSpawn_GetStdHandles(BfpSpawn* spawn, BfpFile** o } } +BFP_EXPORT int BFP_CALLTYPE BfpSpawn_GetProcessId(BfpSpawn* spawn) +{ + return spawn->mProcessId; +} + /// BfpThread BFP_EXPORT BfpThread* BFP_CALLTYPE BfpThread_Create(BfpThreadStartProc startProc, void* threadParam, intptr stackSize, BfpThreadCreateFlags flags, BfpThreadId* outThreadId) diff --git a/IDE/src/Commands.bf b/IDE/src/Commands.bf index f4c5ca86..7a51bcd3 100644 --- a/IDE/src/Commands.bf +++ b/IDE/src/Commands.bf @@ -309,6 +309,8 @@ namespace IDE Add("Show File Externally", new => gApp.Cmd_ShowFileExternally); Add("Show Find Results", new => gApp.ShowFindResults); Add("Show Fixit", new => gApp.Cmd_ShowFixit); + Add("Show Terminal", new => gApp.ShowTerminal); + Add("Show Console", new => gApp.ShowConsole); Add("Show Immediate", new => gApp.ShowImmediatePanel); Add("Show Memory", new => gApp.ShowMemory); Add("Show Modules", new => gApp.ShowModules); diff --git a/IDE/src/IDEApp.bf b/IDE/src/IDEApp.bf index 5c345cec..54e5eb71 100644 --- a/IDE/src/IDEApp.bf +++ b/IDE/src/IDEApp.bf @@ -160,6 +160,7 @@ namespace IDE public PropertiesPanel mPropertiesPanel; public Font mTinyCodeFont ~ delete _; public Font mCodeFont ~ delete _; + public Font mTermFont ~ delete _; protected bool mInitialized; public bool mConfig_NoIR; public bool mFailed; @@ -196,6 +197,8 @@ namespace IDE public bool mWantShowOutput; public OutputPanel mOutputPanel; + public TerminalPanel mTerminalPanel; + public ConsolePanel mConsolePanel; public ImmediatePanel mImmediatePanel; public FindResultsPanel mFindResultsPanel; public WatchPanel mAutoWatchPanel; @@ -711,6 +714,8 @@ namespace IDE RemoveAndDelete!(mProjectPanel); RemoveAndDelete!(mClassViewPanel); RemoveAndDelete!(mOutputPanel); + RemoveAndDelete!(mTerminalPanel); + RemoveAndDelete!(mConsolePanel); RemoveAndDelete!(mImmediatePanel); RemoveAndDelete!(mFindResultsPanel); RemoveAndDelete!(mAutoWatchPanel); @@ -815,6 +820,8 @@ namespace IDE dlg(mProjectPanel); dlg(mClassViewPanel); dlg(mOutputPanel); + dlg(mTerminalPanel); + dlg(mConsolePanel); dlg(mImmediatePanel); dlg(mFindResultsPanel); dlg(mAutoWatchPanel); @@ -5152,6 +5159,18 @@ namespace IDE ShowPanel(mAutoWatchPanel, "Auto Watches"); } + [IDECommand] + public void ShowTerminal() + { + ShowPanel(mTerminalPanel, "Terminal"); + } + + [IDECommand] + public void ShowConsole() + { + ShowPanel(mConsolePanel, "Console"); + } + [IDECommand] public void ShowImmediatePanel() { @@ -5924,12 +5943,14 @@ namespace IDE AddMenuItem(subMenu, "&Diagnostics", "Show Diagnostics"); AddMenuItem(subMenu, "E&rrors", "Show Errors"); AddMenuItem(subMenu, "&Find Results", "Show Find Results"); + AddMenuItem(subMenu, "&Terminal", "Show Terminal"); + AddMenuItem(subMenu, "Co&nsole", "Show Console"); AddMenuItem(subMenu, "&Immediate Window", "Show Immediate"); AddMenuItem(subMenu, "&Memory", "Show Memory"); AddMenuItem(subMenu, "Mod&ules", "Show Modules"); AddMenuItem(subMenu, "&Output", "Show Output"); AddMenuItem(subMenu, "&Profiler", "Show Profiler"); - AddMenuItem(subMenu, "&Threads", "Show Threads"); + AddMenuItem(subMenu, "T&hreads", "Show Threads"); AddMenuItem(subMenu, "&Watches", "Show Watches"); AddMenuItem(subMenu, "Work&space Explorer", "Show Workspace Explorer"); subMenu.AddMenuItem(null); @@ -6076,6 +6097,7 @@ namespace IDE public void SetupNewWindow(WidgetWindow window, bool isMainWindow) { window.mOnWindowKeyDown.Add(new => SysKeyDown); + window.mOnWindowKeyUp.Add(new => SysKeyUp); window.mOnMouseUp.Add(new => MouseUp); if (isMainWindow) window.mOnWindowCloseQuery.Add(new => SecondaryAllowClose); @@ -8228,6 +8250,9 @@ namespace IDE NOP!(); } + mConsolePanel.SysKeyDown(evt); + //mTerminalPanel.SysKeyDown(evt); + if (evt.mHandled) return; @@ -8276,7 +8301,7 @@ namespace IDE { var keyState = scope KeyState(); keyState.mKeyCode = evt.mKeyCode; - keyState.mKeyFlags = evt.mKeyFlags; + keyState.mKeyFlags = evt.mKeyFlags.HeldKeys; var curKeyMap = mCommands.mKeyMap; @@ -8377,7 +8402,7 @@ namespace IDE //if (focusWidget is DisassemblyPanel) //break; - if (evt.mKeyFlags == 0) // No ctrl/shift/alt + if (evt.mKeyFlags.HeldKeys == 0) // No ctrl/shift/alt { switch (evt.mKeyCode) { @@ -8398,6 +8423,12 @@ namespace IDE } } } + + void SysKeyUp(KeyCode keyCode) + { + //mTerminalPanel.SysKeyUp(keyCode); + mConsolePanel.SysKeyUp(keyCode); + } void ShowOpenFileInSolutionDialog() { @@ -12242,6 +12273,7 @@ namespace IDE mTinyCodeFont = new Font(); mCodeFont = new Font(); + mTermFont = new Font(); //mCodeFont = Font.LoadFromFile(BFApp.sApp.mInstallDir + "fonts/SourceCodePro32.fnt"); @@ -12268,6 +12300,10 @@ namespace IDE mClassViewPanel.mAutoDelete = false; mOutputPanel = new OutputPanel(true); mOutputPanel.mAutoDelete = false; + mTerminalPanel = new TerminalPanel(); + mTerminalPanel.mAutoDelete = false; + mConsolePanel = new ConsolePanel(); + mConsolePanel.mAutoDelete = false; mImmediatePanel = new ImmediatePanel(); mImmediatePanel.mAutoDelete = false; mFindResultsPanel = new FindResultsPanel(); @@ -12395,6 +12431,7 @@ namespace IDE mMainWindow.mIsMainWindow = true; mMainWindow.mOnMouseUp.Add(new => MouseUp); mMainWindow.mOnWindowKeyDown.Add(new => SysKeyDown); + mMainWindow.mOnWindowKeyUp.Add(new => SysKeyUp); mMainWindow.mOnWindowCloseQuery.Add(new => AllowClose); mMainWindow.mOnDragDropFile.Add(new => DragDropFile); CreateMenu(); @@ -12619,6 +12656,8 @@ namespace IDE mTinyCodeFont.AddAlternate(new String("fonts/seguihis.ttf"), tinyFontSize);*/ } + mTermFont.Load("Cascadia Mono Regular", fontSize); + if (!err.IsEmpty) { OutputErrorLine(err); diff --git a/IDE/src/ui/AboutDialog.bf b/IDE/src/ui/AboutDialog.bf index 903e69a0..d5481fd4 100644 --- a/IDE/src/ui/AboutDialog.bf +++ b/IDE/src/ui/AboutDialog.bf @@ -242,7 +242,7 @@ namespace IDE.ui { base.KeyDown(keyCode, isRepeat); - if ((keyCode == (.)'C') && (mWidgetWindow.GetKeyFlags() == .Ctrl)) + if ((keyCode == (.)'C') && (mWidgetWindow.GetKeyFlags(true) == .Ctrl)) { String versionInfo = scope String(); versionInfo.AppendF("Beef IDE Version {}", gApp.mVersionInfo.FileVersion); diff --git a/IDE/src/ui/ConsolePanel.bf b/IDE/src/ui/ConsolePanel.bf new file mode 100644 index 00000000..ca273eb1 --- /dev/null +++ b/IDE/src/ui/ConsolePanel.bf @@ -0,0 +1,1031 @@ +#pragma warning disable 168 + +using System; +using Beefy.geom; +using Beefy.gfx; +using System.Text; +using Beefy.theme.dark; +using System.Security.Cryptography; +using Beefy.widgets; +using Beefy.events; +using System.Diagnostics; +using Beefy.utils; +using System.Threading; + +namespace IDE.ui; + +class ConsolePanel : Panel +{ + [CRepr] + struct CONSOLE_SCREEN_BUFFER_INFOEX + { + public uint32 mSize; + public int16 mWidth; + public int16 mHeight; + public uint16 mCursorX; + public uint16 mCursorY; + public uint16 wAttributes; + public RECT mWindowRect; + public POINT mMaximumWindowSize; + public uint16 mPopupAttributes; + public Windows.IntBool mFullscreenSupported; + public uint32[16] mColorTable; + + public this() + { + this = default; + mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + } + } + + [CRepr] + struct POINT : this(int16 x, int16 y) + { + } + + [CRepr] + struct RECT : this(int16 left, int16 top, int16 right, int16 bottom) + { + public int16 Width => right - left; + public int16 Height => bottom - top; + } + + [CRepr] + struct CHAR_INFO + { + public char16 mChar; + public uint16 mAttributes; + } + + [CRepr] + struct CONSOLE_FONT_INFO + { + public uint32 mNumFont; + public POINT mSize; + } + + [CRepr] + struct CONSOLE_CURSOR_INFO + { + public uint32 mSize; + public uint32 mVisible; + } + + [CRepr] + struct CONSOLE_SELECTION_INFO + { + public uint32 mFlags; + public POINT mSelectionAnchor; + public RECT mSelection; + } + + [CRepr] + struct KEY_EVENT_RECORD + { + public int32 mKeyDown; + public uint16 mRepeatCount; + public uint16 mVirtualKeyCode; + public uint16 mVirtualScanCode; + public char16 mChar; + public uint32 mControlKeyState; + } + + [CRepr] + struct MOUSE_EVENT_RECORD + { + public POINT mMousePosition; + public uint32 mButtonState; + public uint32 mControlKeyState; + public uint32 mEventFlags; + } + + [CRepr] + struct INPUT_RECORD + { + public uint16 mEventType; + public INPUT_RECORD_DATA mEventData; + } + + [Union] + struct INPUT_RECORD_DATA + { + public KEY_EVENT_RECORD mKeyEvent; + public MOUSE_EVENT_RECORD mMouseEvent; + } + +#if BF_PLATFORM_WINDOWS + [CLink, CallingConvention(.Stdcall)] + public static extern void AllocConsole(); + + [CLink, CallingConvention(.Stdcall)] + public static extern void AttachConsole(int processId); + + [CLink, CallingConvention(.Stdcall)] + public static extern void FreeConsole(); + + [CLink, CallingConvention(.Stdcall)] + public static extern Windows.IntBool GetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); + + [CLink, CallingConvention(.Stdcall)] + public static extern Windows.IntBool SetConsoleScreenBufferInfoEx(Windows.Handle handle, ref CONSOLE_SCREEN_BUFFER_INFOEX info); + + [CLink] + public static extern Windows.IntBool ReadConsoleOutputW(Windows.Handle handle, void* buffer, POINT bufferSize, POINT bufferCoord, ref RECT readRegion); + + [CLink] + public static extern Windows.IntBool SetConsoleScreenBufferSize(Windows.Handle handle, POINT bufferSize); + + [CLink] + public static extern Windows.IntBool SetConsoleWindowInfo(Windows.Handle handle, Windows.IntBool absolute, in RECT window); + + [CLink] + public static extern Windows.HWnd GetConsoleWindow(); + + [CLink] + public static extern Windows.IntBool GetCurrentConsoleFont(Windows.Handle handle, Windows.IntBool maxWindow, out CONSOLE_FONT_INFO fontInfo); + + [CLink] + public static extern Windows.IntBool GetConsoleCursorInfo(Windows.Handle handle, out CONSOLE_CURSOR_INFO cursorInfo); + + [CLink] + public static extern Windows.IntBool GetConsoleSelectionInfo(out CONSOLE_SELECTION_INFO selectionInfo); + + [CLink] + public static extern Windows.IntBool WriteConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsWritten); + + [CLink] + public static extern Windows.IntBool ReadConsoleInputW(Windows.Handle handle, INPUT_RECORD* eventsPtr, int32 eventCount, out int32 numEventsRead); +#endif + + class View : Widget + { + public ConsolePanel mConsolePanel; + + public this(ConsolePanel ConsolePanel) + { + mConsolePanel = ConsolePanel; + } + + public override void MouseDown(float x, float y, int32 btn, int32 btnCount) + { + base.MouseDown(x, y, btn, btnCount); + + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + if (mConsolePanel.mMousePassThrough) + { + var cell = mConsolePanel.GetCell(x, y); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; + if (btnCount > 1) + input.mEventData.mMouseEvent.mEventFlags |= 2; + input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); + input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + else + { + if (btn == 0) + { + + } + else if (btn == 1) + { + var text = gApp.GetClipboardText(.. scope .()); + for (var c in text.DecodedChars) + { + INPUT_RECORD input = default; + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mKeyDown = 1; + input.mEventData.mKeyEvent.mRepeatCount = 1; + //input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyEvent.mKeyCode; + //input.mEventData.mKeyEvent.mVirtualScanCode = 61; + //input.mEventData.mKeyEvent.mControlKeyState = GetControlKeyState(keyEvent.mKeyFlags); + input.mEventData.mKeyEvent.mChar = (.)c; + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + } + } + } + + /*int flags = (.)mMouseFlags; + var window = GetConsoleWindow(); + + //Windows.SendMessageW(window, 0x0006, 0, 0); + Windows.SendMessageW(window, 0x0007, 0, 0); + //Windows.SetActiveWindow(window); + //Windows.SetFocus(window); + + if (btn == 0) + Windows.SendMessageW(window, 0x0201 /*WM_LBUTTONDOWN*/, flags, (int)x | ((int)y << 16)); + else if (btn == 1) + { + Windows.SendMessageW(window, 0x0204 /*WM_RBUTTONDOWN*/, flags, (int)x | ((int)y << 16)); + + //Windows.SendMessageW(window, 0x0100, 0, 0); + }*/ + } + + public override void MouseMove(float x, float y) + { + base.MouseMove(x, y); + + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + var cell = mConsolePanel.GetCell(x, y); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; + input.mEventData.mMouseEvent.mEventFlags |= 1; /* MOUSE_MOVED */ + input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); + input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + + /*var window = GetConsoleWindow(); + Windows.SendMessageW(window, 0x0200 /*WM_MOUSEMOVE*/, 0, (int)x | ((int)y << 16));*/ + } + + public override void MouseUp(float x, float y, int32 btn) + { + base.MouseUp(x, y, btn); + + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + var cell = mConsolePanel.GetCell(x, y); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)mMouseFlags; + //input.mEventData.mMouseEvent.mEventFlags |= 1; /* MOUSE_MOVED */ + input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); + input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags(false)); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten); + + /*var window = GetConsoleWindow(); + Windows.SendMessageW(window, 0x0202 /*WM_LBUTTONUP*/, 0, (int)x | ((int)y << 16));*/ + } + + public override void KeyDown(KeyDownEvent keyEvent) + { + base.KeyDown(keyEvent); + + if (keyEvent.mKeyCode == .Insert) + { + ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName("Powershell.exe"); + + String resultStr = scope String(); + var spawn = scope SpawnedProcess(); + spawn.Start(procInfo); + } + + if (keyEvent.mKeyCode == .Tilde) + { + if (mConsolePanel.mHasConsole) + { + mConsolePanel.Detach(); + } + else + { + mConsolePanel.Attach(); + + ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName("Powershell.exe"); + + String resultStr = scope String(); + mConsolePanel.mExecSpawn = new SpawnedProcess(); + mConsolePanel.mExecSpawn.Start(procInfo); + } + } + } + + public override void KeyUp(KeyCode keyCode) + { + base.KeyUp(keyCode); + + + } + + public override void GotFocus() + { + base.GotFocus(); + mConsolePanel.mCursorBlinkTicks = 0; + } + + public override void MouseWheel(MouseEvent evt) + { + if ((mConsolePanel.mPaused) || (mConsolePanel.mHasConsole)) + { + base.MouseWheel(evt); + return; + } + + /*var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + var cell = mConsolePanel.GetCell(evt.mX, evt.mY); + INPUT_RECORD input = default; + input.mEventType = 2 /*MOUSE_EVENT */; + input.mEventData.mMouseEvent.mButtonState = (.)((int32)mMouseFlags | ((int32)evt.mWheelDeltaY << 16)); + input.mEventData.mMouseEvent.mEventFlags |= 4 /* MOUSE_WHEELED */; + input.mEventData.mMouseEvent.mMousePosition = .((.)cell.mX, (.)cell.mY); + input.mEventData.mMouseEvent.mControlKeyState = mConsolePanel.GetControlKeyState(mWidgetWindow.GetKeyFlags()); + WriteConsoleInputW(inHandle, &input, 1, var numEVentsWritten);*/ + + float x = evt.mX; + float y = evt.mY; + + var window = GetConsoleWindow(); + Windows.SendMessageW(window, 0x0007, 0, 0); // WM_SETFOCUS + //Windows.SendMessageW(window, 0x0006, 0, 0); // WM_ACTIVATE + + + Windows.SendMessageW(window, 0x0200 /*WM_MOUSEMOVE*/, 0, (int)x | ((int)y << 16)); + Windows.SendMessageW(window, 0x020A /*WM_MOUSEWHEEL*/, (int32)(120 * evt.mWheelDeltaY) << 16, (int)x | ((int)y << 16)); + } + } + + class ScreenInfo + { + public CONSOLE_SCREEN_BUFFER_INFOEX mInfo; + public CONSOLE_CURSOR_INFO mCursorInfo; + public CONSOLE_SELECTION_INFO mSelectionInfo; + public int32 mScrollTop; + public CHAR_INFO* mCharInfo; + public CHAR_INFO* mFullCharInfo; + + public int32 WindowWidth => mInfo.mWindowRect.Width; + public int32 WindowHeight => mInfo.mWindowRect.Height; + + public ~this() + { + delete mCharInfo; + delete mFullCharInfo; + } + + public int GetHashCode() + { + MD5 md5 = scope .(); + md5.Update(.((.)&mInfo, sizeof(CONSOLE_SCREEN_BUFFER_INFOEX))); + md5.Update(.((.)&mSelectionInfo, sizeof(CONSOLE_SELECTION_INFO))); + md5.Update(.((.)&mCursorInfo, sizeof(CONSOLE_CURSOR_INFO))); + md5.Update(.((.)mCharInfo, (int32)mInfo.mWindowRect.Width * mInfo.mWindowRect.Height * sizeof(CHAR_INFO))); + var hash = md5.Finish(); + return hash.GetHashCode(); + } + } + + public int mLastDrawnHashCode; + public DarkScrollbar mScrollbar; + public ScrollableWidget mScrollableWidget; + public int32 mCellWidth; + public int32 mCellHeight; + public bool mPaused; + ScreenInfo mScreenInfo ~ delete _; + View mView; + int mCursorBlinkTicks; + SpawnedProcess mCmdSpawn ~ delete _; + SpawnedProcess mExecSpawn ~ delete _; + bool mHasConsole; + bool mMousePassThrough; + (POINT start, POINT end)? mSelection; + + public this() + { + /*mScrollbar = new DarkScrollbar(); + mScrollbar.mOrientation = .Vert; + mScrollbar.Init(); + AddWidget(mScrollbar);*/ + + mScrollableWidget = new ScrollableWidget(); + mScrollableWidget.InitScrollbars(false, true); + AddWidget(mScrollableWidget); + + mView = new View(this); + mView.mAutoFocus = true; + mScrollableWidget.mScrollContentContainer.AddWidget(mView); + mScrollableWidget.mScrollContent = mView; + + mScrollableWidget.mVertScrollbar.mOnScrollEvent.Add(new (evt) => + { + mPaused = true; + }); + } + + public ~this() + { + mCmdSpawn?.Kill(); + mExecSpawn?.Kill(); + } + + public override void Serialize(StructuredData data) + { + base.Serialize(data); + + data.Add("Type", "ConsolePanel"); + } + + public override void AddedToParent() + { + base.AddedToParent(); + + } + + public void Attach() + { + if (mHasConsole) + return; + + mHasConsole = true; + +#if BF_PLATFORM_WINDOWS + //AllocConsole(); + + /*ProcessStartInfo procInfo = scope ProcessStartInfo(); + procInfo.UseShellExecute = false; + procInfo.SetFileName(scope $"{gApp.mInstallDir}/BeefCon_d.exe"); + procInfo.SetArguments(scope $"{Process.CurrentId}"); + + String resultStr = scope String(); + mCmdSpawn = new SpawnedProcess(); + mCmdSpawn.Start(procInfo); + + Thread.Sleep(2000); + + var processId = mCmdSpawn.ProcessId; + if (processId > 0) + AttachConsole(processId); + else*/ + AllocConsole(); + + var window = GetConsoleWindow(); + Windows.SetWindowPos(window, default, 0, 0, 0, 0, 0x290 /* SWP_NOACTIVATE | SWP_NOREPOSITION | SWP_HIDEWINDOW */); + + ResizeComponents(); +#endif + } + + public void Detach() + { + if (!mHasConsole) + return; + + mHasConsole = false; + +#if BF_PLATFORM_WINDOWS + FreeConsole(); +#endif + + mCmdSpawn?.Kill(); + DeleteAndNullify!(mCmdSpawn); + mExecSpawn?.Kill(); + DeleteAndNullify!(mExecSpawn); + } + + public override void Update() + { + base.Update(); + + if (mScrollableWidget.mVertScrollbar.mThumb.mMouseDown) + mPaused = true; + + if (!mPaused) + { + ScreenInfo newScreenInfo = new .(); + if (GetScreenInfo(newScreenInfo)) + { + delete mScreenInfo; + mScreenInfo = newScreenInfo; + } + else + { + Detach(); + delete newScreenInfo; + } + } + + if (mScreenInfo != null) + { + if ((mPaused) || (!mHasConsole)) + { + mScreenInfo.mScrollTop = (.)(mScrollableWidget.mVertScrollbar.mContentPos / mCellHeight); + + int windowHeight = mScreenInfo.mInfo.mWindowRect.Height; + mScreenInfo.mInfo.mWindowRect.top = (.)mScreenInfo.mScrollTop; + mScreenInfo.mInfo.mWindowRect.bottom = (.)(mScreenInfo.mScrollTop + windowHeight); + + UpdateScreenInfo(mScreenInfo); + } + } + + //mPaused = false; + + /*if (mUpdatingScrollPos) + { + int32 windowHeight = screenInfo.WindowHeight; + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + + screenInfo.mInfo.mWindowRect.top = (.)(mScrollableWidget.mVertScrollbar.mContentPos / mCellHeight); + screenInfo.mInfo.mWindowRect.bottom = (.)(screenInfo.mInfo.mWindowRect.top + windowHeight); + + var result = SetConsoleScreenBufferInfoEx(outHandle, ref screenInfo.mInfo); + + CONSOLE_SCREEN_BUFFER_INFOEX info = .(); + GetConsoleScreenBufferInfoEx(outHandle, ref info); + + mUpdatingScrollPos = false; + }*/ + + if (mWidgetWindow.IsKeyDown(.Control)) + { + if (mUpdateCnt % 30 == 0) + { + //var window = GetConsoleWindow(); + //Windows.SetWindowPos(window, default, 0, 0, 0, 0, 0x93); + /*screenInfo.mInfo.mColorTable[7] = 0xCCCCCC; + screenInfo.mInfo.mWindowRect.top++; + screenInfo.mInfo.mWindowRect.bottom++; + + //screenInfo.mInfo.mCursorY--; + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + SetConsoleScreenBufferInfoEx(outHandle, ref screenInfo.mInfo);*/ + } + } + + int hashCode = (mScreenInfo?.GetHashCode()).GetValueOrDefault(); + + if (hashCode != mLastDrawnHashCode) + { + mLastDrawnHashCode = hashCode; + MarkDirty(); + } + + //float height = mScreenInfo.mInfo.mHeight * mCellHeight; + //mScrollableWidget.mScrollContent.Resize(0, 0, 0, height); + //mScrollableWidget.RehupSize(); + + mCursorBlinkTicks++; + if (mView.mHasFocus) + MarkDirty(); + } + + public bool GetScreenInfo(ScreenInfo screenInfo) + { + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFOEX info = default; + info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); + +#if BF_PLATFORM_WINDOWS + if (!GetConsoleScreenBufferInfoEx(outHandle, ref info)) + return false; +#endif + info.mWindowRect.right++; + info.mWindowRect.bottom++; + screenInfo.mInfo = info; + + screenInfo.mScrollTop = info.mWindowRect.top; + + mScrollableWidget.VertScrollTo(screenInfo.mInfo.mWindowRect.top * mCellHeight); + + int width = info.mWindowRect.Width; + int height = info.mWindowRect.Height; + + POINT bufferSize = .(info.mWindowRect.Width, info.mWindowRect.Height); + screenInfo.mCharInfo = new .[(int32)info.mWindowRect.Width * info.mWindowRect.Height]*; + RECT readRegion = .(screenInfo.mInfo.mWindowRect.left, (.)screenInfo.mScrollTop, screenInfo.mInfo.mWindowRect.right, (.)(screenInfo.mScrollTop + screenInfo.mInfo.mWindowRect.Height - 1)); +#if BF_PLATFORM_WINDOWS + ReadConsoleOutputW(outHandle, screenInfo.mCharInfo, bufferSize, POINT(0, 0), ref readRegion); + + GetConsoleCursorInfo(outHandle, out screenInfo.mCursorInfo); + GetConsoleSelectionInfo(out screenInfo.mSelectionInfo); +#endif + return true; + } + + public bool GetFullScreenInfo(ScreenInfo screenInfo) + { + if (screenInfo.mFullCharInfo != null) + return true; + + if (screenInfo.mCharInfo == null) + { + if (!GetScreenInfo(screenInfo)) + return false; + } + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + POINT bufferSize = .(screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); + screenInfo.mFullCharInfo = new .[(int32)screenInfo.mInfo.mWidth * screenInfo.mInfo.mHeight]*; + RECT readRegion = .(0, 0, screenInfo.mInfo.mWidth, screenInfo.mInfo.mHeight); +#if BF_PLATFORM_WINDOWS + ReadConsoleOutputW(outHandle, screenInfo.mFullCharInfo, bufferSize, POINT(0, 0), ref readRegion); +#endif + + return true; + } + + public bool UpdateScreenInfo(ScreenInfo screenInfo) + { + if (screenInfo.mFullCharInfo == null) + { + if (!GetFullScreenInfo(screenInfo)) + return false; + } + + Internal.MemCpy(screenInfo.mCharInfo, + screenInfo.mFullCharInfo + screenInfo.mScrollTop * screenInfo.mInfo.mWidth, + screenInfo.mInfo.mWindowRect.Width * screenInfo.mInfo.mWindowRect.Height * sizeof(CHAR_INFO)); + return true; + } + + public Vector2 GetCoord(int col, int row) + { + return .(col * mCellWidth + GS!(6), row * mCellHeight + GS!(4)); + } + + public Vector2 GetCell(float x, float y) + { + return .((x - GS!(6)) / mCellWidth, (y - GS!(4)) / mCellHeight); + } + + public override void Draw(Graphics g) + { + base.Draw(g); + + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + String str = scope .(" "); + + uint32[16] colorTable = .(0xFF000000, ); + if (mScreenInfo != null) + { + for (int i < 16) + { + colorTable[i] = 0xFF000000 | + ((mScreenInfo.mInfo.mColorTable[i] >> 16) & 0x0000FF) | + ((mScreenInfo.mInfo.mColorTable[i] ) & 0x00FF00) | + ((mScreenInfo.mInfo.mColorTable[i] << 16) & 0xFF0000); + } + } + + g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.EditBox), 0, 0, mWidth, mScrollableWidget.mHeight); + using (g.PushColor(colorTable[0])) + g.FillRect(GS!(2), GS!(2), mScrollableWidget.mVertScrollbar.mX - GS!(0), mScrollableWidget.mHeight - GS!(4)); + if (mView.mHasFocus) + { + using (g.PushColor(DarkTheme.COLOR_SELECTED_OUTLINE)) + g.DrawBox(DarkTheme.sDarkTheme.GetImage(DarkTheme.ImageIdx.Outline), 0, 0, mWidth, mHeight); + } + + if (mScreenInfo != null) + { + g.SetFont(gApp.mTermFont); + using (g.PushClip(0, 0, mScrollableWidget.mVertScrollbar.mX, mScrollableWidget.mHeight - GS!(2))) + { + int32 numVisibleCols = mScreenInfo.WindowWidth; + int32 numVisibleRows = mScreenInfo.WindowHeight; + + for (int32 row < numVisibleRows) + { + for (int32 col < numVisibleCols) + { + int srcRow = row + mScreenInfo.mScrollTop; + + var coord = GetCoord(col, row); + var cInfo = mScreenInfo.mCharInfo[row * mScreenInfo.WindowWidth + col]; + + int32 attrs = cInfo.mAttributes; + + if (mScreenInfo.mSelectionInfo.mFlags != 0) + { + //TODO: Fix rendering, listen to flags. + + bool selected = false; + if (srcRow == mScreenInfo.mSelectionInfo.mSelection.top) + { + selected = (col >= mScreenInfo.mSelectionInfo.mSelection.left); + if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) + selected &= (col <= mScreenInfo.mSelectionInfo.mSelection.right); + } + else if ((srcRow > mScreenInfo.mSelectionInfo.mSelection.top) && (srcRow < mScreenInfo.mSelectionInfo.mSelection.bottom)) + { + selected = true; + } + else if (srcRow == mScreenInfo.mSelectionInfo.mSelection.bottom) + { + selected = (col <= mScreenInfo.mSelectionInfo.mSelection.right); + } + + if (selected) + attrs ^= 0xFF; + } + + uint32 fgColor = colorTable[(attrs & 0xF)]; + uint32 bgColor = colorTable[(attrs >> 4)]; + + + using (g.PushColor(bgColor)) + { + int32 fillX = (.)coord.mX; + int32 fillY = (.)coord.mY; + int32 fillWidth = mCellWidth; + int32 fillHeight = mCellHeight; + + /*if (col == 0) + { + fillX -= GS!(4); + fillWidth += GS!(4); + } + + if (row == 0) + { + fillY -= GS!(2); + fillHeight += GS!(2); + } + + if (row == numVisibleRows - 1) + { + + } + + g.FillRect( + fillX, fillY, + (col == numVisibleCols - 1) ? (mScrollableWidget.mVertScrollbar.mX - coord.mX + 1) : fillWidth, + (row == numVisibleRows - 1) ? (mView.mHeight - coord.mY) : fillHeight + );*/ + g.FillRect(fillX, fillY, fillWidth, fillHeight); + } + + if (cInfo.mChar > .(32)) + { + str[0] = (.)cInfo.mChar; + using (g.PushColor(fgColor)) + gApp.mTermFont.Draw(g, str, coord.mX, coord.mY); + } + } + } + + if ((mView.mHasFocus) && (mHasConsole) && (!mPaused)) + { + float brightness = (float)Math.Cos(Math.Max(0.0f, mCursorBlinkTicks - 20) / 9.0f); + brightness = Math.Clamp(brightness * 2.0f + 1.6f, 0, 1); + if (mScrollableWidget.mVertPos.IsMoving) + brightness = 0; // When we animate a pgup or pgdn, it's weird seeing the cursor scrolling around + + if (brightness > 0) + { + using (g.PushColor(Color.Get(brightness))) + { + var cursorCoord = GetCoord(mScreenInfo.mInfo.mCursorX, mScreenInfo.mInfo.mCursorY - mScreenInfo.mScrollTop); + if (mScreenInfo.mCursorInfo.mVisible != 0) + { + int32 cursorHeight = (int32)mScreenInfo.mCursorInfo.mSize * mCellHeight / 100; + g.FillRect(cursorCoord.mX, cursorCoord.mY + mCellHeight - cursorHeight, mCellWidth, cursorHeight); + } + } + } + } + } + + + g.SetFont(DarkTheme.sDarkTheme.mSmallFont); + g.DrawString(scope $"Ln {mScreenInfo.mInfo.mCursorY + 1}", mWidth - GS!(120), mHeight - GS!(21)); + g.DrawString(scope $"Col {mScreenInfo.mInfo.mCursorX + 1}", mWidth - GS!(60), mHeight - GS!(21)); + } + } + + public override void DrawAll(Graphics g) + { + if (!mHasConsole) + { + using (g.PushColor(0x80FFFFFF)) + { + base.DrawAll(g); + } + } + else if (mPaused) + { + using (g.PushColor(0xA0FFFFFF)) + { + base.DrawAll(g); + } + } + else + { + base.DrawAll(g); + } + } + + public void ResizeComponents() + { + var outHandle = Console.[Friend]GetStdHandle(Console.STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFOEX info = default; + info.mSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); +#if BF_PLATFORM_WINDOWS + GetConsoleScreenBufferInfoEx(outHandle, ref info); +#endif + + mCellWidth = (.)gApp.mTermFont.GetWidth('W'); + mCellHeight = (.)gApp.mTermFont.GetLineSpacing(); + + mScrollableWidget.Resize(0, 0, mWidth, Math.Max(mHeight - GS!(22), 0)); + + int32 cols = (.)((mWidth - GS!(2)) / mCellWidth); + int32 rows = (.)((mScrollableWidget.mHeight - GS!(8)) / mCellHeight); + + mView.Resize(0, 0, mWidth, Math.Max(info.mHeight * mCellHeight, mHeight)); + mScrollableWidget.RehupSize(); + + info.mWindowRect.right = (.)(info.mWindowRect.left + cols); + info.mWindowRect.bottom = (.)(info.mWindowRect.top + rows); + //SetConsoleScreenBufferInfoEx(outHandle, ref info); + + //SetConsoleScreenBufferSize(outHandle, .((.)cols, (.)rows)); + + //SetConsoleWindowInfo(outHandle, true, info.mWindowRect); + +#if BF_PLATFORM_WINDOWS + GetCurrentConsoleFont(outHandle, false, var fontInfo); + + var window = GetConsoleWindow(); + + uint32 style = (.)Windows.GetWindowLong(window, Windows.GWL_STYLE); + uint32 styleEx = (.)Windows.GetWindowLong(window, Windows.GWL_EXSTYLE); + + Windows.Rect rect = .(0, 0, (.)(cols * fontInfo.mSize.x), (.)(rows * fontInfo.mSize.y)); + Windows.AdjustWindowRectEx(ref rect, style, false, styleEx); + + Windows.SetWindowPos(window, default, 0, 0, rect.Width, rect.Height, + 0x10 /* SWP_NOACTIVATE */ + //0x90 /* SWP_HIDEWINDOW | SWP_NOACTIVATE */ + ); +#endif + } + + public override void Resize(float x, float y, float width, float height) + { + base.Resize(x, y, width, height); + ResizeComponents(); + } + + public override void MouseDown(float x, float y, int32 btn, int32 btnCount) + { + base.MouseDown(x, y, btn, btnCount); + SetFocus(); + } + + static uint8[256*5] sKeyCharMap = .(0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, + 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, + 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 0, 0, 0, 0, 0, 0, + 0, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 61, 44, 45, 46, 47, + 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 91, 92, 93, 39, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 8, 9, 0, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41, 33, 64, 35, 36, 37, 94, 38, 42, 40, 0, 0, 0, 0, 0, 0, + 0, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42, 43, 0, 45, 46, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 58, 43, 60, 95, 62, 63, + 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 123, 124, 125, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + + public void SysKeyDown(KeyDownEvent keyEvent) + { + if (mPaused) + { + mPaused = false; + return; + } + + if (mView.mHasFocus) + { + mCursorBlinkTicks = 0; + + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + if (keyEvent.mKeyCode != .Shift) + { + + } + + INPUT_RECORD input = default; + + if (keyEvent.mKeyCode == .F1) + { + Debug.WriteLine("Key Events:"); + while (true) + { + ReadConsoleInputW(inHandle, &input, 1, var numEventsRead); + + if (input.mEventType == 1) + { + if (input.mEventData.mKeyEvent.mChar != 0) + { + int keyMod = default; + if ((input.mEventData.mKeyEvent.mControlKeyState & 8) != 0) // Ctrl + { + keyMod |= 4; + } + else + { + if ((input.mEventData.mKeyEvent.mControlKeyState & 0x10) != 0) // Shift + keyMod |= 1; + if ((input.mEventData.mKeyEvent.mControlKeyState & 0x80) != 0) // Caps Lock + keyMod |= 2; + } + + /*if ((input.mEventData.mKeyEvent.mControlKeyState & 2) != 0) // Alt + flags |= .Alt;*/ + + Debug.WriteLine($"{input.mEventData.mKeyEvent.mVirtualKeyCode} {keyMod} : {(int)input.mEventData.mKeyEvent.mChar} {input.mEventData.mKeyEvent.mChar}"); + + uint16 keyState = ((uint16)keyMod << 8) + (uint16)input.mEventData.mKeyEvent.mVirtualKeyCode; + sKeyCharMap[keyState] = (uint8)input.mEventData.mKeyEvent.mChar; + } + + if (input.mEventData.mKeyEvent.mChar == '?') + { + for (int i < sKeyCharMap.Count) + { + if (i % 64 == 0) + Debug.WriteLine(); + Debug.Write($"{sKeyCharMap[i]}, "); + } + Debug.WriteLine(); + } + } + else if (input.mEventType == 2) + { + + } + } + return; + } + + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mKeyDown = 1; + input.mEventData.mKeyEvent.mRepeatCount = 1; + input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyEvent.mKeyCode; + //input.mEventData.mKeyEvent.mVirtualScanCode = 61; + + int keyMod = 0; + if (keyEvent.mKeyFlags.HasFlag(.Ctrl)) + { + keyMod |= 4; + } + else + { + if (keyEvent.mKeyFlags.HasFlag(.Shift)) + keyMod |= 1; + if (keyEvent.mKeyFlags.HasFlag(.CapsLock)) + keyMod |= 2; + } + + input.mEventData.mKeyEvent.mControlKeyState = GetControlKeyState(keyEvent.mKeyFlags); + input.mEventData.mKeyEvent.mChar = (.)sKeyCharMap[(keyMod << 8) | (int)keyEvent.mKeyCode]; + + var result = WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); + int32 err = Windows.GetLastError(); + } + } + + public uint32 GetControlKeyState(KeyFlags keyFlags) + { + uint16 controlKeyState = 0; + if (keyFlags.HasFlag(.Alt)) + controlKeyState |= 1; + if (keyFlags.HasFlag(.Ctrl)) + controlKeyState |= 4; + if (keyFlags.HasFlag(.Shift)) + controlKeyState |= 0x10; + if (keyFlags.HasFlag(.CapsLock)) + controlKeyState |= 0x80; + return controlKeyState; + } + + public void SysKeyUp(KeyCode keyCode) + { + if (mView.mHasFocus) + { + var inHandle = Console.[Friend]GetStdHandle(Console.STD_INPUT_HANDLE); + + INPUT_RECORD input = default; + input.mEventType = 1 /*KEY_EVENT */; + input.mEventData.mKeyEvent.mVirtualKeyCode = (.)keyCode; + WriteConsoleInputW(inHandle, &input, 1, var numEventsWritten); + } + } + + public override void MouseWheel(MouseEvent evt) + { + + } + + public override void MouseWheel(float x, float y, float deltaX, float deltaY) + { + //base.MouseWheel(x, y, deltaX, deltaY); + } +} \ No newline at end of file diff --git a/IDE/src/ui/Panel.bf b/IDE/src/ui/Panel.bf index 57ad9e18..7bd7546a 100644 --- a/IDE/src/ui/Panel.bf +++ b/IDE/src/ui/Panel.bf @@ -83,6 +83,9 @@ namespace IDE.ui data.GetString("Type", type); Panel panel = null; + if (type == "") + return gApp.mTerminalPanel; + if (type == "CallStackPanel") panel = gApp.mCallStackPanel; else if (type == "BreakpointPanel") @@ -93,6 +96,10 @@ namespace IDE.ui { panel = gApp.mOutputPanel; } + else if (type == "TerminalPanel") + { + panel = gApp.mTerminalPanel; + } else if (type == "ImmediatePanel") { panel = gApp.mImmediatePanel; @@ -159,6 +166,14 @@ namespace IDE.ui { panel = gApp.mBookmarksPanel; } + else if (type == "TerminalPanel") + { + panel = gApp.mTerminalPanel; + } + else if (type == "ConsolePanel") + { + panel = gApp.mConsolePanel; + } if (panel != null) { diff --git a/IDE/src/ui/ProjectPanel.bf b/IDE/src/ui/ProjectPanel.bf index 98e8d152..af7735ab 100644 --- a/IDE/src/ui/ProjectPanel.bf +++ b/IDE/src/ui/ProjectPanel.bf @@ -2375,7 +2375,7 @@ namespace IDE.ui base.KeyDown(keyCode, isRepeat); - if (mWidgetWindow.GetKeyFlags() == .Ctrl) + if (mWidgetWindow.GetKeyFlags(true) == .Ctrl) { switch (keyCode) { @@ -2388,7 +2388,7 @@ namespace IDE.ui default: } } - else if (mWidgetWindow.GetKeyFlags() == .None) + else if (mWidgetWindow.GetKeyFlags(true) == .None) { if (keyCode == KeyCode.Delete) RemoveSelectedItems(); diff --git a/IDE/src/ui/PropertiesDialog.bf b/IDE/src/ui/PropertiesDialog.bf index 014dd468..7793841c 100644 --- a/IDE/src/ui/PropertiesDialog.bf +++ b/IDE/src/ui/PropertiesDialog.bf @@ -394,7 +394,7 @@ namespace IDE.ui { var focusedListViewItem = GetRoot().FindFocusedItem(); - bool changeFocus = (keyCode == .Tab) && (mWidgetWindow.GetKeyFlags() == .None); + bool changeFocus = (keyCode == .Tab) && (mWidgetWindow.GetKeyFlags(true) == .None); if ((keyCode == .Right) && ((focusedListViewItem == null) || (focusedListViewItem.GetChildCount() == 0))) changeFocus = true; @@ -454,7 +454,7 @@ namespace IDE.ui { var propertiesDialog = (PropertiesDialog)mParent; - let keyFlags = mWidgetWindow.GetKeyFlags(); + let keyFlags = mWidgetWindow.GetKeyFlags(true); bool changeFocus = (keyCode == .Tab) && (keyFlags == .Shift); if (keyCode == .Left) { diff --git a/IDE/src/ui/TerminalPanel.bf b/IDE/src/ui/TerminalPanel.bf new file mode 100644 index 00000000..d7a10990 --- /dev/null +++ b/IDE/src/ui/TerminalPanel.bf @@ -0,0 +1,24 @@ +#pragma warning disable 168 + +using System; +using Beefy.geom; +using Beefy.gfx; +using System.Text; +using Beefy.theme.dark; +using System.Security.Cryptography; +using Beefy.widgets; +using Beefy.events; +using System.Diagnostics; +using Beefy.utils; + +namespace IDE.ui; + +class TerminalPanel : Panel +{ + public override void Serialize(StructuredData data) + { + base.Serialize(data); + + data.Add("Type", "TerminalPanel"); + } +} \ No newline at end of file diff --git a/IDE/src/util/ConsoleProvider.bf b/IDE/src/util/ConsoleProvider.bf new file mode 100644 index 00000000..5fa97a3f --- /dev/null +++ b/IDE/src/util/ConsoleProvider.bf @@ -0,0 +1,20 @@ +namespace IDE.util; + +class ConsoleProvider +{ + public struct Cell + { + public char32 mChar; + public uint32 mFgColor; + public uint32 mBgColor; + } + + public virtual void Get(int col, int row) + { + + } +} + +class WinNativeConsoleProvider +{ +} \ No newline at end of file