mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-13 00:31:10 +02:00
892 lines
26 KiB
C++
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()
|
|
|