diff --git a/neo/engine/framework/Common.cpp b/neo/engine/framework/Common.cpp index 509a46ec..7fe1a9b5 100644 --- a/neo/engine/framework/Common.cpp +++ b/neo/engine/framework/Common.cpp @@ -3212,6 +3212,12 @@ idCommonLocal::Frame ================= */ void idCommonLocal::Frame(void) { + if (com_editorActive) + { + gameFrame++; + return; + } + try { // pump all the events diff --git a/neo/engine/renderer/Image_program.cpp b/neo/engine/renderer/Image_program.cpp index 972078ce..1053a2ff 100644 --- a/neo/engine/renderer/Image_program.cpp +++ b/neo/engine/renderer/Image_program.cpp @@ -430,6 +430,14 @@ static bool R_ParseImageProgram_r( idLexer &src, byte **pic, int *width, int *he // process it if ( pic ) { + if (pic2 == NULL || width2 == 0 || height2 == 0) { + if (pic) { + R_StaticFree(*pic); + *pic = NULL; + } + return false; + } + R_AddNormalMaps( *pic, *width, *height, pic2, width2, height2 ); R_StaticFree( pic2 ); if ( depth ) { diff --git a/neo/engine/tools/radiant/CamWnd.cpp b/neo/engine/tools/radiant/CamWnd.cpp index b5b25e8d..cd0e29b8 100644 --- a/neo/engine/tools/radiant/CamWnd.cpp +++ b/neo/engine/tools/radiant/CamWnd.cpp @@ -1026,6 +1026,16 @@ static const char* CAMWND_PROP_FILTER_MASK = "QER_CamWnd_FilterMask"; #define CAMWND_FILTER_HIDE_TARGET_LINES 0x00000020 #define CAMWND_FILTER_HIDE_TRIGGER_BRUSHES 0x00000040 +static const COLORREF CAMWND_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF CAMWND_DARK_MENU = RGB(28, 31, 36); +static const COLORREF CAMWND_DARK_BORDER = RGB(58, 64, 72); +static const COLORREF CAMWND_DARK_TEXT = RGB(226, 232, 240); + +static HBRUSH CamWnd_DarkMenuBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(CAMWND_DARK_MENU); + return hBrush; +} + static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static int CamWnd_GetMenuHeight() { @@ -1292,7 +1302,7 @@ static void CamWnd_RegisterMenuBarClass() { wc.lpfnWndProc = CamWnd_MenuBarProc; wc.hInstance = AfxGetInstanceHandle(); wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wc.hbrBackground = CamWnd_DarkMenuBrush(); wc.lpszClassName = CAMWND_MENU_BAR_CLASS; AfxRegisterClass(&wc); registered = true; @@ -1368,7 +1378,7 @@ static bool CamWnd_PointInMenuBar(CCamWnd* cam, const CPoint& point) { if (!cam || !cam->GetSafeHwnd()) { return false; } - return point.y >= 0 && point.y < CAMWND_MENU_HEIGHT; + return point.y >= 0 && point.y < CAMWND_TOPBAR_HEIGHT; } static void CamWnd_UpdateRuntimeState(CCamWnd* cam, bool renderMode, bool rebuildMode, bool entityMode) { @@ -1932,18 +1942,18 @@ static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, menuRect.top = CAMWND_CAPTION_HEIGHT; menuRect.bottom = CAMWND_TOPBAR_HEIGHT; - CamWnd_FillHorizontalGradient(hDC, captionRect, RGB(24, 82, 150), RGB(92, 96, 104)); - FillRect(hDC, &menuRect, (HBRUSH)(COLOR_BTNFACE + 1)); + CamWnd_FillHorizontalGradient(hDC, captionRect, RGB(20, 36, 62), RGB(28, 31, 36)); + FillRect(hDC, &menuRect, CamWnd_DarkMenuBrush()); RECT dividerRect = rect; dividerRect.top = CAMWND_CAPTION_HEIGHT - 1; dividerRect.bottom = CAMWND_CAPTION_HEIGHT; - FillRect(hDC, ÷rRect, (HBRUSH)(COLOR_3DSHADOW + 1)); + FillRect(hDC, ÷rRect, CamWnd_DarkMenuBrush()); RECT lineRect = rect; lineRect.top = CAMWND_TOPBAR_HEIGHT - 1; lineRect.bottom = CAMWND_TOPBAR_HEIGHT; - FillRect(hDC, &lineRect, (HBRUSH)(COLOR_3DSHADOW + 1)); + FillRect(hDC, &lineRect, CamWnd_DarkMenuBrush()); HFONT font = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0); HFONT oldFont = NULL; @@ -1956,16 +1966,26 @@ static LRESULT CALLBACK CamWnd_MenuBarProc(HWND hWnd, UINT uMsg, WPARAM wParam, RECT captionTextRect = captionRect; captionTextRect.left += 8; captionTextRect.right -= 8; - SetTextColor(hDC, RGB(235, 242, 255)); + SetTextColor(hDC, CAMWND_DARK_TEXT); DrawText(hDC, "Viewport", -1, &captionTextRect, DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_END_ELLIPSIS); - SetTextColor(hDC, GetSysColor(COLOR_BTNTEXT)); + SetTextColor(hDC, CAMWND_DARK_TEXT); RECT itemRect; + HBRUSH itemBrush = ::CreateSolidBrush(CAMWND_DARK_PANEL); + HPEN borderPen = ::CreatePen(PS_SOLID, 1, CAMWND_DARK_BORDER); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, itemBrush); + HPEN oldPen = (HPEN)::SelectObject(hDC, borderPen); CamWnd_GetMenuItemRect(hWnd, 0, itemRect); + RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); DrawText(hDC, "Lighting", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); CamWnd_GetMenuItemRect(hWnd, 1, itemRect); + RoundRect(hDC, itemRect.left + 3, itemRect.top + 2, itemRect.right - 3, itemRect.bottom - 2, 6, 6); DrawText(hDC, "Filters", -1, &itemRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + ::SelectObject(hDC, oldPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(borderPen); + ::DeleteObject(itemBrush); if (oldFont) { SelectObject(hDC, oldFont); diff --git a/neo/engine/tools/radiant/DialogTextures.cpp b/neo/engine/tools/radiant/DialogTextures.cpp index a2cc4150..e14f0724 100644 --- a/neo/engine/tools/radiant/DialogTextures.cpp +++ b/neo/engine/tools/radiant/DialogTextures.cpp @@ -35,6 +35,7 @@ If you have questions concerning this license or the applicable additional terms #include "DialogTextures.h" #include "DialogInfo.h" #include "EditViewDlg.h" +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -42,6 +43,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} HTREEITEM FindTreeItem(CTreeCtrl *tree, HTREEITEM root, const char *text, HTREEITEM forceParent); extern void Select_SetKeyVal(const char *key, const char *val); @@ -122,8 +643,12 @@ void CDialogTextures::OnOK() { BOOL CDialogTextures::OnInitDialog() { CDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + m_image.Create(IDB_BITMAP_MATERIAL, 16, 1, RGB(255, 255, 255)); m_treeTextures.SetImageList(&m_image, TVSIL_NORMAL); + m_treeTextures.SetBkColor(ICE_DARK_FIELD); + m_treeTextures.SetTextColor(ICE_DARK_TEXT); // m_wndPreview.SubclassDlgItem(IDC_PREVIEW, this); m_wndPreview.setDrawable(&m_testDrawable); diff --git a/neo/engine/tools/radiant/EntityDlg.cpp b/neo/engine/tools/radiant/EntityDlg.cpp index 381af09d..870c9a09 100644 --- a/neo/engine/tools/radiant/EntityDlg.cpp +++ b/neo/engine/tools/radiant/EntityDlg.cpp @@ -28,6 +28,598 @@ void Select_Ungroup(); IMPLEMENT_DYNAMIC(CEntityDlg, CDialog) + +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ENTITY_DARK_BG = RGB(17, 19, 23); +static const COLORREF ENTITY_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ENTITY_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ENTITY_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ENTITY_DARK_FIELD_ALT = RGB(18, 21, 26); +static const COLORREF ENTITY_DARK_PROP_NAME = RGB(31, 36, 44); +static const COLORREF ENTITY_DARK_PROP_NAME_SEL = RGB(54, 76, 105); +static const COLORREF ENTITY_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ENTITY_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ENTITY_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ENTITY_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ENTITY_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ENTITY_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ENTITY_DARK_BUTTON_OLDPROC = "IceTech.Entity.DarkButtonOldProc"; +static const char* ENTITY_DARK_PROP_OLDPROC = "IceTech.Entity.DarkPropertyOldProc"; +static const char* ENTITY_DARK_PROP_DIVIDER = "IceTech.Entity.DarkPropertyDivider"; +static const char* ENTITY_DARK_FIELD_OLDPROC = "IceTech.Entity.DarkFieldOldProc"; +static const char* ENTITY_DARK_COMBO_OLDPROC = "IceTech.Entity.DarkComboOldProc"; + +static void EntityDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void EntityDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void EntityDark_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[256]; + 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 ? ENTITY_DARK_TEXT : ENTITY_DARK_TEXT_DISABLED); + + if (isCheck) { + EntityDark_FillRect(hDC, rc, ENTITY_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; + + EntityDark_FillRect(hDC, box, enabled ? ENTITY_DARK_FIELD : RGB(20, 22, 26)); + EntityDark_FrameRect(hDC, box, checked ? ENTITY_DARK_ACCENT : ENTITY_DARK_BORDER); + + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ENTITY_DARK_ACCENT : ENTITY_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ENTITY_DARK_PANEL_ALT; + EntityDark_FillRect(hDC, rc, face); + EntityDark_FrameRect(hDC, rc, enabled ? ENTITY_DARK_BORDER : RGB(39, 44, 52)); + + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + EntityDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + + 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 EntityDarkButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ENTITY_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); + EntityDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + + case WM_PRINTCLIENT: + EntityDark_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, ENTITY_DARK_BUTTON_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void EntityDark_SubclassButton(CWnd& wnd) { + HWND hWnd = wnd.GetSafeHwnd(); + if (!hWnd || ::GetPropA(hWnd, ENTITY_DARK_BUTTON_OLDPROC)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, ENTITY_DARK_BUTTON_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)EntityDarkButtonProc); + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void EntityDark_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 EntityDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void EntityDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + EntityDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static LRESULT CALLBACK EntityDarkFieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ENTITY_DARK_FIELD_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + EntityDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ENTITY_DARK_BORDER_HOT : ENTITY_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ENTITY_DARK_FIELD_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void EntityDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + EntityDark_FillRect(hDC, rc, enabled ? ENTITY_DARK_FIELD : RGB(20, 22, 26)); + EntityDark_FrameRect(hDC, rc, focused ? ENTITY_DARK_BORDER_HOT : ENTITY_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + EntityDark_FillRect(hDC, arrow, ENTITY_DARK_PANEL_ALT); + EntityDark_FrameRect(hDC, arrow, ENTITY_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ENTITY_DARK_TEXT : ENTITY_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ENTITY_DARK_TEXT : ENTITY_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ENTITY_DARK_TEXT : ENTITY_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK EntityDarkComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ENTITY_DARK_COMBO_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + EntityDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + EntityDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ENTITY_DARK_BORDER_HOT : ENTITY_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ENTITY_DARK_COMBO_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void EntityDark_SubclassField(CWnd& wnd) { + HWND hWnd = wnd.GetSafeHwnd(); + if (!hWnd || ::GetPropA(hWnd, ENTITY_DARK_FIELD_OLDPROC)) { + return; + } + EntityDark_RemoveClientEdge(hWnd); + EntityDark_ApplyNativeTheme(hWnd); + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, ENTITY_DARK_FIELD_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)EntityDarkFieldProc); + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void EntityDark_SubclassCombo(CWnd& wnd) { + HWND hWnd = wnd.GetSafeHwnd(); + if (!hWnd || ::GetPropA(hWnd, ENTITY_DARK_COMBO_OLDPROC)) { + return; + } + EntityDark_RemoveClientEdge(hWnd); + EntityDark_ApplyNativeTheme(hWnd); + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, ENTITY_DARK_COMBO_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)EntityDarkComboProc); + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void EntityDark_SetPropertyListRowHeight(HWND hWnd) { + if (!hWnd) { + return; + } + + // The old resource-sized rows were too tight after the dark-theme font pass. + // Give every row enough breathing room so descenders (g, y, p, q) are not clipped at the bottom. + ::SendMessage(hWnd, LB_SETITEMHEIGHT, 0, 32); +} + +static void EntityDark_CopyPropText(char* out, int outSize, CPropertyItem* propItem, int index, HWND hWnd, bool wantValue) { + if (!out || outSize <= 0) { + return; + } + out[0] = '\0'; + + if (propItem && propItem != (CPropertyItem*)LB_ERR) { + const char* src = wantValue ? propItem->m_curValue : propItem->m_propName; + if (src && src[0]) { + idStr::Copynz(out, src, outSize); + return; + } + } + + char text[2048]; + text[0] = '\0'; + ::SendMessageA(hWnd, LB_GETTEXT, (WPARAM)index, (LPARAM)text); + + char* tab = strchr(text, '\t'); + if (tab) { + if (wantValue) { + idStr::Copynz(out, tab + 1, outSize); + } + else { + *tab = '\0'; + idStr::Copynz(out, text, outSize); + } + } + else if (!wantValue) { + idStr::Copynz(out, text, outSize); + } +} + +static void EntityDark_DrawPropertyList(HWND hWnd, HDC hDC) { + RECT client; + ::GetClientRect(hWnd, &client); + + EntityDark_FillRect(hDC, client, ENTITY_DARK_FIELD); + + const int count = (int)::SendMessage(hWnd, LB_GETCOUNT, 0, 0); + if (count <= 0) { + return; + } + + int divider = (int)(INT_PTR)::GetPropA(hWnd, ENTITY_DARK_PROP_DIVIDER); + if (divider <= 0) { + divider = 170; + } + if (divider > client.right - 80) { + divider = max(80, client.right / 2); + } + + const int topIndex = (int)::SendMessage(hWnd, LB_GETTOPINDEX, 0, 0); + const int curSel = (int)::SendMessage(hWnd, LB_GETCURSEL, 0, 0); + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + HFONT font = (HFONT)::SendMessage(hWnd, WM_GETFONT, 0, 0); + HFONT oldFont = font ? (HFONT)::SelectObject(hDC, font) : NULL; + ::SetBkMode(hDC, TRANSPARENT); + + for (int i = max(0, topIndex); i < count; i++) { + RECT item; + if ((int)::SendMessage(hWnd, LB_GETITEMRECT, (WPARAM)i, (LPARAM)&item) == LB_ERR) { + break; + } + if (item.top >= client.bottom) { + break; + } + if (item.bottom <= client.top) { + continue; + } + + const bool selected = (i == curSel); + + RECT rowRect = item; + rowRect.left = client.left; + rowRect.right = client.right; + EntityDark_FillRect(hDC, rowRect, selected ? RGB(31, 41, 55) : ENTITY_DARK_FIELD); + + RECT nameRect = item; + nameRect.left = client.left; + nameRect.right = min(client.right, divider); + EntityDark_FillRect(hDC, nameRect, selected ? ENTITY_DARK_PROP_NAME_SEL : ENTITY_DARK_PROP_NAME); + + RECT valueRect = item; + valueRect.left = min(client.right, divider + 1); + valueRect.right = client.right; + EntityDark_FillRect(hDC, valueRect, selected ? RGB(30, 43, 59) : ENTITY_DARK_FIELD); + + CPropertyItem* propItem = (CPropertyItem*)::SendMessage(hWnd, LB_GETITEMDATA, (WPARAM)i, 0); + + char nameText[1024]; + char valueText[2048]; + EntityDark_CopyPropText(nameText, sizeof(nameText), propItem, i, hWnd, false); + EntityDark_CopyPropText(valueText, sizeof(valueText), propItem, i, hWnd, true); + + RECT nameTextRect = nameRect; + nameTextRect.left += 10; + nameTextRect.right -= 10; + nameTextRect.top += 3; + nameTextRect.bottom -= 1; + + RECT valueTextRect = valueRect; + valueTextRect.left += 12; + valueTextRect.right -= 10; + valueTextRect.top += 3; + valueTextRect.bottom -= 1; + + ::SetTextColor(hDC, selected ? RGB(255, 255, 255) : ENTITY_DARK_TEXT); + ::DrawTextA(hDC, nameText, -1, &nameTextRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + + ::SetTextColor(hDC, selected ? RGB(235, 245, 255) : RGB(206, 214, 226)); + ::DrawTextA(hDC, valueText, -1, &valueTextRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + + // Dark, vertical-only split. Do not draw horizontal row dividers; they were cutting through the value text. + RECT dividerRect = item; + dividerRect.left = min(client.right - 1, divider); + dividerRect.right = dividerRect.left + 1; + EntityDark_FillRect(hDC, dividerRect, RGB(42, 48, 58)); + } + + if (focused) { + RECT focusRect = client; + ::InflateRect(&focusRect, -1, -1); + EntityDark_FrameRect(hDC, focusRect, ENTITY_DARK_BORDER_HOT); + } + else { + RECT frameRect = client; + ::InflateRect(&frameRect, -1, -1); + EntityDark_FrameRect(hDC, frameRect, ENTITY_DARK_BORDER); + } + + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK EntityDarkPropertyListProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ENTITY_DARK_PROP_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + EntityDark_DrawPropertyList(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + + case WM_PRINTCLIENT: + EntityDark_DrawPropertyList(hWnd, (HDC)wParam); + return 0; + + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + EntityDark_FillRect((HDC)wParam, rc, ENTITY_DARK_FIELD); + return 1; + } + + case WM_VSCROLL: + case WM_HSCROLL: + case WM_MOUSEWHEEL: + case WM_SETFOCUS: + case WM_KILLFOCUS: + case LB_ADDSTRING: + case LB_INSERTSTRING: + case LB_DELETESTRING: + case LB_RESETCONTENT: + case LB_SETCURSEL: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + if (uMsg == LB_ADDSTRING || uMsg == LB_INSERTSTRING || uMsg == LB_RESETCONTENT) { + EntityDark_SetPropertyListRowHeight(hWnd); + } + ::InvalidateRect(hWnd, NULL, FALSE); + return result; + } + + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ENTITY_DARK_PROP_OLDPROC); + ::RemovePropA(hWnd, ENTITY_DARK_PROP_DIVIDER); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void EntityDark_SubclassPropertyList(CWnd& wnd, int divider) { + HWND hWnd = wnd.GetSafeHwnd(); + if (!hWnd) { + return; + } + ::SetPropA(hWnd, ENTITY_DARK_PROP_DIVIDER, (HANDLE)(INT_PTR)divider); + EntityDark_SetPropertyListRowHeight(hWnd); + if (::GetPropA(hWnd, ENTITY_DARK_PROP_OLDPROC)) { + ::InvalidateRect(hWnd, NULL, TRUE); + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, ENTITY_DARK_PROP_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)EntityDarkPropertyListProc); + ::InvalidateRect(hWnd, NULL, TRUE); +} + CEntityDlg::CEntityDlg(CWnd* pParent /*=NULL*/) : CDialog(CEntityDlg::IDD, pParent) { editEntity = NULL; @@ -37,14 +629,14 @@ CEntityDlg::CEntityDlg(CWnd* pParent /*=NULL*/) dict = NULL; themeInitialized = false; - colorBackground = RGB(245, 246, 248); - colorPanel = RGB(255, 255, 255); - colorPanelAlt = RGB(248, 249, 251); - colorBorder = RGB(210, 214, 220); - colorText = RGB(32, 36, 42); - colorTextMuted = RGB(92, 99, 112); - colorAccent = RGB(0, 122, 204); - colorEditBg = RGB(255, 255, 255); + colorBackground = RGB(17, 19, 23); + colorPanel = RGB(26, 29, 34); + colorPanelAlt = RGB(29, 33, 39); + colorBorder = RGB(55, 62, 72); + colorText = RGB(226, 232, 240); + colorTextMuted = RGB(151, 163, 184); + colorAccent = RGB(87, 166, 255); + colorEditBg = RGB(14, 16, 20); } CEntityDlg::~CEntityDlg() { @@ -273,6 +865,30 @@ void CEntityDlg::ApplyModernTheme() { editVal.SetMargins(6, 6); } + EntityDark_SubclassField(editKey); + EntityDark_SubclassField(editVal); + EntityDark_SubclassField(listKeyVal); + EntityDark_SubclassField(listVars); + EntityDark_SubclassField(slFrameSlider); + EntityDark_SubclassCombo(comboClass); + EntityDark_SubclassCombo(cbAnimations); + + EntityDark_SubclassPropertyList(listKeyVal, 170); + EntityDark_SubclassPropertyList(listVars, 170); + + CWnd* darkButtons[] = { + &btnCreate, &btnBrowse, + &btnModel, &btnSound, &btnGui, &btnParticle, &btnSkin, &btnCurve, + &btn135, &btn90, &btn45, &btn180, &btn360, &btn225, &btn270, &btn315, &btnUp, &btnDown, + &btnPlayAnim, &btnStopAnim + }; + for (int i = 0; i < (int)(sizeof(darkButtons) / sizeof(darkButtons[0])); i++) { + if (darkButtons[i] && darkButtons[i]->GetSafeHwnd()) { + EntityDark_ApplyNativeTheme(darkButtons[i]->GetSafeHwnd()); + EntityDark_SubclassButton(*darkButtons[i]); + } + } + staticTitle.SetWindowText("Entity Inspector"); staticKey.SetWindowText("Key"); staticVal.SetWindowText("Value"); @@ -329,11 +945,17 @@ void CEntityDlg::DrawModernBackground(CDC& dc) { int contentBottom = rect.bottom - pad; int width = rect.Width(); - if (width >= 560) { + if (width >= 500) { + const int editorH = 106; + const int minToolH = 112; + const int availableH = max(1, contentBottom - contentTop); + // Give the two property grids the extra vertical space instead of capping them at 420px. + // This keeps the edit/tools panels underneath and prevents the list controls from stacking over each other. + int listH = max(180, availableH - editorH - minToolH - pad * 3); int leftW = (width - pad * 3) / 2; - CRect vars(rect.left + pad, contentTop, rect.left + pad + leftW, contentTop + 220); - CRect keyvals(vars.right + pad, contentTop, rect.right - pad, contentTop + 220); - CRect editor(rect.left + pad, vars.bottom + pad, rect.right - pad, vars.bottom + 106); + CRect vars(rect.left + pad, contentTop, rect.left + pad + leftW, contentTop + listH); + CRect keyvals(vars.right + pad, contentTop, rect.right - pad, contentTop + listH); + CRect editor(rect.left + pad, vars.bottom + pad, rect.right - pad, vars.bottom + editorH); CRect tools(rect.left + pad, editor.bottom + pad, rect.right - pad, contentBottom); DrawPanel(dc, header, "", true); @@ -344,11 +966,14 @@ void CEntityDlg::DrawModernBackground(CDC& dc) { DrawPanel(dc, tools, "Tools", false); } else { - int h = max(120, (contentBottom - contentTop - pad * 3) / 4); + const int editorH = 106; + const int minToolH = 112; + const int availableH = max(1, contentBottom - contentTop); + int h = max(140, (availableH - editorH - minToolH - pad * 3) / 2); CRect vars(rect.left + pad, contentTop, rect.right - pad, contentTop + h); CRect keyvals(rect.left + pad, vars.bottom + pad, rect.right - pad, vars.bottom + h); - CRect editor(rect.left + pad, keyvals.bottom + pad, rect.right - pad, keyvals.bottom + 106); + CRect editor(rect.left + pad, keyvals.bottom + pad, rect.right - pad, keyvals.bottom + editorH); CRect tools(rect.left + pad, editor.bottom + pad, rect.right - pad, contentBottom); DrawPanel(dc, header, "", true); @@ -419,8 +1044,10 @@ BOOL CEntityDlg::OnInitDialog() { InitModernTheme(); listKeyVal.SetUpdateInspectors(true); - listKeyVal.SetDivider(120); - listVars.SetDivider(120); + listKeyVal.SetDivider(170); + listVars.SetDivider(170); + ::SendMessage(listKeyVal.GetSafeHwnd(), LB_SETITEMHEIGHT, 0, 32); + ::SendMessage(listVars.GetSafeHwnd(), LB_SETITEMHEIGHT, 0, 32); staticFrame.SetWindowText("0"); @@ -448,7 +1075,7 @@ void CEntityDlg::OnSize(UINT nType, int cx, int cy) { const int headerH = 42; const int classH = 52; const int editorH = 106; - const int minToolH = 124; + const int minToolH = 112; CRect rect; GetClientRect(&rect); @@ -468,8 +1095,10 @@ void CEntityDlg::OnSize(UINT nType, int cx, int cy) { int contentTop = classPanel.bottom + pad; int contentBottom = rect.bottom - pad; - if (w >= 560) { - int listH = max(120, min(260, (h - contentTop - editorH - minToolH - pad * 3))); + if (w >= 500) { + const int availableH = max(1, contentBottom - contentTop); + // No upper cap: the grids now consume the extra dock/inspector height instead of staying squeezed at the top. + int listH = max(180, availableH - editorH - minToolH - pad * 3); int leftW = (w - pad * 3) / 2; CRect varsPanel(rect.left + pad, contentTop, rect.left + pad + leftW, contentTop + listH); @@ -534,8 +1163,8 @@ void CEntityDlg::OnSize(UINT nType, int cx, int cy) { MoveCtrl(staticFrame, animX + animW - 38, toolY + 68, 36, 20); } else { - int available = contentBottom - contentTop; - int listH = max(82, (available - editorH - minToolH - pad * 3) / 2); + int available = max(1, contentBottom - contentTop); + int listH = max(140, (available - editorH - minToolH - pad * 3) / 2); CRect varsPanel(rect.left + pad, contentTop, rect.right - pad, contentTop + listH); CRect keyPanel(rect.left + pad, varsPanel.bottom + pad, rect.right - pad, varsPanel.bottom + pad + listH); diff --git a/neo/engine/tools/radiant/GLWidget.cpp b/neo/engine/tools/radiant/GLWidget.cpp index 61c2a379..0f3c2829 100644 --- a/neo/engine/tools/radiant/GLWidget.cpp +++ b/neo/engine/tools/radiant/GLWidget.cpp @@ -2,9 +2,9 @@ =========================================================================== IceTech GPL Source Code -Copyright (C) 2026 Justin Marshall +Copyright (C) 2026 Justin Marshall -This file is part of the IceTech GPL Source Code (?IceTech Source Code?). +This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,34 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif +static bool GLWidget_IsValidSize(int w, int h) { + return (w > 0 && h > 0); +} + +static void GLWidget_SetRawViewport(int x, int y, int w, int h) { + if (!GLWidget_IsValidSize(w, h)) { + return; + } + glViewport(x, y, w, h); + glEnable(GL_SCISSOR_TEST); + glScissor(x, y, w, h); +} + +static void GLWidget_Set2DViewport(int x, int y, int w, int h) { + GLWidget_SetRawViewport(x, y, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, w, 0, h, -256, 256); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +static float GLWidget_CalcFovY(int w, int h) { + if (w <= 0 || h <= 0) { + return 90.0f; + } + return 2.0f * atan((float)h / (float)w) * idMath::M_RAD2DEG; +} ///////////////////////////////////////////////////////////////////////////// @@ -47,11 +75,11 @@ class idMiniDrawVert { public: idVec3 xyz; idVec2 st; - idMiniDrawVert(float x, float y, float z, float s, float t) : xyz(x,y,z), st(s, t) { + idMiniDrawVert(float x, float y, float z, float s, float t) : xyz(x, y, z), st(s, t) { }; }; -static idMiniDrawVert cubeData[] = { +static const idMiniDrawVert cubeData[] = { idMiniDrawVert(-1.0, -1.0, +1.0, 0.0, 0.0), idMiniDrawVert(+1.0, -1.0, +1.0, 1.0, 0.0), idMiniDrawVert(+1.0, +1.0, +1.0, 1.0, 1.0), @@ -83,21 +111,45 @@ static idMiniDrawVert cubeData[] = { idMiniDrawVert(-1.0, +1.0, -1.0, 0.0, 1.0) }; -static int cubeSides = sizeof(cubeData) / sizeof(idMiniDrawVert); -static int numQuads = cubeSides / 4; +static const int cubeSides = sizeof(cubeData) / sizeof(cubeData[0]); +static const int numQuads = cubeSides / 4; -void glTexturedBox(idVec3 &point, float size, const idMaterial *mat) { - glTranslatef(point.x, point.y, point.z); - for (int i = 0; i < numQuads; i++) { - glBegin(GL_QUADS); - for (int j = 0; j < 4; j++) { - idVec3 v = cubeData[i * 4 + j].xyz; - v *= size; - glTexCoord2fv(cubeData[i * 4 + j].st.ToFloatPtr()); - glVertex3fv(v.ToFloatPtr()); - } - glEnd(); +void glTexturedBox(idVec3& point, float size, const idMaterial* mat) { + // The material is expected to be bound by the caller. Keep this function + // free of glBegin/glEnd so stricter GL shims only have to handle draw calls. + (void)mat; + + float xyz[cubeSides][3]; + float st[cubeSides][2]; + unsigned short indexes[numQuads * 6]; + + for (int i = 0; i < cubeSides; i++) { + const idMiniDrawVert& src = cubeData[i]; + xyz[i][0] = point.x + src.xyz.x * size; + xyz[i][1] = point.y + src.xyz.y * size; + xyz[i][2] = point.z + src.xyz.z * size; + st[i][0] = src.st.x; + st[i][1] = src.st.y; } + + for (int i = 0; i < numQuads; i++) { + const unsigned short base = (unsigned short)(i * 4); + indexes[i * 6 + 0] = base + 0; + indexes[i * 6 + 1] = base + 1; + indexes[i * 6 + 2] = base + 2; + indexes[i * 6 + 3] = base + 0; + indexes[i * 6 + 4] = base + 2; + indexes[i * 6 + 5] = base + 3; + } + + glDisableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, &xyz[0][0]); + glTexCoordPointer(2, GL_FLOAT, 0, &st[0][0]); + glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, indexes); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); } idGLWidget::idGLWidget() @@ -130,64 +182,73 @@ END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // idGLWidget message handlers -BOOL idGLWidget::PreCreateWindow(CREATESTRUCT& cs) +BOOL idGLWidget::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Add your specialized code here and/or call the base class - + return CWnd::PreCreateWindow(cs); } -BOOL idGLWidget::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) +BOOL idGLWidget::Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID, CCreateContext* pContext) { - if (CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext) == -1) { + if (!CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext)) { + return FALSE; + } + + CDC* dc = GetDC(); + if (dc == NULL) { + return FALSE; + } + if (dc->m_hDC == NULL) { + ReleaseDC(dc); return FALSE; } - CDC *dc = GetDC(); QEW_SetupPixelFormat(dc->m_hDC, false); + if ((hglrc = (HGLRC)wglCreateContext(dc->m_hDC)) == 0) + { + common->FatalError("idGLWidget failed"); + } + + ReleaseDC(dc); + initialized = true; return TRUE; - } -void idGLWidget::OnPaint() +void idGLWidget::OnPaint() { - - if (!initialized) { - CDC *dc = GetDC(); - QEW_SetupPixelFormat(dc->m_hDC, false); - ReleaseDC(dc); - initialized = true; - } CPaintDC dc(this); // device context for painting - + CRect rect; GetClientRect(rect); - idGraphicsDeviceContextHelper context(dc.m_hDC, win32.hGLRC); + const int width = rect.Width(); + const int height = rect.Height(); + if (!GLWidget_IsValidSize(width, height)) { + return; + } - glViewport(0, 0, rect.Width(), rect.Height()); - glScissor(0, 0, rect.Width(), rect.Height()); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glClearColor (0.4f, 0.4f, 0.4f, 0.7f); + if (!initialized) { + QEW_SetupPixelFormat(dc.m_hDC, false); + if ((hglrc = (HGLRC)wglCreateContext(dc.m_hDC)) == 0) + { + common->FatalError("idGLWidget failed"); + } + initialized = true; + } + + idGraphicsDeviceContextHelper context(dc.m_hDC, hglrc); + + GLWidget_Set2DViewport(0, 0, width, height); + glClearColor(0.4f, 0.4f, 0.4f, 0.7f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); - glOrtho(0, rect.Width(), 0, rect.Height(), -256, 256); if (drawable) { - drawable->draw(1, 1, rect.Width()-1, rect.Height()-1); - } else { - glViewport(0, 0, rect.Width(), rect.Height()); - glScissor(0, 0, rect.Width(), rect.Height()); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glClearColor (0.4f, 0.4f, 0.4f, 0.7f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + drawable->draw(0, 0, width, height); } } @@ -212,8 +273,8 @@ void idGLDrawable::mouseMove(float x, float y) { Update(); if (Sys_KeyDown(VK_MENU)) { // scale - float *px = &x; - float *px2 = &pressX; + float* px = &x; + float* px2 = &pressX; if (fDiff(y, pressY) > fDiff(x, pressX)) { px = &y; @@ -223,13 +284,14 @@ void idGLDrawable::mouseMove(float x, float y) { if (*px > *px2) { // zoom in scale += 0.1f; - if ( scale > 10.0f ) { + if (scale > 10.0f) { scale = 10.0f; } - } else if (*px < *px2) { + } + else if (*px < *px2) { // zoom out scale -= 0.1f; - if ( scale <= 0.001f ) { + if (scale <= 0.001f) { scale = 0.001f; } } @@ -237,9 +299,11 @@ void idGLDrawable::mouseMove(float x, float y) { *px2 = *px; ::SetCursorPos(pressX, pressY); - } else if (Sys_KeyDown(VK_SHIFT)) { + } + else if (Sys_KeyDown(VK_SHIFT)) { // rotate - } else { + } + else { // origin if (x != pressX) { xOffset += (x - pressX); @@ -255,27 +319,43 @@ void idGLDrawable::mouseMove(float x, float y) { } void idGLDrawable::draw(int x, int y, int w, int h) { - GL_State( GLS_DEFAULT ); - glViewport(x, y, w, h); - glScissor(x, y, w, h); - glMatrixMode(GL_PROJECTION); - glClearColor( 0.1f, 0.1f, 0.1f, 0.0f ); - glClear(GL_COLOR_BUFFER_BIT); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glLineWidth(0.5); - glColor3f(1, 1, 1); - globalImages->BindNull(); - glBegin(GL_LINE_LOOP); - glColor3f(1, 0, 0); - glVertex2f(x + 3, y + 3); - glColor3f(0, 1, 0); - glVertex2f(x + 3, h - 3); - glColor3f(0, 0, 1); - glVertex2f(w - 3, h - 3); - glColor3f(1, 1, 1); - glVertex2f(w - 3, y + 3); - glEnd(); + if (!GLWidget_IsValidSize(w, h)) { + return; + } + GL_State(GLS_DEFAULT); + GLWidget_Set2DViewport(x, y, w, h); + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glLineWidth(1.0f); + glColor3f(1, 1, 1); + if (globalImages) { + globalImages->BindNull(); + } + + const float verts[4][2] = { + { 3.0f, 3.0f }, + { 3.0f, h - 3.0f }, + { w - 3.0f, h - 3.0f }, + { w - 3.0f, 3.0f } + }; + const float colors[4][4] = { + { 1.0f, 0.0f, 0.0f, 1.0f }, + { 0.0f, 1.0f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f, 1.0f } + }; + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, &verts[0][0]); + glColorPointer(4, GL_FLOAT, 0, &colors[0][0]); + glDrawArrays(GL_LINE_LOOP, 0, 4); + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); } static int viewAngle = -98; @@ -292,8 +372,8 @@ void idGLDrawableMaterial::mouseMove(float x, float y) { bool doLight = Sys_KeyDown(VK_SHIFT); if (doScale || doLight) { // scale - float *px = &x; - float *px2 = &pressX; + float* px = &x; + float* px2 = &pressX; if (fDiff(y, pressY) > fDiff(x, pressX)) { px = &y; @@ -304,40 +384,48 @@ void idGLDrawableMaterial::mouseMove(float x, float y) { // zoom in if (doScale) { scale += 0.1f; - if ( scale > 10.0f ) { + if (scale > 10.0f) { scale = 10.0f; } - } else { + } + else { light += 0.05f; - if ( light > 2.0f ) { + if (light > 2.0f) { light = 2.0f; } } - } else if (*px < *px2) { + worldDirty = true; + } + else if (*px < *px2) { // zoom out if (doScale) { scale -= 0.1f; - if ( scale <= 0.001f ) { + if (scale <= 0.001f) { scale = 0.001f; } - } else { + } + else { light -= 0.05f; - if ( light < 0.0f ) { + if (light < 0.0f) { light = 0.0f; } } + worldDirty = true; } *px2 = *px; ::SetCursorPos(pressX, pressY); - } else { + } + else { // origin if (x != pressX) { xOffset += (x - pressX); pressX = x; + worldDirty = true; } if (y != pressY) { yOffset -= (y - pressY); pressY = y; + worldDirty = true; } //::SetCursorPos(pressX, pressY); } @@ -346,140 +434,150 @@ void idGLDrawableMaterial::mouseMove(float x, float y) { void idGLDrawableMaterial::draw(int x, int y, int w, int h) { - const idMaterial *mat = material; - if (mat) { - glViewport(x, y, w, h); - glScissor(x, y, w, h); - glMatrixMode(GL_PROJECTION); - glClearColor( 0.1f, 0.1f, 0.1f, 0.0f ); - glClear(GL_COLOR_BUFFER_BIT); - - if (worldDirty) { - InitWorld(); - renderLight_t parms; - idDict spawnArgs; - spawnArgs.Set("classname", "light"); - spawnArgs.Set("name", "light_1"); - spawnArgs.Set("origin", "0 0 0"); - idStr str; - sprintf(str, "%f %f %f", light, light, light); - spawnArgs.Set("_color", str); - gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &parms ); - lightDef = world->AddLightDef( &parms ); - - idImage *img = (mat->GetNumStages() > 0) ? mat->GetStage(0)->texture.image : mat->GetEditorImage(); - - if (img == NULL) { - common->Warning("Unable to load image for preview for %s", mat->GetName()); - return; - } - - int width = img->uploadWidth; - int height = img->uploadHeight; - - width *= scale; - height *= scale; - - srfTriangles_t *tris = worldModel->AllocSurfaceTriangles( 4, 6 ); - tris->numVerts = 4; - tris->numIndexes = 6; - - tris->indexes[0] = 0; - tris->indexes[1] = 1; - tris->indexes[2] = 2; - tris->indexes[3] = 3; - tris->indexes[4] = 1; - tris->indexes[5] = 0; - - tris->verts[0].xyz.x = 64; - tris->verts[0].xyz.y = -xOffset + 0 - width / 2; - tris->verts[0].xyz.z = yOffset + 0 - height / 2; - tris->verts[0].st.x = 1; - tris->verts[0].st.y = 1; - - tris->verts[1].xyz.x = 64; - tris->verts[1].xyz.y = -xOffset + width / 2; - tris->verts[1].xyz.z = yOffset + height / 2; - tris->verts[1].st.x = 0; - tris->verts[1].st.y = 0; - - tris->verts[2].xyz.x = 64; - tris->verts[2].xyz.y = -xOffset + 0 - width / 2; - tris->verts[2].xyz.z = yOffset + height / 2; - tris->verts[2].st.x = 1; - tris->verts[2].st.y = 0; - - tris->verts[3].xyz.x = 64; - tris->verts[3].xyz.y = -xOffset + width / 2; - tris->verts[3].xyz.z = yOffset + 0 - height / 2; - tris->verts[3].st.x = 0; - tris->verts[3].st.y = 1; - - tris->verts[0].normal = tris->verts[1].xyz.Cross(tris->verts[3].xyz); - tris->verts[1].normal = tris->verts[2].normal = tris->verts[3].normal = tris->verts[0].normal; - AddTris(tris, mat); - - worldModel->FinishSurfaces(); - - renderEntity_t worldEntity; - - memset( &worldEntity, 0, sizeof( worldEntity ) ); - if ( mat->HasGui() ) { - worldEntity.gui[ 0 ] = mat->GlobalGui(); - } - worldEntity.hModel = worldModel; - worldEntity.axis = mat3_default; - worldEntity.shaderParms[0] = 1; - worldEntity.shaderParms[1] = 1; - worldEntity.shaderParms[2] = 1; - worldEntity.shaderParms[3] = 1; - modelDef = world->AddEntityDef( &worldEntity ); - - worldDirty = false; - } - - renderView_t refdef; - // render it - renderSystem->BeginFrame(w, h); - memset( &refdef, 0, sizeof( refdef ) ); - refdef.vieworg.Set(viewAngle, 0, 0); - - refdef.viewaxis = idAngles(0,0,0).ToMat3(); - refdef.shaderParms[0] = 1; - refdef.shaderParms[1] = 1; - refdef.shaderParms[2] = 1; - refdef.shaderParms[3] = 1; - - refdef.width = SCREEN_WIDTH; - refdef.height = SCREEN_HEIGHT; - refdef.fov_x = 90; - refdef.fov_y = 2 * atan((float)h / w) * idMath::M_RAD2DEG; - - refdef.time = eventLoop->Milliseconds(); - - world->RenderScene( &refdef ); - int frontEnd, backEnd; - renderSystem->EndFrame( &frontEnd, &backEnd ); - - glMatrixMode( GL_MODELVIEW ); - glLoadIdentity(); + const idMaterial* mat = material; + if (!mat || !GLWidget_IsValidSize(w, h)) { + return; } + GLWidget_SetRawViewport(x, y, w, h); + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (worldDirty) { + InitWorld(); + if (world == NULL || worldModel == NULL) { + return; + } + renderLight_t parms; + idDict spawnArgs; + spawnArgs.Set("classname", "light"); + spawnArgs.Set("name", "light_1"); + spawnArgs.Set("origin", "0 0 0"); + idStr str; + sprintf(str, "%f %f %f", light, light, light); + spawnArgs.Set("_color", str); + gameEdit->ParseSpawnArgsToRenderLight(&spawnArgs, &parms); + lightDef = world->AddLightDef(&parms); + + idImage* img = (mat->GetNumStages() > 0) ? mat->GetStage(0)->texture.image : mat->GetEditorImage(); + + if (img == NULL) { + common->Warning("Unable to load image for preview for %s", mat->GetName()); + return; + } + + int width = (int)(img->uploadWidth * scale); + int height = (int)(img->uploadHeight * scale); + if (width < 1) { + width = 1; + } + if (height < 1) { + height = 1; + } + + srfTriangles_t* tris = worldModel->AllocSurfaceTriangles(4, 6); + tris->numVerts = 4; + tris->numIndexes = 6; + + tris->indexes[0] = 0; + tris->indexes[1] = 3; + tris->indexes[2] = 1; + tris->indexes[3] = 0; + tris->indexes[4] = 1; + tris->indexes[5] = 2; + + tris->verts[0].xyz.x = 64; + tris->verts[0].xyz.y = -xOffset + 0 - width / 2; + tris->verts[0].xyz.z = yOffset + 0 - height / 2; + tris->verts[0].st.x = 1; + tris->verts[0].st.y = 1; + + tris->verts[1].xyz.x = 64; + tris->verts[1].xyz.y = -xOffset + width / 2; + tris->verts[1].xyz.z = yOffset + height / 2; + tris->verts[1].st.x = 0; + tris->verts[1].st.y = 0; + + tris->verts[2].xyz.x = 64; + tris->verts[2].xyz.y = -xOffset + 0 - width / 2; + tris->verts[2].xyz.z = yOffset + height / 2; + tris->verts[2].st.x = 1; + tris->verts[2].st.y = 0; + + tris->verts[3].xyz.x = 64; + tris->verts[3].xyz.y = -xOffset + width / 2; + tris->verts[3].xyz.z = yOffset + 0 - height / 2; + tris->verts[3].st.x = 0; + tris->verts[3].st.y = 1; + + idVec3 edge1 = tris->verts[3].xyz - tris->verts[0].xyz; + idVec3 edge2 = tris->verts[1].xyz - tris->verts[0].xyz; + tris->verts[0].normal = edge1.Cross(edge2); + tris->verts[0].normal.Normalize(); + tris->verts[1].normal = tris->verts[2].normal = tris->verts[3].normal = tris->verts[0].normal; + AddTris(tris, mat); + + worldModel->FinishSurfaces(); + + renderEntity_t worldEntity; + + memset(&worldEntity, 0, sizeof(worldEntity)); + if (mat->HasGui()) { + worldEntity.gui[0] = mat->GlobalGui(); + } + worldEntity.hModel = worldModel; + worldEntity.axis = mat3_default; + worldEntity.shaderParms[0] = 1; + worldEntity.shaderParms[1] = 1; + worldEntity.shaderParms[2] = 1; + worldEntity.shaderParms[3] = 1; + modelDef = world->AddEntityDef(&worldEntity); + + worldDirty = false; + } + + renderView_t refdef; + // render it + renderSystem->BeginFrame(w, h); + memset(&refdef, 0, sizeof(refdef)); + refdef.vieworg.Set(viewAngle, 0, 0); + + refdef.viewaxis = idAngles(0, 0, 0).ToMat3(); + refdef.shaderParms[0] = 1; + refdef.shaderParms[1] = 1; + refdef.shaderParms[2] = 1; + refdef.shaderParms[3] = 1; + + refdef.width = w; + refdef.height = h; + refdef.fov_x = 90; + refdef.fov_y = GLWidget_CalcFovY(w, h); + + refdef.time = eventLoop->Milliseconds(); + + world->RenderScene(&refdef); + int frontEnd, backEnd; + renderSystem->EndFrame(&frontEnd, &backEnd, false); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); } -void idGLDrawableMaterial::setMedia(const char *name) { - idImage *img = NULL; +void idGLDrawableMaterial::setMedia(const char* name) { + idImage* img = NULL; if (name && *name) { material = declManager->FindMaterial(name); if (material) { - const shaderStage_t *stage = (material->GetNumStages() > 0) ? material->GetStage(0) : NULL; + const shaderStage_t* stage = (material->GetNumStages() > 0) ? material->GetStage(0) : NULL; if (stage) { img = stage->texture.image; - } else { + } + else { img = material->GetEditorImage(); } } - } else { + } + else { material = NULL; } // set scale to get a good fit @@ -488,8 +586,9 @@ void idGLDrawableMaterial::setMedia(const char *name) { float size = (img->uploadWidth > img->uploadHeight) ? img->uploadWidth : img->uploadHeight; // use 128 as base scale of 1.0 - scale = 128.0 / size; - } else { + scale = (size > 0.0f) ? 128.0f / size : 1.0f; + } + else { scale = 1.0; } xOffset = 0.0; @@ -497,30 +596,47 @@ void idGLDrawableMaterial::setMedia(const char *name) { worldDirty = true; } -idGLDrawableModel::idGLDrawableModel(const char *name) { - worldModel = renderModelManager->FindModel( name ); - light = 1.0; +idGLDrawableModel::idGLDrawableModel(const char* name) { + worldModel = (name && *name) ? renderModelManager->FindModel(name) : renderModelManager->DefaultModel(); + light = 1.0f; worldDirty = true; -} - -idGLDrawableModel::idGLDrawableModel() { - worldModel = renderModelManager->DefaultModel(); - light = 1.0; -} - -void idGLDrawableModel::setMedia(const char *name) { - worldModel = renderModelManager->FindModel(name); - worldDirty = true; - xOffset = 0.0; - yOffset = 0.0; - zOffset = -128; - rotation.Set( 0.0f, 0.0f, 0.0f, 1.0f ); + xOffset = 0.0f; + yOffset = 0.0f; + zOffset = -128.0f; + rotation.Set(0.0f, 0.0f, 0.0f, 1.0f); radius = 2.6f; lastPress.Zero(); } -void idGLDrawableModel::SetSkin( const char *skin ) { - skinStr = skin; +idGLDrawableModel::idGLDrawableModel() { + worldModel = renderModelManager->DefaultModel(); + light = 1.0f; + worldDirty = true; + xOffset = 0.0f; + yOffset = 0.0f; + zOffset = -128.0f; + rotation.Set(0.0f, 0.0f, 0.0f, 1.0f); + radius = 2.6f; + lastPress.Zero(); +} + +void idGLDrawableModel::setMedia(const char* name) { + worldModel = (name && *name) ? renderModelManager->FindModel(name) : renderModelManager->DefaultModel(); + if (worldModel == NULL) { + worldModel = renderModelManager->DefaultModel(); + } + worldDirty = true; + xOffset = 0.0f; + yOffset = 0.0f; + zOffset = -128.0f; + rotation.Set(0.0f, 0.0f, 0.0f, 1.0f); + radius = 2.6f; + lastPress.Zero(); +} + +void idGLDrawableModel::SetSkin(const char* skin) { + skinStr = skin ? skin : ""; + worldDirty = true; } @@ -528,8 +644,10 @@ void idGLDrawableModel::buttonDown(int _button, float x, float y) { pressX = x; pressY = y; - lastPress.y = -( float )( 2 * x - rect.z ) / rect.z; - lastPress.x = -( float )( 2 * y - rect.w ) / rect.w; + const float rw = (rect.z != 0.0f) ? rect.z : 1.0f; + const float rh = (rect.w != 0.0f) ? rect.w : 1.0f; + lastPress.y = -(float)(2 * x - rw) / rw; + lastPress.x = -(float)(2 * y - rh) / rh; lastPress.z = 0.0f; button = _button; if (button == MK_RBUTTON || button == MK_LBUTTON) { @@ -541,34 +659,38 @@ void idGLDrawableModel::mouseMove(float x, float y) { if (handleMove) { Update(); if (button == MK_LBUTTON) { - float cury = ( float )( 2 * x - rect.z ) / rect.z; - float curx = ( float )( 2 * y - rect.w ) / rect.w; - idVec3 to( -curx, -cury, 0.0f ); - to.ProjectSelfOntoSphere( radius ); - lastPress.ProjectSelfOntoSphere( radius ); + const float rw = (rect.z != 0.0f) ? rect.z : 1.0f; + const float rh = (rect.w != 0.0f) ? rect.w : 1.0f; + float cury = (float)(2 * x - rw) / rw; + float curx = (float)(2 * y - rh) / rh; + idVec3 to(-curx, -cury, 0.0f); + to.ProjectSelfOntoSphere(radius); + lastPress.ProjectSelfOntoSphere(radius); idVec3 axis; - axis.Cross( to, lastPress ); - float len = ( lastPress - to ).Length() / ( 2.0f * radius ); - len = idMath::ClampFloat( -1.0f, 1.0f, len ); - float phi = 2.0f * asin ( len ) ; + axis.Cross(to, lastPress); + float len = (lastPress - to).Length() / (2.0f * radius); + len = idMath::ClampFloat(-1.0f, 1.0f, len); + float phi = 2.0f * asin(len); axis.Normalize(); - axis *= sin( phi / 2.0f ); - idQuat rot( axis.z, axis.y, axis.x, cos( phi / 2.0f ) ); + axis *= sin(phi / 2.0f); + idQuat rot(axis.z, axis.y, axis.x, cos(phi / 2.0f)); rot.Normalize(); rotation *= rot; rotation.Normalize(); + worldDirty = true; lastPress = to; lastPress.z = 0.0f; - } else { + } + else { bool doScale = Sys_KeyDown(VK_MENU); bool doLight = Sys_KeyDown(VK_SHIFT); if (doLight) { // scale - float *px = &x; - float *px2 = &pressX; + float* px = &x; + float* px2 = &pressX; if (fDiff(y, pressY) > fDiff(x, pressX)) { px = &y; @@ -577,23 +699,28 @@ void idGLDrawableModel::mouseMove(float x, float y) { if (*px > *px2) { light += 0.05f; - if ( light > 2.0f ) { + if (light > 2.0f) { light = 2.0f; } - } else if (*px < *px2) { + worldDirty = true; + } + else if (*px < *px2) { light -= 0.05f; - if ( light < 0.0f ) { + if (light < 0.0f) { light = 0.0f; } + worldDirty = true; } *px2 = *px; ::SetCursorPos(pressX, pressY); - } else { + } + else { // origin if (x != pressX) { if (doScale) { zOffset += (x - pressX); - } else { + } + else { xOffset += (x - pressX); } pressX = x; @@ -601,7 +728,8 @@ void idGLDrawableModel::mouseMove(float x, float y) { if (y != pressY) { if (doScale) { zOffset -= (y - pressY); - } else { + } + else { yOffset -= (y - pressY); } pressY = y; @@ -614,24 +742,28 @@ void idGLDrawableModel::mouseMove(float x, float y) { void idGLDrawableModel::draw(int x, int y, int w, int h) { - if ( !worldModel ) { + if (!worldModel || !GLWidget_IsValidSize(w, h)) { return; } - if ( worldModel->IsDynamicModel() != DM_STATIC ) { - //return; + if (worldModel->IsDynamicModel() != DM_STATIC) { + // Keep dynamic models renderable for previews; the renderer will choose + // the best static/dynamic representation available. } - rect.Set( x, y, w, h ); + rect.Set(x, y, w, h); - glViewport(x, y, w, h); - glScissor(x, y, w, h); - glMatrixMode(GL_PROJECTION); - glClearColor( 0.1f, 0.1f, 0.1f, 0.0f ); - glClear(GL_COLOR_BUFFER_BIT); + GLWidget_SetRawViewport(x, y, w, h); + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (worldDirty) { - //InitWorld(); - world->InitFromMap( NULL ); + if (world == NULL) { + world = renderSystem->AllocRenderWorld(dxrWorldId_t::DXR_WIDGET_0); + } + if (world == NULL) { + return; + } + world->InitFromMap(NULL); renderLight_t parms; idDict spawnArgs; spawnArgs.Set("classname", "light"); @@ -640,17 +772,17 @@ void idGLDrawableModel::draw(int x, int y, int w, int h) { idStr str; sprintf(str, "%f %f %f", light, light, light); spawnArgs.Set("_color", str); - gameEdit->ParseSpawnArgsToRenderLight( &spawnArgs, &parms ); - lightDef = world->AddLightDef( &parms ); + gameEdit->ParseSpawnArgsToRenderLight(&spawnArgs, &parms); + lightDef = world->AddLightDef(&parms); renderEntity_t worldEntity; - memset( &worldEntity, 0, sizeof( worldEntity ) ); + memset(&worldEntity, 0, sizeof(worldEntity)); spawnArgs.Clear(); spawnArgs.Set("classname", "func_static"); - spawnArgs.Set("name", spawnArgs.GetString("model")); + spawnArgs.Set("name", "preview_model"); spawnArgs.Set("origin", "0 0 0"); - if ( skinStr.Length() ) { - spawnArgs.Set( "skin", skinStr ); + if (skinStr.Length()) { + spawnArgs.Set("skin", skinStr); } gameEdit->ParseSpawnArgsToRenderEntity(&spawnArgs, &worldEntity); worldEntity.hModel = worldModel; @@ -661,55 +793,55 @@ void idGLDrawableModel::draw(int x, int y, int w, int h) { worldEntity.shaderParms[1] = 1; worldEntity.shaderParms[2] = 1; worldEntity.shaderParms[3] = 1; - modelDef = world->AddEntityDef( &worldEntity ); + modelDef = world->AddEntityDef(&worldEntity); worldDirty = false; } - + renderView_t refdef; // render it renderSystem->BeginFrame(w, h); - memset( &refdef, 0, sizeof( refdef ) ); + memset(&refdef, 0, sizeof(refdef)); refdef.vieworg.Set(zOffset, xOffset, -yOffset); - refdef.viewaxis = idAngles(0,0,0).ToMat3(); + refdef.viewaxis = idAngles(0, 0, 0).ToMat3(); refdef.shaderParms[0] = 1; refdef.shaderParms[1] = 1; refdef.shaderParms[2] = 1; refdef.shaderParms[3] = 1; - refdef.width = SCREEN_WIDTH; - refdef.height = SCREEN_HEIGHT; + refdef.width = w; + refdef.height = h; refdef.fov_x = 90; - refdef.fov_y = 2 * atan((float)h / w) * idMath::M_RAD2DEG; + refdef.fov_y = GLWidget_CalcFovY(w, h); refdef.time = eventLoop->Milliseconds(); - world->RenderScene( &refdef ); + world->RenderScene(&refdef); int frontEnd, backEnd; - renderSystem->EndFrame( &frontEnd, &backEnd ); + renderSystem->EndFrame(&frontEnd, &backEnd); - glMatrixMode( GL_MODELVIEW ); + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } -void idGLWidget::OnLButtonDown(UINT nFlags, CPoint point) +void idGLWidget::OnLButtonDown(UINT nFlags, CPoint point) { SetCapture(); if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonDown(MK_LBUTTON, point.x, point.y); } } -void idGLWidget::OnLButtonUp(UINT nFlags, CPoint point) +void idGLWidget::OnLButtonUp(UINT nFlags, CPoint point) { if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonUp(MK_LBUTTON, point.x, point.y); @@ -717,21 +849,21 @@ void idGLWidget::OnLButtonUp(UINT nFlags, CPoint point) ReleaseCapture(); } -void idGLWidget::OnMButtonDown(UINT nFlags, CPoint point) +void idGLWidget::OnMButtonDown(UINT nFlags, CPoint point) { SetCapture(); if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonDown(MK_MBUTTON, point.x, point.y); } } -void idGLWidget::OnMButtonUp(UINT nFlags, CPoint point) +void idGLWidget::OnMButtonUp(UINT nFlags, CPoint point) { if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonUp(MK_MBUTTON, point.x, point.y); @@ -739,10 +871,10 @@ void idGLWidget::OnMButtonUp(UINT nFlags, CPoint point) ReleaseCapture(); } -void idGLWidget::OnMouseMove(UINT nFlags, CPoint point) +void idGLWidget::OnMouseMove(UINT nFlags, CPoint point) { if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->mouseMove(point.x, point.y); @@ -750,41 +882,44 @@ void idGLWidget::OnMouseMove(UINT nFlags, CPoint point) } } -BOOL idGLWidget::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) +BOOL idGLWidget::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { if (drawable) { float f = drawable->getScale(); - if ( zDelta > 0.0f ) { + if (zDelta > 0.0f) { f += 0.1f; - } else { + } + else { f -= 0.1f; } - if ( f <= 0.0f ) { + if (f <= 0.0f) { f = 0.1f; } - if ( f > 5.0f ) { + if (f > 5.0f) { f = 5.0f; } + drawable->Update(); drawable->setScale(f); + Invalidate(FALSE); } return TRUE; } -void idGLWidget::OnRButtonDown(UINT nFlags, CPoint point) +void idGLWidget::OnRButtonDown(UINT nFlags, CPoint point) { SetCapture(); if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonDown(MK_RBUTTON, point.x, point.y); } } -void idGLWidget::OnRButtonUp(UINT nFlags, CPoint point) +void idGLWidget::OnRButtonUp(UINT nFlags, CPoint point) { if (drawable) { - if ( drawable->ScreenCoords() ) { + if (drawable->ScreenCoords()) { ClientToScreen(&point); } drawable->buttonUp(MK_RBUTTON, point.x, point.y); @@ -792,10 +927,14 @@ void idGLWidget::OnRButtonUp(UINT nFlags, CPoint point) ReleaseCapture(); } -void idGLWidget::setDrawable(idGLDrawable *d) { +void idGLWidget::setDrawable(idGLDrawable* d) { drawable = d; - if (d->getRealTime()) { - SetTimer(1, d->getRealTime(), NULL); + if (m_hWnd != NULL) { + KillTimer(1); + if (d && d->getRealTime()) { + SetTimer(1, d->getRealTime(), NULL); + } + Invalidate(FALSE); } } @@ -803,14 +942,15 @@ void idGLWidget::setDrawable(idGLDrawable *d) { void idGLWidget::OnTimer(UINT_PTR nIDEvent) { if (drawable && drawable->getRealTime()) { Invalidate(FALSE); - } else { + } + else { KillTimer(1); } } idGLDrawable::idGLDrawable() { - scale = 1.0; + scale = 1.0; xOffset = 0.0; yOffset = 0.0; handleMove = false; @@ -819,32 +959,35 @@ idGLDrawable::idGLDrawable() { } void idGLDrawableConsole::draw(int x, int y, int w, int h) { - glPushAttrib( GL_ALL_ATTRIB_BITS ); - glClearColor( 0.1f, 0.1f, 0.1f, 0.0f ); - glScissor( 0, 0, w, h ); - glClear( GL_COLOR_BUFFER_BIT ); - renderSystem->BeginFrame( w, h ); + if (!GLWidget_IsValidSize(w, h)) { + return; + } - console->Draw( true ); + GL_State(GLS_DEFAULT); + GLWidget_SetRawViewport(x, y, w, h); + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + renderSystem->BeginFrame(w, h); - renderSystem->EndFrame( NULL, NULL ); - glPopAttrib(); + console->Draw(true); + + renderSystem->EndFrame(NULL, NULL); } void idGLConsoleWidget::init() { setDrawable(&console); } -void idGLConsoleWidget::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) +void idGLConsoleWidget::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { sysEvent_t ev; - memset( &ev, 0, sizeof( ev ) ); + memset(&ev, 0, sizeof(ev)); ev.evType = SE_KEY; ev.evValue2 = 1; ev.evValue = nChar; - ::console->ProcessEvent( &ev, true ); + ::console->ProcessEvent(&ev, true); } BEGIN_MESSAGE_MAP(idGLConsoleWidget, idGLWidget) @@ -859,42 +1002,40 @@ END_MESSAGE_MAP() -void idGLConsoleWidget::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) +void idGLConsoleWidget::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { sysEvent_t ev; - memset( &ev, 0, sizeof( ev ) ); + memset(&ev, 0, sizeof(ev)); ev.evType = SE_KEY; ev.evValue2 = 0; ev.evValue = nChar; - ::console->ProcessEvent( &ev, true ); + ::console->ProcessEvent(&ev, true); } void idGLConsoleWidget::OnPaint() { idGLWidget::OnPaint(); } -void idGLConsoleWidget::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) +void idGLConsoleWidget::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { sysEvent_t ev; - memset( &ev, 0, sizeof( ev ) ); + memset(&ev, 0, sizeof(ev)); ev.evType = SE_CHAR; ev.evValue = nChar; - ::console->ProcessEvent( &ev, true ); + ::console->ProcessEvent(&ev, true); } void idGLConsoleWidget::OnLButtonDown(UINT nFlags, CPoint point) { SetFocus(); } -BOOL idGLWidget::OnEraseBkgnd(CDC* pDC) +BOOL idGLWidget::OnEraseBkgnd(CDC* pDC) { - return FALSE; - - //return CWnd::OnEraseBkgnd(pDC); + return TRUE; } @@ -908,24 +1049,27 @@ idGLDrawableWorld::~idGLDrawableWorld() { delete world; } -void idGLDrawableWorld::AddTris(srfTriangles_t *tris, const idMaterial *mat) { +void idGLDrawableWorld::AddTris(srfTriangles_t* tris, const idMaterial* mat) { modelSurface_t surf; surf.geometry = tris; surf.shader = mat; - worldModel->AddSurface( surf ); + worldModel->AddSurface(surf); } void idGLDrawableWorld::draw(int x, int y, int w, int h) { - + } void idGLDrawableWorld::InitWorld() { - if ( world == NULL ) { + if (world == NULL) { world = renderSystem->AllocRenderWorld(dxrWorldId_t::DXR_WIDGET_0); } - if ( worldModel == NULL ) { + if (worldModel == NULL) { worldModel = renderModelManager->AllocModel(); } - world->InitFromMap( NULL ); - worldModel->InitEmpty( va( "GLWorldModel_%i", Sys_Milliseconds() ) ); + if (world == NULL || worldModel == NULL) { + return; + } + world->InitFromMap(NULL); + worldModel->InitEmpty(va("GLWorldModel_%i", Sys_Milliseconds())); } diff --git a/neo/engine/tools/radiant/GLWidget.h b/neo/engine/tools/radiant/GLWidget.h index ee71c4ff..51f5472d 100644 --- a/neo/engine/tools/radiant/GLWidget.h +++ b/neo/engine/tools/radiant/GLWidget.h @@ -202,6 +202,7 @@ public: protected: idGLDrawable *drawable; bool initialized; + HGLRC hglrc; //{{AFX_MSG(idGLWidget) afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); diff --git a/neo/engine/tools/radiant/InspectorDialog.cpp b/neo/engine/tools/radiant/InspectorDialog.cpp index 4e8661bd..9efbf970 100644 --- a/neo/engine/tools/radiant/InspectorDialog.cpp +++ b/neo/engine/tools/radiant/InspectorDialog.cpp @@ -17,19 +17,356 @@ Copyright (C) 2026 Justin Marshall #include "InspectorDialog.h" #include "OutlinerDlg.h" #include "TabsDlg.h" +#include CInspectorDialog* g_Inspectors = NULL; -static const COLORREF INSPECTOR_BG = RGB(245, 247, 250); -static const COLORREF INSPECTOR_PANEL_BG = RGB(255, 255, 255); -static const COLORREF INSPECTOR_TEXT = RGB(32, 36, 42); -static const COLORREF INSPECTOR_MUTED_TEXT = RGB(88, 96, 105); -static const COLORREF INSPECTOR_BORDER = RGB(218, 223, 230); +static const COLORREF INSPECTOR_BG = RGB(17, 19, 23); +static const COLORREF INSPECTOR_PANEL_BG = RGB(23, 26, 31); +static const COLORREF INSPECTOR_TEXT = RGB(226, 232, 240); +static const COLORREF INSPECTOR_MUTED_TEXT = RGB(151, 163, 184); +static const COLORREF INSPECTOR_BORDER = RGB(58, 64, 72); -static const int INSPECTOR_MARGIN = 8; -static const int INSPECTOR_TAB_HEIGHT = 42; +static const int INSPECTOR_MARGIN = 6; +static const int INSPECTOR_TAB_HEIGHT = 36; static const int INSPECTOR_DIVIDER_HEIGHT = 1; + +static const COLORREF INSPECTOR_TAB_BG = RGB(17, 19, 23); +static const COLORREF INSPECTOR_TAB_INACTIVE = RGB(28, 31, 36); +static const COLORREF INSPECTOR_TAB_ACTIVE = RGB(24, 27, 32); +static const COLORREF INSPECTOR_ACCENT = RGB(87, 166, 255); +static const char* INSPECTOR_DARK_TAB_OLDPROC = "IceTech.Inspector.DarkTabOldProc"; +static const char* INSPECTOR_DARK_PARENT_OLDPROC = "IceTech.Inspector.DarkParentOldProc"; +static const char* INSPECTOR_DARK_FIELD_OLDPROC = "IceTech.Inspector.DarkFieldOldProc"; + +#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 void InspectorDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void InspectorDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void InspectorDark_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 InspectorDark_DrawTabControl(HWND hWnd, HDC hDC) { + RECT client; + ::GetClientRect(hWnd, &client); + InspectorDark_FillRect(hDC, client, INSPECTOR_TAB_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; + InspectorDark_FillRect(hDC, fill, active ? INSPECTOR_TAB_ACTIVE : INSPECTOR_TAB_INACTIVE); + InspectorDark_FrameRect(hDC, fill, active ? INSPECTOR_ACCENT : INSPECTOR_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) : INSPECTOR_TEXT); + ::DrawTextA(hDC, text, -1, &item, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + + RECT line = client; + line.top = client.bottom - 1; + InspectorDark_FillRect(hDC, line, INSPECTOR_BORDER); + + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK InspectorDarkTabProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, INSPECTOR_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); + InspectorDark_DrawTabControl(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + InspectorDark_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, INSPECTOR_DARK_TAB_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void InspectorDark_SubclassTab(HWND hWnd) { + if (!hWnd || ::GetPropA(hWnd, INSPECTOR_DARK_TAB_OLDPROC)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, INSPECTOR_DARK_TAB_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)InspectorDarkTabProc); + ::InvalidateRect(hWnd, NULL, TRUE); +} + + +static HBRUSH InspectorDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(INSPECTOR_BG); + return hBrush; +} + +static HBRUSH InspectorDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(RGB(14, 16, 20)); + return hBrush; +} + +static void InspectorDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void InspectorDark_DrawWindowFrame(HWND hWnd) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + InspectorDark_FrameRect(hDC, rc, INSPECTOR_BORDER); + ::ReleaseDC(hWnd, hDC); +} + +static LRESULT CALLBACK InspectorDarkFieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, INSPECTOR_DARK_FIELD_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + InspectorDark_DrawWindowFrame(hWnd); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, INSPECTOR_DARK_FIELD_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK InspectorDarkParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, INSPECTOR_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + InspectorDark_FillRect((HDC)wParam, rc, INSPECTOR_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)InspectorDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, INSPECTOR_MUTED_TEXT); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)InspectorDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, INSPECTOR_TEXT); + ::SetBkColor(hDC, RGB(14, 16, 20)); + return (LRESULT)InspectorDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, INSPECTOR_TEXT); + ::SetBkColor(hDC, INSPECTOR_BG); + return (LRESULT)InspectorDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, INSPECTOR_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static bool InspectorDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool InspectorDark_IsKnownControl(HWND hWnd) { + return InspectorDark_IsClass(hWnd, "Button") || + InspectorDark_IsClass(hWnd, "Edit") || + InspectorDark_IsClass(hWnd, "ComboBox") || + InspectorDark_IsClass(hWnd, "ListBox") || + InspectorDark_IsClass(hWnd, "SysTreeView32") || + InspectorDark_IsClass(hWnd, "SysListView32") || + InspectorDark_IsClass(hWnd, "SysTabControl32"); +} + +static void InspectorDark_SubclassParent(HWND hWnd) { + if (!hWnd || ::GetPropA(hWnd, INSPECTOR_DARK_PARENT_OLDPROC)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, INSPECTOR_DARK_PARENT_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)InspectorDarkParentProc); +} + +static void InspectorDark_SubclassField(HWND hWnd) { + if (!hWnd || ::GetPropA(hWnd, INSPECTOR_DARK_FIELD_OLDPROC)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, INSPECTOR_DARK_FIELD_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)InspectorDarkFieldProc); +} + +static void InspectorDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + InspectorDark_ApplyNativeTheme(hWnd); + InspectorDark_RemoveClientEdge(hWnd); + + if (InspectorDark_IsClass(hWnd, "Edit") || InspectorDark_IsClass(hWnd, "ListBox") || InspectorDark_IsClass(hWnd, "SysTreeView32") || InspectorDark_IsClass(hWnd, "SysListView32")) { + InspectorDark_SubclassField(hWnd); + } + else if (!InspectorDark_IsKnownControl(hWnd)) { + InspectorDark_SubclassParent(hWnd); + } + + if (InspectorDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)RGB(14, 16, 20)); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)INSPECTOR_TEXT); + } + if (InspectorDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)RGB(14, 16, 20)); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)RGB(14, 16, 20)); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)INSPECTOR_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + InspectorDark_ApplyToWindowAndChildren(child); + } +} + +static void InspectorDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + InspectorDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} + void InspectorsDockingCallback(bool docked, int ID, CWnd* wnd) { if (g_Inspectors) { g_Inspectors->SetDockedTabs(docked, ID); @@ -140,6 +477,8 @@ void CInspectorDialog::ApplyModernTheme() { if (m_Tabs.GetSafeHwnd()) { m_Tabs.SetFont(&modernFont, FALSE); m_Tabs.ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED); + InspectorDark_ApplyNativeTheme(m_Tabs.GetSafeHwnd()); + InspectorDark_SubclassTab(m_Tabs.GetSafeHwnd()); } ApplyModernFontRecursive(&consoleWnd); @@ -147,6 +486,11 @@ void CInspectorDialog::ApplyModernTheme() { ApplyModernFontRecursive(&entityDlg); ApplyModernFontRecursive(&outlinerDlg); + InspectorDark_ApplyToDialog(&consoleWnd); + InspectorDark_ApplyToDialog(&mediaDlg); + InspectorDark_ApplyNativeTheme(entityDlg.GetSafeHwnd()); + InspectorDark_ApplyToDialog(&outlinerDlg); + ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED); Invalidate(TRUE); diff --git a/neo/engine/tools/radiant/LightDlg.cpp b/neo/engine/tools/radiant/LightDlg.cpp index 358f7331..41ed5e16 100644 --- a/neo/engine/tools/radiant/LightDlg.cpp +++ b/neo/engine/tools/radiant/LightDlg.cpp @@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms #include "Radiant.h" #include "../comafx/DialogColorPicker.h" #include "LightDlg.h" +#include #ifdef ID_DEBUG_MEMORY #undef new @@ -269,6 +270,526 @@ CLightInfo::CLightInfo() { + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} + ///////////////////////////////////////////////////////////////////////////// // CLightDlg dialog @@ -646,6 +1167,8 @@ BOOL CLightDlg::OnInitDialog() { CDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + com_editors |= EDITOR_LIGHT; UpdateDialog( true ); @@ -877,6 +1400,28 @@ void CLightDlg::OnCancel() { HBRUSH CLightDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); + if (!pDC) { + return hbr; + } + + switch (nCtlColor) { + case CTLCOLOR_DLG: + pDC->SetBkColor(ICE_DARK_BG); + return IceDark_BgBrush(); + case CTLCOLOR_STATIC: + pDC->SetTextColor(ICE_DARK_TEXT_MUTED); + pDC->SetBkMode(TRANSPARENT); + return IceDark_BgBrush(); + case CTLCOLOR_EDIT: + case CTLCOLOR_LISTBOX: + pDC->SetTextColor(ICE_DARK_TEXT); + pDC->SetBkColor(ICE_DARK_FIELD); + return IceDark_FieldBrush(); + case CTLCOLOR_BTN: + pDC->SetTextColor(ICE_DARK_TEXT); + pDC->SetBkColor(ICE_DARK_BG); + return IceDark_BgBrush(); + } return hbr; } diff --git a/neo/engine/tools/radiant/MainFrm.cpp b/neo/engine/tools/radiant/MainFrm.cpp index 5d22fd4a..b6789c7e 100644 --- a/neo/engine/tools/radiant/MainFrm.cpp +++ b/neo/engine/tools/radiant/MainFrm.cpp @@ -82,9 +82,63 @@ static char THIS_FILE[] = __FILE__; #else #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") #endif + #endif +static void RadiantEnableWin32DarkMode() { + typedef int (WINAPI* SetPreferredAppModeProc)(int); + static HMODULE hUxTheme = ::LoadLibraryA("uxtheme.dll"); + static SetPreferredAppModeProc pSetPreferredAppMode = hUxTheme ? (SetPreferredAppModeProc)::GetProcAddress(hUxTheme, MAKEINTRESOURCEA(135)) : NULL; + if (pSetPreferredAppMode) { + // 2 == AllowDark in current Windows 10/11 uxtheme builds. The call is + // dynamic so older Windows builds simply skip it. + pSetPreferredAppMode(2); + } +} + +static void RadiantApplyNativeDarkMode(HWND hWnd) { + if (!hWnd || !::IsWindow(hWnd)) { + return; + } + + typedef HRESULT(WINAPI* SetWindowThemeProc)(HWND, LPCWSTR, LPCWSTR); + typedef BOOL(WINAPI* AllowDarkModeForWindowProc)(HWND, BOOL); + typedef HRESULT(WINAPI* DwmSetWindowAttributeProc)(HWND, DWORD, LPCVOID, DWORD); + + 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; + + static HMODULE hDwm = ::LoadLibraryA("dwmapi.dll"); + static DwmSetWindowAttributeProc pDwmSetWindowAttribute = hDwm ? (DwmSetWindowAttributeProc)::GetProcAddress(hDwm, "DwmSetWindowAttribute") : NULL; + + if (pAllowDarkModeForWindow) { + pAllowDarkModeForWindow(hWnd, TRUE); + } + if (pSetWindowTheme) { + pSetWindowTheme(hWnd, L"DarkMode_Explorer", NULL); + } + if (pDwmSetWindowAttribute) { + BOOL dark = TRUE; + pDwmSetWindowAttribute(hWnd, 20, &dark, sizeof(dark)); + pDwmSetWindowAttribute(hWnd, 19, &dark, sizeof(dark)); + } + ::SendMessage(hWnd, WM_THEMECHANGED, 0, 0); +} + +static BOOL CALLBACK RadiantApplyNativeDarkModeEnumProc(HWND hWnd, LPARAM lParam) { + RadiantApplyNativeDarkMode(hWnd); + return TRUE; +} + +static void RadiantApplyNativeDarkModeRecursive(HWND hWnd) { + RadiantApplyNativeDarkMode(hWnd); + ::EnumChildWindows(hWnd, RadiantApplyNativeDarkModeEnumProc, 0); +} + static void InitRadiantCommonControls() { + RadiantEnableWin32DarkMode(); + INITCOMMONCONTROLSEX commonControls; memset(&commonControls, 0, sizeof(commonControls)); commonControls.dwSize = sizeof(commonControls); @@ -106,19 +160,31 @@ static void InitRadiantCommonControls() { static COLORREF RadiantDarkPaneColor() { - return RGB(24, 24, 24); + return RGB(17, 19, 23); +} + +static COLORREF RadiantDarkPanelColor() { + return RGB(24, 27, 32); +} + +static COLORREF RadiantDarkBorderColor() { + return RGB(58, 64, 72); +} + +static COLORREF RadiantDarkTextColor() { + return RGB(226, 232, 240); } static COLORREF RadiantDarkSplitterColor() { - return RGB(38, 38, 38); + return RGB(38, 42, 48); } static COLORREF RadiantDarkSplitterHighlightColor() { - return RGB(58, 58, 58); + return RadiantDarkBorderColor(); } static COLORREF RadiantDarkSplitterShadowColor() { - return RGB(12, 12, 12); + return RGB(8, 10, 12); } static HBRUSH RadiantDarkPaneBrush() { @@ -145,6 +211,28 @@ static void ApplyRadiantDarkXYAndZTheme() { SetRadiantSavedInfoColor(COLOR_CLIPPER, 0.270f, 0.540f, 1.000f); SetRadiantSavedInfoColor(COLOR_VIEWNAME, 0.600f, 0.720f, 1.000f); SetRadiantSavedInfoColor(COLOR_PRECISION_CROSSHAIR, 1.000f, 0.230f, 0.850f); + SetRadiantSavedInfoColor(COLOR_CAMERABACK, 0.055f, 0.060f, 0.070f); + SetRadiantSavedInfoColor(COLOR_TEXTUREBACK, 0.050f, 0.055f, 0.065f); +} + +static void ApplyRadiantDarkToolbarTheme(CToolBar& toolbar) { + if (!toolbar.GetSafeHwnd()) { + return; + } + HWND hToolBar = toolbar.GetToolBarCtrl().GetSafeHwnd(); + if (hToolBar) { +#ifdef TB_SETBKCOLOR + ::SendMessage(hToolBar, TB_SETBKCOLOR, 0, (LPARAM)RadiantDarkPanelColor()); +#endif +#ifdef TB_SETTEXTCOLOR + ::SendMessage(hToolBar, TB_SETTEXTCOLOR, 0, (LPARAM)RadiantDarkTextColor()); +#endif + } + toolbar.Invalidate(FALSE); +} + +static void ApplyRadiantDarkFrameControlTheme(CToolBar& toolbar) { + ApplyRadiantDarkToolbarTheme(toolbar); } #ifdef IDI_ICON1 @@ -814,14 +902,7 @@ BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_UPDATE_COMMAND_UI(ID_AUTOCAULK, OnUpdateAutocaulk) ON_COMMAND(ID_SELECT_ALLTARGETS, OnSelectAlltargets) END_MESSAGE_MAP() -static UINT indicators[] = { - ID_SEPARATOR, // status line indicator - ID_SEPARATOR, // status line indicator - ID_SEPARATOR, // status line indicator - ID_SEPARATOR, // status line indicator - ID_SEPARATOR, // status line indicator - ID_SEPARATOR, // status line indicator -}; +// Status bar removed: the frame now gives the full client area to the editor layout. //============================================================================= // CMainFrameLeftPaneWnd @@ -841,7 +922,7 @@ END_MESSAGE_MAP() CMainFrameLeftPaneWnd::CMainFrameLeftPaneWnd() { m_pCameraWnd = NULL; m_pInspectorWnd = NULL; - m_nCameraPercent = 45; + m_nCameraPercent = 30; m_nCameraHeight = 0; m_nSplitterHeight = 5; m_bInLayout = false; @@ -921,8 +1002,8 @@ int CMainFrameLeftPaneWnd::ClampCameraHeight(int cameraHeight, const CRect& clie return height / 2; } - const int minCamera = 96; - const int minInspector = 120; + const int minCamera = 128; + const int minInspector = 240; int minHeight = minCamera; int maxHeight = available - minInspector; @@ -1113,7 +1194,7 @@ END_MESSAGE_MAP() CMainFrameLayoutWnd::CMainFrameLayoutWnd() { m_pXYDockWnd = NULL; - m_nLeftPercent = 36; + m_nLeftPercent = 48; m_nLeftWidth = 0; m_nSplitterWidth = 5; m_bInLayout = false; @@ -1196,8 +1277,8 @@ int CMainFrameLayoutWnd::ClampLeftWidth(int leftWidth, const CRect& client) cons return width / 2; } - const int minLeft = 280; - const int minRight = 420; + const int minLeft = 420; + const int minRight = 520; int minWidth = minLeft; int maxWidth = available - minRight; @@ -1465,17 +1546,18 @@ static HMENU RadiantMainMenuHandle() { ======================================================================================================================= */ BOOL CMainFrame::CreateEmbeddedMainToolBar(UINT nID) { - CWnd* pToolBarParent = m_pXYDockWnd ? static_cast(m_pXYDockWnd) : static_cast(this); - if (!m_wndToolBar.CreateEx(pToolBarParent, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(nID)) { - TRACE0("Failed to create toolbar\n"); + // Keep the CToolBar object alive for existing command-state code, but do not + // attach or show it in the workspace. The main menu remains embedded in the + // XY dock; the old icon toolbar is intentionally removed from the UI. + CWnd* pToolBarParent = static_cast(this); + if (!m_wndToolBar.CreateEx(pToolBarParent, TBSTYLE_FLAT, WS_CHILD | CBRS_TOP | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) || !m_wndToolBar.LoadToolBar(nID)) { + TRACE0("Failed to create hidden toolbar state holder\n"); return FALSE; } m_wndToolBar.SetOwner(this); RemoveDoomToolbarButton(); - if (m_pXYDockWnd) { - m_pXYDockWnd->SetEmbeddedToolBar(&m_wndToolBar); - } + m_wndToolBar.ShowWindow(SW_HIDE); return TRUE; } @@ -1956,6 +2038,7 @@ void MFCCreate(HINSTANCE hInstance) ======================================================================================================================= ======================================================================================================================= */ +#define IDI_ICON1 4001 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { char* pBuffer = g_strAppPath.GetBufferSetLength(_MAX_PATH + 1); int nResult = ::GetModuleFileName(NULL, pBuffer, _MAX_PATH); @@ -1974,7 +2057,36 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { return -1; } - ApplyRadiantFrameIcon(this); + //ApplyRadiantFrameIcon(this); + HICON hIconLarge = (HICON)::LoadImage( + AfxGetInstanceHandle(), + MAKEINTRESOURCE(IDI_ICON1), + IMAGE_ICON, + ::GetSystemMetrics(SM_CXICON), + ::GetSystemMetrics(SM_CYICON), + LR_DEFAULTCOLOR + ); + + HICON hIconSmall = (HICON)::LoadImage( + AfxGetInstanceHandle(), + MAKEINTRESOURCE(IDI_ICON1), + IMAGE_ICON, + ::GetSystemMetrics(SM_CXSMICON), + ::GetSystemMetrics(SM_CYSMICON), + LR_DEFAULTCOLOR + ); + + if (hIconLarge != NULL) { + SetIcon(hIconLarge, TRUE); + ::SendMessage(GetSafeHwnd(), WM_SETICON, ICON_BIG, (LPARAM)hIconLarge); + } + + if (hIconSmall != NULL) { + SetIcon(hIconSmall, FALSE); + ::SendMessage(GetSafeHwnd(), WM_SETICON, ICON_SMALL, (LPARAM)hIconSmall); + } + + RadiantApplyNativeDarkMode(GetSafeHwnd()); ApplyRadiantDarkXYAndZTheme(); UINT nID = (g_PrefsDlg.m_bWideToolbar) ? IDR_TOOLBAR_ADVANCED : IDR_TOOLBAR1; @@ -1982,13 +2094,11 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (!CreateEmbeddedMainToolBar(nID)) { return -1; // fail to create } + ApplyRadiantDarkFrameControlTheme(m_wndToolBar); MoveFrameMenuIntoXYWnd(); - if (!m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators) / sizeof(UINT))) { - TRACE0("Failed to create status bar\n"); - return -1; // fail to create - } + // Status bar intentionally removed. m_bCamPreview = true; @@ -2005,6 +2115,7 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SELECTION_MOVEONLY, FALSE); m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundAlways); m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSELECTEDSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundWhenSelected); + ApplyRadiantDarkFrameControlTheme(m_wndToolBar); EnableDocking(CBRS_ALIGN_ANY); RecalcXYDockLayout(); @@ -2024,11 +2135,6 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { CMenu* pMenu = GetMenu(); ShowMenuItemKeyBindings(pMenu); - CFont* pFont = new CFont(); - pFont->CreatePointFont(g_PrefsDlg.m_nStatusSize * 10, "Arial"); - m_wndStatusBar.SetFont(pFont); - - if (g_PrefsDlg.m_bRunBefore == FALSE) { g_PrefsDlg.m_bRunBefore = TRUE; g_PrefsDlg.SavePrefs(); @@ -2140,17 +2246,6 @@ void CMainFrame::LoadCommandMap() { */ BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { BOOL result = CFrameWnd::PreCreateWindow(cs); -#ifdef IDI_ICON1 - HICON hClassIcon = LoadRadiantFrameIcon(::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON)); - if (hClassIcon) { - cs.lpszClass = AfxRegisterWndClass( - CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, - ::LoadCursor(NULL, IDC_ARROW), - (HBRUSH)(COLOR_WINDOW + 1), - hClassIcon - ); - } -#endif return result; } @@ -2697,8 +2792,10 @@ BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { } RecalcMainLayout(); + RadiantApplyNativeDarkModeRecursive(GetSafeHwnd()); CreateQEChildren(); + RadiantApplyNativeDarkModeRecursive(GetSafeHwnd()); ApplyRadiantDarkXYAndZTheme(); AttachDoomWindowToGameTab(); ApplyActiveTabInputMode(); @@ -2721,28 +2818,6 @@ CRect g_rctOld(0, 0, 0, 0); */ void CMainFrame::OnSize(UINT nType, int cx, int cy) { CFrameWnd::OnSize(nType, cx, cy); - - CRect rctParent; - GetClientRect(rctParent); - - UINT nID; - UINT nStyle; - int nWidth; - if (m_wndStatusBar.GetSafeHwnd()) { - m_wndStatusBar.GetPaneInfo(0, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(0, nID, nStyle, rctParent.Width() * 0.15f); - m_wndStatusBar.GetPaneInfo(1, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(1, nID, nStyle, rctParent.Width() * 0.15f); - m_wndStatusBar.GetPaneInfo(2, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(2, nID, nStyle, rctParent.Width() * 0.15f); - m_wndStatusBar.GetPaneInfo(3, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(3, nID, nStyle, rctParent.Width() * 0.39f); - m_wndStatusBar.GetPaneInfo(4, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(4, nID, nStyle, rctParent.Width() * 0.15f); - m_wndStatusBar.GetPaneInfo(5, nID, nStyle, nWidth); - m_wndStatusBar.SetPaneInfo(5, nID, nStyle, rctParent.Width() * 0.01f); - } - RecalcMainLayout(); } @@ -5045,13 +5120,13 @@ void CMainFrame::OnToggleviewYz() { void CMainFrame::OnToggleToolbar() { - BOOL bShow = !m_wndToolBar.IsWindowVisible(); if (m_pXYDockWnd && m_pXYDockWnd->GetSafeHwnd()) { - m_pXYDockWnd->ShowEmbeddedToolBar(bShow); + m_pXYDockWnd->ShowEmbeddedToolBar(FALSE); } - else { - m_wndToolBar.ShowWindow(bShow ? SW_SHOW : SW_HIDE); + if (m_wndToolBar.GetSafeHwnd()) { + m_wndToolBar.ShowWindow(SW_HIDE); } + RecalcMainLayout(); } void CMainFrame::OnToggleTextureBar() @@ -7770,6 +7845,7 @@ void CMainFrame::OnSoundShowsoundvolumes() } m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundAlways); m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSELECTEDSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundWhenSelected); + ApplyRadiantDarkFrameControlTheme(m_wndToolBar); Sys_UpdateWindows(W_XY | W_CAMERA); } @@ -7806,6 +7882,7 @@ void CMainFrame::OnSoundShowselectedsoundvolumes() } m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundAlways); m_wndToolBar.GetToolBarCtrl().CheckButton(ID_SOUND_SHOWSELECTEDSOUNDVOLUMES, g_qeglobals.d_savedinfo.showSoundWhenSelected); + ApplyRadiantDarkFrameControlTheme(m_wndToolBar); Sys_UpdateWindows(W_XY | W_CAMERA); } diff --git a/neo/engine/tools/radiant/NewTexWnd.cpp b/neo/engine/tools/radiant/NewTexWnd.cpp index 9aa45e04..b56247d2 100644 --- a/neo/engine/tools/radiant/NewTexWnd.cpp +++ b/neo/engine/tools/radiant/NewTexWnd.cpp @@ -42,6 +42,28 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// NewTexWnd dark native-control helper +// -------------------------------------------------------------------------- +static void NewTexDark_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); +} /* ======================================================================================================================= ======================================================================================================================= @@ -131,6 +153,7 @@ int CNewTexWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) { } ShowScrollBar(SB_VERT, g_PrefsDlg.m_bTextureScrollbar); + NewTexDark_ApplyNativeTheme(GetSafeHwnd()); m_bNeedRange = true; hdcTexture = GetDC(); @@ -170,6 +193,7 @@ void CNewTexWnd::OnParentNotify(UINT message, LPARAM lParam) { */ void CNewTexWnd::UpdatePrefs() { ShowScrollBar(SB_VERT, g_PrefsDlg.m_bTextureScrollbar); + NewTexDark_ApplyNativeTheme(GetSafeHwnd()); m_bNeedRange = true; Invalidate(); UpdateWindow(); @@ -444,6 +468,7 @@ BOOL CNewTexWnd::Create ) { BOOL ret = CWnd::Create(lpszClassName, lpszWindowName, dwStyle, rect, pParentWnd, nID, pContext); if (ret) { + NewTexDark_ApplyNativeTheme(GetSafeHwnd()); hdcTexture = GetDC(); QEW_SetupPixelFormat(hdcTexture->m_hDC, false); } diff --git a/neo/engine/tools/radiant/OutlinerDlg.cpp b/neo/engine/tools/radiant/OutlinerDlg.cpp index 9245efd1..723a34d1 100644 --- a/neo/engine/tools/radiant/OutlinerDlg.cpp +++ b/neo/engine/tools/radiant/OutlinerDlg.cpp @@ -29,13 +29,212 @@ along with IceTech Source Code. If not, see . #include "Radiant.h" #include "OutlinerDlg.h" #include "InspectorDialog.h" +#include + +#pragma comment(lib, "comctl32.lib") IMPLEMENT_DYNAMIC(COutlinerDlg, CWnd) -static const COLORREF OUTLINER_BG = RGB(245, 247, 250); -static const COLORREF OUTLINER_PANEL_BG = RGB(255, 255, 255); -static const COLORREF OUTLINER_TEXT = RGB(32, 36, 42); -static const COLORREF OUTLINER_MUTED_TEXT = RGB(88, 96, 105); +static const COLORREF OUTLINER_BG = RGB(17, 19, 23); +static const COLORREF OUTLINER_PANEL_BG = RGB(14, 16, 20); +static const COLORREF OUTLINER_TEXT = RGB(226, 232, 240); +static const COLORREF OUTLINER_MUTED_TEXT = RGB(151, 163, 184); + +static const COLORREF OUTLINER_FIELD_BG = RGB(14, 16, 20); +static const COLORREF OUTLINER_BORDER = RGB(55, 62, 72); +static const COLORREF OUTLINER_ACCENT = RGB(87, 166, 255); +static const char* OUTLINER_TREE_OLDPROC = "IceTech.Outliner.TreeOldProc"; + +static void Outliner_ApplyNativeDarkTheme(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 Outliner_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void Outliner_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static HBITMAP Outliner_CreateDarkCheckBitmap(bool checked) { + HDC screenDC = ::GetDC(NULL); + HDC memDC = ::CreateCompatibleDC(screenDC); + HBITMAP bitmap = ::CreateCompatibleBitmap(screenDC, 16, 16); + HBITMAP oldBitmap = (HBITMAP)::SelectObject(memDC, bitmap); + + RECT rc = { 0, 0, 16, 16 }; + Outliner_FillRect(memDC, rc, RGB(255, 0, 255)); + + RECT box = { 2, 2, 14, 14 }; + Outliner_FillRect(memDC, box, OUTLINER_FIELD_BG); + Outliner_FrameRect(memDC, box, checked ? OUTLINER_ACCENT : OUTLINER_BORDER); + + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, OUTLINER_ACCENT); + HPEN oldPen = (HPEN)::SelectObject(memDC, pen); + ::MoveToEx(memDC, 5, 8, NULL); + ::LineTo(memDC, 7, 11); + ::LineTo(memDC, 12, 4); + ::SelectObject(memDC, oldPen); + ::DeleteObject(pen); + } + + ::SelectObject(memDC, oldBitmap); + ::DeleteDC(memDC); + ::ReleaseDC(NULL, screenDC); + return bitmap; +} + +static HIMAGELIST Outliner_GetDarkCheckImageList() { + static HIMAGELIST imageList = NULL; + if (imageList) { + return imageList; + } + + imageList = ::ImageList_Create(16, 16, ILC_COLOR24 | ILC_MASK, 2, 0); + if (!imageList) { + return NULL; + } + + HBITMAP uncheckedBitmap = Outliner_CreateDarkCheckBitmap(false); + HBITMAP checkedBitmap = Outliner_CreateDarkCheckBitmap(true); + ::ImageList_AddMasked(imageList, uncheckedBitmap, RGB(255, 0, 255)); + ::ImageList_AddMasked(imageList, checkedBitmap, RGB(255, 0, 255)); + ::DeleteObject(uncheckedBitmap); + ::DeleteObject(checkedBitmap); + + return imageList; +} + +static void Outliner_ApplyDarkTreeImages(HWND hTree) { + HIMAGELIST imageList = Outliner_GetDarkCheckImageList(); + if (hTree && imageList) { + ::SendMessage(hTree, TVM_SETIMAGELIST, TVSIL_STATE, (LPARAM)imageList); + } +} + + +static bool Outliner_TreeItemHasStateImage(HWND hTree, HTREEITEM item) { + if (!hTree || !item) { + return false; + } + TVITEM tvi; + memset(&tvi, 0, sizeof(tvi)); + tvi.mask = TVIF_STATE; + tvi.hItem = item; + tvi.stateMask = TVIS_STATEIMAGEMASK; + if (!TreeView_GetItem(hTree, &tvi)) { + return false; + } + return (tvi.state & TVIS_STATEIMAGEMASK) != 0; +} + +static bool Outliner_TreePointInCheckZone(HWND hTree, HTREEITEM item, const POINT& point, UINT flags) { + if (!item || !Outliner_TreeItemHasStateImage(hTree, item)) { + return false; + } + + if (flags & TVHT_ONITEMSTATEICON) { + return true; + } + + RECT label; + if (!TreeView_GetItemRect(hTree, item, &label, TRUE)) { + return false; + } + + RECT row; + if (!TreeView_GetItemRect(hTree, item, &row, FALSE)) { + row = label; + } + + // Make the checkbox easier to hit than the stock 16px state image. The + // actual checkbox remains on the left, but clicks in a wider leading gutter + // are treated as checkbox clicks instead of requiring pixel-perfect aim. + RECT checkZone; + checkZone.left = max(0, label.left - 30); + checkZone.right = label.left + 10; + checkZone.top = row.top; + checkZone.bottom = row.bottom; + + return ::PtInRect(&checkZone, point) ? true : false; +} + +static LRESULT CALLBACK Outliner_TreeProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, OUTLINER_TREE_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_LBUTTONDOWN: + { + POINT point; + point.x = (short)LOWORD(lParam); + point.y = (short)HIWORD(lParam); + + TVHITTESTINFO hit; + memset(&hit, 0, sizeof(hit)); + hit.pt = point; + HTREEITEM item = TreeView_HitTest(hWnd, &hit); + if (item && Outliner_TreePointInCheckZone(hWnd, item, point, hit.flags)) { + BOOL checked = TreeView_GetCheckState(hWnd, item) ? TRUE : FALSE; + TreeView_SelectItem(hWnd, item); + TreeView_SetCheckState(hWnd, item, checked ? FALSE : TRUE); + HWND parent = ::GetParent(hWnd); + if (parent) { + ::SendMessage(parent, COutlinerDlg::WM_OUTLINER_APPLY_CHECK, (WPARAM)item, 0); + } + ::InvalidateRect(hWnd, NULL, FALSE); + return 0; + } + break; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, OUTLINER_TREE_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void Outliner_SubclassTreeForCheckClicks(HWND hTree) { + if (!hTree || ::GetPropA(hTree, OUTLINER_TREE_OLDPROC)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hTree, GWLP_WNDPROC); + ::SetPropA(hTree, OUTLINER_TREE_OLDPROC, (HANDLE)oldProc); + ::SetWindowLongPtr(hTree, GWLP_WNDPROC, (LONG_PTR)Outliner_TreeProc); +} + +static HBRUSH Outliner_BackgroundBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(OUTLINER_BG); + return hBrush; +} static const int OUTLINER_MARGIN = 6; static const int OUTLINER_FILTER_LABEL_W = 42; @@ -222,7 +421,7 @@ BOOL COutlinerDlg::Create(CWnd* pParentWnd) { CString className = AfxRegisterWndClass( CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), - (HBRUSH)(COLOR_BTNFACE + 1), + Outliner_BackgroundBrush(), NULL ); @@ -278,8 +477,16 @@ int COutlinerDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { this, IDC_OUTLINER_TREE ); - m_tree.ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_FRAMECHANGED); + m_filter.ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED); + m_tree.ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED); m_tree.SetItemHeight(20); + m_tree.SetBkColor(OUTLINER_PANEL_BG); + m_tree.SetTextColor(OUTLINER_TEXT); + Outliner_ApplyNativeDarkTheme(GetSafeHwnd()); + Outliner_ApplyNativeDarkTheme(m_filter.GetSafeHwnd()); + Outliner_ApplyNativeDarkTheme(m_tree.GetSafeHwnd()); + Outliner_ApplyDarkTreeImages(m_tree.GetSafeHwnd()); + Outliner_SubclassTreeForCheckClicks(m_tree.GetSafeHwnd()); ApplyFont(); diff --git a/neo/engine/tools/radiant/PatchDensityDlg.cpp b/neo/engine/tools/radiant/PatchDensityDlg.cpp index 18ee59b5..37854ebd 100644 --- a/neo/engine/tools/radiant/PatchDensityDlg.cpp +++ b/neo/engine/tools/radiant/PatchDensityDlg.cpp @@ -32,6 +32,7 @@ If you have questions concerning this license or the applicable additional terms #include "qe3.h" #include "Radiant.h" #include "PatchDensityDlg.h" +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -39,6 +40,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} ///////////////////////////////////////////////////////////////////////////// // CPatchDensityDlg dialog @@ -89,6 +610,8 @@ BOOL CPatchDensityDlg::OnInitDialog() { CDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + m_wndWidth.SetCurSel(0); m_wndHeight.SetCurSel(0); return TRUE; // return TRUE unless you set the focus to a control diff --git a/neo/engine/tools/radiant/PatchDialog.cpp b/neo/engine/tools/radiant/PatchDialog.cpp index 2889d786..4e79633d 100644 --- a/neo/engine/tools/radiant/PatchDialog.cpp +++ b/neo/engine/tools/radiant/PatchDialog.cpp @@ -32,6 +32,7 @@ If you have questions concerning this license or the applicable additional terms #include "qe3.h" #include "Radiant.h" #include "PatchDialog.h" +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -39,6 +40,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} ///////////////////////////////////////////////////////////////////////////// // CPatchDialog dialog @@ -175,6 +696,8 @@ BOOL CPatchDialog::OnInitDialog() { CDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + m_wndHScale.SetRange(0, 1000); m_wndVScale.SetRange(0, 1000); m_wndHShift.SetRange(0, 1000); diff --git a/neo/engine/tools/radiant/SurfaceDlg.cpp b/neo/engine/tools/radiant/SurfaceDlg.cpp index b8a29f76..637a09e3 100644 --- a/neo/engine/tools/radiant/SurfaceDlg.cpp +++ b/neo/engine/tools/radiant/SurfaceDlg.cpp @@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms #include "Radiant.h" #include "SurfaceDlg.h" #include "mainfrm.h" +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -40,6 +41,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} ///////////////////////////////////////////////////////////////////////////// // CSurfaceDlg dialog @@ -350,6 +871,8 @@ bool ByeByeSurfaceDialog() { BOOL CSurfaceDlg::OnInitDialog() { CDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + g_surfwin = GetSafeHwnd(); SetTexMods (); @@ -457,6 +980,29 @@ void CSurfaceDlg::OnBtnColor() { HBRUSH CSurfaceDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); + if (!pDC) { + return hbr; + } + + switch (nCtlColor) { + case CTLCOLOR_DLG: + pDC->SetBkColor(ICE_DARK_BG); + return IceDark_BgBrush(); + case CTLCOLOR_STATIC: + pDC->SetTextColor(ICE_DARK_TEXT_MUTED); + pDC->SetBkMode(TRANSPARENT); + return IceDark_BgBrush(); + case CTLCOLOR_EDIT: + case CTLCOLOR_LISTBOX: + pDC->SetTextColor(ICE_DARK_TEXT); + pDC->SetBkColor(ICE_DARK_FIELD); + return IceDark_FieldBrush(); + case CTLCOLOR_BTN: + pDC->SetTextColor(ICE_DARK_TEXT); + pDC->SetBkColor(ICE_DARK_BG); + return IceDark_BgBrush(); + } + return hbr; } diff --git a/neo/engine/tools/radiant/TextureBar.cpp b/neo/engine/tools/radiant/TextureBar.cpp index 47c504fc..b5838df5 100644 --- a/neo/engine/tools/radiant/TextureBar.cpp +++ b/neo/engine/tools/radiant/TextureBar.cpp @@ -32,6 +32,7 @@ If you have questions concerning this license or the applicable additional terms #include "qe3.h" #include "Radiant.h" #include "TextureBar.h" +#include //++timo TODO : the whole CTextureBar has to be modified for the new texture code @@ -41,6 +42,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} ///////////////////////////////////////////////////////////////////////////// // CTextureBar dialog @@ -168,6 +689,7 @@ int CTextureBar::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialogBar::OnCreate(lpCreateStruct) == -1) return -1; + IceDark_ApplyToDialog(this); return 0; } diff --git a/neo/engine/tools/radiant/WaveOpen.cpp b/neo/engine/tools/radiant/WaveOpen.cpp index 3a2b21e2..f8ad062c 100644 --- a/neo/engine/tools/radiant/WaveOpen.cpp +++ b/neo/engine/tools/radiant/WaveOpen.cpp @@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms #include "Radiant.h" #include "WaveOpen.h" #include "mmsystem.h" +#include #ifdef _DEBUG #define new DEBUG_NEW @@ -40,6 +41,526 @@ If you have questions concerning this license or the applicable additional terms static char THIS_FILE[] = __FILE__; #endif + + +// -------------------------------------------------------------------------- +// IceTech dark MFC/common-control helper +// -------------------------------------------------------------------------- +#ifndef BS_TYPEMASK +#define BS_TYPEMASK 0x0000000F +#endif + +static const COLORREF ICE_DARK_BG = RGB(17, 19, 23); +static const COLORREF ICE_DARK_PANEL = RGB(24, 27, 32); +static const COLORREF ICE_DARK_PANEL_ALT = RGB(29, 33, 39); +static const COLORREF ICE_DARK_FIELD = RGB(14, 16, 20); +static const COLORREF ICE_DARK_BORDER = RGB(55, 62, 72); +static const COLORREF ICE_DARK_BORDER_HOT = RGB(87, 166, 255); +static const COLORREF ICE_DARK_TEXT = RGB(226, 232, 240); +static const COLORREF ICE_DARK_TEXT_MUTED = RGB(151, 163, 184); +static const COLORREF ICE_DARK_TEXT_DISABLED = RGB(103, 112, 128); +static const COLORREF ICE_DARK_ACCENT = RGB(87, 166, 255); + +static const char* ICE_DARK_PARENT_OLDPROC = "IceTech.Dark.ParentOldProc"; +static const char* ICE_DARK_CONTROL_OLDPROC = "IceTech.Dark.ControlOldProc"; + +static int IceDark_MaxInt(int a, int b) { + return (a > b) ? a : b; +} + +static HBRUSH IceDark_BgBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_BG); + return hBrush; +} + +static HBRUSH IceDark_PanelBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_PANEL); + return hBrush; +} + +static HBRUSH IceDark_FieldBrush() { + static HBRUSH hBrush = ::CreateSolidBrush(ICE_DARK_FIELD); + return hBrush; +} + +static void IceDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FillRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { + HBRUSH brush = ::CreateSolidBrush(color); + ::FrameRect(hDC, &rc, brush); + ::DeleteObject(brush); +} + +static void IceDark_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 IceDark_RemoveClientEdge(HWND hWnd) { + if (!hWnd) { + return; + } + LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); + if (exStyle & WS_EX_CLIENTEDGE) { + ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE); + ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } +} + +static void IceDark_DrawFrame(HWND hWnd, COLORREF color) { + HDC hDC = ::GetWindowDC(hWnd); + if (!hDC) { + return; + } + RECT rc; + ::GetWindowRect(hWnd, &rc); + ::OffsetRect(&rc, -rc.left, -rc.top); + IceDark_FrameRect(hDC, rc, color); + ::ReleaseDC(hWnd, hDC); +} + +static void IceDark_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 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); + const bool isCheck = (type == BS_CHECKBOX || type == BS_AUTOCHECKBOX || type == BS_3STATE || type == BS_AUTO3STATE); + const bool isRadio = (type == BS_RADIOBUTTON || type == BS_AUTORADIOBUTTON); + const bool isGroup = (type == BS_GROUPBOX); + + char text[512]; + 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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + + if (isGroup) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + RECT textRect = rc; + textRect.left += 10; + textRect.right -= 8; + textRect.bottom = textRect.top + 18; + SIZE textSize; + textSize.cx = textSize.cy = 0; + ::GetTextExtentPoint32A(hDC, text, (int)strlen(text), &textSize); + RECT frame = rc; + frame.top += 8; + HPEN pen = ::CreatePen(PS_SOLID, 1, ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, ::GetStockObject(NULL_BRUSH)); + ::RoundRect(hDC, frame.left, frame.top, frame.right, frame.bottom, 8, 8); + ::SelectObject(hDC, oldBrush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + RECT fillLabel = textRect; + fillLabel.right = fillLabel.left + textSize.cx + 8; + IceDark_FillRect(hDC, fillLabel, ICE_DARK_BG); + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_TOP | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else if (isCheck || isRadio) { + IceDark_FillRect(hDC, rc, ICE_DARK_BG); + + RECT box = rc; + box.left += 3; + box.right = box.left + 15; + box.top += IceDark_MaxInt(0, (rc.bottom - rc.top - 15) / 2); + box.bottom = box.top + 15; + + if (isRadio) { + HPEN pen = ::CreatePen(PS_SOLID, 1, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + HPEN oldPen = (HPEN)::SelectObject(hDC, pen); + HBRUSH brush = ::CreateSolidBrush(enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, brush); + ::Ellipse(hDC, box.left, box.top, box.right, box.bottom); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(brush); + ::SelectObject(hDC, oldPen); + ::DeleteObject(pen); + if (checked) { + RECT dot = box; + ::InflateRect(&dot, -4, -4); + HBRUSH dotBrush = ::CreateSolidBrush(enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldDotBrush = (HBRUSH)::SelectObject(hDC, dotBrush); + HPEN dotPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + HPEN oldDotPen = (HPEN)::SelectObject(hDC, dotPen); + ::Ellipse(hDC, dot.left, dot.top, dot.right, dot.bottom); + ::SelectObject(hDC, oldDotPen); + ::DeleteObject(dotPen); + ::SelectObject(hDC, oldDotBrush); + ::DeleteObject(dotBrush); + } + } + else { + IceDark_FillRect(hDC, box, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, box, checked ? ICE_DARK_ACCENT : ICE_DARK_BORDER); + if (checked) { + HPEN pen = ::CreatePen(PS_SOLID, 2, enabled ? ICE_DARK_ACCENT : ICE_DARK_TEXT_DISABLED); + 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; + textRect.right -= 2; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + } + else { + COLORREF face = pushed ? RGB(22, 25, 30) : ICE_DARK_PANEL_ALT; + IceDark_FillRect(hDC, rc, face); + IceDark_FrameRect(hDC, rc, enabled ? ICE_DARK_BORDER : RGB(39, 44, 52)); + RECT inner = rc; + ::InflateRect(&inner, -1, -1); + if (!pushed) { + IceDark_FrameRect(hDC, inner, RGB(35, 40, 47)); + } + 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 IceDark_ButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT style = (UINT)::GetWindowLong(hWnd, GWL_STYLE); + if (style & (BS_BITMAP | BS_ICON)) { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, ICE_DARK_BORDER); + return result; + } + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawButton(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + case WM_PRINTCLIENT: + IceDark_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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_DrawCombo(HWND hWnd, HDC hDC) { + RECT rc; + ::GetClientRect(hWnd, &rc); + const bool enabled = ::IsWindowEnabled(hWnd) ? true : false; + const bool focused = (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? true : false; + + IceDark_FillRect(hDC, rc, enabled ? ICE_DARK_FIELD : RGB(20, 22, 26)); + IceDark_FrameRect(hDC, rc, focused ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + + RECT arrow = rc; + arrow.left = arrow.right - 22; + IceDark_FillRect(hDC, arrow, ICE_DARK_PANEL_ALT); + IceDark_FrameRect(hDC, arrow, ICE_DARK_BORDER); + + POINT pts[3]; + int cx = arrow.left + (arrow.right - arrow.left) / 2; + int cy = arrow.top + (arrow.bottom - arrow.top) / 2 + 1; + pts[0].x = cx - 4; pts[0].y = cy - 2; + pts[1].x = cx + 4; pts[1].y = cy - 2; + pts[2].x = cx; pts[2].y = cy + 3; + HBRUSH arrowBrush = ::CreateSolidBrush(enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HBRUSH oldBrush = (HBRUSH)::SelectObject(hDC, arrowBrush); + HPEN arrowPen = ::CreatePen(PS_SOLID, 1, enabled ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + HPEN oldPen = (HPEN)::SelectObject(hDC, arrowPen); + ::Polygon(hDC, pts, 3); + ::SelectObject(hDC, oldPen); + ::DeleteObject(arrowPen); + ::SelectObject(hDC, oldBrush); + ::DeleteObject(arrowBrush); + + char text[512]; + text[0] = '\0'; + int sel = (int)::SendMessage(hWnd, CB_GETCURSEL, 0, 0); + if (sel >= 0) { + ::SendMessageA(hWnd, CB_GETLBTEXT, (WPARAM)sel, (LPARAM)text); + } + else { + ::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 ? ICE_DARK_TEXT : ICE_DARK_TEXT_DISABLED); + RECT textRect = rc; + textRect.left += 7; + textRect.right = arrow.left - 3; + ::DrawTextA(hDC, text, -1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); + if (oldFont) { + ::SelectObject(hDC, oldFont); + } +} + +static LRESULT CALLBACK IceDark_ComboProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + return 1; + case WM_PAINT: + { + UINT comboType = (UINT)::GetWindowLong(hWnd, GWL_STYLE) & 0x00000003; + if (comboType == CBS_DROPDOWNLIST) { + PAINTSTRUCT ps; + HDC hDC = ::BeginPaint(hWnd, &ps); + IceDark_DrawCombo(hWnd, hDC); + ::EndPaint(hWnd, &ps); + return 0; + } + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd || ::IsChild(hWnd, ::GetFocus())) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + case WM_SETTEXT: + case CB_SETCURSEL: + case CB_ADDSTRING: + case CB_DELETESTRING: + case CB_RESETCONTENT: + { + 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, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_FieldProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_NCPAINT: + case WM_PAINT: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + IceDark_DrawFrame(hWnd, (::GetFocus() == hWnd) ? ICE_DARK_BORDER_HOT : ICE_DARK_BORDER); + return result; + } + case WM_SETFOCUS: + case WM_KILLFOCUS: + case WM_ENABLE: + { + LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + ::RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW); + return result; + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_CONTROL_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static LRESULT CALLBACK IceDark_ParentProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ICE_DARK_PARENT_OLDPROC); + if (!oldProc) { + return ::DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + switch (uMsg) { + case WM_ERASEBKGND: + { + RECT rc; + ::GetClientRect(hWnd, &rc); + IceDark_FillRect((HDC)wParam, rc, ICE_DARK_BG); + return 1; + } + case WM_CTLCOLORDLG: + return (LRESULT)IceDark_BgBrush(); + case WM_CTLCOLORSTATIC: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT_MUTED); + ::SetBkMode(hDC, TRANSPARENT); + return (LRESULT)IceDark_BgBrush(); + } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_FIELD); + return (LRESULT)IceDark_FieldBrush(); + } + case WM_CTLCOLORBTN: + { + HDC hDC = (HDC)wParam; + ::SetTextColor(hDC, ICE_DARK_TEXT); + ::SetBkColor(hDC, ICE_DARK_BG); + return (LRESULT)IceDark_BgBrush(); + } + case WM_NCDESTROY: + { + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); + ::RemovePropA(hWnd, ICE_DARK_PARENT_OLDPROC); + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); + } + } + + return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); +} + +static void IceDark_Subclass(HWND hWnd, WNDPROC proc, const char* propName) { + if (!hWnd || ::GetPropA(hWnd, propName)) { + return; + } + WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); + ::SetPropA(hWnd, propName, (HANDLE)oldProc); + ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)proc); +} + +static bool IceDark_IsClass(HWND hWnd, const char* className) { + char actual[128]; + actual[0] = '\0'; + ::GetClassNameA(hWnd, actual, sizeof(actual)); + return ::lstrcmpiA(actual, className) == 0; +} + +static bool IceDark_IsKnownControl(HWND hWnd) { + return IceDark_IsClass(hWnd, "Button") || + IceDark_IsClass(hWnd, "Edit") || + IceDark_IsClass(hWnd, "ComboBox") || + IceDark_IsClass(hWnd, "ListBox") || + IceDark_IsClass(hWnd, "SysTreeView32") || + IceDark_IsClass(hWnd, "SysListView32") || + IceDark_IsClass(hWnd, "msctls_trackbar32") || + IceDark_IsClass(hWnd, "msctls_updown32") || + IceDark_IsClass(hWnd, "ScrollBar") || + IceDark_IsClass(hWnd, "SysTabControl32"); +} + +static void IceDark_ApplyToWindowAndChildren(HWND hWnd) { + if (!hWnd) { + return; + } + + IceDark_ApplyNativeTheme(hWnd); + IceDark_RemoveClientEdge(hWnd); + + if (IceDark_IsClass(hWnd, "Button")) { + IceDark_Subclass(hWnd, IceDark_ButtonProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "ComboBox")) { + IceDark_Subclass(hWnd, IceDark_ComboProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (IceDark_IsClass(hWnd, "Edit") || IceDark_IsClass(hWnd, "ListBox") || IceDark_IsClass(hWnd, "SysTreeView32") || IceDark_IsClass(hWnd, "SysListView32")) { + IceDark_Subclass(hWnd, IceDark_FieldProc, ICE_DARK_CONTROL_OLDPROC); + } + else if (!IceDark_IsKnownControl(hWnd)) { + IceDark_Subclass(hWnd, IceDark_ParentProc, ICE_DARK_PARENT_OLDPROC); + } + + if (IceDark_IsClass(hWnd, "SysTreeView32")) { + ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + if (IceDark_IsClass(hWnd, "SysListView32")) { + ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ICE_DARK_FIELD); + ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ICE_DARK_TEXT); + } + + for (HWND child = ::GetWindow(hWnd, GW_CHILD); child != NULL; child = ::GetWindow(child, GW_HWNDNEXT)) { + IceDark_ApplyToWindowAndChildren(child); + } + + ::InvalidateRect(hWnd, NULL, TRUE); +} + +static void IceDark_ApplyToDialog(CWnd* wnd) { + if (wnd && wnd->GetSafeHwnd()) { + IceDark_ApplyToWindowAndChildren(wnd->GetSafeHwnd()); + } +} ///////////////////////////////////////////////////////////////////////////// // CWaveOpen @@ -94,6 +615,8 @@ BOOL CWaveOpen::OnInitDialog() { CFileDialog::OnInitDialog(); + IceDark_ApplyToDialog(this); + CWnd *pWnd = GetDlgItem(IDC_BTN_PLAY); if (pWnd != NULL) { diff --git a/neo/engine/tools/radiant/XYWnd.cpp b/neo/engine/tools/radiant/XYWnd.cpp index 78ef6e90..c37c093c 100644 --- a/neo/engine/tools/radiant/XYWnd.cpp +++ b/neo/engine/tools/radiant/XYWnd.cpp @@ -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; }