Files
DoomRTX/neo/engine/tools/radiant/EntityDlg.cpp
T
2026-05-15 10:32:24 -07:00

2469 lines
64 KiB
C++

/*
===========================================================================
IceTech GPL Source Code
Copyright (C) 2026 Justin Marshall
Modernized Entity Inspector UI pass by Justin / IceBridge workflow.
===========================================================================
*/
#include "precompiled.h"
#pragma hdrstop
#include "qe3.h"
#include "Radiant.h"
#include "GLWidget.h"
#include "PropertyList.h"
#include "entitydlg.h"
#include "PreviewDlg.h"
#include "CurveDlg.h"
#include "../../models/model_local.h" // for idRenderModelPrt
void Select_Ungroup();
// CEntityDlg dialog
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;
multipleEntities = false;
currentAnimation = NULL;
currentAnimationFrame = 0;
dict = NULL;
themeInitialized = false;
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() {
DestroyModernTheme();
}
void CEntityDlg::DoDataExchange(CDataExchange* pDX) {
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST_KEYVAL, listKeyVal);
DDX_Control(pDX, IDC_COMBO_CLASS, comboClass);
DDX_Control(pDX, IDC_EDIT_KEY, editKey);
DDX_Control(pDX, IDC_EDIT_VAL, editVal);
DDX_Control(pDX, IDC_STATIC_TITLE, staticTitle);
DDX_Control(pDX, IDC_STATIC_KEY, staticKey);
DDX_Control(pDX, IDC_STATIC_VAL, staticVal);
DDX_Control(pDX, IDC_BUTTON_BROWSE, btnBrowse);
DDX_Control(pDX, IDC_E_135, btn135);
DDX_Control(pDX, IDC_E_90, btn90);
DDX_Control(pDX, IDC_E_45, btn45);
DDX_Control(pDX, IDC_E_180, btn180);
DDX_Control(pDX, IDC_E_0, btn360);
DDX_Control(pDX, IDC_E_225, btn225);
DDX_Control(pDX, IDC_E_270, btn270);
DDX_Control(pDX, IDC_E_315, btn315);
DDX_Control(pDX, IDC_E_UP, btnUp);
DDX_Control(pDX, IDC_E_DOWN, btnDown);
DDX_Control(pDX, IDC_BUTTON_MODEL, btnModel);
DDX_Control(pDX, IDC_BUTTON_SOUND, btnSound);
DDX_Control(pDX, IDC_BUTTON_GUI, btnGui);
DDX_Control(pDX, IDC_BUTTON_PARTICLE, btnParticle);
DDX_Control(pDX, IDC_BUTTON_SKIN, btnSkin);
DDX_Control(pDX, IDC_BUTTON_CURVE, btnCurve);
DDX_Control(pDX, IDC_BUTTON_CREATE, btnCreate);
DDX_Control(pDX, IDC_LIST_VARS, listVars);
DDX_Control(pDX, IDC_ENTITY_ANIMATIONS, cbAnimations);
DDX_Control(pDX, IDC_ANIMATION_SLIDER, slFrameSlider);
DDX_Control(pDX, IDC_ENTITY_CURRENT_ANIM, staticFrame);
DDX_Control(pDX, IDC_ENTITY_PLAY_ANIM, btnPlayAnim);
DDX_Control(pDX, IDC_ENTITY_STOP_ANIM, btnStopAnim);
}
BEGIN_MESSAGE_MAP(CEntityDlg, CDialog)
ON_WM_SIZE()
ON_WM_CTLCOLOR()
ON_WM_ERASEBKGND()
ON_WM_PAINT()
ON_CBN_SELCHANGE(IDC_COMBO_CLASS, OnCbnSelchangeComboClass)
ON_LBN_SELCHANGE(IDC_LIST_KEYVAL, OnLbnSelchangeListkeyval)
ON_BN_CLICKED(IDC_E_135, OnBnClickedE135)
ON_BN_CLICKED(IDC_E_90, OnBnClickedE90)
ON_BN_CLICKED(IDC_E_45, OnBnClickedE45)
ON_BN_CLICKED(IDC_E_180, OnBnClickedE180)
ON_BN_CLICKED(IDC_E_0, OnBnClickedE0)
ON_BN_CLICKED(IDC_E_225, OnBnClickedE225)
ON_BN_CLICKED(IDC_E_270, OnBnClickedE270)
ON_BN_CLICKED(IDC_E_315, OnBnClickedE315)
ON_BN_CLICKED(IDC_E_UP, OnBnClickedEUp)
ON_BN_CLICKED(IDC_E_DOWN, OnBnClickedEDown)
ON_BN_CLICKED(IDC_BUTTON_MODEL, OnBnClickedButtonModel)
ON_BN_CLICKED(IDC_BUTTON_SOUND, OnBnClickedButtonSound)
ON_BN_CLICKED(IDC_BUTTON_GUI, OnBnClickedButtonGui)
ON_BN_CLICKED(IDC_BUTTON_BROWSE, OnBnClickedButtonBrowse)
ON_CBN_DBLCLK(IDC_COMBO_CLASS, OnCbnDblclkComboClass)
ON_BN_CLICKED(IDC_BUTTON_CREATE, OnBnClickedButtonCreate)
ON_LBN_DBLCLK(IDC_LIST_KEYVAL, OnLbnDblclkListkeyval)
ON_LBN_SELCHANGE(IDC_LIST_VARS, OnLbnSelchangeListVars)
ON_LBN_DBLCLK(IDC_LIST_VARS, OnLbnDblclkListVars)
ON_NOTIFY(NM_RELEASEDCAPTURE, IDC_ANIMATION_SLIDER, OnNMReleasedcaptureSlider1)
ON_BN_CLICKED(IDC_BUTTON_PARTICLE, OnBnClickedButtonParticle)
ON_BN_CLICKED(IDC_BUTTON_SKIN, OnBnClickedButtonSkin)
ON_BN_CLICKED(IDC_BUTTON_CURVE, OnBnClickedButtonCurve)
ON_CBN_SELCHANGE(IDC_ENTITY_ANIMATIONS, OnCbnAnimationChange)
ON_BN_CLICKED(IDC_ENTITY_PLAY_ANIM, OnBnClickedStartAnimation)
ON_BN_CLICKED(IDC_ENTITY_STOP_ANIM, OnBnClickedStopAnimation)
ON_WM_TIMER()
ON_BN_CLICKED(IDOK, OnOK)
END_MESSAGE_MAP()
// --------------------------------------------------------------------------
// Modern theme helpers
// --------------------------------------------------------------------------
void CEntityDlg::DestroyModernTheme() {
if (fontTitle.GetSafeHandle()) {
fontTitle.DeleteObject();
}
if (fontSection.GetSafeHandle()) {
fontSection.DeleteObject();
}
if (fontNormal.GetSafeHandle()) {
fontNormal.DeleteObject();
}
if (fontMono.GetSafeHandle()) {
fontMono.DeleteObject();
}
if (brushBackground.GetSafeHandle()) {
brushBackground.DeleteObject();
}
if (brushPanel.GetSafeHandle()) {
brushPanel.DeleteObject();
}
if (brushPanelAlt.GetSafeHandle()) {
brushPanelAlt.DeleteObject();
}
if (brushEdit.GetSafeHandle()) {
brushEdit.DeleteObject();
}
if (brushStatic.GetSafeHandle()) {
brushStatic.DeleteObject();
}
themeInitialized = false;
}
void CEntityDlg::InitModernTheme() {
if (themeInitialized) {
return;
}
NONCLIENTMETRICS ncm;
memset(&ncm, 0, sizeof(ncm));
ncm.cbSize = sizeof(ncm);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
LOGFONT lf = ncm.lfMessageFont;
strcpy(lf.lfFaceName, "Segoe UI");
lf.lfHeight = -12;
lf.lfWeight = FW_NORMAL;
fontNormal.CreateFontIndirect(&lf);
lf.lfHeight = -17;
lf.lfWeight = FW_SEMIBOLD;
fontTitle.CreateFontIndirect(&lf);
lf.lfHeight = -12;
lf.lfWeight = FW_SEMIBOLD;
fontSection.CreateFontIndirect(&lf);
strcpy(lf.lfFaceName, "Consolas");
lf.lfHeight = -12;
lf.lfWeight = FW_NORMAL;
fontMono.CreateFontIndirect(&lf);
brushBackground.CreateSolidBrush(colorBackground);
brushPanel.CreateSolidBrush(colorPanel);
brushPanelAlt.CreateSolidBrush(colorPanelAlt);
brushEdit.CreateSolidBrush(colorEditBg);
brushStatic.CreateSolidBrush(colorPanel);
themeInitialized = true;
}
void CEntityDlg::SetCtrlFont(CWnd& wnd, CFont* font) {
if (wnd.GetSafeHwnd() && font && font->GetSafeHandle()) {
wnd.SetFont(font, FALSE);
}
}
void CEntityDlg::SetCtrlFont(int id, CFont* font) {
CWnd* wnd = GetDlgItem(id);
if (wnd && wnd->GetSafeHwnd() && font && font->GetSafeHandle()) {
wnd->SetFont(font, FALSE);
}
}
void CEntityDlg::ApplyModernTheme() {
InitModernTheme();
SetCtrlFont(staticTitle, &fontTitle);
SetCtrlFont(staticKey, &fontSection);
SetCtrlFont(staticVal, &fontSection);
SetCtrlFont(staticFrame, &fontNormal);
SetCtrlFont(comboClass, &fontNormal);
SetCtrlFont(cbAnimations, &fontNormal);
SetCtrlFont(editKey, &fontMono);
SetCtrlFont(editVal, &fontMono);
SetCtrlFont(listKeyVal, &fontMono);
SetCtrlFont(listVars, &fontNormal);
SetCtrlFont(btnCreate, &fontSection);
SetCtrlFont(btnBrowse, &fontSection);
SetCtrlFont(btnModel, &fontNormal);
SetCtrlFont(btnSound, &fontNormal);
SetCtrlFont(btnGui, &fontNormal);
SetCtrlFont(btnParticle, &fontNormal);
SetCtrlFont(btnSkin, &fontNormal);
SetCtrlFont(btnCurve, &fontNormal);
SetCtrlFont(btn135, &fontNormal);
SetCtrlFont(btn90, &fontNormal);
SetCtrlFont(btn45, &fontNormal);
SetCtrlFont(btn180, &fontNormal);
SetCtrlFont(btn360, &fontNormal);
SetCtrlFont(btn225, &fontNormal);
SetCtrlFont(btn270, &fontNormal);
SetCtrlFont(btn315, &fontNormal);
SetCtrlFont(btnUp, &fontNormal);
SetCtrlFont(btnDown, &fontNormal);
SetCtrlFont(btnPlayAnim, &fontNormal);
SetCtrlFont(btnStopAnim, &fontNormal);
if (editKey.GetSafeHwnd()) {
editKey.SetMargins(6, 6);
}
if (editVal.GetSafeHwnd()) {
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");
}
void CEntityDlg::MoveCtrl(CWnd& wnd, int x, int y, int w, int h, UINT flags) {
if (wnd.GetSafeHwnd()) {
wnd.SetWindowPos(NULL, x, y, max(1, w), max(1, h), flags | SWP_NOZORDER);
}
}
void CEntityDlg::MoveCtrl(int id, int x, int y, int w, int h, UINT flags) {
CWnd* wnd = GetDlgItem(id);
if (wnd && wnd->GetSafeHwnd()) {
wnd->SetWindowPos(NULL, x, y, max(1, w), max(1, h), flags | SWP_NOZORDER);
}
}
void CEntityDlg::DrawPanel(CDC& dc, const CRect& r, const char* title, bool accent) {
CBrush* oldBrush = dc.SelectObject(&brushPanel);
CPen borderPen(PS_SOLID, 1, accent ? colorAccent : colorBorder);
CPen* oldPen = dc.SelectObject(&borderPen);
dc.RoundRect(r.left, r.top, r.right, r.bottom, 8, 8);
if (title && title[0]) {
CRect titleRect = r;
titleRect.left += 10;
titleRect.top += 6;
titleRect.bottom = titleRect.top + 18;
CFont* oldFont = dc.SelectObject(&fontSection);
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(accent ? colorAccent : colorTextMuted);
dc.DrawText(title, -1, &titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
dc.SelectObject(oldFont);
}
dc.SelectObject(oldPen);
dc.SelectObject(oldBrush);
}
void CEntityDlg::DrawModernBackground(CDC& dc) {
CRect rect;
GetClientRect(&rect);
dc.FillSolidRect(&rect, colorBackground);
const int pad = 8;
CRect header(rect.left + pad, rect.top + pad, rect.right - pad, rect.top + 42);
CRect classPanel(rect.left + pad, header.bottom + pad, rect.right - pad, header.bottom + 52 + pad);
int contentTop = classPanel.bottom + pad;
int contentBottom = rect.bottom - pad;
int width = rect.Width();
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 + 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);
DrawPanel(dc, classPanel, "Class", false);
DrawPanel(dc, vars, "Spawn Args / Help", false);
DrawPanel(dc, keyvals, "Key / Value Pairs", true);
DrawPanel(dc, editor, "Edit Property", false);
DrawPanel(dc, tools, "Tools", false);
}
else {
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 + editorH);
CRect tools(rect.left + pad, editor.bottom + pad, rect.right - pad, contentBottom);
DrawPanel(dc, header, "", true);
DrawPanel(dc, classPanel, "Class", false);
DrawPanel(dc, vars, "Spawn Args / Help", false);
DrawPanel(dc, keyvals, "Key / Value Pairs", true);
DrawPanel(dc, editor, "Edit Property", false);
DrawPanel(dc, tools, "Tools", false);
}
}
BOOL CEntityDlg::OnEraseBkgnd(CDC* pDC) {
if (pDC) {
DrawModernBackground(*pDC);
}
return TRUE;
}
void CEntityDlg::OnPaint() {
CPaintDC dc(this);
DrawModernBackground(dc);
}
HBRUSH CEntityDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) {
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
if (!themeInitialized) {
return hbr;
}
const int id = pWnd ? pWnd->GetDlgCtrlID() : 0;
switch (nCtlColor) {
case CTLCOLOR_STATIC:
pDC->SetTextColor((id == IDC_STATIC_TITLE) ? colorText : colorTextMuted);
pDC->SetBkMode(TRANSPARENT);
return (HBRUSH)brushPanel.GetSafeHandle();
case CTLCOLOR_EDIT:
pDC->SetTextColor(colorText);
pDC->SetBkColor(colorEditBg);
return (HBRUSH)brushEdit.GetSafeHandle();
case CTLCOLOR_LISTBOX:
pDC->SetTextColor(colorText);
pDC->SetBkColor(colorEditBg);
return (HBRUSH)brushEdit.GetSafeHandle();
case CTLCOLOR_BTN:
pDC->SetTextColor(colorText);
pDC->SetBkColor(colorPanelAlt);
return (HBRUSH)brushPanelAlt.GetSafeHandle();
case CTLCOLOR_DLG:
return (HBRUSH)brushBackground.GetSafeHandle();
}
return hbr;
}
// --------------------------------------------------------------------------
// Dialog init / layout
// --------------------------------------------------------------------------
BOOL CEntityDlg::OnInitDialog() {
CDialog::OnInitDialog();
InitModernTheme();
listKeyVal.SetUpdateInspectors(true);
listKeyVal.SetDivider(170);
listVars.SetDivider(170);
::SendMessage(listKeyVal.GetSafeHwnd(), LB_SETITEMHEIGHT, 0, 32);
::SendMessage(listVars.GetSafeHwnd(), LB_SETITEMHEIGHT, 0, 32);
staticFrame.SetWindowText("0");
ApplyModernTheme();
ModifyStyleEx(0, WS_EX_CONTROLPARENT);
Invalidate(FALSE);
return TRUE;
}
INT_PTR CEntityDlg::OnToolHitTest(CPoint point, TOOLINFO* pTI) const {
return CDialog::OnToolHitTest(point, pTI);
}
void CEntityDlg::OnSize(UINT nType, int cx, int cy) {
CDialog::OnSize(nType, cx, cy);
if (staticTitle.GetSafeHwnd() == NULL) {
return;
}
const int pad = 8;
const int inner = 10;
const int headerH = 42;
const int classH = 52;
const int editorH = 106;
const int minToolH = 112;
CRect rect;
GetClientRect(&rect);
int w = max(1, rect.Width());
int h = max(1, rect.Height());
CRect header(rect.left + pad, rect.top + pad, rect.right - pad, rect.top + pad + headerH);
MoveCtrl(staticTitle, header.left + 12, header.top + 8, header.Width() - 24, 24);
CRect classPanel(rect.left + pad, header.bottom + pad, rect.right - pad, header.bottom + pad + classH);
int classY = classPanel.top + 22;
int createW = 82;
MoveCtrl(comboClass, classPanel.left + inner, classY, classPanel.Width() - inner * 3 - createW, 22);
MoveCtrl(btnCreate, classPanel.right - inner - createW, classY, createW, 22);
int contentTop = classPanel.bottom + pad;
int contentBottom = rect.bottom - pad;
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);
CRect keyPanel(varsPanel.right + pad, contentTop, rect.right - pad, contentTop + listH);
MoveCtrl(listVars, varsPanel.left + inner, varsPanel.top + 28, varsPanel.Width() - inner * 2, varsPanel.Height() - 38);
MoveCtrl(listKeyVal, keyPanel.left + inner, keyPanel.top + 28, keyPanel.Width() - inner * 2, keyPanel.Height() - 38);
CRect editPanel(rect.left + pad, varsPanel.bottom + pad, rect.right - pad, varsPanel.bottom + pad + editorH);
int labelW = 52;
int browseW = 28;
int rowH = 22;
int rowY = editPanel.top + 30;
MoveCtrl(staticKey, editPanel.left + inner, rowY + 2, labelW, rowH);
MoveCtrl(editKey, editPanel.left + inner + labelW + 6, rowY, editPanel.Width() - inner * 2 - labelW - 6, rowH);
rowY += rowH + 10;
MoveCtrl(staticVal, editPanel.left + inner, rowY + 2, labelW, rowH);
MoveCtrl(editVal, editPanel.left + inner + labelW + 6, rowY, editPanel.Width() - inner * 2 - labelW - 6 - browseW - 6, rowH);
MoveCtrl(btnBrowse, editPanel.right - inner - browseW, rowY, browseW, rowH);
CRect toolsPanel(rect.left + pad, editPanel.bottom + pad, rect.right - pad, contentBottom);
int toolY = toolsPanel.top + 28;
int toolX = toolsPanel.left + inner;
int b = 30;
MoveCtrl(btn135, toolX, toolY, b, b);
MoveCtrl(btn90, toolX + b + 4, toolY, b, b);
MoveCtrl(btn45, toolX + (b + 4) * 2, toolY, b, b);
MoveCtrl(btn180, toolX, toolY + b + 4, b, b);
MoveCtrl(btn360, toolX + (b + 4) * 2, toolY + b + 4, b, b);
MoveCtrl(btn225, toolX, toolY + (b + 4) * 2, b, b);
MoveCtrl(btn270, toolX + b + 4, toolY + (b + 4) * 2, b, b);
MoveCtrl(btn315, toolX + (b + 4) * 2, toolY + (b + 4) * 2, b, b);
MoveCtrl(btnUp, toolX + (b + 4) * 3 + 8, toolY + 16, b + 8, b);
MoveCtrl(btnDown, toolX + (b + 4) * 3 + 8, toolY + 16 + b + 6, b + 8, b);
int mediaX = toolX + 190;
int mediaW = 76;
int mediaH = 24;
MoveCtrl(btnModel, mediaX, toolY, mediaW, mediaH);
MoveCtrl(btnSound, mediaX, toolY + mediaH + 6, mediaW, mediaH);
MoveCtrl(btnGui, mediaX, toolY + (mediaH + 6) * 2, mediaW, mediaH);
MoveCtrl(btnCurve, mediaX + mediaW + 8, toolY, mediaW, mediaH);
MoveCtrl(btnSkin, mediaX + mediaW + 8, toolY + mediaH + 6, mediaW, mediaH);
MoveCtrl(btnParticle, mediaX + mediaW + 8, toolY + (mediaH + 6) * 2, mediaW, mediaH);
int animX = mediaX + (mediaW + 8) * 2 + 18;
int animW = max(120, toolsPanel.right - animX - inner);
MoveCtrl(cbAnimations, animX, toolY, animW, 24);
MoveCtrl(btnPlayAnim, animX, toolY + 32, 54, 24);
MoveCtrl(btnStopAnim, animX + 60, toolY + 32, 54, 24);
MoveCtrl(slFrameSlider, animX, toolY + 66, max(80, animW - 44), 24);
MoveCtrl(staticFrame, animX + animW - 38, toolY + 68, 36, 20);
}
else {
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);
MoveCtrl(listVars, varsPanel.left + inner, varsPanel.top + 28, varsPanel.Width() - inner * 2, varsPanel.Height() - 38);
MoveCtrl(listKeyVal, keyPanel.left + inner, keyPanel.top + 28, keyPanel.Width() - inner * 2, keyPanel.Height() - 38);
CRect editPanel(rect.left + pad, keyPanel.bottom + pad, rect.right - pad, keyPanel.bottom + pad + editorH);
int labelW = 48;
int browseW = 28;
int rowH = 22;
int rowY = editPanel.top + 30;
MoveCtrl(staticKey, editPanel.left + inner, rowY + 2, labelW, rowH);
MoveCtrl(editKey, editPanel.left + inner + labelW + 6, rowY, editPanel.Width() - inner * 2 - labelW - 6, rowH);
rowY += rowH + 10;
MoveCtrl(staticVal, editPanel.left + inner, rowY + 2, labelW, rowH);
MoveCtrl(editVal, editPanel.left + inner + labelW + 6, rowY, editPanel.Width() - inner * 2 - labelW - 6 - browseW - 6, rowH);
MoveCtrl(btnBrowse, editPanel.right - inner - browseW, rowY, browseW, rowH);
CRect toolsPanel(rect.left + pad, editPanel.bottom + pad, rect.right - pad, contentBottom);
int toolY = toolsPanel.top + 28;
int toolX = toolsPanel.left + inner;
int b = 26;
MoveCtrl(btn135, toolX, toolY, b, b);
MoveCtrl(btn90, toolX + b + 4, toolY, b, b);
MoveCtrl(btn45, toolX + (b + 4) * 2, toolY, b, b);
MoveCtrl(btn180, toolX, toolY + b + 4, b, b);
MoveCtrl(btn360, toolX + (b + 4) * 2, toolY + b + 4, b, b);
MoveCtrl(btn225, toolX, toolY + (b + 4) * 2, b, b);
MoveCtrl(btn270, toolX + b + 4, toolY + (b + 4) * 2, b, b);
MoveCtrl(btn315, toolX + (b + 4) * 2, toolY + (b + 4) * 2, b, b);
MoveCtrl(btnUp, toolX + (b + 4) * 3 + 6, toolY + 12, b + 8, b);
MoveCtrl(btnDown, toolX + (b + 4) * 3 + 6, toolY + 12 + b + 6, b + 8, b);
int mediaX = toolX + 152;
int mediaW = max(58, (toolsPanel.right - mediaX - inner - 8) / 2);
int mediaH = 22;
MoveCtrl(btnModel, mediaX, toolY, mediaW, mediaH);
MoveCtrl(btnSound, mediaX, toolY + mediaH + 5, mediaW, mediaH);
MoveCtrl(btnGui, mediaX, toolY + (mediaH + 5) * 2, mediaW, mediaH);
MoveCtrl(btnCurve, mediaX + mediaW + 8, toolY, mediaW, mediaH);
MoveCtrl(btnSkin, mediaX + mediaW + 8, toolY + mediaH + 5, mediaW, mediaH);
MoveCtrl(btnParticle, mediaX + mediaW + 8, toolY + (mediaH + 5) * 2, mediaW, mediaH);
int animY = toolY + 88;
MoveCtrl(cbAnimations, toolX, animY, toolsPanel.Width() - inner * 2, 22);
MoveCtrl(btnPlayAnim, toolX, animY + 28, 54, 22);
MoveCtrl(btnStopAnim, toolX + 60, animY + 28, 54, 22);
MoveCtrl(slFrameSlider, toolX + 120, animY + 28, toolsPanel.Width() - inner * 2 - 162, 22);
MoveCtrl(staticFrame, toolsPanel.right - inner - 36, animY + 30, 36, 18);
}
Invalidate(FALSE);
}
// --------------------------------------------------------------------------
// Entity/class/keyval behavior
// --------------------------------------------------------------------------
void CEntityDlg::AddClassNames() {
comboClass.ResetContent();
for (eclass_t* pec = eclass; pec; pec = pec->next) {
comboClass.AddString(pec->name);
}
}
void CEntityDlg::OnCbnSelchangeComboClass() {
int index = comboClass.GetCurSel();
if (index != LB_ERR) {
CString str;
comboClass.GetLBText(index, str);
eclass_t* ent = Eclass_ForName(str, false);
if (ent) {
if (selected_brushes.next == &selected_brushes) {
editEntity = world_entity;
multipleEntities = false;
}
else {
editEntity = selected_brushes.next->owner;
multipleEntities = false;
for (brush_t* b = selected_brushes.next->next; b != &selected_brushes; b = b->next) {
if (b->owner != editEntity) {
multipleEntities = true;
break;
}
}
}
listVars.ResetContent();
CPropertyItem* pi = new CPropertyItem("Usage:", ent->desc.c_str(), PIT_VAR, "");
listVars.AddPropItem(pi);
int c = ent->vars.Num();
for (int i = 0; i < c; i++) {
pi = new CPropertyItem(ent->vars[i].name.c_str(), ent->vars[i].desc.c_str(), PIT_VAR, "");
pi->SetData(ent->vars[i].type);
listVars.AddPropItem(pi);
}
listVars.Invalidate();
SetKeyValPairs();
}
}
}
const char* CEntityDlg::TranslateString(const char* buf) {
static char buf2[32768];
int l = strlen(buf);
char* out = buf2;
for (int i = 0; i < l; i++) {
if (buf[i] == '\n') {
*out++ = '\r';
*out++ = '\n';
}
else {
*out++ = buf[i];
}
}
*out++ = 0;
return buf2;
}
void CEntityDlg::UpdateFromListBox() {
if (editEntity == NULL) {
return;
}
int c = listKeyVal.GetCount();
for (int i = 0; i < c; i++) {
CPropertyItem* pItem = (CPropertyItem*)listKeyVal.GetItemDataPtr(i);
if (pItem) {
editEntity->epairs.Set(pItem->m_propName, pItem->m_curValue);
}
}
SetKeyValPairs();
}
void CEntityDlg::SetKeyValPairs(bool updateAnims) {
if (editEntity) {
listKeyVal.ResetContent();
int c = editEntity->epairs.GetNumKeyVals();
for (int i = 0; i < c; i++) {
const idKeyValue* kv = editEntity->epairs.GetKeyVal(i);
CPropertyItem* pi = new CPropertyItem(kv->GetKey().c_str(), kv->GetValue().c_str(), PIT_EDIT, "");
bool found = false;
int vc = editEntity->eclass->vars.Num();
for (int j = 0; j < vc; j++) {
if (editEntity->eclass->vars[j].name.Icmp(kv->GetKey()) == 0) {
switch (editEntity->eclass->vars[j].type) {
case EVAR_STRING:
case EVAR_INT:
case EVAR_FLOAT:
pi->m_nItemType = PIT_EDIT;
break;
case EVAR_BOOL:
pi->m_nItemType = PIT_EDIT;
break;
case EVAR_COLOR:
pi->m_nItemType = PIT_COLOR;
break;
case EVAR_MATERIAL:
pi->m_nItemType = PIT_MATERIAL;
break;
case EVAR_MODEL:
pi->m_nItemType = PIT_MODEL;
break;
case EVAR_GUI:
pi->m_nItemType = PIT_GUI;
break;
case EVAR_SOUND:
pi->m_nItemType = PIT_SOUND;
break;
}
found = true;
break;
}
}
if (!found) {
if (kv->GetKey().Icmp("model") == 0) {
pi->m_nItemType = PIT_MODEL;
}
if (kv->GetKey().Icmp("_color") == 0) {
pi->m_nItemType = PIT_COLOR;
}
if (kv->GetKey().Icmp("gui") == 0) {
pi->m_nItemType = PIT_GUI;
}
if (kv->GetKey().Icmp("gui2") == 0) {
pi->m_nItemType = PIT_GUI;
}
if (kv->GetKey().Icmp("gui3") == 0) {
pi->m_nItemType = PIT_GUI;
}
if (kv->GetKey().Icmp("s_shader") == 0) {
pi->m_nItemType = PIT_SOUND;
}
}
listKeyVal.AddPropItem(pi);
}
if (updateAnims) {
int i, num;
cbAnimations.ResetContent();
num = gameEdit->ANIM_GetNumAnimsFromEntityDef(&editEntity->eclass->defArgs);
for (i = 0; i < num; i++) {
cbAnimations.AddString(gameEdit->ANIM_GetAnimNameFromEntityDef(&editEntity->eclass->defArgs, i));
}
const idKeyValue* kv = editEntity->epairs.FindKey("anim");
if (kv) {
int selIndex = cbAnimations.FindStringExact(0, kv->GetValue().c_str());
if (selIndex != -1) {
cbAnimations.SetCurSel(selIndex);
OnCbnAnimationChange();
}
}
}
}
}
void CEntityDlg::UpdateEntitySel(eclass_t* ent) {
assert(ent);
assert(ent->name);
int index = comboClass.FindString(-1, ent->name);
if (index != LB_ERR) {
comboClass.SetCurSel(index);
OnCbnSelchangeComboClass();
}
}
void CEntityDlg::OnLbnSelchangeListkeyval() {
int index = listKeyVal.GetCurSel();
if (index != LB_ERR) {
CString str;
listKeyVal.GetText(index, str);
int i;
for (i = 0; str[i] != '\t' && str[i] != '\0'; i++) {
}
idStr key = str.Left(i);
while (str[i] == '\t' && str[i] != '\0') {
i++;
}
idStr val = str.Right(str.GetLength() - i);
editKey.SetWindowText(key);
editVal.SetWindowText(val);
}
}
static int TabOrder[] = {
IDC_COMBO_CLASS,
IDC_BUTTON_CREATE,
IDC_LIST_KEYVAL,
IDC_EDIT_KEY,
IDC_EDIT_VAL,
IDC_BUTTON_BROWSE,
IDC_E_135,
IDC_E_90,
IDC_E_45,
IDC_E_180,
IDC_E_0,
IDC_E_225,
IDC_E_270,
IDC_E_315,
IDC_E_UP,
IDC_E_DOWN,
IDC_BUTTON_MODEL,
IDC_BUTTON_SOUND,
IDC_BUTTON_GUI,
IDC_ENTITY_ANIMATIONS
};
int TabCount = sizeof(TabOrder) / sizeof(int);
void CEntityDlg::DelProp() {
CString key;
if (editEntity == NULL) {
return;
}
editKey.GetWindowText(key);
if (multipleEntities) {
for (brush_t* b = selected_brushes.next; b != &selected_brushes; b = b->next) {
DeleteKey(b->owner, key);
Entity_UpdateCurveData(b->owner);
}
}
else {
DeleteKey(editEntity, key);
Entity_UpdateCurveData(editEntity);
}
SetKeyValPairs();
Sys_UpdateWindows(W_ENTITY | W_XY | W_CAMERA);
}
BOOL CEntityDlg::PreTranslateMessage(MSG* pMsg) {
if (pMsg->hwnd == editVal.GetSafeHwnd()) {
if (pMsg->message == WM_LBUTTONDOWN) {
editVal.SetFocus();
return TRUE;
}
}
if (pMsg->hwnd == editKey.GetSafeHwnd()) {
if (pMsg->message == WM_LBUTTONDOWN) {
editKey.SetFocus();
return TRUE;
}
}
if (GetFocus() == &editVal || GetFocus() == &editKey) {
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
AddProp();
return TRUE;
}
}
if (GetFocus() == listKeyVal.GetEditBox()) {
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
listKeyVal.OnChangeEditBox();
listKeyVal.OnSelchange();
listKeyVal.OnKillfocusEditBox();
AddProp();
SetKeyValPairs();
return TRUE;
}
}
if (GetFocus() == &listKeyVal) {
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_DELETE && editEntity) {
DelProp();
return TRUE;
}
}
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE) {
g_pParentWnd->GetCamera()->SetFocus();
Select_Deselect();
return TRUE;
}
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
return TRUE;
}
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_TAB) {
if (GetFocus()) {
int id = GetFocus()->GetDlgCtrlID();
for (int i = 0; i < TabCount; i++) {
if (TabOrder[i] == id) {
i++;
if (i >= TabCount) {
i = 0;
}
CWnd* next = GetDlgItem(TabOrder[i]);
if (next) {
next->SetFocus();
if (TabOrder[i] == IDC_EDIT_VAL) {
editVal.SetSel(0, -1);
}
return TRUE;
}
}
}
}
}
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RIGHT && pMsg->hwnd == slFrameSlider.GetSafeHwnd()) {
int maxRange = slFrameSlider.GetRangeMax();
if (maxRange <= 0) {
return TRUE;
}
int pos = slFrameSlider.GetPos() + 1;
pos = (pos % maxRange);
slFrameSlider.SetPos(pos);
UpdateFromAnimationFrame();
return TRUE;
}
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_LEFT && pMsg->hwnd == slFrameSlider.GetSafeHwnd()) {
int maxRange = slFrameSlider.GetRangeMax();
if (maxRange <= 0) {
return TRUE;
}
int pos = slFrameSlider.GetPos() - 1;
if (pos < 1) {
pos = maxRange;
}
slFrameSlider.SetPos(pos);
UpdateFromAnimationFrame();
return TRUE;
}
return CDialog::PreTranslateMessage(pMsg);
}
void CEntityDlg::AddProp() {
if (editEntity == NULL) {
return;
}
CString Key, Value;
editKey.GetWindowText(Key);
editVal.GetWindowText(Value);
bool isName = (stricmp(Key, "name") == 0);
bool isModel = static_cast<bool>((stricmp(Key, "model") == 0 && Value.GetLength() > 0));
bool isOrigin = (idStr::Icmp(Key, "origin") == 0);
if (multipleEntities) {
for (brush_t* b = selected_brushes.next; b != &selected_brushes; b = b->next) {
if (isName) {
Entity_SetName(b->owner, Value);
}
else {
if (!((isModel || isOrigin) && (b->owner->eclass->nShowFlags & ECLASS_WORLDSPAWN))) {
SetKeyValue(b->owner, Key, Value);
}
}
}
}
else {
if (isName) {
Entity_SetName(editEntity, Value);
}
else {
if (!((isModel || isOrigin) && (editEntity->eclass->nShowFlags & ECLASS_WORLDSPAWN))) {
SetKeyValue(editEntity, Key, Value);
}
}
if (isModel && !(editEntity->eclass->nShowFlags & ECLASS_WORLDSPAWN)) {
idBounds bo;
idVec3 mins, maxs;
selected_brushes.next->modelHandle = renderModelManager->FindModel(Value);
if (dynamic_cast<idRenderModelPrt*>(selected_brushes.next->modelHandle) ||
dynamic_cast<idRenderModelLiquid*>(selected_brushes.next->modelHandle)) {
bo.Zero();
bo.ExpandSelf(12.0f);
}
else {
bo = selected_brushes.next->modelHandle->Bounds(NULL);
}
VectorCopy(bo[0], mins);
VectorCopy(bo[1], maxs);
VectorAdd(mins, editEntity->origin, mins);
VectorAdd(maxs, editEntity->origin, maxs);
Brush_RebuildBrush(selected_brushes.next, mins, maxs, false);
Brush_Build(selected_brushes.next, false, false, false, true);
}
}
SetKeyValPairs();
Sys_UpdateWindows(W_ALL);
}
const char* CEntityDlg::AngleKey() {
if (editEntity == NULL) {
return "";
}
if (editEntity->eclass->nShowFlags & ECLASS_MOVER) {
return "movedir";
}
return "angle";
}
void CEntityDlg::OnBnClickedE135() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("135");
AddProp();
}
void CEntityDlg::OnBnClickedE90() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("90");
AddProp();
}
void CEntityDlg::OnBnClickedE45() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("45");
AddProp();
}
void CEntityDlg::OnBnClickedE180() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("180");
AddProp();
}
void CEntityDlg::OnBnClickedE0() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("0");
AddProp();
}
void CEntityDlg::OnBnClickedE225() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("225");
AddProp();
}
void CEntityDlg::OnBnClickedE270() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("270");
AddProp();
}
void CEntityDlg::OnBnClickedE315() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("315");
AddProp();
}
void CEntityDlg::OnBnClickedEUp() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("-1");
AddProp();
}
void CEntityDlg::OnBnClickedEDown() {
if (editEntity == NULL) {
return;
}
editKey.SetWindowText(AngleKey());
editVal.SetWindowText("-2");
AddProp();
}
// --------------------------------------------------------------------------
// Choosers
// --------------------------------------------------------------------------
CPreviewDlg* CEntityDlg::ShowModelChooser() {
static CPreviewDlg modelDlg;
modelDlg.SetMode(CPreviewDlg::MODELS);
modelDlg.SetModal();
if (modelDlg.GetSafeHwnd() == NULL) {
modelDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
modelDlg.ShowWindow(SW_SHOW);
modelDlg.BringWindowToTop();
while (modelDlg.Waiting()) {
}
return &modelDlg;
}
CPreviewDlg* CEntityDlg::ShowParticleChooser() {
static CPreviewDlg modelDlg;
modelDlg.SetMode(CPreviewDlg::PARTICLES);
modelDlg.SetModal();
if (modelDlg.GetSafeHwnd() == NULL) {
modelDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
modelDlg.ShowWindow(SW_SHOW);
modelDlg.BringWindowToTop();
while (modelDlg.Waiting()) {
}
return &modelDlg;
}
CPreviewDlg* CEntityDlg::ShowSkinChooser(entity_t* ent) {
static CPreviewDlg modelDlg;
modelDlg.SetMode(CPreviewDlg::SKINS);
modelDlg.SetModal();
if (modelDlg.GetSafeHwnd() == NULL) {
modelDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
modelDlg.RebuildTree((ent) ? ent->epairs.GetString("model") : "");
modelDlg.ShowWindow(SW_SHOW);
modelDlg.BringWindowToTop();
while (modelDlg.Waiting()) {
}
return &modelDlg;
}
CPreviewDlg* CEntityDlg::ShowGuiChooser() {
static CPreviewDlg guiDlg;
guiDlg.SetMode(CPreviewDlg::GUIS);
guiDlg.SetModal();
if (guiDlg.GetSafeHwnd() == NULL) {
guiDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
guiDlg.ShowWindow(SW_SHOW);
guiDlg.BringWindowToTop();
while (guiDlg.Waiting()) {
}
return &guiDlg;
}
CPreviewDlg* CEntityDlg::ShowSoundChooser() {
static CPreviewDlg soundDlg;
soundDlg.SetMode(CPreviewDlg::SOUNDS);
soundDlg.SetModal();
if (soundDlg.GetSafeHwnd() == NULL) {
soundDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
soundDlg.ShowWindow(SW_SHOW);
while (soundDlg.Waiting()) {
}
return &soundDlg;
}
CPreviewDlg* CEntityDlg::ShowMaterialChooser() {
static CPreviewDlg matDlg;
matDlg.SetMode(CPreviewDlg::MATERIALS);
matDlg.SetModal();
if (matDlg.GetSafeHwnd() == NULL) {
matDlg.Create(MAKEINTRESOURCE(IDD_DIALOG_PREVIEW));
}
matDlg.ShowWindow(SW_SHOW);
matDlg.BringWindowToTop();
while (matDlg.Waiting()) {
}
return &matDlg;
}
void CEntityDlg::AssignModel() {
OnBnClickedButtonModel();
}
void CEntityDlg::OnBnClickedButtonModel() {
CPreviewDlg* dlg = ShowModelChooser();
if (dlg->returnCode == IDOK) {
editKey.SetWindowText("model");
editVal.SetWindowText(dlg->mediaName);
AddProp();
}
}
void CEntityDlg::OnBnClickedButtonSound() {
CPreviewDlg* dlg = ShowSoundChooser();
if (dlg->returnCode == IDOK) {
editKey.SetWindowText("s_shader");
editVal.SetWindowText(dlg->mediaName);
AddProp();
}
}
void CEntityDlg::OnBnClickedButtonGui() {
CPreviewDlg* dlg = ShowGuiChooser();
if (dlg->returnCode == IDOK) {
editKey.SetWindowText("gui");
editVal.SetWindowText(dlg->mediaName);
AddProp();
}
}
void CEntityDlg::OnBnClickedButtonParticle() {
CPreviewDlg* dlg = ShowParticleChooser();
if (dlg->returnCode == IDOK) {
editKey.SetWindowText("model");
editVal.SetWindowText(dlg->mediaName);
AddProp();
}
}
void CEntityDlg::OnBnClickedButtonSkin() {
CPreviewDlg* dlg = ShowSkinChooser(editEntity);
if (dlg->returnCode == IDOK) {
editKey.SetWindowText("skin");
editVal.SetWindowText(dlg->mediaName);
AddProp();
}
}
void CEntityDlg::OnBnClickedButtonCurve() {
CCurveDlg dlg;
if (dlg.DoModal() == IDOK) {
if (editEntity) {
idStr str = "curve_" + dlg.strCurveType;
editKey.SetWindowText(str);
idVec3 org = editEntity->origin;
str = "3 ( ";
str += org.ToString();
org.x += 64;
str += " ";
str += org.ToString();
org.y += 64;
str += " ";
str += org.ToString();
str += " )";
editVal.SetWindowText(str);
AddProp();
Entity_SetCurveData(editEntity);
}
}
}
void CEntityDlg::OnBnClickedButtonBrowse() {
DelProp();
}
void CEntityDlg::OnCbnDblclkComboClass() {
}
// --------------------------------------------------------------------------
// Entity creation
// --------------------------------------------------------------------------
void CEntityDlg::CreateEntity() {
entity_t* petNew;
bool forceFixed = false;
CXYWnd* pWnd = g_pParentWnd->ActiveXY();
if (pWnd) {
CRect rctZ;
pWnd->GetClientRect(rctZ);
if (selected_brushes.next == &selected_brushes) {
CreateEntityBrush(g_nSmartX, rctZ.Height() - 1 - g_nSmartY, pWnd);
forceFixed = true;
}
}
else {
if (selected_brushes.next == &selected_brushes) {
MessageBox("You must have a selected brush to create an entity", "info", 0);
return;
}
}
int index = comboClass.GetCurSel();
if (index == LB_ERR) {
MessageBox("You must have a selected class to create an entity", "info", 0);
return;
}
CString str;
comboClass.GetLBText(index, str);
if (!stricmp(str, "worldspawn")) {
MessageBox("Can't create an entity with worldspawn.", "info", 0);
return;
}
eclass_t* pecNew = Eclass_ForName(str, false);
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000)) {
extern void Brush_CopyList(brush_t * pFrom, brush_t * pTo);
brush_t temp_brushes;
temp_brushes.next = &temp_brushes;
Brush_CopyList(&selected_brushes, &temp_brushes);
Select_Deselect();
brush_t* pBrush = temp_brushes.next;
while (pBrush != NULL && pBrush != &temp_brushes) {
brush_t* pNext = pBrush->next;
Brush_RemoveFromList(pBrush);
Brush_AddToList(pBrush, &selected_brushes);
pBrush = pNext;
petNew = Entity_Create(pecNew, forceFixed);
Select_Deselect();
}
}
else if ((GetAsyncKeyState(VK_SHIFT) & 0x8000)) {
Select_Ungroup();
petNew = Entity_Create(pecNew, forceFixed);
}
else {
petNew = Entity_Create(pecNew, forceFixed);
}
if (petNew == NULL) {
MessageBox("Failed to create entity.", "info", 0);
return;
}
if (selected_brushes.next == &selected_brushes) {
editEntity = world_entity;
}
else {
editEntity = selected_brushes.next->owner;
}
SetKeyValPairs();
Select_Deselect();
Select_Brush(editEntity->brushes.onext);
Sys_UpdateWindows(W_ALL);
}
void CEntityDlg::OnBnClickedButtonCreate() {
CreateEntity();
}
void CEntityDlg::OnLbnDblclkListkeyval() {
CString Key, Value;
idStr work;
editKey.GetWindowText(Key);
editVal.GetWindowText(Value);
if (stricmp(Key, "script") == 0) {
Key = Value;
Value = "script/" + Key;
if (fileSystem->ReadFile(Value, NULL, NULL) == -1) {
sprintf(work, "// Script for %s\n// \n\nvoid main() {\n\n}\n\n", currentmap);
fileSystem->WriteFile(Value, work.c_str(), work.Length(), "fs_devpath");
}
work = fileSystem->RelativePathToOSPath(Value);
WinExec(va("notepad.exe %s", work.c_str()), SW_SHOW);
}
}
void CEntityDlg::OnLbnSelchangeListVars() {
}
void CEntityDlg::OnLbnDblclkListVars() {
if (editEntity == NULL) {
return;
}
int sel = listVars.GetCurSel();
if (sel == LB_ERR) {
return;
}
CPropertyItem* pi = (CPropertyItem*)listVars.GetItemDataPtr(sel);
if (pi) {
if (editEntity->epairs.FindKey(pi->m_propName) == NULL) {
editKey.SetWindowText(pi->m_propName);
editVal.SetWindowText("");
editVal.SetFocus();
}
}
}
void CEntityDlg::UpdateKeyVal(const char* key, const char* val) {
if (editEntity) {
editEntity->epairs.Set(key, val);
SetKeyValPairs();
g_pParentWnd->GetCamera()->BuildEntityRenderState(editEntity, true);
Entity_UpdateSoundEmitter(editEntity);
}
}
// --------------------------------------------------------------------------
// Animation
// --------------------------------------------------------------------------
void CEntityDlg::OnNMReleasedcaptureSlider1(NMHDR* pNMHDR, LRESULT* pResult) {
if (!editEntity) {
return;
}
UpdateFromAnimationFrame();
*pResult = 0;
}
void CEntityDlg::UpdateFromAnimationFrame(bool updateKeyValueDisplay) {
if (!editEntity) {
return;
}
int frame = slFrameSlider.GetPos();
editEntity->epairs.SetInt("frame", frame);
SetDlgItemText(IDC_ENTITY_CURRENT_ANIM, va("%i", frame));
if (updateKeyValueDisplay) {
SetKeyValPairs();
}
g_pParentWnd->GetCamera()->BuildEntityRenderState(editEntity, true);
Sys_UpdateWindows(W_ALL);
}
void CEntityDlg::OnCbnAnimationChange() {
if (!editEntity) {
return;
}
int sel = cbAnimations.GetCurSel();
CString animName;
currentAnimation = NULL;
int currFrame = 0;
if (sel != -1) {
cbAnimations.GetLBText(sel, animName);
if (animName.GetLength() > 0) {
currFrame = editEntity->epairs.GetInt("frame", "1");
editEntity->epairs.Set("anim", animName.GetBuffer(0));
SetKeyValPairs(false);
currentAnimation = gameEdit->ANIM_GetAnimFromEntityDef(editEntity->eclass->name, animName.GetBuffer(0));
currentAnimationFrame = 0;
if (currentAnimation) {
slFrameSlider.SetRange(1, gameEdit->ANIM_GetNumFrames(currentAnimation), TRUE);
slFrameSlider.SetPos(currFrame);
currentAnimationFrame = currFrame;
}
Sys_UpdateWindows(W_ALL);
}
}
}
void CEntityDlg::OnBnClickedStartAnimation() {
if (!editEntity) {
return;
}
SetTimer(0, 1000 / 24, NULL);
}
void CEntityDlg::OnBnClickedStopAnimation() {
KillTimer(0);
}
void CEntityDlg::OnTimer(UINT_PTR nIDEvent) {
if (!editEntity) {
OnBnClickedStopAnimation();
return;
}
if (currentAnimation) {
int numFrames = gameEdit->ANIM_GetNumFrames(currentAnimation);
if (numFrames <= 0) {
return;
}
currentAnimationFrame = ((currentAnimationFrame++) % numFrames);
editEntity->epairs.SetInt("frame", currentAnimationFrame);
slFrameSlider.SetPos(currentAnimationFrame);
UpdateFromAnimationFrame(false);
Sys_UpdateWindows(W_CAMERA | W_XY);
}
}
// --------------------------------------------------------------------------
// Curves
// --------------------------------------------------------------------------
void CEntityDlg::AddCurvePoints() {
if (editEntity == NULL || editEntity->curve == NULL) {
return;
}
int c = editEntity->curve->GetNumValues();
idVec3 start;
idVec3 end;
if (c > 1) {
start = editEntity->curve->GetValue(c - 2);
end = editEntity->curve->GetValue(c - 1);
idVec3 dir = end - start;
dir.Normalize();
start = end + 64 * dir;
}
else if (c > 0) {
start = editEntity->curve->GetValue(0);
start.x += 64;
start.y += 64;
}
else {
start = editEntity->origin;
}
editEntity->curve->AddValue(editEntity->curve->GetNumValues() * 100, start);
if (g_qeglobals.d_select_mode == sel_editpoint) {
g_qeglobals.d_select_mode = sel_brush;
EditCurvePoints();
}
Sys_UpdateWindows(W_CAMERA | W_XY);
}
void CEntityDlg::EditCurvePoints() {
if (editEntity == NULL || editEntity->curve == NULL) {
return;
}
if (g_qeglobals.d_select_mode == sel_editpoint) {
g_qeglobals.d_select_mode = sel_brush;
return;
}
g_qeglobals.d_select_mode = sel_editpoint;
g_qeglobals.d_numpoints = 0;
g_qeglobals.d_num_move_points = 0;
int c = editEntity->curve->GetNumValues();
for (int i = 0; i < c; i++) {
if (g_qeglobals.d_numpoints < MAX_POINTS - 1) {
g_qeglobals.d_points[g_qeglobals.d_numpoints++] = editEntity->curve->GetValue(i);
}
}
Sys_UpdateWindows(W_XY | W_CAMERA);
}
void CEntityDlg::InsertCurvePoint() {
if (editEntity == NULL || editEntity->curve == NULL) {
return;
}
if (g_qeglobals.d_select_mode != sel_editpoint) {
return;
}
if (g_qeglobals.d_num_move_points == 0) {
return;
}
for (int i = 0; i < editEntity->curve->GetNumValues(); i++) {
if (PointInMoveList(editEntity->curve->GetValueAddress(i)) >= 0) {
if (i == editEntity->curve->GetNumValues() - 1) {
AddCurvePoints();
}
else {
idCurve<idVec3>* newCurve = Entity_MakeCurve(editEntity);
if (newCurve == NULL) {
return;
}
for (int j = 0; j < editEntity->curve->GetNumValues(); j++) {
if (j == i) {
idVec3 start;
idVec3 end;
if (i > 0) {
start = editEntity->curve->GetValue(i - 1);
end = editEntity->curve->GetValue(i);
start += end;
start *= 0.5f;
}
else {
start = editEntity->curve->GetValue(0);
if (editEntity->curve->GetNumValues() > 1) {
end = start;
start = editEntity->curve->GetValue(1);
idVec3 dir = end - start;
dir.Normalize();
start = end + 64 * dir;
}
else {
end = start;
end.x += 64;
end.y += 64;
}
}
newCurve->AddValue(newCurve->GetNumValues() * 100, start);
}
newCurve->AddValue(newCurve->GetNumValues() * 100, editEntity->curve->GetValue(j));
}
delete editEntity->curve;
editEntity->curve = newCurve;
}
g_qeglobals.d_num_move_points = 0;
break;
}
}
UpdateEntityCurve();
Sys_UpdateWindows(W_XY | W_CAMERA);
}
void CEntityDlg::DeleteCurvePoint() {
if (editEntity == NULL || editEntity->curve == NULL) {
return;
}
if (g_qeglobals.d_select_mode != sel_editpoint) {
return;
}
if (g_qeglobals.d_num_move_points == 0) {
return;
}
for (int i = 0; i < editEntity->curve->GetNumValues(); i++) {
if (PointInMoveList(editEntity->curve->GetValueAddress(i)) >= 0) {
editEntity->curve->RemoveIndex(i);
g_qeglobals.d_num_move_points = 0;
break;
}
}
UpdateEntityCurve();
Sys_UpdateWindows(W_XY | W_CAMERA);
}
void CEntityDlg::UpdateEntityCurve() {
if (editEntity == NULL) {
return;
}
Entity_UpdateCurveData(editEntity);
if (g_qeglobals.d_select_mode == sel_editpoint) {
g_qeglobals.d_numpoints = 0;
int c = editEntity->curve->GetNumValues();
for (int i = 0; i < c; i++) {
if (g_qeglobals.d_numpoints < MAX_POINTS - 1) {
g_qeglobals.d_points[g_qeglobals.d_numpoints++] = editEntity->curve->GetValue(i);
}
}
}
Sys_UpdateWindows(W_ENTITY);
}
void CEntityDlg::SelectCurvePointByRay(const idVec3& org, const idVec3& dir, int buttons) {
if (editEntity == NULL) {
return;
}
int besti = -1;
float scale = g_pParentWnd->ActiveXY()->Scale();
float bestd = 8 / scale / 2;
for (int i = 0; i < g_qeglobals.d_numpoints; i++) {
idVec3 temp = g_qeglobals.d_points[i] - org;
float d = temp * dir;
temp = org + d * dir;
temp = g_qeglobals.d_points[i] - temp;
d = temp.Length();
if (d <= bestd) {
bestd = d;
besti = i;
}
}
if (besti == -1) {
return;
}
g_qeglobals.d_num_move_points = 0;
assert(besti < editEntity->curve->GetNumValues());
g_qeglobals.d_move_points[g_qeglobals.d_num_move_points++] = editEntity->curve->GetValueAddress(besti);
}