/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IceTech Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IceTech Source Code. If not, see . In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "../../sys/win32/win_local.h" #include "MaterialEditor.h" #ifndef TVM_SETLINECOLOR #define TVM_SETLINECOLOR (TV_FIRST + 40) #endif #include "MEMainFrame.h" #ifdef _DEBUG #define new DEBUG_NEW #endif MEMainFrame* meMainFrame = NULL; CFont* materialEditorFont = NULL; static bool materialEditorInitialized = false; static bool materialEditorEmbedded = false; static CWnd* materialEditorEmbeddedParent = NULL; static const COLORREF ME_DARK_BG = RGB(17, 19, 23); static const COLORREF ME_DARK_PANEL = RGB(23, 26, 31); static const COLORREF ME_DARK_MENU = RGB(28, 31, 36); static const COLORREF ME_DARK_MENU_HOT = RGB(45, 50, 58); static const COLORREF ME_DARK_BORDER = RGB(58, 64, 72); static const COLORREF ME_DARK_TEXT = RGB(226, 232, 240); static const COLORREF ME_DARK_TEXT_DISABLED = RGB(101, 110, 126); static const COLORREF ME_DARK_ACCENT = RGB(87, 166, 255); #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* ME_DARK_BUTTON_OLDPROC = "IceTech.MaterialEditor.DarkButtonOldProc"; static const char* ME_DARK_STATIC_OLDPROC = "IceTech.MaterialEditor.DarkStaticOldProc"; static const char* ME_DARK_TAB_OLDPROC = "IceTech.MaterialEditor.DarkTabOldProc"; static const char* ME_DARK_RECOLOR_OLDPROC = "IceTech.MaterialEditor.DarkRecolorOldProc"; bool MaterialEditorOpenDockTab(); static HBRUSH MaterialEditorDarkPanelBrush() { static HBRUSH hBrush = ::CreateSolidBrush(ME_DARK_PANEL); return hBrush; } static void MaterialEditorDark_FillRect(HDC hDC, const RECT& rc, COLORREF color) { HBRUSH brush = ::CreateSolidBrush(color); ::FillRect(hDC, &rc, brush); ::DeleteObject(brush); } static void MaterialEditorDark_FrameRect(HDC hDC, const RECT& rc, COLORREF color) { HBRUSH brush = ::CreateSolidBrush(color); ::FrameRect(hDC, &rc, brush); ::DeleteObject(brush); } static void MaterialEditorDark_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 MaterialEditorDark_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 ? ME_DARK_TEXT : ME_DARK_TEXT_DISABLED); if (isCheck) { MaterialEditorDark_FillRect(hDC, rc, ME_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; MaterialEditorDark_FillRect(hDC, box, RGB(14, 16, 20)); MaterialEditorDark_FrameRect(hDC, box, checked ? ME_DARK_ACCENT : ME_DARK_BORDER); if (checked) { HPEN pen = ::CreatePen(PS_SOLID, 2, ME_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); MaterialEditorDark_FillRect(hDC, rc, face); MaterialEditorDark_FrameRect(hDC, rc, enabled ? ME_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 MaterialEditorDarkButtonProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ME_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); MaterialEditorDark_DrawButton(hWnd, hDC); ::EndPaint(hWnd, &ps); return 0; } case WM_PRINTCLIENT: MaterialEditorDark_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, ME_DARK_BUTTON_OLDPROC); return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } } return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } static void MaterialEditorDark_SubclassButton(HWND hWnd) { if (!hWnd || ::GetPropA(hWnd, ME_DARK_BUTTON_OLDPROC)) { return; } WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); ::SetPropA(hWnd, ME_DARK_BUTTON_OLDPROC, (HANDLE)oldProc); ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MaterialEditorDarkButtonProc); ::InvalidateRect(hWnd, NULL, TRUE); } static void MaterialEditorDark_DrawStatic(HWND hWnd, HDC hDC) { RECT rc; ::GetClientRect(hWnd, &rc); MaterialEditorDark_FillRect(hDC, rc, ME_DARK_PANEL); 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, ME_DARK_TEXT); ::DrawTextA(hDC, text, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); if (oldFont) { ::SelectObject(hDC, oldFont); } } static LRESULT CALLBACK MaterialEditorDarkStaticProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ME_DARK_STATIC_OLDPROC); if (!oldProc) { return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } if (uMsg == WM_PAINT) { PAINTSTRUCT ps; HDC hDC = ::BeginPaint(hWnd, &ps); MaterialEditorDark_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, ME_DARK_STATIC_OLDPROC); return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } static void MaterialEditorDark_SubclassStatic(HWND hWnd) { if (!hWnd || ::GetPropA(hWnd, ME_DARK_STATIC_OLDPROC)) { return; } WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); ::SetPropA(hWnd, ME_DARK_STATIC_OLDPROC, (HANDLE)oldProc); ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MaterialEditorDarkStaticProc); } static void MaterialEditorDark_DrawTabControl(HWND hWnd, HDC hDC) { RECT client; ::GetClientRect(hWnd, &client); MaterialEditorDark_FillRect(hDC, client, ME_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; MaterialEditorDark_FillRect(hDC, fill, active ? ME_DARK_PANEL : ME_DARK_MENU); MaterialEditorDark_FrameRect(hDC, fill, active ? ME_DARK_ACCENT : ME_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) : ME_DARK_TEXT); ::DrawTextA(hDC, text, -1, &item, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS); } RECT line = client; line.top = client.bottom - 1; MaterialEditorDark_FillRect(hDC, line, ME_DARK_BORDER); if (oldFont) { ::SelectObject(hDC, oldFont); } } static LRESULT CALLBACK MaterialEditorDarkTabProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ME_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); MaterialEditorDark_DrawTabControl(hWnd, hDC); ::EndPaint(hWnd, &ps); return 0; } case WM_PRINTCLIENT: MaterialEditorDark_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, ME_DARK_TAB_OLDPROC); return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } } return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } static void MaterialEditorDark_SubclassTab(HWND hWnd) { if (!hWnd || ::GetPropA(hWnd, ME_DARK_TAB_OLDPROC)) { return; } WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); ::SetPropA(hWnd, ME_DARK_TAB_OLDPROC, (HANDLE)oldProc); ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MaterialEditorDarkTabProc); ::InvalidateRect(hWnd, NULL, TRUE); } static void MaterialEditorDark_SetDIBPixel(BYTE *pixel, COLORREF color) { pixel[0] = GetBValue(color); pixel[1] = GetGValue(color); pixel[2] = GetRValue(color); } static void MaterialEditorDark_RecolorPropTreeWindow(HWND hWnd) { RECT rc; ::GetClientRect(hWnd, &rc); const int width = rc.right - rc.left; const int height = rc.bottom - rc.top; if (width <= 0 || height <= 0) { return; } HDC hDC = ::GetDC(hWnd); if (!hDC) { return; } BITMAPINFO bmi; memset(&bmi, 0, sizeof(bmi)); bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; void *bits = NULL; HBITMAP hBitmap = ::CreateDIBSection(hDC, &bmi, DIB_RGB_COLORS, &bits, NULL, 0); if (!hBitmap || !bits) { if (hBitmap) { ::DeleteObject(hBitmap); } ::ReleaseDC(hWnd, hDC); return; } HDC memDC = ::CreateCompatibleDC(hDC); HBITMAP oldBitmap = (HBITMAP)::SelectObject(memDC, hBitmap); ::BitBlt(memDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY); BYTE *data = (BYTE *)bits; const int pixelCount = width * height; for (int i = 0; i < pixelCount; i++) { BYTE *pixel = data + i * 4; const int b = pixel[0]; const int g = pixel[1]; const int r = pixel[2]; const int maxColor = max(r, max(g, b)); const int minColor = min(r, min(g, b)); // Only remap neutral system-color pixels. Non-gray pixels such as the // preview light color swatch are left alone as much as possible. if (maxColor - minColor <= 18) { if (maxColor >= 245) { MaterialEditorDark_SetDIBPixel(pixel, ME_DARK_PANEL); } else if (maxColor >= 215) { MaterialEditorDark_SetDIBPixel(pixel, RGB(31, 35, 41)); } else if (maxColor >= 165) { MaterialEditorDark_SetDIBPixel(pixel, ME_DARK_MENU_HOT); } else if (maxColor <= 35) { MaterialEditorDark_SetDIBPixel(pixel, ME_DARK_TEXT); } else if (maxColor <= 110) { MaterialEditorDark_SetDIBPixel(pixel, ME_DARK_TEXT_DISABLED); } } else if (b > 130 && g > 70 && r < 120) { MaterialEditorDark_SetDIBPixel(pixel, ME_DARK_ACCENT); } } ::BitBlt(hDC, 0, 0, width, height, memDC, 0, 0, SRCCOPY); ::SelectObject(memDC, oldBitmap); ::DeleteDC(memDC); ::DeleteObject(hBitmap); ::ReleaseDC(hWnd, hDC); } static bool MaterialEditorDark_IsPropTreePart(HWND hWnd) { if (!hWnd) { return false; } if (::GetDlgCtrlID(hWnd) == IDC_PROPERTYTREE) { return true; } HWND parent = ::GetParent(hWnd); if (parent && ::GetDlgCtrlID(parent) == IDC_PROPERTYTREE) { return true; } HWND grandParent = parent ? ::GetParent(parent) : NULL; if (grandParent && ::GetDlgCtrlID(grandParent) == IDC_PROPERTYTREE) { return true; } return false; } static bool MaterialEditorDark_ShouldRecolorClass(const char *className) { if (!className || !className[0]) { return true; } if (!_stricmp(className, "Edit") || !_stricmp(className, "RICHEDIT") || !_stricmp(className, "RichEdit20A") || !_stricmp(className, "RichEdit20W")) { return false; } if (!_stricmp(className, "Button") || !_stricmp(className, "Static") || !_stricmp(className, "ComboBox")) { return false; } if (!_stricmp(className, "SysTreeView32") || !_stricmp(className, "SysListView32") || !_stricmp(className, "SysTabControl32")) { return false; } return true; } static LRESULT CALLBACK MaterialEditorDarkRecolorProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { WNDPROC oldProc = (WNDPROC)::GetPropA(hWnd, ME_DARK_RECOLOR_OLDPROC); if (!oldProc) { return ::DefWindowProc(hWnd, uMsg, wParam, lParam); } switch (uMsg) { case WM_ERASEBKGND: { RECT rc; ::GetClientRect(hWnd, &rc); MaterialEditorDark_FillRect((HDC)wParam, rc, ME_DARK_PANEL); return 1; } case WM_PAINT: { LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); MaterialEditorDark_RecolorPropTreeWindow(hWnd); return result; } case WM_PRINTCLIENT: { LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); return result; } case WM_SIZE: case WM_ENABLE: case WM_SETTEXT: { LRESULT result = ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); ::InvalidateRect(hWnd, NULL, TRUE); return result; } case WM_NCDESTROY: { ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)oldProc); ::RemovePropA(hWnd, ME_DARK_RECOLOR_OLDPROC); return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } } return ::CallWindowProc(oldProc, hWnd, uMsg, wParam, lParam); } static void MaterialEditorDark_SubclassRecolor(HWND hWnd) { if (!hWnd || ::GetPropA(hWnd, ME_DARK_RECOLOR_OLDPROC)) { return; } WNDPROC oldProc = (WNDPROC)::GetWindowLongPtr(hWnd, GWLP_WNDPROC); ::SetPropA(hWnd, ME_DARK_RECOLOR_OLDPROC, (HANDLE)oldProc); ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)MaterialEditorDarkRecolorProc); ::InvalidateRect(hWnd, NULL, TRUE); } static void MaterialEditorDark_ApplySingleWindow(HWND hWnd) { if (!hWnd || !::IsWindow(hWnd)) { return; } MaterialEditorDark_ApplyNativeTheme(hWnd); char className[128]; className[0] = '\0'; ::GetClassNameA(hWnd, className, sizeof(className)); if (MaterialEditorDark_IsPropTreePart(hWnd) && MaterialEditorDark_ShouldRecolorClass(className)) { MaterialEditorDark_SubclassRecolor(hWnd); } if (!_stricmp(className, "SysTreeView32")) { ::SendMessage(hWnd, TVM_SETBKCOLOR, 0, (LPARAM)ME_DARK_PANEL); ::SendMessage(hWnd, TVM_SETTEXTCOLOR, 0, (LPARAM)ME_DARK_TEXT); ::SendMessage(hWnd, TVM_SETLINECOLOR, 0, (LPARAM)ME_DARK_BORDER); } else if (!_stricmp(className, "SysListView32")) { ::SendMessage(hWnd, LVM_SETBKCOLOR, 0, (LPARAM)ME_DARK_PANEL); ::SendMessage(hWnd, LVM_SETTEXTBKCOLOR, 0, (LPARAM)ME_DARK_PANEL); ::SendMessage(hWnd, LVM_SETTEXTCOLOR, 0, (LPARAM)ME_DARK_TEXT); ::SendMessage(hWnd, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); } else if (!_stricmp(className, "SysTabControl32")) { MaterialEditorDark_SubclassTab(hWnd); } else if (!_stricmp(className, "Button")) { MaterialEditorDark_SubclassButton(hWnd); } else if (!_stricmp(className, "Static")) { MaterialEditorDark_SubclassStatic(hWnd); } else if (!_stricmp(className, "Edit") || !_stricmp(className, "RICHEDIT") || !_stricmp(className, "RichEdit20A") || !_stricmp(className, "RichEdit20W")) { ::SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, (LPARAM)ME_DARK_PANEL); CHARFORMAT cf; memset(&cf, 0, sizeof(cf)); cf.cbSize = sizeof(cf); cf.dwMask = CFM_COLOR; cf.crTextColor = ME_DARK_TEXT; ::SendMessage(hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf); } else if (!_stricmp(className, "ComboBox")) { ::SendMessage(hWnd, CB_SETDROPPEDWIDTH, 260, 0); } } static BOOL CALLBACK MaterialEditorDark_EnumChildProc(HWND hWnd, LPARAM lParam) { MaterialEditorDark_ApplySingleWindow(hWnd); return TRUE; } void MaterialEditorDark_ApplyToWindow(HWND hWnd) { if (!hWnd || !::IsWindow(hWnd)) { return; } MaterialEditorDark_ApplySingleWindow(hWnd); ::EnumChildWindows(hWnd, MaterialEditorDark_EnumChildProc, 0); ::InvalidateRect(hWnd, NULL, TRUE); } COLORREF MaterialEditorDark_BackgroundColor() { return ME_DARK_BG; } COLORREF MaterialEditorDark_PanelColor() { return ME_DARK_PANEL; } COLORREF MaterialEditorDark_TextColor() { return ME_DARK_TEXT; } COLORREF MaterialEditorDark_AccentColor() { return ME_DARK_ACCENT; } COLORREF MaterialEditorDark_BorderColor() { return ME_DARK_BORDER; } static bool MaterialEditorInitCommon( void ) { if (materialEditorInitialized) { return true; } InitPropTree(win32.hInstance); com_editors |= EDITOR_MATERIAL; Sys_GrabMouseCursor(false); InitAfx(); InitCommonControls(); // Initialize OLE libraries once for the embedded editor controls. if (!AfxOleInit()) { return false; } AfxEnableControlContainer(); NONCLIENTMETRICS info; info.cbSize = sizeof(info); ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0); LOGFONT lf; memset(&lf, 0, sizeof(LOGFONT)); CWindowDC dc(NULL); lf.lfCharSet = (BYTE)GetTextCharsetInfo(dc.GetSafeHdc(), NULL, 0); lf.lfHeight = info.lfMenuFont.lfHeight; lf.lfWeight = info.lfMenuFont.lfWeight; lf.lfItalic = info.lfMenuFont.lfItalic; _tcscpy(lf.lfFaceName, info.lfMenuFont.lfFaceName); materialEditorFont = new CFont; materialEditorFont->CreateFontIndirect(&lf); materialEditorInitialized = true; return true; } static void MaterialEditorNormalizeEmbeddedFrameStyle( void ) { if (!meMainFrame || !meMainFrame->GetSafeHwnd()) { return; } HWND hWnd = meMainFrame->GetSafeHwnd(); LONG style = ::GetWindowLong(hWnd, GWL_STYLE); style &= ~(WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX); style |= WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; ::SetWindowLong(hWnd, GWL_STYLE, style); LONG exStyle = ::GetWindowLong(hWnd, GWL_EXSTYLE); exStyle &= ~(WS_EX_APPWINDOW | WS_EX_TOOLWINDOW | WS_EX_WINDOWEDGE | WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE); exStyle |= WS_EX_CONTROLPARENT; ::SetWindowLong(hWnd, GWL_EXSTYLE, exStyle); ::SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); } /** * Creates or returns the embedded material editor frame hosted by the Radiant tab control. */ HWND MaterialEditorCreateEmbeddedWindow(CWnd *parent, const RECT& rect) { if (!parent || !parent->GetSafeHwnd()) { return NULL; } if (!MaterialEditorInitCommon()) { return NULL; } materialEditorEmbedded = true; materialEditorEmbeddedParent = parent; if (!meMainFrame) { meMainFrame = new MEMainFrame; if (!meMainFrame->LoadFrame(IDR_ME_MAINFRAME, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, parent, NULL)) { delete meMainFrame; meMainFrame = NULL; return NULL; } } else if (meMainFrame->GetSafeHwnd() && ::GetParent(meMainFrame->GetSafeHwnd()) != parent->GetSafeHwnd()) { meMainFrame->SetParent(parent); } MaterialEditorNormalizeEmbeddedFrameStyle(); meMainFrame->MoveWindow(&rect, TRUE); meMainFrame->ShowWindow(SW_HIDE); MaterialEditorDark_ApplyToWindow(meMainFrame->GetSafeHwnd()); return meMainFrame->GetSafeHwnd(); } void MaterialEditorMoveEmbeddedWindow(const RECT& rect) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { meMainFrame->MoveWindow(&rect, TRUE); } } void MaterialEditorShowEmbeddedWindow(BOOL show) { if (!meMainFrame || !meMainFrame->GetSafeHwnd()) { return; } meMainFrame->ShowWindow(show ? SW_SHOW : SW_HIDE); if (show) { com_editors |= EDITOR_MATERIAL; MaterialEditorDark_ApplyToWindow(meMainFrame->GetSafeHwnd()); meMainFrame->SetActiveWindow(); meMainFrame->SetFocus(); } else if (materialEditorEmbedded) { com_editors &= ~EDITOR_MATERIAL; } } void MaterialEditorFocusEmbeddedWindow( void ) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { meMainFrame->SetFocus(); } } BOOL MaterialEditorPreTranslateEmbeddedMessage(MSG *pMsg) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { return meMainFrame->PreTranslateMessage(pMsg); } return FALSE; } BOOL MaterialEditorEmbeddedOnCommand(WPARAM wParam, LPARAM lParam) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { return (BOOL)meMainFrame->SendMessage(WM_COMMAND, wParam, lParam); } return FALSE; } BOOL MaterialEditorEmbeddedOnNotify(WPARAM wParam, LPARAM lParam, LRESULT *pResult) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { LRESULT result = meMainFrame->SendMessage(WM_NOTIFY, wParam, lParam); if (pResult) { *pResult = result; } return result != 0; } return FALSE; } BOOL MaterialEditorEmbeddedOnCmdMsg(UINT nID, int nCode, void *pExtra, AFX_CMDHANDLERINFO *pHandlerInfo) { if (meMainFrame && meMainFrame->GetSafeHwnd()) { return meMainFrame->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } return FALSE; } bool MaterialEditorIsEmbedded( void ) { return materialEditorEmbedded; } /** * Initializes the material editor tool. In the integrated editor this selects the * Material Editor tab instead of creating a separate top-level tool window. */ void MaterialEditorInit( void ) { if (MaterialEditorOpenDockTab()) { return; } if (!MaterialEditorInitCommon()) { return; } materialEditorEmbedded = false; if (!meMainFrame) { meMainFrame = new MEMainFrame; meMainFrame->LoadFrame(IDR_ME_MAINFRAME, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL, NULL); } // Standalone fallback is kept for tools launched before the Radiant tab host exists. meMainFrame->ShowWindow(SW_SHOW); meMainFrame->UpdateWindow(); MaterialEditorDark_ApplyToWindow(meMainFrame->GetSafeHwnd()); } /** * Called every frame by the doom engine to allow the material editor to process messages. */ void MaterialEditorRun( void ) { MSG *msg = AfxGetCurrentMessage(); while( ::PeekMessage(msg, NULL, NULL, NULL, PM_NOREMOVE) ) { if ( !AfxGetApp()->PumpMessage() ) { } } } /** * Called by the doom engine when the material editor needs to be destroyed. */ void MaterialEditorShutdown( void ) { delete meMainFrame; meMainFrame = NULL; delete materialEditorFont; materialEditorFont = NULL; materialEditorInitialized = false; materialEditorEmbedded = false; materialEditorEmbeddedParent = NULL; com_editors &= ~EDITOR_MATERIAL; } /** * Allows the doom engine to reflect console output to the material editors console. */ void MaterialEditorPrintConsole( const char *msg ) { if ((com_editors & EDITOR_MATERIAL) && meMainFrame) { meMainFrame->PrintConsoleMessage(msg); } } /** * Returns the handle to the main Material Editor Window */ HWND GetMaterialEditorWindow() { return meMainFrame ? meMainFrame->GetSafeHwnd() : NULL; } /** * Simple about box for the material editor. */ class CAboutDlg : public CDialog { public: CAboutDlg(); enum { IDD = IDD_ME_ABOUTBOX }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support DECLARE_MESSAGE_MAP() }; /** * Constructor for the about box. */ CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { } /** * Called by the MFC framework to exchange data with the window controls. */ void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) END_MESSAGE_MAP()