Files
DoomRTX/neo/engine/tools/materialeditor/MaterialEditor.cpp
T
2026-05-18 15:54:16 -07:00

892 lines
26 KiB
C++

/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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()