AuroraRuntime/Source/Console/ConsoleWxWidgets/ConsoleWxWidgets.cpp

605 lines
15 KiB
C++
Raw Normal View History

2021-06-27 21:25:29 +00:00
/***
Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: ConsoleWxWidgets.cpp
Date: 2021-6-8
Author: Reece
***/
#include <RuntimeInternal.hpp>
#include "ConsoleWxWidgets.hpp"
#if defined(_AUHAS_WXWIDGETS)
#include <asio.hpp>
#include <wx/wxprec.h>
#include <wx/wx.h>
#include <wx/wxprec.h>
#include <wx/wx.h>
#include <wx/richtext/richtextctrl.h>
static bool DarkModeEnabled();
class ConsoleFrame;
static AuList<Aurora::Console::ConsoleMessage> gPendingLines;
static Aurora::Threading::Primitives::MutexUnique_t gMutex;
static bool gWxConsoleReady;
static ConsoleFrame *gWxFrame;
class WxSplitterLine : public wxWindow
{
public:
WxSplitterLine() {}
WxSplitterLine(wxSize splitter, wxColor color, wxWindow *parent, wxWindowID winid) : wxWindow(parent, winid), _color(color)
{
SetMinSize(splitter);
}
void OnPaint(wxPaintEvent &paint)
{
wxPaintDC re(this);
re.SetBrush(_color);
re.SetBackground(_color);
re.Clear();
}
private:
DECLARE_EVENT_TABLE()
wxColor _color;
};
wxBEGIN_EVENT_TABLE(WxSplitterLine, wxPanel)
EVT_PAINT(WxSplitterLine::OnPaint)
wxEND_EVENT_TABLE()
class ConsoleApp : public wxApp
{
public:
~ConsoleApp();
virtual bool OnInit();
};
IMPLEMENT_APP_NO_MAIN(ConsoleApp);
ConsoleApp::~ConsoleApp()
{
}
class ConsoleFrame : public wxFrame
{
public:
ConsoleFrame(const wxString &title, const wxPoint &pos, const wxSize &size);
~ConsoleFrame()
{
Aurora::Threading::Primitives::MutexUnique_t re(gMutex.get());
gWxConsoleReady = false;
}
void WriteLine(const Aurora::Console::ConsoleMessage &msg);
private:
void OnHello(wxCommandEvent &event);
void OnExit(wxCommandEvent &event);
void OnAbout(wxCommandEvent &event);
void OnBugWrite(wxCommandEvent &event);
void OnInit(wxWindowCreateEvent &event);
void OnShow(wxWindowCreateEvent &event);
2021-06-27 21:25:29 +00:00
void OnCmd(wxCommandEvent &event);
void OnPump(wxTimerEvent& event);
2021-06-27 21:25:29 +00:00
WxSplitterLine *NewSplitter(wxSize splitter, wxColor color);
wxDECLARE_EVENT_TABLE();
wxRichTextCtrl *richtextbox_;
wxTextCtrl *commandbox_;
wxMenuBar *menuBar_;
wxBoxSizer *sizer_;
AuList<WxSplitterLine *> _splitters;
wxTimer timer_;
2021-06-27 21:25:29 +00:00
};
enum
{
ID_Hello = 1,
ID_BLACKBOX,
ID_WRITE_REPORT,
ID_OPENTXT,
ID_OPENBIN
};
wxBEGIN_EVENT_TABLE(ConsoleFrame, wxFrame)
EVT_MENU(ID_Hello, ConsoleFrame::OnHello)
EVT_MENU(wxID_EXIT, ConsoleFrame::OnExit)
EVT_MENU(wxID_ABOUT, ConsoleFrame::OnAbout)
EVT_MENU(ID_WRITE_REPORT, ConsoleFrame::OnBugWrite)
EVT_WINDOW_CREATE(ConsoleFrame::OnInit)
wxEND_EVENT_TABLE()
bool ConsoleApp::OnInit()
{
ConsoleFrame *frame = _new ConsoleFrame("Aurora Engine Console", wxPoint(50, 50), wxSize(1080, 550));
frame->Show(true);
gWxFrame = frame;
return true;
}
void ConsoleFrame::OnCmd(wxCommandEvent &event)
{
// im lazy, i know, bite me
auto textbox = reinterpret_cast<wxTextCtrl *>(this);
AuString line = textbox->GetLineText(0);
if (line.empty())
{
return;
}
textbox->Clear();
Aurora::Console::Commands::DispatchCommand(line);
}
WxSplitterLine *ConsoleFrame::NewSplitter(wxSize splitter, wxColor color)
{
auto re = new WxSplitterLine(splitter, color, this, -1);
_splitters.push_back(re);
return re;
}
void ConsoleFrame::WriteLine(const Aurora::Console::ConsoleMessage &string)
{
static std::array<const wxColour, static_cast<size_t>(Aurora::Console::EAnsiColor::eCount)> ColorMap
{
*wxRED, // kRed,
*wxRED, // kBoldRed,
*wxGREEN, // kGreen,
*wxGREEN, // kBoldGreen,
*wxYELLOW, // kYellow,
*wxYELLOW, // kBoldYellow,
*wxBLUE, // kBlue,
*wxBLUE, // kBoldBlue,
*wxRED, // kMagenta,
*wxRED, // kBoldMagenta,
*wxBLUE, // kCyan,
*wxBLUE, // kBoldCyan,
*wxWHITE, // kReset
};
static std::array<bool, static_cast<size_t>(Aurora::Console::EAnsiColor::eCount)> HahaBald
{
false, // kRed,
true, // kBoldRed,
false, // kGreen,
true, // kBoldGreen,
false, // kYellow,
true, // kBoldYellow,
false, // kBlue,
true, // kBoldBlue,
false, // kMagenta,
true, // kBoldMagenta,
false, // kCyan,
true, // kBoldCyan,
false, // kReset
};
auto writeLine = string.ToSimplified();
bool useColor = DarkModeEnabled();
auto color = useColor ? ColorMap[static_cast<int>(string.color)] : wxColor(0, 0, 0);
auto bold = useColor ? HahaBald[static_cast<int>(string.color)] : false;
if (richtextbox_->GetNumberOfLines() > 1000)
richtextbox_->Clear();
//richtextbox_->BeginParagraphSpacing(0, 20);
auto pos = richtextbox_->GetCaretPosition();
auto selPos = richtextbox_->GetLastPosition() - 1;
richtextbox_->SetCaretPosition(selPos);
richtextbox_->BeginTextColour(color);
if (bold)
richtextbox_->BeginBold();
richtextbox_->WriteText(writeLine);
richtextbox_->Newline();
if (bold)
richtextbox_->EndBold();
richtextbox_->EndTextColour();
if ((pos == -1) || // true once
(pos == selPos) || // most likely
(abs(pos - selPos) < 5)) // ux
{
auto newPos = richtextbox_->GetLastPosition() - 1;
richtextbox_->ScrollIntoView(newPos, WXK_DOWN);
richtextbox_->SetCaretPosition(newPos);
}
else
{
richtextbox_->SetCaretPosition(pos);
}
//richtextbox_->Disable
//richtextbox_->EndParagraphSpacing();
}
ConsoleFrame::ConsoleFrame(const wxString &title, const wxPoint &pos, const wxSize &size)
: wxFrame(NULL, wxID_ANY, title, pos, size)
{
#if defined(AURORA_PLATFORM_WIN32)
bool useWin32DarkHack = DarkModeEnabled();
#else
bool useWin32DarkHack = false;
#endif
// 1080, 550
this->SetClientSize(this->FromDIP(wxSize(903, 434)));
menuBar_ = _new wxMenuBar;
if (!menuBar_) return;
{
wxMenu *menuFile = _new wxMenu;
if (!menuFile) return;
menuFile->Append(wxID_SAVE, "Export text log");
menuFile->AppendSeparator();
menuFile->Append(ID_OPENTXT, "Open text log");
menuFile->Append(ID_OPENBIN, "Open binary log");
menuBar_->Append(menuFile, "&Logs");
}
{
wxMenu *menuHelp = _new wxMenu;
if (!menuHelp) return;
menuHelp->Append(ID_BLACKBOX, "&Report application state to the black box");
menuHelp->Append(ID_WRITE_REPORT, "&[Internal] Write a bug report");
menuHelp->Append(ID_WRITE_REPORT, "&[Public] Write a bug report");
menuBar_->Append(menuHelp, "&Bugs");
}
richtextbox_ = _new wxRichTextCtrl(this, -1, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
(useWin32DarkHack ? wxBORDER_NONE : 0) | wxTE_MULTILINE | wxRE_READONLY | wxRE_CENTER_CARET);
if (!richtextbox_) return;
commandbox_ = _new wxTextCtrl(this, -1, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
(useWin32DarkHack ? wxBORDER_NONE : 0) | wxTE_PROCESS_ENTER);
if (!commandbox_) return;
if (useWin32DarkHack)
{
this->SetBackgroundColour(wxColour(0, 0, 0));
richtextbox_->SetBackgroundColour(wxColour(0, 0, 0));
richtextbox_->SetForegroundColour(wxColour(255, 255, 255));
commandbox_->SetBackgroundColour(wxColour(0, 0, 0));
commandbox_->SetForegroundColour(wxColour(255, 255, 255));
//this->Connect(wxEVT_SHOW, wxWindowCreateEventHandler(ConsoleFrame::OnShow));
2021-06-27 21:25:29 +00:00
}
commandbox_->SetHint("Type a command here");
commandbox_->Connect(wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler(ConsoleFrame::OnCmd));
//commandbox_->Bind(wxEVT_COMMAND_TEXT_ENTER, &ConsoleFrame::OnCmd, commandbox_);
2021-06-27 21:25:29 +00:00
sizer_ = _new wxBoxSizer(wxVERTICAL);
if (!sizer_) return;
sizer_->Add(richtextbox_, 3, wxEXPAND);
if (useWin32DarkHack)
{
sizer_->Add(NewSplitter(wxSize(4, 2), wxColour(0x38, 0x38, 0x38)), 0, wxEXPAND);
sizer_->Add(NewSplitter(wxSize(4, 1), wxColour(0, 0, 0)), 0, wxEXPAND);
}
sizer_->Add(commandbox_, 0, wxEXPAND | wxBOTTOM);
if (useWin32DarkHack)
{
sizer_->Add(NewSplitter(wxSize(4, 1), wxColour(0, 0, 0)), 0, wxEXPAND);
sizer_->Add(NewSplitter(wxSize(4, 2), wxColour(0x38, 0x38, 0x38)), 0, wxEXPAND);
}
SetSizer(sizer_);
SetMenuBar(menuBar_);
timer_.Bind(wxEVT_TIMER, &ConsoleFrame::OnPump, this);
timer_.Start(100);
2021-06-27 21:25:29 +00:00
gWxConsoleReady = true;
}
#if defined(AURORA_PLATFORM_WIN32)
#include <Uxtheme.h>
#include <Extensions/Win32/DarkTheme.hpp>
static bool DarkModeEnabled()
{
return Aurora::Extensions::Win32::g_darkModeSupported;
}
void ConsoleFrame::OnInit(wxWindowCreateEvent &event)
{
OnShow(event);
}
void ConsoleFrame::OnShow(wxWindowCreateEvent &event)
2021-06-27 21:25:29 +00:00
{
if (!DarkModeEnabled()) return;
auto handle = event.GetWindow()->GetHWND();
Aurora::Extensions::Win32::AllowDarkModeForWindow(handle, true);
if (Aurora::Extensions::Win32::_FlushMenuThemes)
Aurora::Extensions::Win32::_FlushMenuThemes();
SetWindowTheme(handle, L"DarkMode_Explorer", NULL);
SendMessageW(handle, WM_THEMECHANGED, 0, 0);
Aurora::Extensions::Win32::RefreshTitleBarThemeColor(handle);
UpdateWindow(handle);
}
#else
void ConsoleFrame::OnInit(wxWindowCreateEvent &event)
{}
void ConsoleFrame::OnShow(wxWindowCreateEvent &event)
{}
2021-06-27 21:25:29 +00:00
static bool DarkModeEnabled()
{
return Aurora::Build::EPlatform == Aurora::Build::EPlatform::kPlatformLinux;
}
#endif
void ConsoleFrame::OnExit(wxCommandEvent &event)
{
Close(true);
}
void ConsoleFrame::OnAbout(wxCommandEvent &event)
{
}
void ConsoleFrame::OnHello(wxCommandEvent &event)
{
LogGame("nani?!");
}
void ConsoleFrame::OnBugWrite(wxCommandEvent &event)
{
Aurora::Processes::OpenUri(gRuntimeConfig.console.supportInternal);
}
namespace Aurora::Console::ConsoleWxWidgets
{
static void WxWidgetsPump();
}
void ConsoleFrame::OnPump(wxTimerEvent &event)
{
Aurora::Console::ConsoleWxWidgets::WxWidgetsPump();
}
2021-06-27 21:25:29 +00:00
namespace Aurora::Console::ConsoleWxWidgets
{
static wxInitializer *gInitializer;
static bool UseWxConsole()
{
if (gRuntimeConfig.console.disableAll)
{
return false;
}
if (GetConsoleWindow())
{
if (!gRuntimeConfig.console.forceToolKitWindow)
{
return false;
}
return true;
}
return true;
}
static bool WxWidgetsInit()
{
wxApp::SetInstance(_new ConsoleApp());
#if defined(AURORA_PLATFORM_WIN32) || true
std::wstring auroraw(L"Aurora");
static wxChar *s = auroraw.data();
static int count = 1;
#endif
gInitializer = new wxInitializer(count, &s);
if (!gInitializer->IsOk())
{
#if wxUSE_LOG
delete wxLog::SetActiveTarget(NULL);
#endif
}
try
{
if (!wxTheApp->CallOnInit())
{
return false;
}
}
catch (...)
{
wxTheApp->OnUnhandledException();
return false;
}
return true;
}
void WxWidgetsRequestExit()
{
if (!gMutex)
{
return;
}
Threading::LockGuardPtr re(gMutex.get()); // pupose two of the mutex: locking deconstruction/gWxConsoleReady
// its a horible hack, but -
// 1) our dtor comes before the final apps class, and
// 2) the app will deregister the top window before the dtor
// which means
// `if (!gWxConsoleReady) return;` under lock
// -> gWxConsoleReady must be true, and `wxTheApp->GetTopWindow`'s dtor is locked,
// even as a remote thread, we should be able to do ipc given a dtor pinned object safely
// the shitty wiki doesn't describe how we're supposed to dispatch cmds to the os loop (w/o a window)
if (!gWxConsoleReady) return;
auto window = wxTheApp->GetTopWindow();
if (!window) return;
window->GetEventHandler()->CallAfter([]()
{
wxTheApp->Exit();
});
}
static Aurora::Threading::Threads::ThreadUnique_t gWxWidgetsThread;
static void WxWidgetsThreadMain()
{
if (!WxWidgetsInit())
{
return;
}
try
{
wxTheApp->OnRun();
}
catch (...)
{
wxTheApp->OnUnhandledException();
}
}
void Init()
{
if (!UseWxConsole())
{
return;
}
gMutex = Aurora::Threading::Primitives::MutexUnique();
Aurora::Console::Hooks::AddHook([&](const Aurora::Console::ConsoleMessage &string) -> void
{
gMutex->Lock();
gPendingLines.push_back(string);
gMutex->Unlock();
});
Aurora::Threading::Threads::AbstractThreadVectors handler;
handler.DoRun = [](Aurora::Threading::Threads::IAuroraThread *)
{
WxWidgetsThreadMain();
};
gWxWidgetsThread = Aurora::Threading::Threads::ThreadUnique(handler);
if (!gWxWidgetsThread)
{
return;
}
gWxWidgetsThread->SetName("WxWidgets");
gWxWidgetsThread->Run();
}
static void WxWidgetsPump()
{
// called from within WxWidget main loop, do not worry about locking is ready
// we _know_ the wxwidget app is referenceable
if (!wxTheApp) return;
if (!gWxConsoleReady) return;
AuList<Console::ConsoleMessage> lines;
{
Aurora::Threading::LockGuardPtr re(gMutex.get());
lines = std::exchange(gPendingLines, {});
}
for (const auto& line : lines)
{
gWxFrame->WriteLine(line);
}
}
void Pump()
{
#if 0
2021-06-27 21:25:29 +00:00
Aurora::Threading::LockGuardPtr re(gMutex.get());
if (!gWxConsoleReady) return;
wxTheApp->GetTopWindow()->GetEventHandler()->CallAfter([]()
{
WxWidgetsPump();
});
#endif
2021-06-27 21:25:29 +00:00
}
void Exit()
{
WxWidgetsRequestExit();
gWxWidgetsThread.reset();
auto wxHandle = wxTheApp;
if (wxHandle)
{
delete wxHandle;
}
gMutex.reset();
gPendingLines.clear();
}
}
#else
namespace Aurora::Console::ConsoleWxWidgets
{
void Init()
{
}
void Pump()
{
}
void Exit()
{
}
}
#endif