Editor theme and bug fixes.

This commit is contained in:
Justin Marshall
2026-05-15 10:32:24 -07:00
parent ea14ecb595
commit f6b489b2ff
18 changed files with 5972 additions and 455 deletions
+361 -12
View File
@@ -52,6 +52,336 @@ If you have questions concerning this license or the applicable additional terms
#define ID_GAME_FILL_75 0x7A42
#define ID_GAME_FILL_50 0x7A43
static const COLORREF XY_DARK_BG = RGB(17, 19, 23);
static const COLORREF XY_DARK_PANEL = RGB(23, 26, 31);
static const COLORREF XY_DARK_MENU = RGB(28, 31, 36);
static const COLORREF XY_DARK_MENU_HOT = RGB(45, 50, 58);
static const COLORREF XY_DARK_SPLITTER = RGB(38, 42, 48);
static const COLORREF XY_DARK_BORDER = RGB(58, 64, 72);
static const COLORREF XY_DARK_SHADOW = RGB(8, 10, 12);
static const COLORREF XY_DARK_TEXT = RGB(226, 232, 240);
static const COLORREF XY_DARK_ACCENT = RGB(87, 166, 255);
static HBRUSH XYDarkPanelBrush() {
static HBRUSH hBrush = ::CreateSolidBrush(XY_DARK_PANEL);
return hBrush;
}
static HBRUSH XYDarkMenuBrush() {
static HBRUSH hBrush = ::CreateSolidBrush(XY_DARK_MENU);
return hBrush;
}
#ifndef BS_TYPEMASK
#define BS_TYPEMASK 0x0000000F
#endif
#ifndef TCM_GETITEMA
#define TCM_GETITEMA TCM_GETITEM
#endif
#ifndef TCITEMA
#define TCITEMA TCITEM
#endif
#ifndef TCM_INSERTITEMA
#define TCM_INSERTITEMA TCM_INSERTITEM
#endif
static const char* XY_DARK_BUTTON_OLDPROC = "IceTech.XY.DarkButtonOldProc";
static const char* XY_DARK_STATIC_OLDPROC = "IceTech.XY.DarkStaticOldProc";
static const char* XY_DARK_TAB_OLDPROC = "IceTech.XY.DarkTabOldProc";
static void XYDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) {
HBRUSH brush = ::CreateSolidBrush(color);
::FillRect(hDC, &rc, brush);
::DeleteObject(brush);
}
static void XYDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) {
HBRUSH brush = ::CreateSolidBrush(color);
::FrameRect(hDC, &rc, brush);
::DeleteObject(brush);
}
static void XYDark_ApplyNativeTheme(HWND hWnd) {
if (!hWnd) {
return;
}
typedef HRESULT (WINAPI *SetWindowThemeProc)(HWND, LPCWSTR, LPCWSTR);
typedef BOOL (WINAPI *AllowDarkModeForWindowProc)(HWND, BOOL);
static HMODULE hUxTheme = ::LoadLibraryA("uxtheme.dll");
static SetWindowThemeProc pSetWindowTheme = hUxTheme ? (SetWindowThemeProc)::GetProcAddress(hUxTheme, "SetWindowTheme") : NULL;
static AllowDarkModeForWindowProc pAllowDarkModeForWindow = hUxTheme ? (AllowDarkModeForWindowProc)::GetProcAddress(hUxTheme, MAKEINTRESOURCEA(133)) : NULL;
if (pAllowDarkModeForWindow) {
pAllowDarkModeForWindow(hWnd, TRUE);
}
if (pSetWindowTheme) {
pSetWindowTheme(hWnd, L"DarkMode_Explorer", NULL);
}
::SendMessage(hWnd, WM_THEMECHANGED, 0, 0);
}
static void XYDark_DrawButton(HWND hWnd, HDC hDC) {
RECT rc;
::GetClientRect(hWnd, &rc);
const UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE);
const UINT type = style & BS_TYPEMASK;
const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE);
const bool enabled = ::IsWindowEnabled(hWnd) ? true : false;
const bool pushed = (::SendMessage(hWnd, BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
const bool checked = (::SendMessage(hWnd, BM_GETCHECK, 0, 0) == BST_CHECKED);
char text[128];
text[0] = '\0';
::GetWindowTextA(hWnd, text, sizeof(text));
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
::SetBkMode(hDC, TRANSPARENT);
::SetTextColor(hDC, enabled ? XY_DARK_TEXT : RGB(101, 110, 126));
if (isCheck) {
XYDark_FillRect(hDC, rc, XY_DARK_PANEL);
RECT box = rc;
box.left += 3;
box.right = box.left + 14;
box.top += max(0, (rc.bottom - rc.top - 14) / 2);
box.bottom = box.top + 14;
XYDark_FillRect(hDC, box, RGB(14, 16, 20));
XYDark_FrameRect(hDC, box, checked ? XY_DARK_ACCENT : XY_DARK_BORDER);
if (checked) {
HPEN pen = ::CreatePen(PS_SOLID, 2, XY_DARK_ACCENT);
HPEN oldPen = (HPEN)::SelectObject(hDC, pen);
::MoveToEx(hDC, box.left + 3, box.top + 7, NULL);
::LineTo(hDC, box.left + 6, box.top + 10);
::LineTo(hDC, box.right - 3, box.top + 3);
::SelectObject(hDC, oldPen);
::DeleteObject(pen);
}
RECT textRect = rc;
textRect.left = box.right + 7;
::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
}
else {
COLORREF face = pushed ? RGB(22, 25, 30) : RGB(31, 35, 41);
XYDark_FillRect(hDC, rc, face);
XYDark_FrameRect(hDC, rc, enabled ? XY_DARK_BORDER : RGB(39, 44, 52));
RECT textRect = rc;
if (pushed) {
::OffsetRect(&textRect, 1, 1);
}
::DrawTextA(hDC, text, -1, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
}
if (oldFont) {
::SelectObject(hDC, oldFont);
}
}
static LRESULT CALLBACK XYDarkButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, XY_DARK_BUTTON_OLDPROC);
if (!oldProc) {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint(hWnd, &ps);
XYDark_DrawButton(hWnd, hDC);
::EndPaint(hWnd, &ps);
return 0;
}
case WM_PRINTCLIENT:
XYDark_DrawButton(hWnd, (HDC)wParam);
return 0;
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_ENABLE:
case WM_SETTEXT:
case BM_SETCHECK:
case BM_SETSTATE:
{
LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
::InvalidateRect(hWnd, NULL, FALSE);
return result;
}
case WM_NCDESTROY:
{
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
::RemovePropA(hWnd, XY_DARK_BUTTON_OLDPROC);
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
static void XYDark_SubclassButton(CWnd& wnd) {
HWND hWnd = wnd.GetSafeHwnd();
if (!hWnd || ::GetPropA(hWnd, XY_DARK_BUTTON_OLDPROC)) {
return;
}
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
::SetPropA(hWnd, XY_DARK_BUTTON_OLDPROC, (HANDLE)oldProc);
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)XYDarkButtonProc);
::InvalidateRect(hWnd, NULL, TRUE);
}
static void XYDark_DrawStatic(HWND hWnd, HDC hDC) {
RECT rc;
::GetClientRect(hWnd, &rc);
XYDark_FillRect(hDC, rc, XY_DARK_PANEL);
char text[128];
text[0] = '\0';
::GetWindowTextA(hWnd, text, sizeof(text));
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
::SetBkMode(hDC, TRANSPARENT);
::SetTextColor(hDC, XY_DARK_TEXT);
::DrawTextA(hDC, text, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
if (oldFont) {
::SelectObject(hDC, oldFont);
}
}
static LRESULT CALLBACK XYDarkStaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, XY_DARK_STATIC_OLDPROC);
if (!oldProc) {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
if (uMsg == WM_PAINT) {
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint(hWnd, &ps);
XYDark_DrawStatic(hWnd, hDC);
::EndPaint(hWnd, &ps);
return 0;
}
if (uMsg == WM_ERASEBKGND) {
return 1;
}
if (uMsg == WM_NCDESTROY) {
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
::RemovePropA(hWnd, XY_DARK_STATIC_OLDPROC);
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
static void XYDark_SubclassStatic(CWnd& wnd) {
HWND hWnd = wnd.GetSafeHwnd();
if (!hWnd || ::GetPropA(hWnd, XY_DARK_STATIC_OLDPROC)) {
return;
}
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
::SetPropA(hWnd, XY_DARK_STATIC_OLDPROC, (HANDLE)oldProc);
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)XYDarkStaticProc);
}
static void XYDark_DrawTabControl(HWND hWnd, HDC hDC) {
RECT client;
::GetClientRect(hWnd, &client);
XYDark_FillRect(hDC, client, XY_DARK_BG);
HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0);
HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL;
::SetBkMode(hDC, TRANSPARENT);
const int count = (int)::SendMessage(hWnd, TCM_GETITEMCOUNT, 0, 0);
const int sel = (int)::SendMessage(hWnd, TCM_GETCURSEL, 0, 0);
for (int i = 0; i < count; i++) {
RECT item;
if (!::SendMessage(hWnd, TCM_GETITEMRECT, (WPARAM)i, (LPARAM)&item)) {
continue;
}
const bool active = (i == sel);
RECT fill = item;
fill.bottom += active ? 2 : 0;
XYDark_FillRect(hDC, fill, active ? XY_DARK_PANEL : XY_DARK_MENU);
XYDark_FrameRect(hDC, fill, active ? XY_DARK_ACCENT : XY_DARK_BORDER);
char text[128];
text[0] = '\0';
TCITEMA ti;
memset(&ti, 0, sizeof(ti));
ti.mask = TCIF_TEXT;
ti.pszText = text;
ti.cchTextMax = sizeof(text);
::SendMessageA(hWnd, TCM_GETITEMA, (WPARAM)i, (LPARAM)&ti);
::SetTextColor(hDC, active ? RGB(245, 249, 255) : XY_DARK_TEXT);
::DrawTextA(hDC, text, -1, &item, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
}
RECT line = client;
line.top = client.bottom - 1;
XYDark_FillRect(hDC, line, XY_DARK_BORDER);
if (oldFont) {
::SelectObject(hDC, oldFont);
}
}
static LRESULT CALLBACK XYDarkTabProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, XY_DARK_TAB_OLDPROC);
if (!oldProc) {
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC = ::BeginPaint(hWnd, &ps);
XYDark_DrawTabControl(hWnd, hDC);
::EndPaint(hWnd, &ps);
return 0;
}
case WM_PRINTCLIENT:
XYDark_DrawTabControl(hWnd, (HDC)wParam);
return 0;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case TCM_INSERTITEMA:
case TCM_DELETEITEM:
case TCM_DELETEALLITEMS:
case TCM_SETCURSEL:
{
LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
::InvalidateRect(hWnd, NULL, FALSE);
return result;
}
case WM_NCDESTROY:
{
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc);
::RemovePropA(hWnd, XY_DARK_TAB_OLDPROC);
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
}
return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam);
}
static void XYDark_SubclassTab(HWND hWnd) {
if (!hWnd || ::GetPropA(hWnd, XY_DARK_TAB_OLDPROC)) {
return;
}
WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC);
::SetPropA(hWnd, XY_DARK_TAB_OLDPROC, (HANDLE)oldProc);
::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)XYDarkTabProc);
::InvalidateRect(hWnd, NULL, TRUE);
}
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
@@ -152,7 +482,7 @@ BOOL CXYMenuBar::Create(CWnd *pParent, UINT nID) {
CString className = AfxRegisterWndClass(
CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW),
(HBRUSH)(COLOR_MENU + 1),
XYDarkMenuBrush(),
NULL
);
return CWnd::CreateEx(
@@ -368,7 +698,7 @@ void CXYMenuBar::OnPaint() {
CRect client;
GetClientRect(client);
CBrush backBrush(::GetSysColor(COLOR_MENU));
CBrush backBrush(XY_DARK_MENU);
dc.FillRect(client, &backBrush);
RebuildItemRects(dc);
@@ -381,17 +711,18 @@ void CXYMenuBar::OnPaint() {
CRect itemRect = m_itemRects[i];
if (i == m_nHotItem) {
dc.Draw3dRect(itemRect, ::GetSysColor(COLOR_3DHILIGHT), ::GetSysColor(COLOR_3DSHADOW));
dc.FillSolidRect(itemRect, XY_DARK_MENU_HOT);
dc.Draw3dRect(itemRect, XY_DARK_ACCENT, XY_DARK_BORDER);
itemRect.DeflateRect(1, 1);
}
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(::GetSysColor(COLOR_MENUTEXT));
dc.SetTextColor(XY_DARK_TEXT);
dc.DrawText(text, itemRect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
}
}
CPen shadowPen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
CPen shadowPen(PS_SOLID, 1, XY_DARK_BORDER);
CPen *oldPen = dc.SelectObject(&shadowPen);
dc.MoveTo(client.left, client.bottom - 1);
dc.LineTo(client.right, client.bottom - 1);
@@ -616,12 +947,12 @@ void CXYMDIContainerWnd::DrawSplitter(CDC *pDC) {
}
CRect rc = m_rcSplitter;
CBrush brush(::GetSysColor(COLOR_BTNFACE));
CBrush brush(XY_DARK_SPLITTER);
pDC->FillRect(rc, &brush);
pDC->Draw3dRect(rc, ::GetSysColor(COLOR_3DHILIGHT), ::GetSysColor(COLOR_3DSHADOW));
pDC->Draw3dRect(rc, XY_DARK_BORDER, XY_DARK_SHADOW);
int x = rc.left + rc.Width() / 2;
CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
CPen pen(PS_SOLID, 1, XY_DARK_SHADOW);
CPen *oldPen = pDC->SelectObject(&pen);
pDC->MoveTo(x, rc.top + 3);
pDC->LineTo(x, rc.bottom - 3);
@@ -753,6 +1084,9 @@ BOOL CXYMDIContainerWnd::OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message) {
}
BOOL CXYMDIContainerWnd::OnEraseBkgnd(CDC *pDC) {
CRect client;
GetClientRect(client);
pDC->FillSolidRect(client, XY_DARK_BG);
DrawSplitter(pDC);
return TRUE;
}
@@ -881,6 +1215,16 @@ int CGameDockWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
m_btn75.Create("75%", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_GAME_FILL_75);
m_btn50.Create("50%", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 0, 0), this, ID_GAME_FILL_50);
XYDark_SubclassStatic(m_wndTitle);
XYDark_SubclassButton(m_btnFit);
XYDark_SubclassButton(m_btn100);
XYDark_SubclassButton(m_btn75);
XYDark_SubclassButton(m_btn50);
XYDark_ApplyNativeTheme(m_btnFit.GetSafeHwnd());
XYDark_ApplyNativeTheme(m_btn100.GetSafeHwnd());
XYDark_ApplyNativeTheme(m_btn75.GetSafeHwnd());
XYDark_ApplyNativeTheme(m_btn50.GetSafeHwnd());
if (!m_wndHost.CreateEx(
WS_EX_CLIENTEDGE,
hostClass,
@@ -1167,7 +1511,7 @@ void CGameDockWnd::OnSize(UINT nType, int cx, int cy) {
BOOL CGameDockWnd::OnEraseBkgnd(CDC *pDC) {
CRect client;
GetClientRect(client);
pDC->FillSolidRect(client, RGB(18, 18, 18));
pDC->FillSolidRect(client, XY_DARK_BG);
return TRUE;
}
@@ -1222,7 +1566,7 @@ BOOL CXYDockWnd::Create(const RECT &rect, CWnd *pParent, UINT nID) {
CString className = AfxRegisterWndClass(
CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW),
(HBRUSH)(COLOR_BTNFACE + 1),
XYDarkPanelBrush(),
NULL
);
return CWnd::CreateEx(
@@ -1250,7 +1594,7 @@ int CXYDockWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
return -1;
}
TCITEM item;
TCITEMA item;
memset(&item, 0, sizeof(item));
item.mask = TCIF_TEXT;
@@ -1263,7 +1607,7 @@ int CXYDockWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
CString pageClass = AfxRegisterWndClass(
CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
::LoadCursor(NULL, IDC_ARROW),
(HBRUSH)(COLOR_BTNFACE + 1),
XYDarkPanelBrush(),
NULL
);
@@ -1293,6 +1637,8 @@ int CXYDockWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) {
}
m_wndTabs.SetCurSel(XYDOCK_TAB_XY);
XYDark_ApplyNativeTheme(m_wndTabs.GetSafeHwnd());
XYDark_SubclassTab(m_wndTabs.GetSafeHwnd());
return 0;
}
@@ -1639,6 +1985,9 @@ void CXYDockWnd::OnSize(UINT nType, int cx, int cy) {
}
BOOL CXYDockWnd::OnEraseBkgnd(CDC *pDC) {
CRect client;
GetClientRect(client);
pDC->FillSolidRect(client, XY_DARK_BG);
return TRUE;
}