port(2V0-b): EterPythonLib 窗口树实现 + EterLib 资源层

- port: PythonWindow/PythonSlotWindow/PythonGridSlotWindow/PythonWindowManager(布局/命中/事件/插槽渲染)
- port: EterLib ReferenceObject/Resource/ResourceManager(资源引用与查找)
- platform: EterLib Util、EterPythonLib PythonGraphic
- tests: port.window_tree(需要解释器,不需要 40250 Client/pack)
- 顺带补上 4 个缺失的 Godot .uid

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-23 11:09:05 +09:00
co-authored by Claude Code
parent 369a80fc10
commit b4c9d6cefc
15 changed files with 6255 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
// 40250 EterLib/Util.cpp 的“默认代码页”那一小块。
//
// Util.cpp 本身是逻辑单元(LoadTextData / base64 / TokenTo*),但代码页这几个函数和它们旁边的
// GetFontFaceFromCodePage 是连在一起的,后者靠 GDI 的 EnumFontFamiliesEx 枚举字体,win32 shim 里没有,
// 真正的实现要等 2V0-f 的文字渲染。所以整份 Util.cpp 的移植留到那时,这里只放现在真正被调用到的
// GetDefaultCodePageEterPythonLib/PythonWindow.cpp 的 CTextLine::OnChangePosition 用它判断 CP_ARABIC。
//
// 默认值和 40250 一致:gs_codePage = 0,也就是“还没有 SetDefaultCodePage”。40250 在
// PythonApplication 里根据 locale 调 SetDefaultCodePage(),那条路径同样等 2V0-d。
#include "EterLib/StdAfx.h"
#include "EterLib/Util.h"
static DWORD gs_codePage = 0;
DWORD GetDefaultCodePage()
{
return gs_codePage;
}
@@ -0,0 +1,17 @@
// CPythonGraphic40250 EterPythonLib/PythonGraphic.cpp)的平台骨架 —— 目前只有窗口树链接时真正需要
// 的那一个函数。
//
// CPythonGraphic 是 UI 的 2D 绘制层(D3D8 的正交投影、按钮的立体边框、冷却圈、截图),整份实现属于
// 2V0-fplatform_stub.py 不认 EterPythonLibport_map 把它划成 python 层),所以这里按手写平台桩的
// 惯例来:先只放被引用到的符号,链接器会告诉我们下一个是谁。
//
// RenderCoolTimeBoxCSlotWindow::OnRender 画技能冷却扇形用的,没有渲染设备时什么都不做。
#include "EterPythonLib/StdAfx.h"
#include "EterPythonLib/PythonGraphic.h"
#include "../PlatformStub.h"
void CPythonGraphic::RenderCoolTimeBox(float fxCenter, float fyCenter, float fRadius, float fTime)
{
MT_PLATFORM_STUB();
}
+9
View File
@@ -171,5 +171,14 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME)
add_test(NAME port.python_launcher
COMMAND $<TARGET_FILE:port_python_launcher_test> ${MT_40250_CLIENT} ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.python_launcher PROPERTIES SKIP_RETURN_CODE 77)
# EterPythonLib 的窗口树(批次 2V0-b)。需要解释器(CWindowManager 的构造函数就调 Python),
# 但不需要 40250 的 Client/pack —— 布局/命中/事件都不读资源。
add_executable(port_window_tree_test ${CMAKE_CURRENT_SOURCE_DIR}/../../tests/port_window_tree_test.cpp)
target_link_libraries(port_window_tree_test PRIVATE port_platform)
add_dependencies(port_window_tree_test mtpython_stdlib)
add_test(NAME port.window_tree
COMMAND $<TARGET_FILE:port_window_tree_test> ${MT_PYTHON_STDLIB_ZIP})
set_tests_properties(port.window_tree PROPERTIES SKIP_RETURN_CODE 77)
endif()
endif()
@@ -0,0 +1,61 @@
#include "StdAfx.h"
#include "ReferenceObject.h"
CReferenceObject::CReferenceObject() : m_refCount(0), m_destructed(false)
{
}
CReferenceObject::~CReferenceObject()
{
}
void CReferenceObject::AddReference()
{
if (m_refCount == 0)
OnConstruct();
++m_refCount;
}
int CReferenceObject::GetReferenceCount()
{
return m_refCount;
}
void CReferenceObject::AddReferenceOnly()
{
++m_refCount;
}
void CReferenceObject::Release()
{
if (m_refCount > 1)
{
--m_refCount;
return;
}
assert(m_destructed == false);
assert(m_refCount >= 0);
m_refCount = 0;
OnSelfDestruct();
}
void CReferenceObject::OnConstruct()
{
m_destructed = false;
}
void CReferenceObject::OnSelfDestruct()
{
m_destructed = true;
delete this;
}
bool CReferenceObject::canDestroy()
{
if (m_refCount > 0)
return false;
return true;
}
+181
View File
@@ -0,0 +1,181 @@
#include "StdAfx.h"
#include "../EterPack/EterPackManager.h"
#include "../EterBase/CRC32.h"
#include "../EterBase/Timer.h"
#include "Resource.h"
#include "ResourceManager.h"
bool CResource::ms_bDeleteImmediately = false;
CResource::CResource(const char* c_szFileName) : me_state(STATE_EMPTY)
{
SetFileName(c_szFileName);
}
CResource::~CResource()
{
}
void CResource::SetDeleteImmediately(bool isSet)
{
ms_bDeleteImmediately = isSet;
}
void CResource::OnConstruct()
{
Load();
}
void CResource::OnSelfDestruct()
{
if (ms_bDeleteImmediately)
Clear();
else
CResourceManager::Instance().ReserveDeletingResource(this);
}
void CResource::Load()
{
if (me_state != STATE_EMPTY)
return;
const char * c_szFileName = GetFileName();
DWORD dwStart = ELTimer_GetMSec();
CMappedFile file;
LPCVOID fileData;
//Tracenf("Load %s", c_szFileName);
if (CEterPackManager::Instance().Get(file, c_szFileName, &fileData))
{
m_dwLoadCostMiliiSecond = ELTimer_GetMSec() - dwStart;
//Tracef("CResource::Load %s (%d bytes) in %d ms\n", c_szFileName, file.Size(), m_dwLoadCostMiliiSecond);
if (OnLoad(file.Size(), fileData))
{
me_state = STATE_EXIST;
}
else
{
Tracef("CResource::Load Error %s\n", c_szFileName);
me_state = STATE_ERROR;
return;
}
}
else
{
if (OnLoad(0, NULL))
me_state = STATE_EXIST;
else
{
Tracef("CResource::Load file not exist %s\n", c_szFileName);
me_state = STATE_ERROR;
}
}
}
void CResource::Reload()
{
Clear();
Tracef("CResource::Reload %s\n", GetFileName());
CMappedFile file;
LPCVOID fileData;
if (CEterPackManager::Instance().Get(file, GetFileName(), &fileData))
{
if (OnLoad(file.Size(), fileData))
{
me_state = STATE_EXIST;
}
else
{
me_state = STATE_ERROR;
return;
}
}
else
{
if (OnLoad(0, NULL))
me_state = STATE_EXIST;
else
{
me_state = STATE_ERROR;
}
}
}
CResource::TType CResource::StringToType(const char* c_szType)
{
return GetCRC32(c_szType, strlen(c_szType));
}
int CResource::ConvertPathName(const char * c_szPathName, char * pszRetPathName, int retLen)
{
const char * pc;
int len = 0;
for (pc = c_szPathName; *pc && len < retLen; ++pc, ++len)
{
if (*pc == '/')
*(pszRetPathName++) = '\\';
else
*(pszRetPathName++) = (char) korean_tolower(*pc);
}
*pszRetPathName = '\0';
return len;
}
void CResource::SetFileName(const char* c_szFileName)
{
// 2004. 2. 1. myevan. 쓰레드가 사용되는 상황에서 static 변수는 사용하지 않는것이 좋다.
// 2004. 2. 1. myevan. 파일 이름 처리를 std::string 사용
m_stFileName=c_szFileName;
}
void CResource::Clear()
{
OnClear();
me_state = STATE_EMPTY;
}
bool CResource::IsType(TType type)
{
return OnIsType(type);
}
CResource::TType CResource::Type()
{
static TType s_type = StringToType("CResource");
return s_type;
}
bool CResource::OnIsType(TType type)
{
if (CResource::Type() == type)
return true;
return false;
}
bool CResource::IsData() const
{
return me_state != STATE_EMPTY;
}
bool CResource::IsEmpty() const
{
return OnIsEmpty();
}
bool CResource::CreateDeviceObjects()
{
return true;
}
void CResource::DestroyDeviceObjects()
{
}
@@ -0,0 +1,525 @@
#include "StdAfx.h"
#include <io.h>
#include "../EterBase/CRC32.h"
#include "../EterBase/Timer.h"
#include "../EterBase/Stl.h"
#include "../EterPack/EterPackManager.h"
#include "ResourceManager.h"
#include "GrpImage.h"
int g_iLoadingDelayTime = 20;
const long c_Deleting_Wait_Time = 30000; // 삭제 대기 시간 (30초)
const long c_DeletingCountPerFrame = 30; // 프레임당 체크 리소스 갯수
const long c_Reference_Decrease_Wait_Time = 30000; // 선로딩 리소스의 해제 대기 시간 (30초)
CFileLoaderThread CResourceManager::ms_loadingThread;
void CResourceManager::LoadStaticCache(const char* c_szFileName)
{
CResource* pkRes=GetResourcePointer(c_szFileName);
if (!pkRes)
{
Lognf(1, "CResourceManager::LoadStaticCache %s - FAILED", c_szFileName);
return;
}
DWORD dwCacheKey=GetCRC32(c_szFileName, strlen(c_szFileName));
TResourcePointerMap::iterator f=m_pCacheMap.find(dwCacheKey);
if (m_pCacheMap.end()!=f)
return;
pkRes->AddReference();
m_pCacheMap.insert(TResourcePointerMap::value_type(dwCacheKey, pkRes));
}
void CResourceManager::ProcessBackgroundLoading()
{
TResourceRequestMap::iterator itor = m_RequestMap.begin();
while (itor != m_RequestMap.end())
{
DWORD dwFileCRC = itor->first;
std::string & stFileName = itor->second;
if (isResourcePointerData(dwFileCRC) ||
(m_WaitingMap.end() != m_WaitingMap.find(dwFileCRC)))
{
//printf("SKP %s\n", stFileName.c_str());
itor = m_RequestMap.erase(itor);
continue;
}
//printf("REQ %s\n", stFileName.c_str());
ms_loadingThread.Request(stFileName);
m_WaitingMap.insert(TResourceRequestMap::value_type(dwFileCRC, stFileName));
itor = m_RequestMap.erase(itor);
//break; // NOTE: 여기서 break 하면 천천히 로딩 된다.
}
DWORD dwCurrentTime = ELTimer_GetMSec();
CFileLoaderThread::TData * pData;
while (ms_loadingThread.Fetch(&pData))
{
//printf("LOD %s\n", pData->stFileName.c_str());
CResource * pResource = GetResourcePointer(pData->stFileName.c_str());
if (pResource)
{
if (pResource->IsEmpty())
{
pResource->OnLoad(pData->dwSize, pData->pvBuf);
pResource->AddReferenceOnly();
// 여기서 올라간 레퍼런스 카운트를 일정 시간이 지난 뒤에 풀어주기 위하여
m_pResRefDecreaseWaitingMap.insert(TResourceRefDecreaseWaitingMap::value_type(dwCurrentTime, pResource));
}
}
m_WaitingMap.erase(GetCRC32(pData->stFileName.c_str(), pData->stFileName.size()));
delete [] ((char *) pData->pvBuf);
delete pData;
}
// DO : 일정 시간이 지나고 난뒤 미리 로딩해 두었던 리소스의 레퍼런스 카운트를 감소 시킨다 - [levites]
long lCurrentTime = ELTimer_GetMSec();
TResourceRefDecreaseWaitingMap::iterator itorRef = m_pResRefDecreaseWaitingMap.begin();
while (itorRef != m_pResRefDecreaseWaitingMap.end())
{
const long & rCreatingTime = itorRef->first;
if (lCurrentTime - rCreatingTime > c_Reference_Decrease_Wait_Time)
{
CResource * pResource = itorRef->second;
// Decrease Reference Count
pResource->Release();
itorRef = m_pResRefDecreaseWaitingMap.erase(itorRef);
//Tracef("Decrease Pre Loading Resource\n", rCreatingTime);
}
else
++itorRef;
}
}
void CResourceManager::PushBackgroundLoadingSet(std::set<std::string> & LoadingSet)
{
std::set<std::string>::iterator itor = LoadingSet.begin();
while (itor != LoadingSet.end())
{
DWORD dwFileCRC = __GetFileCRC(itor->c_str());
if (NULL != isResourcePointerData(dwFileCRC))
{
++itor;
continue;
}
m_RequestMap.insert(TResourceRequestMap::value_type(dwFileCRC, itor->c_str()));
++itor;
}
}
void CResourceManager::__DestroyCacheMap()
{
TResourcePointerMap::iterator i;
for (i = m_pCacheMap.begin(); i != m_pCacheMap.end(); ++i)
{
CResource* pResource = i->second;
pResource->Release();
}
m_pCacheMap.clear();
}
void CResourceManager::__DestroyDeletingResourceMap()
{
Tracenf("CResourceManager::__DestroyDeletingResourceMap %d", m_ResourceDeletingMap.size());
for (TResourceDeletingMap::iterator i = m_ResourceDeletingMap.begin(); i != m_ResourceDeletingMap.end(); ++i)
(i->first)->Clear();
m_ResourceDeletingMap.clear();
}
void CResourceManager::__DestroyResourceMap()
{
Tracenf("CResourceManager::__DestroyResourceMap %d", m_pResMap.size());
TResourcePointerMap::iterator i;
for (i = m_pResMap.begin(); i != m_pResMap.end(); ++i)
{
CResource* pResource = i->second;
pResource->Clear();
}
stl_wipe_second(m_pResMap);
}
void CResourceManager::DestroyDeletingList()
{
CResource::SetDeleteImmediately(true);
__DestroyCacheMap();
__DestroyDeletingResourceMap();
}
void CResourceManager::Destroy()
{
assert(m_ResourceDeletingMap.empty() && "CResourceManager::Destroy - YOU MUST CALL DestroyDeletingList");
__DestroyResourceMap();
}
void CResourceManager::RegisterResourceNewFunctionPointer(const char* c_szFileExt, CResource* (*pNewFunc)(const char* c_szFileName))
{
m_pResNewFuncMap[c_szFileExt] = pNewFunc;
}
void CResourceManager::RegisterResourceNewFunctionByTypePointer(int iType, CResource* (*pNewFunc) (const char* c_szFileName))
{
assert(iType >= 0);
m_pResNewFuncByTypeMap[iType] = pNewFunc;
}
CResource * CResourceManager::InsertResourcePointer(DWORD dwFileCRC, CResource* pResource)
{
TResourcePointerMap::iterator itor = m_pResMap.find(dwFileCRC);
if (m_pResMap.end() != itor)
{
TraceError("CResource::InsertResourcePointer: %s is already registered\n", pResource->GetFileName());
assert(!"CResource::InsertResourcePointer: Resource already resistered");
delete pResource;
return itor->second;
}
m_pResMap.insert(TResourcePointerMap::value_type(dwFileCRC, pResource));
return pResource;
}
int __ConvertPathName(const char * c_szPathName, char * pszRetPathName, int retLen)
{
const char * pc;
int len = 0;
for (pc = c_szPathName; *pc && len < retLen; ++pc, ++len)
{
if (*pc == '/')
*(pszRetPathName++) = '\\';
else
*(pszRetPathName++) = (char) korean_tolower(*pc);
}
*pszRetPathName = '\0';
return len;
}
CResource * CResourceManager::GetTypeResourcePointer(const char * c_szFileName, int iType)
{
if (!c_szFileName || !*c_szFileName)
{
assert(c_szFileName != NULL && *c_szFileName != '\0');
return NULL;
}
const char * c_pszFile;
DWORD dwFileCRC = __GetFileCRC(c_szFileName, &c_pszFile);
CResource * pResource = FindResourcePointer(dwFileCRC);
if (pResource) // 이미 리소스가 있으면 리턴 한다.
return pResource;
CResource * (*newFunc) (const char *) = NULL;
if (iType != -1)
{
TResourceNewFunctionByTypePointerMap::iterator f = m_pResNewFuncByTypeMap.find(iType);
if (m_pResNewFuncByTypeMap.end() != f)
newFunc = f->second;
}
else
{
const char * pcFileExt = strrchr(c_pszFile, '.');
if (pcFileExt)
{
static char s_szFileExt[8 + 1];
strncpy(s_szFileExt, pcFileExt + 1, 8);
TResourceNewFunctionPointerMap::iterator f = m_pResNewFuncMap.find(s_szFileExt);
if (m_pResNewFuncMap.end() != f)
newFunc = f->second;
}
}
if (!newFunc)
{
TraceError("ResourceManager::GetResourcePointer: NOT SUPPORT FILE %s", c_pszFile);
return NULL;
}
pResource = InsertResourcePointer(dwFileCRC, newFunc(c_pszFile));
return pResource;
}
CResource * CResourceManager::GetResourcePointer(const char * c_szFileName)
{
if (!c_szFileName || !*c_szFileName)
{
TraceError("CResourceManager::GetResourcePointer: filename error!");
return NULL;
}
const char * c_pszFile;
DWORD dwFileCRC = __GetFileCRC(c_szFileName, &c_pszFile);
CResource * pResource = FindResourcePointer(dwFileCRC);
if (pResource) // 이미 리소스가 있으면 리턴 한다.
return pResource;
const char * pcFileExt = strrchr(c_pszFile, '.');
#ifdef _DEBUG
if (!IsFileExist(c_szFileName) )
{
if( pcFileExt == NULL || (stricmp( pcFileExt, ".fnt" ) != 0) ) {
TraceError("CResourceManager::GetResourcePointer: File not exist %s", c_szFileName);
}
}
#endif
CResource * (*newFunc) (const char *) = NULL;
if (pcFileExt)
{
static char s_szFileExt[8 + 1];
strncpy(s_szFileExt, pcFileExt + 1, 8);
TResourceNewFunctionPointerMap::iterator f = m_pResNewFuncMap.find(s_szFileExt);
if (m_pResNewFuncMap.end() != f)
newFunc = f->second;
}
if (!newFunc)
{
TraceError("ResourceManager::GetResourcePointer: NOT SUPPORT FILE %s", c_pszFile);
return NULL;
}
pResource = InsertResourcePointer(dwFileCRC, newFunc(c_pszFile));
return pResource;
}
CResource * CResourceManager::FindResourcePointer(DWORD dwFileCRC)
{
TResourcePointerMap::iterator itor = m_pResMap.find(dwFileCRC);
if (m_pResMap.end() == itor)
return NULL;
return itor->second;
}
bool CResourceManager::isResourcePointerData(DWORD dwFileCRC)
{
TResourcePointerMap::iterator itor = m_pResMap.find(dwFileCRC);
if (m_pResMap.end() == itor)
return NULL;
return (itor->second)->IsData();
}
DWORD CResourceManager::__GetFileCRC(const char * c_szFileName, const char ** c_ppszLowerFileName)
{
static char s_szFullPathFileName[MAX_PATH];
const char * src = c_szFileName;
char * dst = s_szFullPathFileName;
int len = 0;
while (src[len])
{
if (src[len]=='/')
dst[len] = '\\';
else
dst[len] = (char) korean_tolower(src[len]);
++len;
}
dst[len] = '\0';
if (c_ppszLowerFileName)
*c_ppszLowerFileName = &s_szFullPathFileName[0];
return (GetCRC32(s_szFullPathFileName, len));
}
typedef struct SDumpData
{
const char * filename;
float KB;
DWORD cost;
} TDumpData;
bool DumpKBCompare(const TDumpData& lhs, const TDumpData& rhs)
{
return (lhs.KB > rhs.KB) ? true : false;
}
bool DumpCostCompare(const TDumpData& lhs, const TDumpData& rhs)
{
return (lhs.cost > rhs.cost) ? true : false;
}
struct FDumpPrint
{
FILE * m_fp;
static float m_totalKB;
void operator () (TDumpData & data)
{
m_totalKB += data.KB;
fprintf(m_fp, "%6.1f %s\n", data.KB, data.filename);
}
};
float FDumpPrint::m_totalKB;
struct FDumpCostPrint
{
FILE * m_fp;
void operator() (TDumpData & data)
{
fprintf(m_fp, "%-4d %s\n", data.cost, data.filename);
}
};
void CResourceManager::DumpFileListToTextFile(const char* c_szFileName)
{
std::vector<TDumpData> dumpVector;
for (TResourcePointerMap::iterator i = m_pResMap.begin(); i != m_pResMap.end(); ++i)
{
CResource* pResource = i->second;
TDumpData data;
if (pResource->IsEmpty())
continue;
data.filename = pResource->GetFileName();
int filesize;
const char * ext = strrchr(data.filename, '.');
if (pResource->IsType(CGraphicImage::Type()) && strnicmp(ext, ".sub", 4))
filesize = ((CGraphicImage*) pResource)->GetWidth() * ((CGraphicImage*) pResource)->GetHeight() * 4;
else
{
FILE * fp2 = fopen(data.filename, "rb");
if (fp2)
{
fseek(fp2, 0L, SEEK_END);
filesize = ftell(fp2);
fclose(fp2);
}
else
filesize = 0;
}
data.KB = (float) filesize / (float) 1024;
data.cost = pResource->GetLoadCostMilliSecond();
dumpVector.push_back(data);
}
FILE * fp = fopen(c_szFileName, "w");
if (fp)
{
std::sort(dumpVector.begin(), dumpVector.end(), DumpKBCompare);
FDumpPrint DumpPrint;
DumpPrint.m_fp = fp;
DumpPrint.m_totalKB = 0;
std::for_each(dumpVector.begin(), dumpVector.end(), DumpPrint);
fprintf(fp, "total: %.2fmb", DumpPrint.m_totalKB / 1024.0f);
FDumpCostPrint DumpCostPrint;
DumpCostPrint.m_fp = fp;
std::sort(dumpVector.begin(), dumpVector.end(), DumpCostCompare);
std::for_each(dumpVector.begin(), dumpVector.end(), DumpCostPrint);
fprintf(fp, "total: %.2fmb", DumpPrint.m_totalKB / 1024.0f);
fclose(fp);
}
}
bool CResourceManager::IsFileExist(const char * c_szFileName)
{
return CEterPackManager::Instance().isExist(c_szFileName);
}
void CResourceManager::Update()
{
DWORD CurrentTime = ELTimer_GetMSec();
CResource * pResource;
int Count = 0;
TResourceDeletingMap::iterator itor = m_ResourceDeletingMap.begin();
while (itor != m_ResourceDeletingMap.end())
{
pResource = itor->first;
if (CurrentTime >= itor->second)
{
if (pResource->canDestroy())
{
//Tracef("Resource Clear %s\n", pResource->GetFileName());
pResource->Clear();
}
itor = m_ResourceDeletingMap.erase(itor);
if (++Count >= c_DeletingCountPerFrame)
break;
}
else
++itor;
}
ProcessBackgroundLoading();
}
void CResourceManager::ReserveDeletingResource(CResource * pResource)
{
DWORD dwCurrentTime = ELTimer_GetMSec();
m_ResourceDeletingMap.insert(TResourceDeletingMap::value_type(pResource, dwCurrentTime + c_Deleting_Wait_Time));
}
CResourceManager::CResourceManager()
{
//ms_loadingThread.Create(0);
}
CResourceManager::~CResourceManager()
{
Destroy();
//ms_loadingThread.Shutdown();
}
@@ -0,0 +1,400 @@
#include "StdAfx.h"
#include "../EterBase/CRC32.h"
#include "PythonGridSlotWindow.h"
using namespace UI;
void CGridSlotWindow::OnRenderPickingSlot()
{
if (!UI::CWindowManager::Instance().IsAttaching())
return;
BYTE byWidth, byHeight;
UI::CWindowManager::Instance().GetAttachingIconSize(&byWidth, &byHeight);
std::list<TSlot*> SlotList;
if (GetPickedSlotList(byWidth, byHeight, &SlotList))
{
DWORD dwSlotNumber = UI::CWindowManager::Instance().GetAttachingSlotNumber();
DWORD dwItemIndex = UI::CWindowManager::Instance().GetAttachingIndex();
// UseMode 이고..
if (m_isUseMode)
{
// Pick 된 아이템이 있으면..
TSlot * pSlot = *SlotList.begin();
TSlot * pCenterSlot;
if (GetSlotPointer(pSlot->dwCenterSlotNumber, &pCenterSlot))
if (pCenterSlot->isItem)
{
if (m_isUsableItem)
CPythonGraphic::Instance().SetDiffuseColor(1.0f, 1.0f, 0.0f, 0.5f);
else
CPythonGraphic::Instance().SetDiffuseColor(1.0f, 0.0f, 0.0f, 0.5f);
CPythonGraphic::Instance().RenderBar2d( m_rect.left + pCenterSlot->ixPosition,
m_rect.top + pCenterSlot->iyPosition,
m_rect.left + pCenterSlot->ixPosition + pCenterSlot->byxPlacedItemSize * ITEM_WIDTH,
m_rect.top + pCenterSlot->iyPosition + pCenterSlot->byyPlacedItemSize * ITEM_HEIGHT);
return;
}
}
// 아니면 그냥 옮기기
if (CheckMoving(dwSlotNumber, dwItemIndex, SlotList))
CPythonGraphic::Instance().SetDiffuseColor(1.0f, 1.0f, 1.0f, 0.5f);
else
CPythonGraphic::Instance().SetDiffuseColor(1.0f, 0.0f, 0.0f, 0.5f);
RECT Rect;
Rect.left = m_rect.right;
Rect.top = m_rect.bottom;
Rect.right = 0;
Rect.bottom = 0;
for (std::list<TSlot*>::iterator itor = SlotList.begin(); itor != SlotList.end(); ++itor)
{
TSlot * pSlot = *itor;
Rect.left = min(Rect.left, m_rect.left + pSlot->ixPosition);
Rect.top = min(Rect.top, m_rect.top + pSlot->iyPosition);
Rect.right = max(Rect.right, m_rect.left + pSlot->ixPosition + pSlot->byxPlacedItemSize*ITEM_WIDTH);
Rect.bottom = max(Rect.bottom, m_rect.top + pSlot->iyPosition + pSlot->byxPlacedItemSize*ITEM_HEIGHT);
}
CPythonGraphic::Instance().RenderBar2d(Rect.left, Rect.top, Rect.right, Rect.bottom);
}
}
BOOL CGridSlotWindow::GetPickedSlotPointer(TSlot ** ppSlot)
{
if (!UI::CWindowManager::Instance().IsAttaching())
return CSlotWindow::GetPickedSlotPointer(ppSlot);
BYTE byWidth, byHeight;
UI::CWindowManager::Instance().GetAttachingIconSize(&byWidth, &byHeight);
std::list<TSlot*> SlotList;
if (!GetPickedSlotList(byWidth, byHeight, &SlotList))
return FALSE;
TSlot * pMinSlot = NULL;
//DWORD dwSlotNumber = UI::CWindowManager::Instance().GetAttachingSlotNumber();
//DWORD dwAttachingItemIndex = UI::CWindowManager::Instance().GetAttachingIndex();
for (std::list<TSlot*>::iterator itor = SlotList.begin(); itor != SlotList.end(); ++itor)
{
TSlot * pSlot = *itor;
// NOTE : 한 슬롯 이상 사이즈의 아이템의 경우 가장 왼쪽 위의 슬롯 포인터를 리턴한다.
// 명시적이지 못한 코드.. 더 좋은 방법은 없는가? - [levites]
if (!pMinSlot)
{
pMinSlot = pSlot;
}
else
{
if (pSlot->dwSlotNumber < pMinSlot->dwSlotNumber)
{
pMinSlot = pSlot;
}
else
{
if (!pMinSlot->isItem && pSlot->isItem)
{
pMinSlot = pSlot;
}
}
}
}
if (!pMinSlot)
{
return FALSE;
}
else
{
TSlot * pCenterSlot;
if (!GetSlotPointer(pMinSlot->dwCenterSlotNumber, &pCenterSlot))
return FALSE;
*ppSlot = pCenterSlot;
// 현재 아이템을 들고 있는 중이고..
if (UI::CWindowManager::Instance().IsAttaching())
{
DWORD dwSlotNumber = UI::CWindowManager::Instance().GetAttachingSlotNumber();
if (dwSlotNumber == pCenterSlot->dwSlotNumber)
{
*ppSlot = pMinSlot;
}
}
}
return TRUE;
}
BOOL CGridSlotWindow::GetPickedSlotList(int iWidth, int iHeight, std::list<TSlot*> * pSlotPointerList)
{
long lx, ly;
GetMouseLocalPosition(lx, ly);
if (lx < 0)
return FALSE;
if (ly < 0)
return FALSE;
if (lx >= GetWidth())
return FALSE;
if (ly >= GetHeight())
return FALSE;
pSlotPointerList->clear();
int ix, iy;
if (GetPickedGridSlotPosition(lx, ly, &ix, &iy))
{
int ixHalfStep = (iWidth / 2);
//int iyHalfStep = (iHeight / 2);
int ixStart = int(ix) - int(ixHalfStep - (ixHalfStep % 2));
int ixEnd = int(ix) + int(ixHalfStep);
// FIXME : 제대로 된 계산 공식을 찾자 - [levites]
int iyStart = 0, iyEnd = 0;
if (1 == iHeight)
{
iyStart = iy;
iyEnd = iy;
}
else if (2 == iHeight)
{
iyStart = iy;
iyEnd = iy + 1;
}
else if (3 == iHeight)
{
iyStart = iy - 1;
iyEnd = iy + 1;
}
if (ixStart < 0)
{
ixEnd += -ixStart;
ixStart = 0;
}
if (iyStart < 0)
{
iyEnd += -iyStart;
iyStart = 0;
}
if (DWORD(ixEnd) >= m_dwxCount)
{
int ixTemporary = DWORD(ixEnd) - m_dwxCount + 1;
ixStart -= ixTemporary;
ixEnd -= ixTemporary;
}
if (DWORD(iyEnd) >= m_dwyCount)
{
int iyTemporary = DWORD(iyEnd) - m_dwyCount + 1;
iyStart -= iyTemporary;
iyEnd -= iyTemporary;
}
for (int i = ixStart; i <= ixEnd; ++i)
for (int j = iyStart; j <= iyEnd; ++j)
{
TSlot * pSlot;
if (GetGridSlotPointer(DWORD(i), DWORD(j), &pSlot))
{
pSlotPointerList->push_back(pSlot);
}
}
// Refine Scroll 등을 위한 예외 처리
if (m_isUseMode && 1 == pSlotPointerList->size())
{
TSlot * pMainSlot = *pSlotPointerList->begin();
std::vector<TSlot *>::iterator itor = m_SlotVector.begin();
for (; itor != m_SlotVector.end(); ++itor)
{
TSlot * pSlot = *itor;
if (pSlot->dwCenterSlotNumber == pMainSlot->dwCenterSlotNumber)
if (pSlotPointerList->end() == std::find(pSlotPointerList->begin(), pSlotPointerList->end(), pSlot))
{
pSlotPointerList->push_back(pSlot);
}
}
}
if (!pSlotPointerList->empty())
return TRUE;
}
return FALSE;
}
BOOL CGridSlotWindow::GetGridSlotPointer(int ix, int iy, TSlot ** ppSlot)
{
DWORD dwSlotIndex = ix + iy*m_dwxCount;
if (dwSlotIndex >= m_SlotVector.size())
return FALSE;
*ppSlot = m_SlotVector[dwSlotIndex];
return TRUE;
}
BOOL CGridSlotWindow::GetPickedGridSlotPosition(int ixLocal, int iyLocal, int * pix, int * piy)
{
for (DWORD x = 0; x < m_dwxCount; ++x)
for (DWORD y = 0; y < m_dwyCount; ++y)
{
TSlot * pSlot;
if (!GetGridSlotPointer(x, y, &pSlot))
continue;
if (ixLocal >= pSlot->ixPosition)
if (iyLocal >= pSlot->iyPosition)
if (ixLocal <= pSlot->ixPosition + pSlot->ixCellSize)
if (iyLocal <= pSlot->iyPosition + pSlot->iyCellSize)
{
*pix = x;
*piy = y;
return TRUE;
}
}
return FALSE;
}
void CGridSlotWindow::ArrangeGridSlot(DWORD dwStartIndex, DWORD dwxCount, DWORD dwyCount, int ixSlotSize, int iySlotSize, int ixTemporarySize, int iyTemporarySize)
{
Destroy();
m_dwxCount = dwxCount;
m_dwyCount = dwyCount;
m_SlotVector.clear();
m_SlotVector.resize(dwxCount*dwyCount);
for (DWORD x = 0; x < dwxCount; ++x)
for (DWORD y = 0; y < dwyCount; ++y)
{
DWORD dwIndex = dwStartIndex + x + y * dwxCount;
int ixPosition = x * (ixSlotSize + ixTemporarySize);
int iyPosition = y * (iySlotSize + iyTemporarySize);
AppendSlot(dwIndex, ixPosition, iyPosition, ixSlotSize, iySlotSize);
m_SlotVector[x+y*dwxCount] = &(*m_SlotList.rbegin());
}
int iWidth = dwxCount * (ixSlotSize + ixTemporarySize);
int iHeight = dwyCount * (iySlotSize + iyTemporarySize);
SetSize(iWidth, iHeight);
}
void CGridSlotWindow::OnRefreshSlot()
{
DWORD x, y;
for (x = 0; x < m_dwxCount; ++x)
for (y = 0; y < m_dwyCount; ++y)
{
TSlot * pSlot;
if (!GetGridSlotPointer(x, y, &pSlot))
continue;
pSlot->dwCenterSlotNumber = pSlot->dwSlotNumber;
}
for (x = 0; x < m_dwxCount; ++x)
for (y = 0; y < m_dwyCount; ++y)
{
TSlot * pSlot;
if (!GetGridSlotPointer(x, y, &pSlot))
continue;
if (pSlot->isItem)
{
for (DWORD xSub = 0; xSub < pSlot->byxPlacedItemSize; ++xSub)
for (DWORD ySub = 0; ySub < pSlot->byyPlacedItemSize; ++ySub)
{
TSlot * pSubSlot;
if (!GetGridSlotPointer(x+xSub, y+ySub, &pSubSlot))
continue;
pSubSlot->dwCenterSlotNumber = pSlot->dwSlotNumber;
pSubSlot->dwItemIndex = pSlot->dwItemIndex;
}
}
else
{
pSlot->dwItemIndex = 0;
}
}
}
BOOL CGridSlotWindow::CheckMoving(DWORD dwSlotNumber, DWORD dwItemIndex, const std::list<TSlot*> & c_rSlotList)
{
if (m_dwSlotStyle != SLOT_STYLE_PICK_UP)
return TRUE;
for (std::list<TSlot*>::const_iterator itor = c_rSlotList.begin(); itor != c_rSlotList.end(); ++itor)
{
TSlot * pSlot = *itor;
if (dwSlotNumber != pSlot->dwCenterSlotNumber) // 들었던 자리가 아닐 경우에
{
if (0 != pSlot->dwItemIndex || pSlot->dwCenterSlotNumber != pSlot->dwSlotNumber) // 아이템이 있고
{
if (dwItemIndex != pSlot->dwItemIndex) // 다른 아이템이면 못 옮김
return false;
}
}
}
return true;
}
void CGridSlotWindow::Destroy()
{
CSlotWindow::Destroy();
m_SlotVector.clear();
__Initialize();
}
void CGridSlotWindow::__Initialize()
{
m_dwxCount = 0;
m_dwyCount = 0;
}
DWORD CGridSlotWindow::Type()
{
static int s_Type = GetCRC32("CGridSlotWindow", strlen("CGridSlotWindow"));
return s_Type;
}
BOOL CGridSlotWindow::OnIsType(DWORD dwType)
{
if (CGridSlotWindow::Type() == dwType)
return TRUE;
return CSlotWindow::OnIsType(dwType);
}
CGridSlotWindow::CGridSlotWindow(PyObject * ppyObject) : CSlotWindow(ppyObject)
{
}
CGridSlotWindow::~CGridSlotWindow()
{
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+319
View File
@@ -0,0 +1,319 @@
// port/EterPythonLib 的窗口树(批次 2V0-b):UI::CWindow / UI::CWindowManager 在真解释器上跑一遍。
//
// 这一层是 40250 整个 UI 的骨架:Python 侧每个窗口对象背后都是一个 CWindow,布局(UpdateRect 的
// 对齐规则)、命中(PickWindow 的 flag 语义)、拖动、焦点、按键捕获全在 C++ 里,Python 只收事件。
// wndMgr 模块(2V0-c)只是把这些函数包一层,所以这里直接按 C++ API 驱动,验的是同一份逻辑。
//
// 渲染不碰:Render()/OnRender() 需要 CPythonGraphic 和图形设备,那是 2V0-f 的事。解释器是必须的 ——
// CWindowManager 的构造函数就 Py_BuildValue("()"),事件也确实要发到 Python 处理器上。
//
// port_window_tree_test <python27.zip>
//
// 缺 python27.zip 时退 77ctest SKIP),MT_ASSETS_STRICT=1 时改成失败。
#include "EterPythonLib/StdAfx.h"
#include "EterPythonLib/PythonWindow.h"
#include "EterPythonLib/PythonWindowManager.h"
#include "ScriptLib/PythonLauncher.h"
#include "../src/platform/ScriptLib/PythonHost.h"
#include <cstdio>
#include <cstdlib>
#include <string>
static int g_failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::fprintf(stderr, "%s:%d: CHECK(%s)\n", __FILE__, __LINE__, #cond); \
++g_failures; \
} \
} while (0)
// Python 侧的处理器:每个事件往 LOG 里记一条,C++ 这边取出来比对。这正是 40250 的 ui.py 做的事情
// Window 类的 OnMouseOverIn / OnKeyDown ... 都是普通的 Python 方法)。
static const char* const kHandlerScript =
"LOG = []\n"
"class Handler:\n"
" def __init__(self, name, eat = 0):\n"
" self.name = name\n"
" self.eat = eat\n"
" def OnMouseOverIn(self): LOG.append(self.name + ':in')\n"
" def OnMouseOverOut(self): LOG.append(self.name + ':out')\n"
" def OnMouseLeftButtonDown(self): LOG.append(self.name + ':down')\n"
" def OnMouseLeftButtonUp(self): LOG.append(self.name + ':up')\n"
" def OnSetFocus(self): LOG.append(self.name + ':focus')\n"
" def OnKillFocus(self): LOG.append(self.name + ':blur')\n"
" def OnUpdate(self): LOG.append(self.name + ':update')\n"
" def OnKeyDown(self, key):\n"
" LOG.append('%s:kd%d' % (self.name, key))\n"
" return self.eat\n"
" def OnKeyUp(self, key):\n"
" LOG.append('%s:ku%d' % (self.name, key))\n"
" return 1\n"
"A = Handler('A')\n"
"B = Handler('B')\n"
"C = Handler('C', 1)\n";
static PyObject* main_dict()
{
return PyModule_GetDict(PyImport_AddModule((char*) "__main__"));
}
static PyObject* handler(const char* name)
{
PyObject* object = PyDict_GetItemString(main_dict(), name); // Borrowed; __main__ 一直持有
if (!object)
std::fprintf(stderr, "handler('%s') missing\n", name);
return object;
}
// LOG 的内容,取完清空 —— 每个检查点只看自己那一段。
static std::string take_log()
{
PyObject* log = PyDict_GetItemString(main_dict(), "LOG");
if (!log || !PyList_Check(log))
return "<no LOG>";
std::string text;
for (Py_ssize_t i = 0; i < PyList_Size(log); ++i)
{
PyObject* item = PyList_GetItem(log, i);
if (!text.empty())
text += ' ';
text += PyString_Check(item) ? PyString_AS_STRING(item) : "?";
}
PyList_SetSlice(log, 0, PyList_Size(log), NULL);
return text;
}
static bool rect_is(UI::CWindow* window, long left, long top, long right, long bottom)
{
const RECT& rect = window->GetRect();
if (rect.left == left && rect.top == top && rect.right == right && rect.bottom == bottom)
return true;
std::fprintf(stderr, "rect(%s) = %ld,%ld,%ld,%ld, expected %ld,%ld,%ld,%ld\n", window->GetName(),
(long) rect.left, (long) rect.top, (long) rect.right, (long) rect.bottom,
left, top, right, bottom);
return false;
}
static void run(UI::CWindowManager& wm)
{
// 40250 是先 SetScreenSize 再 SetResolutionUserInterface 的 OnSizeChange)。分辨率为 0 时
// SetMousePosition 直接 return,鼠标坐标永远是 0 —— 这条顺序是有意义的。
wm.SetScreenSize(800, 600);
wm.SetResolution(800, 600);
CHECK(wm.GetScreenWidth() == 800 && wm.GetScreenHeight() == 600);
long hres = 0, vres = 0;
wm.GetResolution(hres, vres);
CHECK(hres == 800 && vres == 600);
CHECK(wm.GetAspect() == 800.0f / 600.0f);
// ---- 层与树 ----
UI::CWindow* a = wm.RegisterWindow(handler("A"), "UI");
a->SetName("A");
CHECK(std::string(a->GetParent()->GetName()) == "UI");
// 五个层(GAME/UI_BOTTOM/UI/TOP_MOST/CURTAIN)是 CLayerIsWindow() 为 FALSE,所以注册到层上的
// 窗口自己就是 root —— Python 侧的 GetRoot() 从来拿不到层。
CHECK(a->GetRoot() == a);
CHECK(!a->IsShow());
a->SetSize(100, 50);
a->SetPosition(10, 20);
a->Show();
CHECK(rect_is(a, 10, 20, 110, 70));
UI::CWindow* b = wm.RegisterWindow(handler("B"), "UI");
b->SetName("B");
wm.SetParent(b, a);
CHECK(b->GetParent() == a && a->GetChildCount() == 1);
CHECK(b->GetRoot() == a);
b->SetSize(20, 10);
b->SetPosition(5, 5);
b->Show();
CHECK(rect_is(b, 15, 25, 35, 35));
// ---- UpdateRect 的对齐规则(父窗口 100x50 @ 10,20,子窗口 20x10 @ 5,5----
b->SetHorizontalAlign(UI::CWindow::HORIZONTAL_ALIGN_RIGHT); // left = |100| - 5 + 10
CHECK(rect_is(b, 105, 25, 125, 35));
b->SetHorizontalAlign(UI::CWindow::HORIZONTAL_ALIGN_CENTER); // left = 100/2 - 20/2 + 5 + 10
CHECK(rect_is(b, 55, 25, 75, 35));
b->SetVerticalAlign(UI::CWindow::VERTICAL_ALIGN_BOTTOM); // top = 50 - 5 + 20
CHECK(rect_is(b, 55, 65, 75, 75));
b->SetVerticalAlign(UI::CWindow::VERTICAL_ALIGN_CENTER); // top = (50 - 10)/2 + 5 + 20
CHECK(rect_is(b, 55, 45, 75, 55));
b->SetHorizontalAlign(UI::CWindow::HORIZONTAL_ALIGN_LEFT);
b->SetVerticalAlign(UI::CWindow::VERTICAL_ALIGN_TOP);
CHECK(rect_is(b, 15, 25, 35, 35));
// 父窗口移动,子窗口跟着走(UpdateRect 会 for_each 到所有子窗口)。
a->SetPosition(20, 30);
CHECK(rect_is(a, 20, 30, 120, 80) && rect_is(b, 25, 35, 45, 45));
a->SetPosition(10, 20);
// IsRendering:自己 Show 不够,链路上每一级都要 Show。
CHECK(a->IsRendering() && b->IsRendering());
a->Hide();
CHECK(b->IsShow() && !b->IsRendering());
a->Show();
// ---- 命中 ----
UI::CWindow* c = wm.RegisterWindow(handler("C"), "UI");
c->SetName("C");
c->SetSize(100, 50);
c->SetPosition(10, 20);
c->Show();
take_log();
// 同层后注册的在上面(PickWindow 反向遍历子链表)。
wm.RunMouseMove(50, 40);
CHECK(wm.GetPointWindow() == c);
CHECK(take_log() == "C:in");
// 鼠标坐标按 屏幕尺寸/分辨率 换算,这里 1:1。
long mx = 0, my = 0;
wm.GetMousePosition(mx, my);
CHECK(mx == 50 && my == 40);
// C 藏起来,命中落到 A 的子树上。
c->Hide();
wm.RunMouseMove(20, 28);
CHECK(wm.GetPointWindow() == b);
CHECK(take_log() == "C:out B:in");
// FLAG_NOT_PICK:这个窗口自己不被选中,但仍然往下找子窗口 —— 结果落回父窗口。
b->AddFlag(UI::CWindow::FLAG_NOT_PICK);
wm.RunMouseMove(20, 28);
CHECK(wm.GetPointWindow() == a);
CHECK(take_log() == "B:out A:in");
b->RemoveFlag(UI::CWindow::FLAG_NOT_PICK);
// FLAG_IGNORE_SIZE:不做 IsIn 判断,只要父窗口被命中就轮到它。
b->AddFlag(UI::CWindow::FLAG_IGNORE_SIZE);
wm.RunMouseMove(100, 65); // 在 A 里,不在 B 的矩形里
CHECK(wm.GetPointWindow() == b);
b->RemoveFlag(UI::CWindow::FLAG_IGNORE_SIZE);
wm.RunMouseMove(100, 65);
CHECK(wm.GetPointWindow() == a);
take_log();
// 层外面不命中任何窗口。
wm.RunMouseMove(700, 500);
CHECK(wm.GetPointWindow() == NULL);
take_log();
// SetPickAlways:不管挡在前面的是谁,只要在矩形里就先拿到(工具提示、拖动图标用的)。
c->Show();
wm.SetPickAlways(b);
wm.RunMouseMove(20, 28);
CHECK(wm.GetPointWindow() == b);
take_log();
// ---- SetTop:只对 FLAG_FLOAT 的窗口生效 ----
CHECK(!a->IsFlag(UI::CWindow::FLAG_FLOAT));
wm.SetTop(a); // 没有 FLAG_FLOAT,什么也不做
a->AddFlag(UI::CWindow::FLAG_FLOAT);
wm.SetTop(a); // A 挪到层的链表末尾,压过 C
wm.RunMouseMove(90, 65); // A 和 C 都覆盖这里
CHECK(wm.GetPointWindow() == a);
take_log();
// ---- 拖动:FLAG_MOVABLE 时窗口管理器直接改坐标 ----
a->AddFlag(UI::CWindow::FLAG_MOVABLE);
wm.RunMouseLeftButtonDown(90, 65);
CHECK(take_log() == "A:down");
wm.RunMouseMove(100, 75); // 位移 +10,+10
CHECK(a->GetPositionX() == 20 && a->GetPositionY() == 30);
// FLAG_LIMIT:窗口不许离开屏幕,LimitBias 是四边的留白。
a->AddFlag(UI::CWindow::FLAG_LIMIT);
a->SetLimitBias(0, 0, 0, 0);
wm.RunMouseMove(2000, 2000);
CHECK(a->GetPositionX() == 800 - a->GetWidth() && a->GetPositionY() == 600 - a->GetHeight());
wm.RunMouseMove(-2000, -2000);
CHECK(a->GetPositionX() == 0 && a->GetPositionY() == 0);
wm.RunMouseLeftButtonUp(-2000, -2000);
a->RemoveFlag(UI::CWindow::FLAG_LIMIT);
a->RemoveFlag(UI::CWindow::FLAG_MOVABLE);
a->SetPosition(10, 20);
take_log();
// ---- 焦点 ----
CHECK(!wm.IsFocus());
wm.ActivateWindow(a);
CHECK(wm.IsFocus() && wm.IsFocusWindow(a) && wm.GetActivateWindow() == a);
CHECK(take_log() == "A:focus");
wm.ActivateWindow(c);
CHECK(wm.IsFocusWindow(c) && !wm.IsFocusWindow(a));
CHECK(take_log() == "A:blur C:focus");
wm.DeactivateWindow();
CHECK(!wm.IsFocus());
take_log();
// ---- 按键:RunKeyDownEvent 从 root 往下走,第一个 OnKeyDown 返回真的窗口吃掉它并被记成捕获者 ----
a->Hide(); // A 的处理器返回 0,藏起来让这段日志只剩 C
wm.RunKeyDown(65);
CHECK(take_log() == "C:kd65");
// KeyUp 只发给捕获者,不再重新查找。
wm.RunKeyUp(65);
CHECK(take_log() == "C:ku65");
// 没人捕获的键:RunKeyUpEvent 从 root 往下发一轮(C 的 OnKeyUp 返回 1,到此为止)。
wm.RunKeyUp(66);
CHECK(take_log() == "C:ku66");
a->Show();
// ---- Update 沿着树走,Hide 的分支整支跳过 ----
take_log();
wm.Update();
CHECK(take_log() == "A:update B:update C:update");
b->Hide();
wm.Update();
CHECK(take_log() == "A:update C:update");
b->Show();
// ---- 销毁:先从管理器的各个指针里摘干净,再从父窗口摘掉,真正的 delete 留到下一次 Update ----
wm.RunMouseMove(90, 65);
CHECK(wm.GetPointWindow() == a);
wm.ActivateWindow(a);
wm.RunKeyDown(65); // A 的处理器返回 0,捕获者还是 C
take_log();
wm.DestroyWindow(c);
CHECK(wm.GetActivateWindow() != c);
wm.DestroyWindow(a); // B 是 A 的子窗口,Clear() 只断链不删
CHECK(wm.GetPointWindow() == NULL);
CHECK(!b->HasParent() && !b->IsShow());
wm.Update(); // 这里才 deletea/c 从此不可用
CHECK(take_log() == "");
delete b; // 40250 也不管子窗口,Python 那边各自 Destroy
}
int main(int argc, char** argv)
{
const char* strict = std::getenv("MT_ASSETS_STRICT");
const bool is_strict = strict && std::string(strict) == "1";
const std::string stdlib = argc > 1 ? argv[1] : PythonHost::DefaultStdLibPath();
if (stdlib.empty() || std::fopen(stdlib.c_str(), "rb") == NULL)
{
std::fprintf(stderr, "port_window_tree_test: no python27.zip at '%s'\n", stdlib.c_str());
return is_strict ? 1 : 77;
}
PythonHost::Configure();
{
CPythonLauncher launcher; // 和 40250 Main() 一样在栈上,构造即 Py_Initialize
CHECK(launcher.Create());
std::string error;
CHECK(PythonHost::InstallStdLib(stdlib.c_str(), &error));
if (!error.empty())
std::fprintf(stderr, "InstallStdLib: %s\n", error.c_str());
CHECK(launcher.RunLine(kHandlerScript));
{
UI::CWindowManager wm; // 构造函数建 root + 五个层,析构函数 Py_DECREF 空元组
run(wm);
}
CHECK(PyErr_Occurred() == NULL);
launcher.Clear();
}
CHECK(!Py_IsInitialized());
std::printf("%s: %d failure(s)\n", argv[0], g_failures);
return g_failures == 0 ? 0 : 1;
}
+1
View File
@@ -0,0 +1 @@
uid://bdcaheq2nmoqg
@@ -0,0 +1 @@
uid://8alojuolcg5f
+1
View File
@@ -0,0 +1 @@
uid://b3uq3sf8dy087
+1
View File
@@ -0,0 +1 @@
uid://st31pbybiodp