mirror of
https://github.com/smartcmd/MinecraftConsoles.git
synced 2026-08-20 09:57:09 +00:00
1869 lines
59 KiB
C++
1869 lines
59 KiB
C++
#include "stdafx.h"
|
|
#include "NativeUIRenderer.h"
|
|
#include "UI.h"
|
|
#include <cmath>
|
|
#include <d3d11.h>
|
|
#include <d3dcompiler.h>
|
|
#include <wincodec.h>
|
|
#pragma comment(lib, "windowscodecs.lib")
|
|
|
|
#ifdef _WINDOWS64
|
|
#include <shellapi.h>
|
|
#include "../../Windows64/KeyboardMouseInput.h"
|
|
#pragma comment(lib, "shell32.lib")
|
|
extern KeyboardMouseInput g_KBMInput;
|
|
extern HWND g_hWnd;
|
|
#endif
|
|
|
|
#pragma comment(lib, "d3dcompiler.lib")
|
|
|
|
extern ID3D11Device* g_pd3dDevice;
|
|
extern ID3D11DeviceContext* g_pImmediateContext;
|
|
extern ID3D11RenderTargetView* g_pRenderTargetView;
|
|
extern ID3D11DepthStencilView* g_pDepthStencilView;
|
|
|
|
#include <unordered_map>
|
|
|
|
#include "../../Font.h"
|
|
#include "../../Textures.h"
|
|
#include "../../ResourceLocation.h"
|
|
|
|
namespace
|
|
{
|
|
static constexpr float kVW = 1280.0f;
|
|
static constexpr float kVH = 720.0f;
|
|
static constexpr float kFontUnitH = 8.0f;
|
|
static constexpr int kMaxVerts = 8192;
|
|
static constexpr int kMaxClipStack = 16;
|
|
|
|
inline Font* GetFont()
|
|
{
|
|
return Minecraft::GetInstance()->font;
|
|
}
|
|
|
|
inline float ScaleForSize(float size)
|
|
{
|
|
return size / kFontUnitH;
|
|
}
|
|
|
|
struct UIVertex
|
|
{
|
|
float x, y;
|
|
float r, g, b, a;
|
|
float u, v;
|
|
};
|
|
|
|
enum BatchMode { BATCH_COLOR, BATCH_TEXT, BATCH_TEXTURE };
|
|
|
|
static ID3D11VertexShader* s_pVS = nullptr;
|
|
static ID3D11PixelShader* s_pPS_Color = nullptr;
|
|
static ID3D11PixelShader* s_pPS_Tex = nullptr;
|
|
static ID3D11PixelShader* s_pPS_TexFull = nullptr;
|
|
static ID3D11InputLayout* s_pInputLayout = nullptr;
|
|
static ID3D11Buffer* s_pVB = nullptr;
|
|
static ID3D11RasterizerState* s_pRastState = nullptr;
|
|
static ID3D11RasterizerState* s_pRastScissor = nullptr;
|
|
static ID3D11DepthStencilState* s_pDepthState = nullptr;
|
|
static ID3D11BlendState* s_pBlendState = nullptr;
|
|
static ID3D11SamplerState* s_pSampler = nullptr;
|
|
static ID3D11SamplerState* s_pSamplerLinear = nullptr;
|
|
static bool s_initialized = false;
|
|
|
|
static UIVertex s_vertices[kMaxVerts];
|
|
static int s_vertCount = 0;
|
|
static bool s_inFrame = false;
|
|
static BatchMode s_batchMode = BATCH_COLOR;
|
|
static int s_batchTexId = -1;
|
|
|
|
struct FileTexEntry { int id; int w; int h; };
|
|
static std::unordered_map<std::string, FileTexEntry> s_fileTexCache;
|
|
|
|
struct ClipRect { float x, y, w, h; };
|
|
static ClipRect s_clipStack[kMaxClipStack];
|
|
static int s_clipDepth = 0;
|
|
|
|
static float s_bbWidth = kVW;
|
|
static float s_bbHeight = kVH;
|
|
|
|
static float s_vpX = 0.0f;
|
|
static float s_vpY = 0.0f;
|
|
static float s_vpW = kVW;
|
|
static float s_vpH = kVH;
|
|
|
|
static ID3D11RenderTargetView* s_savedRTV = nullptr;
|
|
static ID3D11DepthStencilView* s_savedDSV = nullptr;
|
|
static D3D11_VIEWPORT s_savedViewport = {};
|
|
static ID3D11RasterizerState* s_savedRast = nullptr;
|
|
static ID3D11DepthStencilState* s_savedDepth = nullptr;
|
|
static UINT s_savedStencilRef = 0;
|
|
static ID3D11BlendState* s_savedBlend = nullptr;
|
|
static float s_savedBlendFactor[4] = {};
|
|
static UINT s_savedSampleMask = 0;
|
|
static ID3D11VertexShader* s_savedVS = nullptr;
|
|
static ID3D11PixelShader* s_savedPS = nullptr;
|
|
static ID3D11InputLayout* s_savedIL = nullptr;
|
|
static D3D11_PRIMITIVE_TOPOLOGY s_savedTopo = D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED;
|
|
|
|
static const char* s_vsCode =
|
|
"struct VS_IN { float2 pos : POSITION; float4 col : COLOR; float2 uv : TEXCOORD0; };\n"
|
|
"struct VS_OUT { float4 pos : SV_Position; float4 col : COLOR; float2 uv : TEXCOORD0; };\n"
|
|
"VS_OUT main(VS_IN i)\n"
|
|
"{\n"
|
|
" VS_OUT o;\n"
|
|
" o.pos.x = i.pos.x / 640.0 - 1.0;\n"
|
|
" o.pos.y = 1.0 - i.pos.y / 360.0;\n"
|
|
" o.pos.z = 0.0;\n"
|
|
" o.pos.w = 1.0;\n"
|
|
" o.col = i.col;\n"
|
|
" o.uv = i.uv;\n"
|
|
" return o;\n"
|
|
"}\n";
|
|
|
|
static const char* s_psColorCode =
|
|
"struct PS_IN { float4 pos : SV_Position; float4 col : COLOR; float2 uv : TEXCOORD0; };\n"
|
|
"float4 main(PS_IN i) : SV_Target { return i.col; }\n";
|
|
|
|
static const char* s_psTexCode =
|
|
"Texture2D tex : register(t0);\n"
|
|
"SamplerState samp : register(s0);\n"
|
|
"struct PS_IN { float4 pos : SV_Position; float4 col : COLOR; float2 uv : TEXCOORD0; };\n"
|
|
"float4 main(PS_IN i) : SV_Target\n"
|
|
"{\n"
|
|
" float4 t = tex.Sample(samp, i.uv);\n"
|
|
" return float4(i.col.rgb, i.col.a * t.a);\n"
|
|
"}\n";
|
|
|
|
static const char* s_psTexFullCode =
|
|
"Texture2D tex : register(t0);\n"
|
|
"SamplerState samp : register(s0);\n"
|
|
"struct PS_IN { float4 pos : SV_Position; float4 col : COLOR; float2 uv : TEXCOORD0; };\n"
|
|
"float4 main(PS_IN i) : SV_Target\n"
|
|
"{\n"
|
|
" float4 t = tex.Sample(samp, i.uv);\n"
|
|
" return t * i.col;\n"
|
|
"}\n";
|
|
|
|
static void DecodeColor(uint32_t color, float& r, float& g, float& b, float& a)
|
|
{
|
|
a = ((color >> 24) & 0xFF) / 255.0f;
|
|
r = ((color >> 16) & 0xFF) / 255.0f;
|
|
g = ((color >> 8) & 0xFF) / 255.0f;
|
|
b = ((color ) & 0xFF) / 255.0f;
|
|
}
|
|
|
|
static ID3D11PixelShader* CompilePS(const char* code, const char* name)
|
|
{
|
|
ID3DBlob* blob = nullptr;
|
|
ID3DBlob* err = nullptr;
|
|
HRESULT hr = D3DCompile(code, strlen(code), name, nullptr, nullptr,
|
|
"main", "ps_4_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, &blob, &err);
|
|
if (FAILED(hr)) { if (err) err->Release(); return nullptr; }
|
|
if (err) err->Release();
|
|
|
|
ID3D11PixelShader* ps = nullptr;
|
|
hr = g_pd3dDevice->CreatePixelShader(blob->GetBufferPointer(),
|
|
blob->GetBufferSize(), nullptr, &ps);
|
|
blob->Release();
|
|
return SUCCEEDED(hr) ? ps : nullptr;
|
|
}
|
|
|
|
static void InitD3D()
|
|
{
|
|
if (s_initialized || !g_pd3dDevice) return;
|
|
|
|
auto SafeRelease = [](auto*& p) { if (p) { p->Release(); p = nullptr; } };
|
|
SafeRelease(s_pVS);
|
|
SafeRelease(s_pInputLayout);
|
|
SafeRelease(s_pPS_Color);
|
|
SafeRelease(s_pPS_Tex);
|
|
SafeRelease(s_pPS_TexFull);
|
|
SafeRelease(s_pVB);
|
|
SafeRelease(s_pRastState);
|
|
SafeRelease(s_pRastScissor);
|
|
SafeRelease(s_pDepthState);
|
|
SafeRelease(s_pBlendState);
|
|
SafeRelease(s_pSampler);
|
|
SafeRelease(s_pSamplerLinear);
|
|
|
|
HRESULT hr;
|
|
ID3DBlob* vsBlob = nullptr;
|
|
ID3DBlob* err = nullptr;
|
|
|
|
hr = D3DCompile(s_vsCode, strlen(s_vsCode), "NativeUI_VS", nullptr, nullptr,
|
|
"main", "vs_4_0", D3DCOMPILE_ENABLE_STRICTNESS, 0, &vsBlob, &err);
|
|
if (FAILED(hr)) { if (err) err->Release(); return; }
|
|
if (err) { err->Release(); err = nullptr; }
|
|
|
|
hr = g_pd3dDevice->CreateVertexShader(vsBlob->GetBufferPointer(),
|
|
vsBlob->GetBufferSize(), nullptr, &s_pVS);
|
|
if (FAILED(hr)) { vsBlob->Release(); return; }
|
|
|
|
D3D11_INPUT_ELEMENT_DESC layout[] = {
|
|
{ "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
|
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, sizeof(float) * 2, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
|
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, sizeof(float) * 6, D3D11_INPUT_PER_VERTEX_DATA, 0 },
|
|
};
|
|
hr = g_pd3dDevice->CreateInputLayout(layout, 3, vsBlob->GetBufferPointer(),
|
|
vsBlob->GetBufferSize(), &s_pInputLayout);
|
|
vsBlob->Release();
|
|
if (FAILED(hr)) return;
|
|
|
|
s_pPS_Color = CompilePS(s_psColorCode, "NativeUI_PS_Color");
|
|
if (!s_pPS_Color) return;
|
|
|
|
s_pPS_Tex = CompilePS(s_psTexCode, "NativeUI_PS_Tex");
|
|
if (!s_pPS_Tex) return;
|
|
|
|
s_pPS_TexFull = CompilePS(s_psTexFullCode, "NativeUI_PS_TexFull");
|
|
if (!s_pPS_TexFull) return;
|
|
|
|
D3D11_BUFFER_DESC vbDesc = {};
|
|
vbDesc.ByteWidth = sizeof(s_vertices);
|
|
vbDesc.Usage = D3D11_USAGE_DYNAMIC;
|
|
vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
|
|
vbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
|
hr = g_pd3dDevice->CreateBuffer(&vbDesc, nullptr, &s_pVB);
|
|
if (FAILED(hr)) return;
|
|
|
|
D3D11_RASTERIZER_DESC rasDesc = {};
|
|
rasDesc.FillMode = D3D11_FILL_SOLID;
|
|
rasDesc.CullMode = D3D11_CULL_NONE;
|
|
rasDesc.DepthClipEnable = FALSE;
|
|
rasDesc.ScissorEnable = FALSE;
|
|
g_pd3dDevice->CreateRasterizerState(&rasDesc, &s_pRastState);
|
|
|
|
rasDesc.ScissorEnable = TRUE;
|
|
g_pd3dDevice->CreateRasterizerState(&rasDesc, &s_pRastScissor);
|
|
|
|
D3D11_DEPTH_STENCIL_DESC dsDesc = {};
|
|
dsDesc.DepthEnable = FALSE;
|
|
dsDesc.StencilEnable = FALSE;
|
|
g_pd3dDevice->CreateDepthStencilState(&dsDesc, &s_pDepthState);
|
|
|
|
D3D11_BLEND_DESC blendDesc = {};
|
|
blendDesc.RenderTarget[0].BlendEnable = TRUE;
|
|
blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
|
|
blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
|
|
blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
|
|
blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
|
|
blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
|
|
blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
|
|
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
|
|
g_pd3dDevice->CreateBlendState(&blendDesc, &s_pBlendState);
|
|
|
|
D3D11_SAMPLER_DESC sampDesc = {};
|
|
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
|
|
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
g_pd3dDevice->CreateSamplerState(&sampDesc, &s_pSampler);
|
|
|
|
D3D11_SAMPLER_DESC sampLinDesc = {};
|
|
sampLinDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
|
sampLinDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampLinDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
sampLinDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
|
g_pd3dDevice->CreateSamplerState(&sampLinDesc, &s_pSamplerLinear);
|
|
|
|
s_initialized = true;
|
|
}
|
|
|
|
static void BindBatchTexture(ID3D11DeviceContext* ctx)
|
|
{
|
|
if (s_batchMode == BATCH_TEXT)
|
|
{
|
|
Font* font = GetFont();
|
|
if (font)
|
|
{
|
|
ResourceLocation* loc = font->getTextureLocation();
|
|
if (loc && loc->isPreloaded())
|
|
{
|
|
int texId = font->getTextures()->loadTexture(loc->getTexture());
|
|
ID3D11ShaderResourceView* srv = RenderManager.TextureGetTexture(texId);
|
|
if (srv)
|
|
{
|
|
ctx->PSSetShaderResources(0, 1, &srv);
|
|
ctx->PSSetSamplers(0, 1, &s_pSampler);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (s_batchMode == BATCH_TEXTURE && s_batchTexId >= 0)
|
|
{
|
|
ID3D11ShaderResourceView* srv = RenderManager.TextureGetTexture(s_batchTexId);
|
|
if (srv)
|
|
{
|
|
ctx->PSSetShaderResources(0, 1, &srv);
|
|
ctx->PSSetSamplers(0, 1, &s_pSampler);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void FlushBatch()
|
|
{
|
|
if (s_vertCount == 0 || !s_initialized) return;
|
|
|
|
ID3D11DeviceContext* ctx = g_pImmediateContext;
|
|
|
|
D3D11_MAPPED_SUBRESOURCE mapped;
|
|
HRESULT hr = ctx->Map(s_pVB, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
|
if (FAILED(hr)) { s_vertCount = 0; return; }
|
|
memcpy(mapped.pData, s_vertices, s_vertCount * sizeof(UIVertex));
|
|
ctx->Unmap(s_pVB, 0);
|
|
|
|
if (s_batchMode == BATCH_COLOR)
|
|
ctx->PSSetShader(s_pPS_Color, nullptr, 0);
|
|
else if (s_batchMode == BATCH_TEXT)
|
|
{
|
|
ctx->PSSetShader(s_pPS_Tex, nullptr, 0);
|
|
BindBatchTexture(ctx);
|
|
}
|
|
else
|
|
{
|
|
ctx->PSSetShader(s_pPS_TexFull, nullptr, 0);
|
|
BindBatchTexture(ctx);
|
|
}
|
|
|
|
ctx->Draw(s_vertCount, 0);
|
|
s_vertCount = 0;
|
|
}
|
|
|
|
static void EnsureBatchMode(BatchMode mode, int texId = -1)
|
|
{
|
|
if (s_vertCount > 0 && (s_batchMode != mode || (mode == BATCH_TEXTURE && s_batchTexId != texId)))
|
|
FlushBatch();
|
|
s_batchMode = mode;
|
|
s_batchTexId = texId;
|
|
}
|
|
|
|
static void SaveD3DState()
|
|
{
|
|
ID3D11DeviceContext* ctx = g_pImmediateContext;
|
|
ctx->OMGetRenderTargets(1, &s_savedRTV, &s_savedDSV);
|
|
UINT numVP = 1;
|
|
ctx->RSGetViewports(&numVP, &s_savedViewport);
|
|
ctx->RSGetState(&s_savedRast);
|
|
ctx->OMGetDepthStencilState(&s_savedDepth, &s_savedStencilRef);
|
|
ctx->OMGetBlendState(&s_savedBlend, s_savedBlendFactor, &s_savedSampleMask);
|
|
ctx->VSGetShader(&s_savedVS, nullptr, nullptr);
|
|
ctx->PSGetShader(&s_savedPS, nullptr, nullptr);
|
|
ctx->IAGetInputLayout(&s_savedIL);
|
|
ctx->IAGetPrimitiveTopology(&s_savedTopo);
|
|
}
|
|
|
|
static void RestoreD3DState()
|
|
{
|
|
ID3D11DeviceContext* ctx = g_pImmediateContext;
|
|
ctx->OMSetRenderTargets(1, &s_savedRTV, s_savedDSV);
|
|
ctx->RSSetViewports(1, &s_savedViewport);
|
|
ctx->RSSetState(s_savedRast);
|
|
ctx->OMSetDepthStencilState(s_savedDepth, s_savedStencilRef);
|
|
ctx->OMSetBlendState(s_savedBlend, s_savedBlendFactor, s_savedSampleMask);
|
|
ctx->VSSetShader(s_savedVS, nullptr, 0);
|
|
ctx->PSSetShader(s_savedPS, nullptr, 0);
|
|
ctx->IASetInputLayout(s_savedIL);
|
|
ctx->IASetPrimitiveTopology(s_savedTopo);
|
|
|
|
if (s_savedRTV) { s_savedRTV->Release(); s_savedRTV = nullptr; }
|
|
if (s_savedDSV) { s_savedDSV->Release(); s_savedDSV = nullptr; }
|
|
if (s_savedRast) { s_savedRast->Release(); s_savedRast = nullptr; }
|
|
if (s_savedDepth) { s_savedDepth->Release(); s_savedDepth = nullptr; }
|
|
if (s_savedBlend) { s_savedBlend->Release(); s_savedBlend = nullptr; }
|
|
if (s_savedVS) { s_savedVS->Release(); s_savedVS = nullptr; }
|
|
if (s_savedPS) { s_savedPS->Release(); s_savedPS = nullptr; }
|
|
if (s_savedIL) { s_savedIL->Release(); s_savedIL = nullptr; }
|
|
}
|
|
|
|
static void SetupD3DPipeline()
|
|
{
|
|
ID3D11DeviceContext* ctx = g_pImmediateContext;
|
|
|
|
ctx->OMSetRenderTargets(1, &g_pRenderTargetView, nullptr);
|
|
|
|
ID3D11Resource* rtvResource = nullptr;
|
|
g_pRenderTargetView->GetResource(&rtvResource);
|
|
ID3D11Texture2D* rtvTex = nullptr;
|
|
rtvResource->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&rtvTex);
|
|
rtvResource->Release();
|
|
|
|
if (rtvTex)
|
|
{
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
rtvTex->GetDesc(&desc);
|
|
s_bbWidth = (float)desc.Width;
|
|
s_bbHeight = (float)desc.Height;
|
|
rtvTex->Release();
|
|
}
|
|
else
|
|
{
|
|
s_bbWidth = kVW;
|
|
s_bbHeight = kVH;
|
|
}
|
|
|
|
{
|
|
float bbAspect = s_bbWidth / s_bbHeight;
|
|
constexpr float targetAspect = kVW / kVH;
|
|
|
|
if (bbAspect > targetAspect)
|
|
{
|
|
s_vpH = s_bbHeight;
|
|
s_vpW = s_bbHeight * targetAspect;
|
|
s_vpX = (s_bbWidth - s_vpW) * 0.5f;
|
|
s_vpY = 0.0f;
|
|
}
|
|
else if (bbAspect < targetAspect)
|
|
{
|
|
s_vpW = s_bbWidth;
|
|
s_vpH = s_bbWidth / targetAspect;
|
|
s_vpX = 0.0f;
|
|
s_vpY = (s_bbHeight - s_vpH) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
s_vpX = 0.0f;
|
|
s_vpY = 0.0f;
|
|
s_vpW = s_bbWidth;
|
|
s_vpH = s_bbHeight;
|
|
}
|
|
}
|
|
|
|
D3D11_VIEWPORT vp = {};
|
|
vp.TopLeftX = s_vpX;
|
|
vp.TopLeftY = s_vpY;
|
|
vp.Width = s_vpW;
|
|
vp.Height = s_vpH;
|
|
vp.MinDepth = 0.0f;
|
|
vp.MaxDepth = 1.0f;
|
|
ctx->RSSetViewports(1, &vp);
|
|
|
|
ctx->IASetInputLayout(s_pInputLayout);
|
|
ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
|
|
|
UINT stride = sizeof(UIVertex);
|
|
UINT offset = 0;
|
|
ctx->IASetVertexBuffers(0, 1, &s_pVB, &stride, &offset);
|
|
|
|
ctx->VSSetShader(s_pVS, nullptr, 0);
|
|
ctx->PSSetShader(s_pPS_Color, nullptr, 0);
|
|
ctx->RSSetState(s_pRastState);
|
|
ctx->OMSetDepthStencilState(s_pDepthState, 0);
|
|
|
|
constexpr float bf[4] = { 0, 0, 0, 0 };
|
|
ctx->OMSetBlendState(s_pBlendState, bf, 0xFFFFFFFF);
|
|
}
|
|
|
|
static void PushQuad(float x0, float y0, float x1, float y1,
|
|
float r, float g, float b, float a,
|
|
float u0, float v0, float u1, float v1)
|
|
{
|
|
if (s_vertCount + 6 > kMaxVerts) FlushBatch();
|
|
|
|
UIVertex* v = &s_vertices[s_vertCount];
|
|
v[0] = { x0, y0, r, g, b, a, u0, v0 };
|
|
v[1] = { x1, y0, r, g, b, a, u1, v0 };
|
|
v[2] = { x0, y1, r, g, b, a, u0, v1 };
|
|
v[3] = { x0, y1, r, g, b, a, u0, v1 };
|
|
v[4] = { x1, y0, r, g, b, a, u1, v0 };
|
|
v[5] = { x1, y1, r, g, b, a, u1, v1 };
|
|
s_vertCount += 6;
|
|
}
|
|
|
|
static void PushGradientQuad(float x0, float y0, float x1, float y1,
|
|
float tr, float tg, float tb, float ta,
|
|
float br, float bg, float bb, float ba)
|
|
{
|
|
if (s_vertCount + 6 > kMaxVerts) FlushBatch();
|
|
|
|
UIVertex* v = &s_vertices[s_vertCount];
|
|
v[0] = { x0, y0, tr, tg, tb, ta, 0, 0 };
|
|
v[1] = { x1, y0, tr, tg, tb, ta, 0, 0 };
|
|
v[2] = { x0, y1, br, bg, bb, ba, 0, 0 };
|
|
v[3] = { x0, y1, br, bg, bb, ba, 0, 0 };
|
|
v[4] = { x1, y0, tr, tg, tb, ta, 0, 0 };
|
|
v[5] = { x1, y1, br, bg, bb, ba, 0, 0 };
|
|
s_vertCount += 6;
|
|
}
|
|
|
|
static void ApplyScissor()
|
|
{
|
|
ID3D11DeviceContext* ctx = g_pImmediateContext;
|
|
if (s_clipDepth <= 0)
|
|
{
|
|
ctx->RSSetState(s_pRastState);
|
|
return;
|
|
}
|
|
|
|
ctx->RSSetState(s_pRastScissor);
|
|
|
|
const ClipRect& c = s_clipStack[s_clipDepth - 1];
|
|
float scaleX = s_vpW / kVW;
|
|
float scaleY = s_vpH / kVH;
|
|
|
|
D3D11_RECT r;
|
|
r.left = (LONG)(s_vpX + c.x * scaleX);
|
|
r.top = (LONG)(s_vpY + c.y * scaleY);
|
|
r.right = (LONG)(s_vpX + (c.x + c.w) * scaleX);
|
|
r.bottom = (LONG)(s_vpY + (c.y + c.h) * scaleY);
|
|
ctx->RSSetScissorRects(1, &r);
|
|
}
|
|
|
|
static void DrawGlyphString(Font* font, const std::wstring& str,
|
|
float startX, float startY, float scale,
|
|
float cr, float cg, float cb, float ca)
|
|
{
|
|
EnsureBatchMode(BATCH_TEXT);
|
|
|
|
const int cols = font->getCols();
|
|
const int cw = font->getCharWidth();
|
|
const int ch = font->getCharHeight();
|
|
const float atlasW = (float)(cols * cw);
|
|
const float atlasH = (float)(font->getRows() * ch);
|
|
|
|
float curX = startX;
|
|
|
|
for (size_t i = 0; i < str.length(); ++i)
|
|
{
|
|
wchar_t c = str[i];
|
|
|
|
if (c == 167 && i + 1 < str.length())
|
|
{
|
|
++i;
|
|
continue;
|
|
}
|
|
|
|
wchar_t mapped = font->mapChar(c);
|
|
int charW = font->getCharPixelWidth(c);
|
|
|
|
float uOff = (float)(mapped % cols * cw);
|
|
float vOff = (float)(mapped / cols * ch);
|
|
|
|
float u0 = uOff / atlasW;
|
|
float v0 = vOff / atlasH;
|
|
float u1 = (uOff + (float)charW - 0.01f) / atlasW;
|
|
float v1 = (vOff + 7.99f) / atlasH;
|
|
|
|
float gx0 = curX;
|
|
float gy0 = startY;
|
|
float gx1 = curX + ((float)charW - 0.01f) * scale;
|
|
float gy1 = startY + (float)ch * scale;
|
|
|
|
PushQuad(gx0, gy0, gx1, gy1, cr, cg, cb, ca, u0, v0, u1, v1);
|
|
|
|
curX += (float)charW * scale;
|
|
}
|
|
}
|
|
|
|
static void ApplyAlignment(Font* font, const std::wstring& wstr,
|
|
float x, float y,
|
|
float scale, uint32_t align,
|
|
float& drawX, float& drawY)
|
|
{
|
|
float textW = static_cast<float>(font->width(wstr)) * scale;
|
|
float textH = kFontUnitH * scale;
|
|
|
|
drawX = x;
|
|
if (align & NativeUI::ALIGN_CENTER_X) drawX = x - textW * 0.5f;
|
|
else if (align & NativeUI::ALIGN_RIGHT) drawX = x - textW;
|
|
|
|
drawY = y;
|
|
if (align & NativeUI::ALIGN_CENTER_Y) drawY = y - textH * 0.5f;
|
|
else if (align & NativeUI::ALIGN_BOTTOM) drawY = y - textH;
|
|
}
|
|
}
|
|
|
|
namespace NativeUI
|
|
{
|
|
|
|
void BeginFrame()
|
|
{
|
|
InitD3D();
|
|
if (!s_initialized) return;
|
|
|
|
SaveD3DState();
|
|
SetupD3DPipeline();
|
|
|
|
s_vertCount = 0;
|
|
s_batchMode = BATCH_COLOR;
|
|
s_batchTexId = -1;
|
|
s_clipDepth = 0;
|
|
s_inFrame = true;
|
|
}
|
|
|
|
void EndFrame()
|
|
{
|
|
if (!s_inFrame) return;
|
|
|
|
FlushBatch();
|
|
|
|
ID3D11ShaderResourceView* nullSrv = nullptr;
|
|
g_pImmediateContext->PSSetShaderResources(0, 1, &nullSrv);
|
|
|
|
RestoreD3DState();
|
|
s_inFrame = false;
|
|
}
|
|
|
|
void Shutdown()
|
|
{
|
|
auto SafeRelease = [](auto*& p) { if (p) { p->Release(); p = nullptr; } };
|
|
SafeRelease(s_pBlendState);
|
|
SafeRelease(s_pDepthState);
|
|
SafeRelease(s_pRastState);
|
|
SafeRelease(s_pRastScissor);
|
|
SafeRelease(s_pSampler);
|
|
SafeRelease(s_pSamplerLinear);
|
|
SafeRelease(s_pVB);
|
|
SafeRelease(s_pInputLayout);
|
|
SafeRelease(s_pPS_TexFull);
|
|
SafeRelease(s_pPS_Tex);
|
|
SafeRelease(s_pPS_Color);
|
|
SafeRelease(s_pVS);
|
|
s_initialized = false;
|
|
}
|
|
|
|
void DrawRect(float x, float y, float w, float h, uint32_t color)
|
|
{
|
|
if (!s_inFrame) return;
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
PushQuad(x, y, x + w, y + h, r, g, b, a, 0, 0, 0, 0);
|
|
}
|
|
|
|
void DrawRectFullscreen(uint32_t color)
|
|
{
|
|
if (!s_inFrame) return;
|
|
|
|
FlushBatch();
|
|
|
|
D3D11_VIEWPORT fullVP = {};
|
|
fullVP.TopLeftX = 0;
|
|
fullVP.TopLeftY = 0;
|
|
fullVP.Width = s_bbWidth;
|
|
fullVP.Height = s_bbHeight;
|
|
fullVP.MinDepth = 0.0f;
|
|
fullVP.MaxDepth = 1.0f;
|
|
g_pImmediateContext->RSSetViewports(1, &fullVP);
|
|
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
PushQuad(0, 0, kVW, kVH, r, g, b, a, 0, 0, 0, 0);
|
|
FlushBatch();
|
|
|
|
D3D11_VIEWPORT vp = {};
|
|
vp.TopLeftX = s_vpX;
|
|
vp.TopLeftY = s_vpY;
|
|
vp.Width = s_vpW;
|
|
vp.Height = s_vpH;
|
|
vp.MinDepth = 0.0f;
|
|
vp.MaxDepth = 1.0f;
|
|
g_pImmediateContext->RSSetViewports(1, &vp);
|
|
}
|
|
|
|
static void EmitCornerFans(const float cx[4], const float cy[4],
|
|
const float startAngle[4], float radius,
|
|
const float cr[4], const float cg[4],
|
|
const float cb[4], const float ca[4])
|
|
{
|
|
static constexpr int kSeg = 6;
|
|
static constexpr float kHalfPi = 1.5707963f;
|
|
|
|
for (int corner = 0; corner < 4; ++corner)
|
|
{
|
|
for (int seg = 0; seg < kSeg; ++seg)
|
|
{
|
|
float a0 = startAngle[corner] + kHalfPi * seg / kSeg;
|
|
float a1 = startAngle[corner] + kHalfPi * (seg + 1) / kSeg;
|
|
|
|
float px0 = cx[corner] + cosf(a0) * radius;
|
|
float py0 = cy[corner] + sinf(a0) * radius;
|
|
float px1 = cx[corner] + cosf(a1) * radius;
|
|
float py1 = cy[corner] + sinf(a1) * radius;
|
|
|
|
if (s_vertCount + 3 > kMaxVerts) FlushBatch();
|
|
UIVertex* v = &s_vertices[s_vertCount];
|
|
v[0] = { cx[corner], cy[corner], cr[corner], cg[corner], cb[corner], ca[corner], 0, 0 };
|
|
v[1] = { px0, py0, cr[corner], cg[corner], cb[corner], ca[corner], 0, 0 };
|
|
v[2] = { px1, py1, cr[corner], cg[corner], cb[corner], ca[corner], 0, 0 };
|
|
s_vertCount += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawRoundedRect(float x, float y, float w, float h,
|
|
float radius, uint32_t color)
|
|
{
|
|
if (!s_inFrame) return;
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
|
|
float maxR = fminf(w, h) * 0.5f;
|
|
if (radius > maxR) radius = maxR;
|
|
|
|
static constexpr float kHalfPi = 1.5707963f;
|
|
|
|
PushQuad(x + radius, y, x + w - radius, y + h, r, g, b, a, 0, 0, 0, 0);
|
|
PushQuad(x, y + radius, x + radius, y + h - radius, r, g, b, a, 0, 0, 0, 0);
|
|
PushQuad(x + w - radius, y + radius, x + w, y + h - radius, r, g, b, a, 0, 0, 0, 0);
|
|
|
|
float cx[4] = { x + radius, x + w - radius, x + radius, x + w - radius };
|
|
float cy[4] = { y + radius, y + radius, y + h - radius, y + h - radius };
|
|
float startAngle[4] = { kHalfPi * 2, kHalfPi * 3, kHalfPi, 0 };
|
|
float cr[4] = { r, r, r, r };
|
|
float cg[4] = { g, g, g, g };
|
|
float cb[4] = { b, b, b, b };
|
|
float ca[4] = { a, a, a, a };
|
|
|
|
EmitCornerFans(cx, cy, startAngle, radius, cr, cg, cb, ca);
|
|
}
|
|
|
|
void DrawRoundedBorder(float x, float y, float w, float h,
|
|
float radius, float thickness, uint32_t color)
|
|
{
|
|
if (!s_inFrame) return;
|
|
|
|
float maxR = fminf(w, h) * 0.5f;
|
|
if (radius > maxR) radius = maxR;
|
|
float ri = fmaxf(0.0f, radius - thickness);
|
|
|
|
DrawRect(x + radius, y, w - 2 * radius, thickness, color);
|
|
DrawRect(x + radius, y + h - thickness, w - 2 * radius, thickness, color);
|
|
DrawRect(x, y + radius, thickness, h - 2 * radius, color);
|
|
DrawRect(x + w - thickness, y + radius, thickness, h - 2 * radius, color);
|
|
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
|
|
static constexpr int kSeg = 6;
|
|
static constexpr float kHalfPi = 1.5707963f;
|
|
|
|
float cx_[4] = { x + radius, x + w - radius, x + radius, x + w - radius };
|
|
float cy_[4] = { y + radius, y + radius, y + h - radius, y + h - radius };
|
|
float startA[4] = { kHalfPi * 2, kHalfPi * 3, kHalfPi, 0 };
|
|
|
|
for (int corner = 0; corner < 4; ++corner)
|
|
{
|
|
for (int seg = 0; seg < kSeg; ++seg)
|
|
{
|
|
float a0 = startA[corner] + kHalfPi * seg / kSeg;
|
|
float a1 = startA[corner] + kHalfPi * (seg + 1) / kSeg;
|
|
|
|
float ox0 = cx_[corner] + cosf(a0) * radius;
|
|
float oy0 = cy_[corner] + sinf(a0) * radius;
|
|
float ox1 = cx_[corner] + cosf(a1) * radius;
|
|
float oy1 = cy_[corner] + sinf(a1) * radius;
|
|
float ix0 = cx_[corner] + cosf(a0) * ri;
|
|
float iy0 = cy_[corner] + sinf(a0) * ri;
|
|
float ix1 = cx_[corner] + cosf(a1) * ri;
|
|
float iy1 = cy_[corner] + sinf(a1) * ri;
|
|
|
|
if (s_vertCount + 6 > kMaxVerts) FlushBatch();
|
|
UIVertex* v = &s_vertices[s_vertCount];
|
|
v[0] = { ox0, oy0, r, g, b, a, 0, 0 };
|
|
v[1] = { ox1, oy1, r, g, b, a, 0, 0 };
|
|
v[2] = { ix0, iy0, r, g, b, a, 0, 0 };
|
|
v[3] = { ix0, iy0, r, g, b, a, 0, 0 };
|
|
v[4] = { ox1, oy1, r, g, b, a, 0, 0 };
|
|
v[5] = { ix1, iy1, r, g, b, a, 0, 0 };
|
|
s_vertCount += 6;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawBorder(float x, float y, float w, float h,
|
|
float thickness, uint32_t color)
|
|
{
|
|
DrawRect(x, y, w, thickness, color);
|
|
DrawRect(x, y + h - thickness, w, thickness, color);
|
|
DrawRect(x, y + thickness, thickness, h - 2.0f * thickness, color);
|
|
DrawRect(x + w - thickness, y + thickness, thickness, h - 2.0f * thickness, color);
|
|
}
|
|
|
|
void DrawLine(float x, float y, float length, float thickness, uint32_t color)
|
|
{
|
|
DrawRect(x, y, length, thickness, color);
|
|
}
|
|
|
|
void DrawLineV(float x, float y, float length, float thickness, uint32_t color)
|
|
{
|
|
DrawRect(x, y, thickness, length, color);
|
|
}
|
|
|
|
void DrawGradientRect(float x, float y, float w, float h,
|
|
uint32_t topColor, uint32_t bottomColor)
|
|
{
|
|
if (!s_inFrame) return;
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
|
|
float tr, tg, tb, ta, br, bg, bb, ba;
|
|
DecodeColor(topColor, tr, tg, tb, ta);
|
|
DecodeColor(bottomColor, br, bg, bb, ba);
|
|
PushGradientQuad(x, y, x + w, y + h, tr, tg, tb, ta, br, bg, bb, ba);
|
|
}
|
|
|
|
void DrawGradientRoundedRect(float x, float y, float w, float h,
|
|
float radius,
|
|
uint32_t topColor, uint32_t bottomColor)
|
|
{
|
|
if (!s_inFrame) return;
|
|
EnsureBatchMode(BATCH_COLOR);
|
|
|
|
float maxR = fminf(w, h) * 0.5f;
|
|
if (radius > maxR) radius = maxR;
|
|
|
|
float tr, tg, tb, ta, br, bg, bb, ba;
|
|
DecodeColor(topColor, tr, tg, tb, ta);
|
|
DecodeColor(bottomColor, br, bg, bb, ba);
|
|
|
|
PushGradientQuad(x + radius, y, x + w - radius, y + h,
|
|
tr, tg, tb, ta, br, bg, bb, ba);
|
|
|
|
float midTopY = y + radius;
|
|
float midBotY = y + h - radius;
|
|
PushGradientQuad(x, midTopY, x + radius, midBotY,
|
|
tr, tg, tb, ta, br, bg, bb, ba);
|
|
PushGradientQuad(x + w - radius, midTopY, x + w, midBotY,
|
|
tr, tg, tb, ta, br, bg, bb, ba);
|
|
|
|
static constexpr float kHalfPi = 1.5707963f;
|
|
|
|
float cx_[4] = { x + radius, x + w - radius, x + radius, x + w - radius };
|
|
float cy_[4] = { y + radius, y + radius, y + h - radius, y + h - radius };
|
|
float startA[4] = { kHalfPi * 2, kHalfPi * 3, kHalfPi, 0 };
|
|
float cr[4] = { tr, tr, br, br };
|
|
float cg[4] = { tg, tg, bg, bg };
|
|
float cb[4] = { tb, tb, bb, bb };
|
|
float ca[4] = { ta, ta, ba, ba };
|
|
|
|
EmitCornerFans(cx_, cy_, startA, radius, cr, cg, cb, ca);
|
|
}
|
|
|
|
void DrawDropShadow(float x, float y, float w, float h,
|
|
float offset, float spread, uint32_t color)
|
|
{
|
|
if (!s_inFrame) return;
|
|
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
|
|
int layers = 3;
|
|
for (int i = 0; i < layers; ++i)
|
|
{
|
|
float t = (float)(i + 1) / (float)layers;
|
|
float expand = spread * t;
|
|
float off = offset * t;
|
|
float layerA = a * (1.0f - t * 0.6f) / (float)layers;
|
|
|
|
DrawRect(x - expand + off, y - expand + off,
|
|
w + expand * 2, h + expand * 2,
|
|
((uint32_t)(layerA * 255.0f) << 24) | (color & 0x00FFFFFFu));
|
|
}
|
|
}
|
|
|
|
void DrawPanel(float x, float y, float w, float h,
|
|
float radius, uint32_t bgColor, uint32_t borderColor,
|
|
float borderThick, bool shadow)
|
|
{
|
|
if (shadow)
|
|
DrawDropShadow(x, y, w, h, 4.0f, 6.0f, 0x60000000u);
|
|
|
|
if (radius > 0.0f)
|
|
{
|
|
DrawRoundedRect(x, y, w, h, radius, bgColor);
|
|
if (borderThick > 0.0f)
|
|
DrawRoundedBorder(x, y, w, h, radius, borderThick, borderColor);
|
|
}
|
|
else
|
|
{
|
|
DrawRect(x, y, w, h, bgColor);
|
|
if (borderThick > 0.0f)
|
|
DrawBorder(x, y, w, h, borderThick, borderColor);
|
|
}
|
|
}
|
|
|
|
void DrawDivider(float x, float y, float w, uint32_t color, float thickness)
|
|
{
|
|
DrawRect(x, y, w, thickness, color);
|
|
}
|
|
|
|
void DrawText(float x, float y, const wchar_t* text,
|
|
uint32_t color, float size, uint32_t align)
|
|
{
|
|
if (!text || !text[0] || !s_inFrame) return;
|
|
Font* font = GetFont();
|
|
if (!font) return;
|
|
|
|
std::wstring wstr = font->sanitize(std::wstring(text));
|
|
const float scale = ScaleForSize(size);
|
|
|
|
float drawX, drawY;
|
|
ApplyAlignment(font, wstr, x, y, scale, align, drawX, drawY);
|
|
|
|
if ((color & 0xFC000000) == 0) color |= 0xFF000000;
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
DrawGlyphString(font, wstr, drawX, drawY, scale, r, g, b, a);
|
|
}
|
|
|
|
void DrawShadowText(float x, float y, const wchar_t* text,
|
|
uint32_t color, float size, uint32_t align)
|
|
{
|
|
if (!text || !text[0] || !s_inFrame) return;
|
|
Font* font = GetFont();
|
|
if (!font) return;
|
|
|
|
std::wstring wstr = font->sanitize(std::wstring(text));
|
|
const float scale = ScaleForSize(size);
|
|
|
|
float drawX, drawY;
|
|
ApplyAlignment(font, wstr, x, y, scale, align, drawX, drawY);
|
|
|
|
if ((color & 0xFC000000) == 0) color |= 0xFF000000;
|
|
|
|
uint32_t shadow = (color & 0xfcfcfc) >> 2 | (color & 0xFF000000);
|
|
float sr, sg, sb, sa;
|
|
DecodeColor(shadow, sr, sg, sb, sa);
|
|
DrawGlyphString(font, wstr, drawX + scale, drawY + scale, scale, sr, sg, sb, sa);
|
|
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
DrawGlyphString(font, wstr, drawX, drawY, scale, r, g, b, a);
|
|
}
|
|
|
|
float DrawTextWrapped(float x, float y, const wchar_t* text,
|
|
float maxWidth, uint32_t color,
|
|
float size, uint32_t align)
|
|
{
|
|
if (!text || !text[0] || !s_inFrame) return 0.0f;
|
|
Font* font = GetFont();
|
|
if (!font) return 0.0f;
|
|
|
|
std::wstring wstr = font->sanitize(std::wstring(text));
|
|
const float scale = ScaleForSize(size);
|
|
const float lineH = LineHeight(size);
|
|
|
|
float curY = y;
|
|
size_t lineStart = 0;
|
|
size_t lastSpace = std::wstring::npos;
|
|
|
|
for (size_t i = 0; i <= wstr.length(); ++i)
|
|
{
|
|
bool endOfString = (i == wstr.length());
|
|
bool isSpace = !endOfString && wstr[i] == L' ';
|
|
|
|
if (isSpace) lastSpace = i;
|
|
|
|
std::wstring segment = wstr.substr(lineStart, i - lineStart);
|
|
float segW = static_cast<float>(font->width(segment)) * scale;
|
|
|
|
if (segW > maxWidth || endOfString)
|
|
{
|
|
size_t breakAt;
|
|
if (endOfString)
|
|
breakAt = i;
|
|
else if (lastSpace != std::wstring::npos && lastSpace > lineStart)
|
|
breakAt = lastSpace;
|
|
else
|
|
breakAt = i > lineStart ? i - 1 : i;
|
|
|
|
std::wstring line = wstr.substr(lineStart, breakAt - lineStart);
|
|
if (!line.empty())
|
|
{
|
|
float drawX, drawY;
|
|
ApplyAlignment(font, line, x, curY, scale, align, drawX, drawY);
|
|
|
|
if ((color & 0xFC000000) == 0) color |= 0xFF000000;
|
|
float r, g, b, a;
|
|
DecodeColor(color, r, g, b, a);
|
|
DrawGlyphString(font, line, drawX, drawY, scale, r, g, b, a);
|
|
|
|
curY += lineH;
|
|
}
|
|
|
|
lineStart = breakAt;
|
|
if (lineStart < wstr.length() && wstr[lineStart] == L' ')
|
|
++lineStart;
|
|
lastSpace = std::wstring::npos;
|
|
i = lineStart;
|
|
}
|
|
}
|
|
|
|
return curY - y;
|
|
}
|
|
|
|
void MeasureText(const wchar_t* text, float size,
|
|
float* outWidth, float* outHeight)
|
|
{
|
|
if (!text || !text[0])
|
|
{
|
|
if (outWidth) *outWidth = 0.0f;
|
|
if (outHeight) *outHeight = 0.0f;
|
|
return;
|
|
}
|
|
Font* font = GetFont();
|
|
if (!font)
|
|
{
|
|
if (outWidth) *outWidth = 0.0f;
|
|
if (outHeight) *outHeight = 0.0f;
|
|
return;
|
|
}
|
|
const float scale = ScaleForSize(size);
|
|
if (outWidth) *outWidth = static_cast<float>(font->width(std::wstring(text))) * scale;
|
|
if (outHeight) *outHeight = size;
|
|
}
|
|
|
|
float LineHeight(float size)
|
|
{
|
|
return size + 2.0f * ScaleForSize(size);
|
|
}
|
|
|
|
void PushClipRect(float x, float y, float w, float h)
|
|
{
|
|
if (!s_inFrame) return;
|
|
FlushBatch();
|
|
|
|
ClipRect newClip = { x, y, w, h };
|
|
|
|
if (s_clipDepth > 0)
|
|
{
|
|
const ClipRect& parent = s_clipStack[s_clipDepth - 1];
|
|
float nx0 = fmaxf(x, parent.x);
|
|
float ny0 = fmaxf(y, parent.y);
|
|
float nx1 = fminf(x + w, parent.x + parent.w);
|
|
float ny1 = fminf(y + h, parent.y + parent.h);
|
|
newClip.x = nx0;
|
|
newClip.y = ny0;
|
|
newClip.w = fmaxf(0.0f, nx1 - nx0);
|
|
newClip.h = fmaxf(0.0f, ny1 - ny0);
|
|
}
|
|
|
|
if (s_clipDepth < kMaxClipStack)
|
|
s_clipStack[s_clipDepth++] = newClip;
|
|
|
|
ApplyScissor();
|
|
}
|
|
|
|
void PopClipRect()
|
|
{
|
|
if (!s_inFrame) return;
|
|
FlushBatch();
|
|
|
|
if (s_clipDepth > 0) --s_clipDepth;
|
|
ApplyScissor();
|
|
}
|
|
|
|
static int s_guiTexId = -2;
|
|
|
|
static int GetGuiTexture()
|
|
{
|
|
if (s_guiTexId == -2)
|
|
s_guiTexId = LoadTexture(L"/gui/gui.png");
|
|
return s_guiTexId;
|
|
}
|
|
|
|
static void DrawGuiSlice(float x, float y, float w, float h,
|
|
int texId, float srcY, float srcH,
|
|
float capL = 2.0f, float capR = 2.0f,
|
|
float capT = 2.0f, float capB = 3.0f)
|
|
{
|
|
const float texSz = 256.0f;
|
|
const float srcW = 200.0f;
|
|
|
|
float scale = h / srcH;
|
|
float cL = capL * scale;
|
|
float cR = capR * scale;
|
|
float cT = capT * scale;
|
|
float cB = capB * scale;
|
|
|
|
if (cL + cR > w) { float s = w / (cL + cR); cL *= s; cR *= s; }
|
|
if (cT + cB > h) { float s = h / (cT + cB); cT *= s; cB *= s; }
|
|
|
|
float midW = w - cL - cR;
|
|
float midH = h - cT - cB;
|
|
|
|
float u0 = 0.0f;
|
|
float uL = capL / texSz;
|
|
float uR = (srcW - capR) / texSz;
|
|
float u1 = srcW / texSz;
|
|
|
|
float v0 = srcY / texSz;
|
|
float vT = (srcY + capT) / texSz;
|
|
float vB = (srcY + srcH - capB) / texSz;
|
|
float v1 = (srcY + srcH) / texSz;
|
|
|
|
DrawTextureUV(x, y, cL, cT, texId, u0, v0, uL, vT);
|
|
DrawTextureUV(x + cL, y, midW, cT, texId, uL, v0, uR, vT);
|
|
DrawTextureUV(x + w - cR, y, cR, cT, texId, uR, v0, u1, vT);
|
|
|
|
DrawTextureUV(x, y + cT, cL, midH, texId, u0, vT, uL, vB);
|
|
DrawTextureUV(x + cL, y + cT, midW, midH, texId, uL, vT, uR, vB);
|
|
DrawTextureUV(x + w - cR, y + cT, cR, midH, texId, uR, vT, u1, vB);
|
|
|
|
DrawTextureUV(x, y + h - cB, cL, cB, texId, u0, vB, uL, v1);
|
|
DrawTextureUV(x + cL, y + h - cB, midW, cB, texId, uL, vB, uR, v1);
|
|
DrawTextureUV(x + w - cR, y + h - cB, cR, cB, texId, uR, vB, u1, v1);
|
|
}
|
|
|
|
void DrawButton(float x, float y, float w, float h,
|
|
const wchar_t* label, bool focused, bool hovered,
|
|
float labelSize)
|
|
{
|
|
int texId = GetGuiTexture();
|
|
|
|
bool active = focused || hovered;
|
|
|
|
if (texId >= 0)
|
|
{
|
|
float srcY = active ? 86.0f : 66.0f;
|
|
DrawGuiSlice(x, y, w, h, texId, srcY, 20.0f, 2.0f, 2.0f, 2.0f, 3.0f);
|
|
}
|
|
else
|
|
{
|
|
uint32_t bg = active ? 0xFF1B5A8Cu : 0xFF282828u;
|
|
DrawRoundedRect(x, y, w, h, 4.0f, bg);
|
|
}
|
|
|
|
if (label && label[0])
|
|
{
|
|
uint32_t textColor = active ? 0xFFFFFF55u : 0xFFFFFFFFu;
|
|
DrawShadowText(x + w * 0.5f, y + h * 0.5f, label, textColor,
|
|
labelSize, ALIGN_CENTER_X | ALIGN_CENTER_Y);
|
|
}
|
|
}
|
|
|
|
static int s_enchantBtnTex = -2;
|
|
static int s_sliderTrackTex = -2;
|
|
static int s_sliderBtnTex = -2;
|
|
|
|
void DrawTextBox(float x, float y, float w, float h, uint32_t tint)
|
|
{
|
|
if (s_enchantBtnTex == -2)
|
|
s_enchantBtnTex = LoadTextureFromFile(
|
|
"Common/Media/Graphics/EnchantmentButtonEmpty.png");
|
|
|
|
if (s_enchantBtnTex >= 0)
|
|
{
|
|
float scale = h / 42.0f;
|
|
float capW = 6.0f * scale;
|
|
if (capW * 2 > w) capW = w * 0.5f;
|
|
float midW = w - capW * 2;
|
|
float uCap = 6.0f / 240.0f;
|
|
float uMid = (240.0f - 6.0f) / 240.0f;
|
|
|
|
DrawTextureUV(x, y, capW, h, s_enchantBtnTex, 0.0f, 0.0f, uCap, 1.0f, tint);
|
|
DrawTextureUV(x + capW, y, midW, h, s_enchantBtnTex, uCap, 0.0f, uMid, 1.0f, tint);
|
|
DrawTextureUV(x + w - capW, y, capW, h, s_enchantBtnTex, uMid, 0.0f, 1.0f, 1.0f, tint);
|
|
}
|
|
else
|
|
{
|
|
DrawRoundedRect(x, y, w, h, 2.0f, 0xFF1A1A2Au);
|
|
DrawRoundedBorder(x, y, w, h, 2.0f, 1.0f, 0xFF444444u);
|
|
}
|
|
}
|
|
|
|
void DrawProgressBar(float x, float y, float w, float h,
|
|
float progress,
|
|
uint32_t fillColor, uint32_t trackColor)
|
|
{
|
|
float radius = h * 0.5f;
|
|
DrawRoundedRect(x, y, w, h, radius, trackColor);
|
|
float fill = w * (progress < 0.0f ? 0.0f : progress > 1.0f ? 1.0f : progress);
|
|
if (fill > radius * 2.0f)
|
|
DrawRoundedRect(x, y, fill, h, radius, fillColor);
|
|
else if (fill > 0.0f)
|
|
DrawRoundedRect(x, y, fmaxf(fill, h), h, radius, fillColor);
|
|
}
|
|
|
|
void DrawSpinner(float cx, float cy, float radius, int tick, uint32_t color)
|
|
{
|
|
static constexpr int kDots = 10;
|
|
static constexpr float kTwoPi = 6.28318530f;
|
|
|
|
const uint32_t baseA = (color >> 24) & 0xFFu;
|
|
const uint32_t rgb = color & 0x00FFFFFFu;
|
|
|
|
for (int i = 0; i < kDots; ++i)
|
|
{
|
|
const float angle = (float)i / kDots * kTwoPi - kTwoPi * 0.25f;
|
|
const float dx = cosf(angle) * radius;
|
|
const float dy = sinf(angle) * radius;
|
|
|
|
const int age = (i - tick % kDots + kDots) % kDots;
|
|
const float t = 1.0f - (float)age / kDots;
|
|
const float dotR = radius * (0.10f + 0.12f * t);
|
|
const uint32_t a = (uint32_t)(baseA * (0.15f + 0.85f * t)) & 0xFFu;
|
|
|
|
DrawRoundedRect(cx + dx - dotR, cy + dy - dotR,
|
|
dotR * 2.0f, dotR * 2.0f, dotR, (a << 24) | rgb);
|
|
}
|
|
}
|
|
|
|
void DrawCheckbox(float x, float y, float size, bool checked, bool focused,
|
|
bool hovered)
|
|
{
|
|
uint32_t bgColor, borderColor;
|
|
float radius = 3.0f;
|
|
|
|
if (focused)
|
|
{
|
|
bgColor = 0xFF1A3A55u;
|
|
borderColor = 0xFF4DC3FFu;
|
|
}
|
|
else if (hovered)
|
|
{
|
|
bgColor = 0xFF2A3A48u;
|
|
borderColor = 0xFF3DA8E0u;
|
|
}
|
|
else
|
|
{
|
|
bgColor = 0xFF222222u;
|
|
borderColor = 0xFF555555u;
|
|
}
|
|
|
|
DrawRoundedRect(x, y, size, size, radius, bgColor);
|
|
DrawRoundedBorder(x, y, size, size, radius, 2.0f, borderColor);
|
|
|
|
if (checked)
|
|
{
|
|
float pad = size * 0.22f;
|
|
DrawRoundedRect(x + pad, y + pad, size - pad * 2, size - pad * 2,
|
|
2.0f, 0xFF4DC3FFu);
|
|
}
|
|
}
|
|
|
|
void DrawSlider(float x, float y, float w, float h,
|
|
float value, bool focused, bool hovered,
|
|
uint32_t fillColor, uint32_t trackColor)
|
|
{
|
|
value = value < 0.0f ? 0.0f : value > 1.0f ? 1.0f : value;
|
|
|
|
if (s_sliderTrackTex == -2)
|
|
s_sliderTrackTex = LoadTextureFromFile(
|
|
"Common/Media/Graphics/Slider_Track.png");
|
|
if (s_sliderBtnTex == -2)
|
|
s_sliderBtnTex = LoadTextureFromFile(
|
|
"Common/Media/Graphics/Slider_Button.png");
|
|
|
|
if (s_sliderTrackTex >= 0)
|
|
{
|
|
float scale = h / 32.0f;
|
|
float capW = 8.0f * scale;
|
|
if (capW * 2 > w) capW = w * 0.5f;
|
|
float midW = w - capW * 2;
|
|
float uCap = 8.0f / 600.0f;
|
|
float uMid = (600.0f - 8.0f) / 600.0f;
|
|
|
|
DrawTextureUV(x, y, capW, h, s_sliderTrackTex, 0.0f, 0.0f, uCap, 1.0f);
|
|
DrawTextureUV(x + capW, y, midW, h, s_sliderTrackTex, uCap, 0.0f, uMid, 1.0f);
|
|
DrawTextureUV(x + w - capW, y, capW, h, s_sliderTrackTex, uMid, 0.0f, 1.0f, 1.0f);
|
|
}
|
|
else
|
|
{
|
|
float radius = h * 0.5f;
|
|
DrawRoundedRect(x, y, w, h, radius, trackColor);
|
|
}
|
|
|
|
if (s_sliderBtnTex >= 0)
|
|
{
|
|
float thumbH = h;
|
|
float thumbW = thumbH * (16.0f / 32.0f);
|
|
float thumbX = x + value * (w - thumbW);
|
|
if (thumbX < x) thumbX = x;
|
|
if (thumbX + thumbW > x + w) thumbX = x + w - thumbW;
|
|
|
|
DrawTexture(thumbX, y, thumbW, thumbH, s_sliderBtnTex);
|
|
}
|
|
else
|
|
{
|
|
float thumbR = h * 0.8f;
|
|
float thumbX = x + w * value;
|
|
if (thumbX < x + thumbR) thumbX = x + thumbR;
|
|
if (thumbX > x + w - thumbR) thumbX = x + w - thumbR;
|
|
|
|
uint32_t thumbColor = (focused || hovered) ? 0xFFFFFFFFu : 0xFFCCCCCCu;
|
|
DrawRoundedRect(thumbX - thumbR, y + h * 0.5f - thumbR,
|
|
thumbR * 2, thumbR * 2, thumbR, thumbColor);
|
|
}
|
|
}
|
|
|
|
void DrawTooltip(float x, float y, const wchar_t* text, float size)
|
|
{
|
|
if (!text || !text[0]) return;
|
|
|
|
float tw, th;
|
|
MeasureText(text, size, &tw, &th);
|
|
|
|
float padX = 10.0f, padY = 6.0f;
|
|
float boxW = tw + padX * 2;
|
|
float boxH = th + padY * 2;
|
|
float radius = 4.0f;
|
|
|
|
float bx = x - boxW * 0.5f;
|
|
float by = y - boxH - 6.0f;
|
|
|
|
DrawDropShadow(bx, by, boxW, boxH, 3.0f, 4.0f, 0x50000000u);
|
|
DrawRoundedRect(bx, by, boxW, boxH, radius, 0xF0181818u);
|
|
DrawRoundedBorder(bx, by, boxW, boxH, radius, 1.0f, 0xFF555555u);
|
|
DrawShadowText(x, by + padY, text, 0xFFFFFFFFu, size, ALIGN_CENTER_X);
|
|
}
|
|
|
|
int LoadTexture(const wchar_t* path)
|
|
{
|
|
if (!path || !path[0]) return -1;
|
|
Minecraft* mc = Minecraft::GetInstance();
|
|
if (!mc || !mc->textures) return -1;
|
|
|
|
return mc->textures->loadTextureByPath(std::wstring(path));
|
|
}
|
|
|
|
int LoadTextureByName(int textureName)
|
|
{
|
|
Minecraft* mc = Minecraft::GetInstance();
|
|
if (!mc || !mc->textures) return -1;
|
|
return mc->textures->loadTexture(textureName);
|
|
}
|
|
|
|
int LoadTextureFromFile(const char* filePath, int* outWidth, int* outHeight)
|
|
{
|
|
if (!filePath || !filePath[0]) return -1;
|
|
|
|
std::string key(filePath);
|
|
auto it = s_fileTexCache.find(key);
|
|
if (it != s_fileTexCache.end())
|
|
{
|
|
if (outWidth) *outWidth = it->second.w;
|
|
if (outHeight) *outHeight = it->second.h;
|
|
return it->second.id;
|
|
}
|
|
|
|
D3DXIMAGE_INFO info;
|
|
ZeroMemory(&info, sizeof(info));
|
|
int* pixelData = nullptr;
|
|
|
|
HRESULT hr = RenderManager.LoadTextureData(filePath, &info, &pixelData);
|
|
if (FAILED(hr) || !pixelData)
|
|
return -1;
|
|
|
|
int texId = RenderManager.TextureCreate();
|
|
if (texId < 0)
|
|
{
|
|
free(pixelData);
|
|
return -1;
|
|
}
|
|
|
|
RenderManager.TextureBind(texId);
|
|
RenderManager.TextureData(info.Width, info.Height, pixelData, 0);
|
|
free(pixelData);
|
|
|
|
s_fileTexCache[key] = { texId, info.Width, info.Height };
|
|
|
|
if (outWidth) *outWidth = info.Width;
|
|
if (outHeight) *outHeight = info.Height;
|
|
return texId;
|
|
}
|
|
|
|
int LoadTextureFromFileDirect(const char* filePath, int* outWidth, int* outHeight)
|
|
{
|
|
if (!filePath || !filePath[0]) return -1;
|
|
|
|
std::string key = std::string("$direct$") + filePath;
|
|
auto it = s_fileTexCache.find(key);
|
|
if (it != s_fileTexCache.end())
|
|
{
|
|
if (outWidth) *outWidth = it->second.w;
|
|
if (outHeight) *outHeight = it->second.h;
|
|
return it->second.id;
|
|
}
|
|
|
|
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
bool needUninit = SUCCEEDED(hr);
|
|
|
|
IWICImagingFactory* factory = nullptr;
|
|
IWICBitmapDecoder* decoder = nullptr;
|
|
IWICBitmapFrameDecode*frame = nullptr;
|
|
IWICFormatConverter* converter = nullptr;
|
|
|
|
auto cleanup = [&]() {
|
|
if (converter) converter->Release();
|
|
if (frame) frame->Release();
|
|
if (decoder) decoder->Release();
|
|
if (factory) factory->Release();
|
|
if (needUninit) CoUninitialize();
|
|
};
|
|
|
|
hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARGS(&factory));
|
|
if (FAILED(hr)) { cleanup(); return -1; }
|
|
|
|
int wlen = MultiByteToWideChar(CP_UTF8, 0, filePath, -1, nullptr, 0);
|
|
std::vector<wchar_t> wpath(wlen);
|
|
MultiByteToWideChar(CP_UTF8, 0, filePath, -1, wpath.data(), wlen);
|
|
|
|
hr = factory->CreateDecoderFromFilename(wpath.data(), nullptr,
|
|
GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder);
|
|
if (FAILED(hr)) { cleanup(); return -1; }
|
|
|
|
hr = decoder->GetFrame(0, &frame);
|
|
if (FAILED(hr)) { cleanup(); return -1; }
|
|
|
|
UINT w = 0, h = 0;
|
|
frame->GetSize(&w, &h);
|
|
|
|
hr = factory->CreateFormatConverter(&converter);
|
|
if (FAILED(hr)) { cleanup(); return -1; }
|
|
|
|
converter->Initialize(frame, GUID_WICPixelFormat32bppRGBA,
|
|
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom);
|
|
|
|
UINT stride = w * 4;
|
|
std::vector<BYTE> pixels(stride * h);
|
|
converter->CopyPixels(nullptr, stride, (UINT)pixels.size(), pixels.data());
|
|
|
|
cleanup();
|
|
|
|
int texId = RenderManager.TextureCreate();
|
|
if (texId < 0) return -1;
|
|
|
|
RenderManager.TextureBind(texId);
|
|
RenderManager.TextureData(w, h, pixels.data(), 0);
|
|
|
|
s_fileTexCache[key] = { texId, (int)w, (int)h };
|
|
if (outWidth) *outWidth = (int)w;
|
|
if (outHeight) *outHeight = (int)h;
|
|
return texId;
|
|
}
|
|
|
|
NineSlice LoadNineSlice(const char* basePath)
|
|
{
|
|
NineSlice ns = {};
|
|
ns.valid = false;
|
|
if (!basePath || !basePath[0]) return ns;
|
|
|
|
std::string base(basePath);
|
|
int cw = 0, ch = 0;
|
|
|
|
ns.tl = LoadTextureFromFile((base + "_TL.png").c_str(), &cw, &ch);
|
|
|
|
if (ns.tl >= 0)
|
|
{
|
|
ns.tm = LoadTextureFromFile((base + "_TM.png").c_str());
|
|
ns.tr = LoadTextureFromFile((base + "_TR.png").c_str());
|
|
ns.ml = LoadTextureFromFile((base + "_ML.png").c_str());
|
|
ns.mm = LoadTextureFromFile((base + "_MM.png").c_str());
|
|
ns.mr = LoadTextureFromFile((base + "_MR.png").c_str());
|
|
ns.bl = LoadTextureFromFile((base + "_BL.png").c_str());
|
|
ns.bm = LoadTextureFromFile((base + "_BM.png").c_str());
|
|
ns.br = LoadTextureFromFile((base + "_BR.png").c_str());
|
|
}
|
|
else
|
|
{
|
|
ns.tl = LoadTextureFromFile((base + "_Top_L.png").c_str(), &cw, &ch);
|
|
ns.tm = LoadTextureFromFile((base + "_Top_M.png").c_str());
|
|
ns.tr = LoadTextureFromFile((base + "_Top_R.png").c_str());
|
|
ns.ml = LoadTextureFromFile((base + "_Mid_L.png").c_str());
|
|
ns.mm = LoadTextureFromFile((base + "_Mid_M.png").c_str());
|
|
ns.mr = LoadTextureFromFile((base + "_Mid_R.png").c_str());
|
|
ns.bl = LoadTextureFromFile((base + "_Bot_L.png").c_str());
|
|
ns.bm = LoadTextureFromFile((base + "_Bot_M.png").c_str());
|
|
ns.br = LoadTextureFromFile((base + "_Bot_R.png").c_str());
|
|
}
|
|
|
|
ns.cornerW = cw;
|
|
ns.cornerH = ch;
|
|
|
|
ns.valid = (ns.tl >= 0 && ns.tm >= 0 && ns.tr >= 0 &&
|
|
ns.ml >= 0 && ns.mm >= 0 && ns.mr >= 0 &&
|
|
ns.bl >= 0 && ns.bm >= 0 && ns.br >= 0 &&
|
|
cw > 0 && ch > 0);
|
|
|
|
return ns;
|
|
}
|
|
|
|
ThreeSlice LoadThreeSlice(const char* basePath)
|
|
{
|
|
ThreeSlice ts = {};
|
|
ts.valid = false;
|
|
if (!basePath || !basePath[0]) return ts;
|
|
|
|
std::string base(basePath);
|
|
int cw = 0, ch = 0;
|
|
|
|
ts.left = LoadTextureFromFile((base + "_Left.png").c_str(), &cw, &ch);
|
|
ts.mid = LoadTextureFromFile((base + "_Middle.png").c_str());
|
|
ts.right = LoadTextureFromFile((base + "_Right.png").c_str());
|
|
|
|
ts.capW = cw;
|
|
ts.capH = ch;
|
|
|
|
ts.valid = (ts.left >= 0 && ts.mid >= 0 && ts.right >= 0 &&
|
|
cw > 0 && ch > 0);
|
|
|
|
return ts;
|
|
}
|
|
|
|
void DrawNineSlice(float x, float y, float w, float h,
|
|
const NineSlice& ns, uint32_t tint)
|
|
{
|
|
if (!ns.valid || !s_inFrame) return;
|
|
|
|
float cw = (float)ns.cornerW;
|
|
float ch = (float)ns.cornerH;
|
|
|
|
if (cw * 2 > w) cw = w * 0.5f;
|
|
if (ch * 2 > h) ch = h * 0.5f;
|
|
|
|
float midW = w - cw * 2;
|
|
float midH = h - ch * 2;
|
|
|
|
DrawTexture(x, y, cw, ch, ns.tl, tint);
|
|
DrawTexture(x + cw, y, midW, ch, ns.tm, tint);
|
|
DrawTexture(x + w - cw, y, cw, ch, ns.tr, tint);
|
|
|
|
DrawTexture(x, y + ch, cw, midH, ns.ml, tint);
|
|
DrawTexture(x + cw, y + ch, midW, midH, ns.mm, tint);
|
|
DrawTexture(x + w - cw, y + ch, cw, midH, ns.mr, tint);
|
|
|
|
DrawTexture(x, y + h - ch, cw, ch, ns.bl, tint);
|
|
DrawTexture(x + cw, y + h - ch, midW, ch, ns.bm, tint);
|
|
DrawTexture(x + w - cw, y + h - ch, cw, ch, ns.br, tint);
|
|
}
|
|
|
|
void DrawThreeSlice(float x, float y, float w, float h,
|
|
const ThreeSlice& ts, uint32_t tint)
|
|
{
|
|
if (!ts.valid || !s_inFrame) return;
|
|
|
|
float capW = (float)ts.capW;
|
|
|
|
if (capW * 2 > w) capW = w * 0.5f;
|
|
|
|
float midW = w - capW * 2;
|
|
|
|
DrawTexture(x, y, capW, h, ts.left, tint);
|
|
DrawTexture(x + capW, y, midW, h, ts.mid, tint);
|
|
DrawTexture(x + w - capW, y, capW, h, ts.right, tint);
|
|
}
|
|
|
|
void DrawTexture(float x, float y, float w, float h,
|
|
int textureId, uint32_t tint)
|
|
{
|
|
DrawTextureUV(x, y, w, h, textureId, 0.0f, 0.0f, 1.0f, 1.0f, tint);
|
|
}
|
|
|
|
void DrawTextureUV(float x, float y, float w, float h,
|
|
int textureId,
|
|
float u0, float v0, float u1, float v1,
|
|
uint32_t tint)
|
|
{
|
|
if (!s_inFrame || textureId < 0) return;
|
|
|
|
EnsureBatchMode(BATCH_TEXTURE, textureId);
|
|
|
|
float r, g, b, a;
|
|
DecodeColor(tint, r, g, b, a);
|
|
PushQuad(x, y, x + w, y + h, r, g, b, a, u0, v0, u1, v1);
|
|
}
|
|
|
|
void DrawTextureRounded(float x, float y, float w, float h,
|
|
int textureId, float radius, uint32_t tint)
|
|
{
|
|
if (!s_inFrame || textureId < 0) return;
|
|
|
|
PushClipRect(x + radius, y, w - radius * 2, h);
|
|
DrawTexture(x, y, w, h, textureId, tint);
|
|
PopClipRect();
|
|
|
|
PushClipRect(x, y + radius, radius, h - radius * 2);
|
|
DrawTexture(x, y, w, h, textureId, tint);
|
|
PopClipRect();
|
|
|
|
PushClipRect(x + w - radius, y + radius, radius, h - radius * 2);
|
|
DrawTexture(x, y, w, h, textureId, tint);
|
|
PopClipRect();
|
|
|
|
static constexpr int kSteps = 8;
|
|
static constexpr float kHalfPi = 1.5707963f;
|
|
|
|
float cx_[4] = { x + radius, x + w - radius, x + radius, x + w - radius };
|
|
float cy_[4] = { y + radius, y + radius, y + h - radius, y + h - radius };
|
|
float startA[4] = { kHalfPi * 2, kHalfPi * 3, kHalfPi, 0 };
|
|
|
|
for (int corner = 0; corner < 4; ++corner)
|
|
{
|
|
for (int step = 0; step < kSteps; ++step)
|
|
{
|
|
float a0 = startA[corner] + kHalfPi * step / kSteps;
|
|
float a1 = startA[corner] + kHalfPi * (step + 1) / kSteps;
|
|
|
|
float px0 = cx_[corner] + fminf(0.0f, fminf(cosf(a0), cosf(a1))) * radius;
|
|
float py0 = cy_[corner] + fminf(0.0f, fminf(sinf(a0), sinf(a1))) * radius;
|
|
float px1 = cx_[corner] + fmaxf(0.0f, fmaxf(cosf(a0), cosf(a1))) * radius;
|
|
float py1 = cy_[corner] + fmaxf(0.0f, fmaxf(sinf(a0), sinf(a1))) * radius;
|
|
|
|
if (px1 > px0 && py1 > py0)
|
|
{
|
|
PushClipRect(px0, py0, px1 - px0, py1 - py0);
|
|
DrawTexture(x, y, w, h, textureId, tint);
|
|
PopClipRect();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DrawTextureFit(float x, float y, float w, float h,
|
|
int textureId, int texW, int texH,
|
|
uint32_t tint)
|
|
{
|
|
if (!s_inFrame || textureId < 0 || texW <= 0 || texH <= 0) return;
|
|
|
|
float aspect = (float)texW / (float)texH;
|
|
float boxAspect = w / h;
|
|
|
|
float drawW, drawH;
|
|
if (aspect > boxAspect)
|
|
{
|
|
drawW = w;
|
|
drawH = w / aspect;
|
|
}
|
|
else
|
|
{
|
|
drawH = h;
|
|
drawW = h * aspect;
|
|
}
|
|
|
|
float drawX = x + (w - drawW) * 0.5f;
|
|
float drawY = y + (h - drawH) * 0.5f;
|
|
|
|
DrawTexture(drawX, drawY, drawW, drawH, textureId, tint);
|
|
}
|
|
|
|
bool NativeUI::GetMouseVirtual(float& outX, float& outY)
|
|
{
|
|
#ifdef _WINDOWS64
|
|
if (!g_hWnd) return false;
|
|
RECT rc;
|
|
GetClientRect(g_hWnd, &rc);
|
|
int winW = rc.right - rc.left;
|
|
int winH = rc.bottom - rc.top;
|
|
if (winW <= 0 || winH <= 0) return false;
|
|
|
|
float pixX = (float)g_KBMInput.GetMouseX() / (float)winW * s_bbWidth;
|
|
float pixY = (float)g_KBMInput.GetMouseY() / (float)winH * s_bbHeight;
|
|
|
|
outX = (pixX - s_vpX) / s_vpW * kVW;
|
|
outY = (pixY - s_vpY) / s_vpH * kVH;
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void FocusList::Add(int id, float x, float y, float w, float h)
|
|
{
|
|
m_entries.push_back({ id, x, y, w, h });
|
|
if (m_focusIdx >= (int)m_entries.size())
|
|
m_focusIdx = 0;
|
|
}
|
|
|
|
int FocusList::GetFocused() const
|
|
{
|
|
if (m_entries.empty()) return -1;
|
|
return m_entries[m_focusIdx].id;
|
|
}
|
|
|
|
void FocusList::MoveNext()
|
|
{
|
|
if (m_entries.empty()) return;
|
|
m_focusIdx = (m_focusIdx + 1) % (int)m_entries.size();
|
|
ui.PlayUISFX(eSFX_Focus);
|
|
}
|
|
|
|
void FocusList::MovePrev()
|
|
{
|
|
if (m_entries.empty()) return;
|
|
m_focusIdx = (m_focusIdx - 1 + (int)m_entries.size()) % (int)m_entries.size();
|
|
ui.PlayUISFX(eSFX_Focus);
|
|
}
|
|
|
|
void FocusList::SetFocus(int id)
|
|
{
|
|
for (int i = 0; i < (int)m_entries.size(); ++i)
|
|
{
|
|
if (m_entries[i].id == id)
|
|
{
|
|
m_focusIdx = i;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FocusList::HitTest(float mx, float my, int& outId) const
|
|
{
|
|
for (const Entry& e : m_entries)
|
|
{
|
|
if (mx >= e.x && mx <= e.x + e.w &&
|
|
my >= e.y && my <= e.y + e.h)
|
|
{
|
|
outId = e.id;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FocusList::UpdateHover(float mx, float my)
|
|
{
|
|
int hitId;
|
|
if (HitTest(mx, my, hitId))
|
|
{
|
|
m_hoveredId = hitId;
|
|
return true;
|
|
}
|
|
m_hoveredId = -1;
|
|
return false;
|
|
}
|
|
|
|
void FocusList::TickMouse()
|
|
{
|
|
#ifdef _WINDOWS64
|
|
if (m_mouseConsumed && !g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_LEFT))
|
|
m_mouseConsumed = false;
|
|
|
|
float mx, my;
|
|
if (!NativeUI::GetMouseVirtual(mx, my))
|
|
{
|
|
ClearHover();
|
|
m_lastHoveredId = -1;
|
|
return;
|
|
}
|
|
|
|
bool mouseMoved = (mx != m_lastMouseX || my != m_lastMouseY);
|
|
m_lastMouseX = mx;
|
|
m_lastMouseY = my;
|
|
|
|
if (!mouseMoved && m_lastDevice == eDevice_Gamepad)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (mouseMoved)
|
|
m_lastDevice = eDevice_Mouse;
|
|
|
|
int prevHover = m_lastHoveredId;
|
|
UpdateHover(mx, my);
|
|
m_lastHoveredId = m_hoveredId;
|
|
|
|
if (m_hoveredId >= 0 && m_hoveredId != prevHover)
|
|
ui.PlayUISFX(eSFX_Focus);
|
|
#endif
|
|
}
|
|
|
|
int FocusList::HandleMenuKey(int key, int backId,
|
|
float panelX, float panelY,
|
|
float panelW, float panelH)
|
|
{
|
|
switch (key)
|
|
{
|
|
case ACTION_MENU_UP:
|
|
case ACTION_MENU_LEFT:
|
|
m_lastDevice = eDevice_Gamepad;
|
|
ClearHover();
|
|
MovePrev();
|
|
return RESULT_NAVIGATED;
|
|
case ACTION_MENU_DOWN:
|
|
case ACTION_MENU_RIGHT:
|
|
m_lastDevice = eDevice_Gamepad;
|
|
ClearHover();
|
|
MoveNext();
|
|
return RESULT_NAVIGATED;
|
|
case ACTION_MENU_OK:
|
|
{
|
|
#ifdef _WINDOWS64
|
|
if (m_lastDevice == eDevice_Mouse &&
|
|
g_KBMInput.IsMouseButtonDown(KeyboardMouseInput::MOUSE_LEFT))
|
|
{
|
|
float mx, my;
|
|
if (NativeUI::GetMouseVirtual(mx, my))
|
|
{
|
|
int hitId;
|
|
if (HitTest(mx, my, hitId))
|
|
{
|
|
SetFocus(hitId);
|
|
ui.PlayUISFX(eSFX_Press);
|
|
m_mouseConsumed = true;
|
|
return hitId;
|
|
}
|
|
if (mx >= panelX && mx <= panelX + panelW &&
|
|
my >= panelY && my <= panelY + panelH)
|
|
{
|
|
m_mouseConsumed = true;
|
|
return RESULT_UNHANDLED;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
m_lastDevice = eDevice_Gamepad;
|
|
ui.PlayUISFX(eSFX_Press);
|
|
return GetFocused();
|
|
}
|
|
case ACTION_MENU_CANCEL:
|
|
ui.PlayUISFX(eSFX_Back);
|
|
return backId;
|
|
default:
|
|
return RESULT_UNHANDLED;
|
|
}
|
|
}
|
|
|
|
void DrawLink(float x, float y, const wchar_t* text,
|
|
const char* /*url*/, bool focused, bool hovered,
|
|
float size, uint32_t align,
|
|
float* outX, float* outY, float* outW, float* outH)
|
|
{
|
|
if (!text || !text[0] || !s_inFrame) return;
|
|
|
|
float tw, th;
|
|
MeasureText(text, size, &tw, &th);
|
|
|
|
float drawX = x, drawY = y;
|
|
if (align & ALIGN_CENTER_X) drawX = x - tw * 0.5f;
|
|
else if (align & ALIGN_RIGHT) drawX = x - tw;
|
|
if (align & ALIGN_CENTER_Y) drawY = y - th * 0.5f;
|
|
else if (align & ALIGN_BOTTOM) drawY = y - th;
|
|
|
|
uint32_t color;
|
|
float padX = 6.0f, padY = 3.0f;
|
|
|
|
if (focused || hovered)
|
|
{
|
|
color = 0xFFFFFF55u;
|
|
}
|
|
else
|
|
{
|
|
color = 0xFF4DC3FFu;
|
|
}
|
|
|
|
DrawShadowText(x, y, text, color, size, align);
|
|
|
|
float underlineY = drawY + th + 1.0f;
|
|
float underlineThick = (focused || hovered) ? 2.0f : 1.0f;
|
|
DrawRect(drawX, underlineY, tw, underlineThick, color);
|
|
|
|
if (outX) *outX = drawX - padX;
|
|
if (outY) *outY = drawY - padY;
|
|
if (outW) *outW = tw + padX * 2;
|
|
if (outH) *outH = th + padY * 2 + 2;
|
|
}
|
|
|
|
void OpenURL(const char* url)
|
|
{
|
|
if (!url || !url[0]) return;
|
|
#ifdef _WINDOWS64
|
|
ShellExecuteA(nullptr, "open", url, nullptr, nullptr, SW_SHOWNORMAL);
|
|
#endif
|
|
}
|
|
|
|
}
|