broken ui system
This commit is contained in:
3
Makefile
3
Makefile
@@ -19,7 +19,8 @@ src/controls.cpp \
|
|||||||
src/profiler.cpp \
|
src/profiler.cpp \
|
||||||
src/collision.cpp \
|
src/collision.cpp \
|
||||||
src/bvh.cpp \
|
src/bvh.cpp \
|
||||||
src/cutscene.cpp
|
src/cutscene.cpp \
|
||||||
|
src/uisystem.cpp
|
||||||
|
|
||||||
CPPFLAGS += -DPCDRV_SUPPORT=1
|
CPPFLAGS += -DPCDRV_SUPPORT=1
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <psyqo/fixed-point.hh>
|
#include <psyqo/fixed-point.hh>
|
||||||
#include <psyqo/soft-math.hh>
|
#include <psyqo/soft-math.hh>
|
||||||
#include <psyqo/trigonometry.hh>
|
#include <psyqo/trigonometry.hh>
|
||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
|
|
||||||
@@ -14,7 +15,8 @@ static bool cs_streq(const char* a, const char* b) {
|
|||||||
return *a == *b;
|
return *a == *b;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio) {
|
void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
|
||||||
|
UISystem* uiSystem) {
|
||||||
m_cutscenes = cutscenes;
|
m_cutscenes = cutscenes;
|
||||||
m_count = count;
|
m_count = count;
|
||||||
m_active = nullptr;
|
m_active = nullptr;
|
||||||
@@ -22,6 +24,7 @@ void CutscenePlayer::init(Cutscene* cutscenes, int count, Camera* camera, AudioM
|
|||||||
m_nextAudio = 0;
|
m_nextAudio = 0;
|
||||||
m_camera = camera;
|
m_camera = camera;
|
||||||
m_audio = audio;
|
m_audio = audio;
|
||||||
|
m_uiSystem = uiSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CutscenePlayer::play(const char* name) {
|
bool CutscenePlayer::play(const char* name) {
|
||||||
@@ -68,6 +71,38 @@ bool CutscenePlayer::play(const char* name) {
|
|||||||
track.initialValues[0] = track.target->isActive() ? 1 : 0;
|
track.initialValues[0] = track.target->isActive() ? 1 : 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TrackType::UICanvasVisible:
|
||||||
|
if (m_uiSystem) {
|
||||||
|
track.initialValues[0] = m_uiSystem->isCanvasVisible(track.uiHandle) ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TrackType::UIElementVisible:
|
||||||
|
if (m_uiSystem) {
|
||||||
|
track.initialValues[0] = m_uiSystem->isElementVisible(track.uiHandle) ? 1 : 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TrackType::UIProgress:
|
||||||
|
if (m_uiSystem) {
|
||||||
|
track.initialValues[0] = m_uiSystem->getProgress(track.uiHandle);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TrackType::UIPosition:
|
||||||
|
if (m_uiSystem) {
|
||||||
|
int16_t px, py;
|
||||||
|
m_uiSystem->getPosition(track.uiHandle, px, py);
|
||||||
|
track.initialValues[0] = px;
|
||||||
|
track.initialValues[1] = py;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TrackType::UIColor:
|
||||||
|
if (m_uiSystem) {
|
||||||
|
uint8_t cr, cg, cb;
|
||||||
|
m_uiSystem->getColor(track.uiHandle, cr, cg, cb);
|
||||||
|
track.initialValues[0] = cr;
|
||||||
|
track.initialValues[1] = cg;
|
||||||
|
track.initialValues[2] = cb;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +139,7 @@ void CutscenePlayer::tick() {
|
|||||||
|
|
||||||
// Advance frame
|
// Advance frame
|
||||||
m_frame++;
|
m_frame++;
|
||||||
if (m_frame >= m_active->totalFrames) {
|
if (m_frame > m_active->totalFrames) {
|
||||||
m_active = nullptr; // Cutscene finished
|
m_active = nullptr; // Cutscene finished
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,6 +331,63 @@ void CutscenePlayer::applyTrack(CutsceneTrack& track) {
|
|||||||
track.target->setActive(activeVal != 0);
|
track.target->setActive(activeVal != 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── UI track types ──
|
||||||
|
|
||||||
|
case TrackType::UICanvasVisible: {
|
||||||
|
if (!m_uiSystem) return;
|
||||||
|
CutsceneKeyframe* kf = track.keyframes;
|
||||||
|
uint8_t count = track.keyframeCount;
|
||||||
|
int16_t val = (count > 0 && m_frame < kf[0].getFrame())
|
||||||
|
? track.initialValues[0] : kf[0].values[0];
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
if (kf[i].getFrame() <= m_frame) val = kf[i].values[0];
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
m_uiSystem->setCanvasVisible(track.uiHandle, val != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TrackType::UIElementVisible: {
|
||||||
|
if (!m_uiSystem) return;
|
||||||
|
CutsceneKeyframe* kf = track.keyframes;
|
||||||
|
uint8_t count = track.keyframeCount;
|
||||||
|
int16_t val = (count > 0 && m_frame < kf[0].getFrame())
|
||||||
|
? track.initialValues[0] : kf[0].values[0];
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
if (kf[i].getFrame() <= m_frame) val = kf[i].values[0];
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
m_uiSystem->setElementVisible(track.uiHandle, val != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TrackType::UIProgress: {
|
||||||
|
if (!m_uiSystem) return;
|
||||||
|
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out);
|
||||||
|
int16_t v = out[0];
|
||||||
|
if (v < 0) v = 0;
|
||||||
|
if (v > 100) v = 100;
|
||||||
|
m_uiSystem->setProgress(track.uiHandle, (uint8_t)v);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TrackType::UIPosition: {
|
||||||
|
if (!m_uiSystem) return;
|
||||||
|
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out);
|
||||||
|
m_uiSystem->setPosition(track.uiHandle, out[0], out[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TrackType::UIColor: {
|
||||||
|
if (!m_uiSystem) return;
|
||||||
|
lerpKeyframes(track.keyframes, track.keyframeCount, track.initialValues, out);
|
||||||
|
uint8_t cr = (out[0] < 0) ? 0 : ((out[0] > 255) ? 255 : (uint8_t)out[0]);
|
||||||
|
uint8_t cg = (out[1] < 0) ? 0 : ((out[1] > 255) ? 255 : (uint8_t)out[1]);
|
||||||
|
uint8_t cb = (out[2] < 0) ? 0 : ((out[2] > 255) ? 255 : (uint8_t)out[2]);
|
||||||
|
m_uiSystem->setColor(track.uiHandle, cr, cg, cb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
|
|
||||||
|
class UISystem; // Forward declaration
|
||||||
|
|
||||||
static constexpr int MAX_CUTSCENES = 16;
|
static constexpr int MAX_CUTSCENES = 16;
|
||||||
static constexpr int MAX_TRACKS = 8;
|
static constexpr int MAX_TRACKS = 8;
|
||||||
static constexpr int MAX_KEYFRAMES = 64;
|
static constexpr int MAX_KEYFRAMES = 64;
|
||||||
@@ -22,6 +24,12 @@ enum class TrackType : uint8_t {
|
|||||||
ObjectPosition = 2,
|
ObjectPosition = 2,
|
||||||
ObjectRotationY = 3,
|
ObjectRotationY = 3,
|
||||||
ObjectActive = 4,
|
ObjectActive = 4,
|
||||||
|
// UI track types (v13+)
|
||||||
|
UICanvasVisible = 5, // Step: values[0] = 0/1. target unused, uiHandle = canvas index.
|
||||||
|
UIElementVisible= 6, // Step: values[0] = 0/1. uiHandle = element handle.
|
||||||
|
UIProgress = 7, // Linear: values[0] = 0-100. uiHandle = element handle.
|
||||||
|
UIPosition = 8, // Linear: values[0] = x, values[1] = y. uiHandle = element handle.
|
||||||
|
UIColor = 9, // Linear: values[0] = r, values[1] = g, values[2] = b. uiHandle = element handle.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Per-keyframe interpolation mode.
|
/// Per-keyframe interpolation mode.
|
||||||
@@ -59,13 +67,16 @@ struct CutsceneTrack {
|
|||||||
uint8_t keyframeCount;
|
uint8_t keyframeCount;
|
||||||
uint8_t pad[2];
|
uint8_t pad[2];
|
||||||
CutsceneKeyframe* keyframes; // Points into splashpack data (resolved at load time)
|
CutsceneKeyframe* keyframes; // Points into splashpack data (resolved at load time)
|
||||||
GameObject* target; // nullptr = camera track
|
GameObject* target; // nullptr = camera track or UI track
|
||||||
|
|
||||||
|
/// For UI tracks: flat handle into UISystem (canvas index or element handle).
|
||||||
|
/// Set during cutscene load by resolving canvas/element names.
|
||||||
|
int16_t uiHandle;
|
||||||
|
|
||||||
/// Initial values captured at play() time for pre-first-keyframe blending.
|
/// Initial values captured at play() time for pre-first-keyframe blending.
|
||||||
/// For position tracks: fp12 x,y,z. For rotation tracks: raw angle values.
|
/// For position tracks: fp12 x,y,z. For rotation tracks: raw angle values.
|
||||||
/// For ObjectActive: values[0] = 1 (active) or 0 (inactive).
|
/// For ObjectActive: values[0] = 1 (active) or 0 (inactive).
|
||||||
int16_t initialValues[3];
|
int16_t initialValues[3];
|
||||||
int16_t _initPad;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Cutscene {
|
struct Cutscene {
|
||||||
@@ -82,7 +93,8 @@ struct Cutscene {
|
|||||||
class CutscenePlayer {
|
class CutscenePlayer {
|
||||||
public:
|
public:
|
||||||
/// Initialize with loaded cutscene data. Safe to pass nullptr/0 if no cutscenes.
|
/// Initialize with loaded cutscene data. Safe to pass nullptr/0 if no cutscenes.
|
||||||
void init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio);
|
void init(Cutscene* cutscenes, int count, Camera* camera, AudioManager* audio,
|
||||||
|
UISystem* uiSystem = nullptr);
|
||||||
|
|
||||||
/// Play cutscene by name. Returns false if not found.
|
/// Play cutscene by name. Returns false if not found.
|
||||||
bool play(const char* name);
|
bool play(const char* name);
|
||||||
@@ -104,6 +116,7 @@ private:
|
|||||||
uint8_t m_nextAudio = 0;
|
uint8_t m_nextAudio = 0;
|
||||||
Camera* m_camera = nullptr;
|
Camera* m_camera = nullptr;
|
||||||
AudioManager* m_audio = nullptr;
|
AudioManager* m_audio = nullptr;
|
||||||
|
UISystem* m_uiSystem = nullptr;
|
||||||
|
|
||||||
psyqo::Trig<> m_trig;
|
psyqo::Trig<> m_trig;
|
||||||
|
|
||||||
|
|||||||
320
src/luaapi.cpp
320
src/luaapi.cpp
@@ -4,6 +4,7 @@
|
|||||||
#include "controls.hh"
|
#include "controls.hh"
|
||||||
#include "camera.hh"
|
#include "camera.hh"
|
||||||
#include "cutscene.hh"
|
#include "cutscene.hh"
|
||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
#include <psyqo/soft-math.hh>
|
#include <psyqo/soft-math.hh>
|
||||||
#include <psyqo/trigonometry.hh>
|
#include <psyqo/trigonometry.hh>
|
||||||
@@ -15,6 +16,7 @@ namespace psxsplash {
|
|||||||
// Static member
|
// Static member
|
||||||
SceneManager* LuaAPI::s_sceneManager = nullptr;
|
SceneManager* LuaAPI::s_sceneManager = nullptr;
|
||||||
CutscenePlayer* LuaAPI::s_cutscenePlayer = nullptr;
|
CutscenePlayer* LuaAPI::s_cutscenePlayer = nullptr;
|
||||||
|
UISystem* LuaAPI::s_uiSystem = nullptr;
|
||||||
|
|
||||||
// Scale factor: FixedPoint<12> stores 1.0 as raw 4096.
|
// Scale factor: FixedPoint<12> stores 1.0 as raw 4096.
|
||||||
// Lua scripts work in world-space units (1 = one unit), so we convert.
|
// Lua scripts work in world-space units (1 = one unit), so we convert.
|
||||||
@@ -36,9 +38,10 @@ static psyqo::Trig<> s_trig;
|
|||||||
// REGISTRATION
|
// REGISTRATION
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer) {
|
void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer, UISystem* uiSystem) {
|
||||||
s_sceneManager = scene;
|
s_sceneManager = scene;
|
||||||
s_cutscenePlayer = cutscenePlayer;
|
s_cutscenePlayer = cutscenePlayer;
|
||||||
|
s_uiSystem = uiSystem;
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// ENTITY API
|
// ENTITY API
|
||||||
@@ -281,6 +284,73 @@ void LuaAPI::RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cut
|
|||||||
L.setField(-2, "IsPlaying");
|
L.setField(-2, "IsPlaying");
|
||||||
|
|
||||||
L.setGlobal("Cutscene");
|
L.setGlobal("Cutscene");
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// UI API
|
||||||
|
// ========================================================================
|
||||||
|
L.newTable(); // UI table
|
||||||
|
|
||||||
|
L.push(UI_FindCanvas);
|
||||||
|
L.setField(-2, "FindCanvas");
|
||||||
|
|
||||||
|
L.push(UI_SetCanvasVisible);
|
||||||
|
L.setField(-2, "SetCanvasVisible");
|
||||||
|
|
||||||
|
L.push(UI_IsCanvasVisible);
|
||||||
|
L.setField(-2, "IsCanvasVisible");
|
||||||
|
|
||||||
|
L.push(UI_FindElement);
|
||||||
|
L.setField(-2, "FindElement");
|
||||||
|
|
||||||
|
L.push(UI_SetVisible);
|
||||||
|
L.setField(-2, "SetVisible");
|
||||||
|
|
||||||
|
L.push(UI_IsVisible);
|
||||||
|
L.setField(-2, "IsVisible");
|
||||||
|
|
||||||
|
L.push(UI_SetText);
|
||||||
|
L.setField(-2, "SetText");
|
||||||
|
|
||||||
|
L.push(UI_GetText);
|
||||||
|
L.setField(-2, "GetText");
|
||||||
|
|
||||||
|
L.push(UI_SetProgress);
|
||||||
|
L.setField(-2, "SetProgress");
|
||||||
|
|
||||||
|
L.push(UI_GetProgress);
|
||||||
|
L.setField(-2, "GetProgress");
|
||||||
|
|
||||||
|
L.push(UI_SetColor);
|
||||||
|
L.setField(-2, "SetColor");
|
||||||
|
|
||||||
|
L.push(UI_GetColor);
|
||||||
|
L.setField(-2, "GetColor");
|
||||||
|
|
||||||
|
L.push(UI_SetPosition);
|
||||||
|
L.setField(-2, "SetPosition");
|
||||||
|
|
||||||
|
L.push(UI_GetPosition);
|
||||||
|
L.setField(-2, "GetPosition");
|
||||||
|
|
||||||
|
L.push(UI_SetSize);
|
||||||
|
L.setField(-2, "SetSize");
|
||||||
|
|
||||||
|
L.push(UI_GetSize);
|
||||||
|
L.setField(-2, "GetSize");
|
||||||
|
|
||||||
|
L.push(UI_SetProgressColors);
|
||||||
|
L.setField(-2, "SetProgressColors");
|
||||||
|
|
||||||
|
L.push(UI_GetElementType);
|
||||||
|
L.setField(-2, "GetElementType");
|
||||||
|
|
||||||
|
L.push(UI_GetElementCount);
|
||||||
|
L.setField(-2, "GetElementCount");
|
||||||
|
|
||||||
|
L.push(UI_GetElementByIndex);
|
||||||
|
L.setField(-2, "GetElementByIndex");
|
||||||
|
|
||||||
|
L.setGlobal("UI");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -1437,4 +1507,252 @@ int LuaAPI::Cutscene_IsPlaying(lua_State* L) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// UI API IMPLEMENTATION
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
int LuaAPI::UI_FindCanvas(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isString(1)) {
|
||||||
|
lua.pushNumber(-1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const char* name = lua.toString(1);
|
||||||
|
int idx = s_uiSystem->findCanvas(name);
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(idx));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetCanvasVisible(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem) return 0;
|
||||||
|
int idx;
|
||||||
|
// Accept number (index) or string (name)
|
||||||
|
if (lua.isNumber(1)) {
|
||||||
|
idx = static_cast<int>(lua.toNumber(1));
|
||||||
|
} else if (lua.isString(1)) {
|
||||||
|
idx = s_uiSystem->findCanvas(lua.toString(1));
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
bool visible = lua.toBoolean(2);
|
||||||
|
s_uiSystem->setCanvasVisible(idx, visible);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_IsCanvasVisible(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem) {
|
||||||
|
lua.push(false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int idx;
|
||||||
|
if (lua.isNumber(1)) {
|
||||||
|
idx = static_cast<int>(lua.toNumber(1));
|
||||||
|
} else if (lua.isString(1)) {
|
||||||
|
idx = s_uiSystem->findCanvas(lua.toString(1));
|
||||||
|
} else {
|
||||||
|
lua.push(false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
lua.push(s_uiSystem->isCanvasVisible(idx));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_FindElement(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1) || !lua.isString(2)) {
|
||||||
|
lua.pushNumber(-1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||||||
|
const char* name = lua.toString(2);
|
||||||
|
int handle = s_uiSystem->findElement(canvasIdx, name);
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(handle));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetVisible(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
bool visible = lua.toBoolean(2);
|
||||||
|
s_uiSystem->setElementVisible(handle, visible);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_IsVisible(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.push(false);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
lua.push(s_uiSystem->isElementVisible(handle));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetText(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
const char* text = lua.isString(2) ? lua.toString(2) : "";
|
||||||
|
s_uiSystem->setText(handle, text);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetProgress(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
int value = static_cast<int>(lua.toNumber(2));
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
if (value > 100) value = 100;
|
||||||
|
s_uiSystem->setProgress(handle, (uint8_t)value);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetProgress(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(s_uiSystem->getProgress(handle)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetColor(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
uint8_t r = static_cast<uint8_t>(lua.toNumber(2));
|
||||||
|
uint8_t g = static_cast<uint8_t>(lua.toNumber(3));
|
||||||
|
uint8_t b = static_cast<uint8_t>(lua.toNumber(4));
|
||||||
|
s_uiSystem->setColor(handle, r, g, b);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetPosition(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
int16_t x = static_cast<int16_t>(lua.toNumber(2));
|
||||||
|
int16_t y = static_cast<int16_t>(lua.toNumber(3));
|
||||||
|
s_uiSystem->setPosition(handle, x, y);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetText(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.push("");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
lua.push(s_uiSystem->getText(handle));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetColor(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(0); lua.pushNumber(0); lua.pushNumber(0);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
uint8_t r, g, b;
|
||||||
|
s_uiSystem->getColor(handle, r, g, b);
|
||||||
|
lua.pushNumber(r); lua.pushNumber(g); lua.pushNumber(b);
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetPosition(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(0); lua.pushNumber(0);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
int16_t x, y;
|
||||||
|
s_uiSystem->getPosition(handle, x, y);
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(x));
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(y));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetSize(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
int16_t w = static_cast<int16_t>(lua.toNumber(2));
|
||||||
|
int16_t h = static_cast<int16_t>(lua.toNumber(3));
|
||||||
|
s_uiSystem->setSize(handle, w, h);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetSize(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(0); lua.pushNumber(0);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
int16_t w, h;
|
||||||
|
s_uiSystem->getSize(handle, w, h);
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(w));
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(h));
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_SetProgressColors(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) return 0;
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
uint8_t bgR = static_cast<uint8_t>(lua.toNumber(2));
|
||||||
|
uint8_t bgG = static_cast<uint8_t>(lua.toNumber(3));
|
||||||
|
uint8_t bgB = static_cast<uint8_t>(lua.toNumber(4));
|
||||||
|
uint8_t fR = static_cast<uint8_t>(lua.toNumber(5));
|
||||||
|
uint8_t fG = static_cast<uint8_t>(lua.toNumber(6));
|
||||||
|
uint8_t fB = static_cast<uint8_t>(lua.toNumber(7));
|
||||||
|
s_uiSystem->setProgressColors(handle, bgR, bgG, bgB, fR, fG, fB);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetElementType(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(-1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int handle = static_cast<int>(lua.toNumber(1));
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(static_cast<uint8_t>(s_uiSystem->getElementType(handle))));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetElementCount(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1)) {
|
||||||
|
lua.pushNumber(0);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(s_uiSystem->getCanvasElementCount(canvasIdx)));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int LuaAPI::UI_GetElementByIndex(lua_State* L) {
|
||||||
|
psyqo::Lua lua(L);
|
||||||
|
if (!s_uiSystem || !lua.isNumber(1) || !lua.isNumber(2)) {
|
||||||
|
lua.pushNumber(-1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int canvasIdx = static_cast<int>(lua.toNumber(1));
|
||||||
|
int elemIdx = static_cast<int>(lua.toNumber(2));
|
||||||
|
int handle = s_uiSystem->getCanvasElementHandle(canvasIdx, elemIdx);
|
||||||
|
lua.pushNumber(static_cast<lua_Number>(handle));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace psxsplash
|
} // namespace psxsplash
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ namespace psxsplash {
|
|||||||
|
|
||||||
class SceneManager; // Forward declaration
|
class SceneManager; // Forward declaration
|
||||||
class CutscenePlayer; // Forward declaration
|
class CutscenePlayer; // Forward declaration
|
||||||
|
class UISystem; // Forward declaration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lua API - Provides game scripting functionality
|
* Lua API - Provides game scripting functionality
|
||||||
@@ -24,7 +25,7 @@ class CutscenePlayer; // Forward declaration
|
|||||||
class LuaAPI {
|
class LuaAPI {
|
||||||
public:
|
public:
|
||||||
// Initialize all API modules
|
// Initialize all API modules
|
||||||
static void RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer = nullptr);
|
static void RegisterAll(psyqo::Lua& L, SceneManager* scene, CutscenePlayer* cutscenePlayer = nullptr, UISystem* uiSystem = nullptr);
|
||||||
|
|
||||||
// Called once per frame to advance the Lua frame counter
|
// Called once per frame to advance the Lua frame counter
|
||||||
static void IncrementFrameCount();
|
static void IncrementFrameCount();
|
||||||
@@ -39,6 +40,9 @@ private:
|
|||||||
// Cutscene player pointer (set during RegisterAll)
|
// Cutscene player pointer (set during RegisterAll)
|
||||||
static CutscenePlayer* s_cutscenePlayer;
|
static CutscenePlayer* s_cutscenePlayer;
|
||||||
|
|
||||||
|
// UI system pointer (set during RegisterAll)
|
||||||
|
static UISystem* s_uiSystem;
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// ENTITY API
|
// ENTITY API
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
@@ -267,6 +271,31 @@ private:
|
|||||||
// Cutscene.IsPlaying() -> boolean
|
// Cutscene.IsPlaying() -> boolean
|
||||||
static int Cutscene_IsPlaying(lua_State* L);
|
static int Cutscene_IsPlaying(lua_State* L);
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// UI API - Canvas and element control
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
static int UI_FindCanvas(lua_State* L);
|
||||||
|
static int UI_SetCanvasVisible(lua_State* L);
|
||||||
|
static int UI_IsCanvasVisible(lua_State* L);
|
||||||
|
static int UI_FindElement(lua_State* L);
|
||||||
|
static int UI_SetVisible(lua_State* L);
|
||||||
|
static int UI_IsVisible(lua_State* L);
|
||||||
|
static int UI_SetText(lua_State* L);
|
||||||
|
static int UI_GetText(lua_State* L);
|
||||||
|
static int UI_SetProgress(lua_State* L);
|
||||||
|
static int UI_GetProgress(lua_State* L);
|
||||||
|
static int UI_SetColor(lua_State* L);
|
||||||
|
static int UI_GetColor(lua_State* L);
|
||||||
|
static int UI_SetPosition(lua_State* L);
|
||||||
|
static int UI_GetPosition(lua_State* L);
|
||||||
|
static int UI_SetSize(lua_State* L);
|
||||||
|
static int UI_GetSize(lua_State* L);
|
||||||
|
static int UI_SetProgressColors(lua_State* L);
|
||||||
|
static int UI_GetElementType(lua_State* L);
|
||||||
|
static int UI_GetElementCount(lua_State* L);
|
||||||
|
static int UI_GetElementByIndex(lua_State* L);
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// HELPERS
|
// HELPERS
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ void PSXSplash::prepare() {
|
|||||||
|
|
||||||
void PSXSplash::createScene() {
|
void PSXSplash::createScene() {
|
||||||
m_font.uploadSystemFont(gpu());
|
m_font.uploadSystemFont(gpu());
|
||||||
|
psxsplash::SceneManager::SetFont(&m_font);
|
||||||
pushScene(&mainScene);
|
pushScene(&mainScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <psyqo/vector.hh>
|
#include <psyqo/vector.hh>
|
||||||
|
|
||||||
#include "gtemath.hh"
|
#include "gtemath.hh"
|
||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
using namespace psyqo::fixed_point_literals;
|
using namespace psyqo::fixed_point_literals;
|
||||||
using namespace psyqo::trig_literals;
|
using namespace psyqo::trig_literals;
|
||||||
@@ -275,8 +276,10 @@ void psxsplash::Renderer::Render(eastl::vector<GameObject*>& objects) {
|
|||||||
for (int i = 0; i < obj->polyCount; i++)
|
for (int i = 0; i < obj->polyCount; i++)
|
||||||
processTriangle(obj->polygons[i], fogFarSZ, ot, balloc);
|
processTriangle(obj->polygons[i], fogFarSZ, ot, balloc);
|
||||||
}
|
}
|
||||||
|
if (m_uiSystem) m_uiSystem->renderOT(m_gpu, ot, balloc);
|
||||||
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
||||||
m_gpu.chain(clear); m_gpu.chain(ot);
|
m_gpu.chain(clear); m_gpu.chain(ot);
|
||||||
|
if (m_uiSystem) m_uiSystem->renderText(m_gpu);
|
||||||
m_frameCount++;
|
m_frameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,8 +310,10 @@ void psxsplash::Renderer::RenderWithBVH(eastl::vector<GameObject*>& objects, con
|
|||||||
}
|
}
|
||||||
processTriangle(obj->polygons[ref.triangleIndex], fogFarSZ, ot, balloc);
|
processTriangle(obj->polygons[ref.triangleIndex], fogFarSZ, ot, balloc);
|
||||||
}
|
}
|
||||||
|
if (m_uiSystem) m_uiSystem->renderOT(m_gpu, ot, balloc);
|
||||||
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
||||||
m_gpu.chain(clear); m_gpu.chain(ot);
|
m_gpu.chain(clear); m_gpu.chain(ot);
|
||||||
|
if (m_uiSystem) m_uiSystem->renderText(m_gpu);
|
||||||
m_frameCount++;
|
m_frameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,8 +755,10 @@ void psxsplash::Renderer::RenderWithRooms(eastl::vector<GameObject*>& objects,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (m_uiSystem) m_uiSystem->renderOT(m_gpu, ot, balloc);
|
||||||
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
m_gpu.getNextClear(clear.primitive, m_clearcolor);
|
||||||
m_gpu.chain(clear); m_gpu.chain(ot);
|
m_gpu.chain(clear); m_gpu.chain(ot);
|
||||||
|
if (m_uiSystem) m_uiSystem->renderText(m_gpu);
|
||||||
m_frameCount++;
|
m_frameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
|
|
||||||
|
class UISystem; // Forward declaration
|
||||||
|
|
||||||
struct FogConfig {
|
struct FogConfig {
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
psyqo::Color color = {.r = 0, .g = 0, .b = 0};
|
psyqo::Color color = {.r = 0, .g = 0, .b = 0};
|
||||||
@@ -55,6 +57,9 @@ class Renderer final {
|
|||||||
void VramUpload(const uint16_t* imageData, int16_t posX, int16_t posY,
|
void VramUpload(const uint16_t* imageData, int16_t posX, int16_t posY,
|
||||||
int16_t width, int16_t height);
|
int16_t width, int16_t height);
|
||||||
|
|
||||||
|
void SetUISystem(UISystem* ui) { m_uiSystem = ui; }
|
||||||
|
psyqo::GPU& getGPU() { return m_gpu; }
|
||||||
|
|
||||||
static Renderer& GetInstance() {
|
static Renderer& GetInstance() {
|
||||||
psyqo::Kernel::assert(instance != nullptr,
|
psyqo::Kernel::assert(instance != nullptr,
|
||||||
"Access to renderer was tried without prior initialization");
|
"Access to renderer was tried without prior initialization");
|
||||||
@@ -78,6 +83,8 @@ class Renderer final {
|
|||||||
FogConfig m_fog;
|
FogConfig m_fog;
|
||||||
psyqo::Color m_clearcolor = {.r = 0, .g = 0, .b = 0};
|
psyqo::Color m_clearcolor = {.r = 0, .g = 0, .b = 0};
|
||||||
|
|
||||||
|
UISystem* m_uiSystem = nullptr;
|
||||||
|
|
||||||
TriangleRef m_visibleRefs[MAX_VISIBLE_TRIANGLES];
|
TriangleRef m_visibleRefs[MAX_VISIBLE_TRIANGLES];
|
||||||
int m_frameCount = 0;
|
int m_frameCount = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ using namespace psyqo::fixed_point_literals;
|
|||||||
|
|
||||||
using namespace psxsplash;
|
using namespace psxsplash;
|
||||||
|
|
||||||
|
// Static member definition
|
||||||
|
psyqo::Font<>* psxsplash::SceneManager::s_font = nullptr;
|
||||||
|
|
||||||
void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
||||||
L.Reset();
|
L.Reset();
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
|||||||
m_audio.init();
|
m_audio.init();
|
||||||
|
|
||||||
// Register the Lua API
|
// Register the Lua API
|
||||||
LuaAPI::RegisterAll(L.getState(), this, &m_cutscenePlayer);
|
LuaAPI::RegisterAll(L.getState(), this, &m_cutscenePlayer, &m_uiSystem);
|
||||||
|
|
||||||
#ifdef PSXSPLASH_PROFILER
|
#ifdef PSXSPLASH_PROFILER
|
||||||
debug::Profiler::getInstance().initialize();
|
debug::Profiler::getInstance().initialize();
|
||||||
@@ -84,9 +87,56 @@ void psxsplash::SceneManager::InitializeScene(uint8_t* splashpackData) {
|
|||||||
m_cutsceneCount > 0 ? m_cutscenes : nullptr,
|
m_cutsceneCount > 0 ? m_cutscenes : nullptr,
|
||||||
m_cutsceneCount,
|
m_cutsceneCount,
|
||||||
&m_currentCamera,
|
&m_currentCamera,
|
||||||
&m_audio
|
&m_audio,
|
||||||
|
&m_uiSystem
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialize UI system (v13+)
|
||||||
|
if (sceneSetup.uiCanvasCount > 0 && sceneSetup.uiTableOffset != 0 && s_font != nullptr) {
|
||||||
|
m_uiSystem.init(*s_font);
|
||||||
|
m_uiSystem.loadFromSplashpack(splashpackData, sceneSetup.uiCanvasCount,
|
||||||
|
sceneSetup.uiFontCount, sceneSetup.uiTableOffset);
|
||||||
|
m_uiSystem.uploadFonts(Renderer::GetInstance().getGPU());
|
||||||
|
Renderer::GetInstance().SetUISystem(&m_uiSystem);
|
||||||
|
|
||||||
|
// Resolve UI track handles: the splashpack loader stored raw name pointers
|
||||||
|
// in CutsceneTrack.target for UI tracks. Now that UISystem is loaded, resolve
|
||||||
|
// those names to canvas indices / element handles.
|
||||||
|
for (int ci = 0; ci < m_cutsceneCount; ci++) {
|
||||||
|
for (uint8_t ti = 0; ti < m_cutscenes[ci].trackCount; ti++) {
|
||||||
|
auto& track = m_cutscenes[ci].tracks[ti];
|
||||||
|
bool isUI = static_cast<uint8_t>(track.trackType) >= 5;
|
||||||
|
if (!isUI || track.target == nullptr) continue;
|
||||||
|
|
||||||
|
const char* nameStr = reinterpret_cast<const char*>(track.target);
|
||||||
|
track.target = nullptr; // Clear the temporary name pointer
|
||||||
|
|
||||||
|
if (track.trackType == TrackType::UICanvasVisible) {
|
||||||
|
// Name is just the canvas name
|
||||||
|
track.uiHandle = static_cast<int16_t>(m_uiSystem.findCanvas(nameStr));
|
||||||
|
} else {
|
||||||
|
// Name is "canvasName/elementName" — find the '/' separator
|
||||||
|
const char* sep = nameStr;
|
||||||
|
while (*sep && *sep != '/') sep++;
|
||||||
|
if (*sep == '/') {
|
||||||
|
// Temporarily null-terminate the canvas portion
|
||||||
|
// (nameStr points into splashpack data, which is mutable)
|
||||||
|
char* mutableSep = const_cast<char*>(sep);
|
||||||
|
*mutableSep = '\0';
|
||||||
|
int canvasIdx = m_uiSystem.findCanvas(nameStr);
|
||||||
|
*mutableSep = '/'; // Restore the separator
|
||||||
|
if (canvasIdx >= 0) {
|
||||||
|
track.uiHandle = static_cast<int16_t>(
|
||||||
|
m_uiSystem.findElement(canvasIdx, sep + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Renderer::GetInstance().SetUISystem(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
m_playerPosition = sceneSetup.playerStartPosition;
|
m_playerPosition = sceneSetup.playerStartPosition;
|
||||||
|
|
||||||
playerRotationX = 0.0_pi;
|
playerRotationX = 0.0_pi;
|
||||||
@@ -607,6 +657,9 @@ void psxsplash::SceneManager::clearScene() {
|
|||||||
m_cutscenePlayer.init(nullptr, 0, nullptr, nullptr); // Reset cutscene player
|
m_cutscenePlayer.init(nullptr, 0, nullptr, nullptr); // Reset cutscene player
|
||||||
// BVH, WorldCollision, and NavRegions will be overwritten by next load
|
// BVH, WorldCollision, and NavRegions will be overwritten by next load
|
||||||
|
|
||||||
|
// Reset UI system (disconnect from renderer before splashpack data disappears)
|
||||||
|
Renderer::GetInstance().SetUISystem(nullptr);
|
||||||
|
|
||||||
// Reset room/portal pointers (they point into splashpack data which is being freed)
|
// Reset room/portal pointers (they point into splashpack data which is being freed)
|
||||||
m_rooms = nullptr;
|
m_rooms = nullptr;
|
||||||
m_roomCount = 0;
|
m_roomCount = 0;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "luaapi.hh"
|
#include "luaapi.hh"
|
||||||
#include "sceneloader.hh"
|
#include "sceneloader.hh"
|
||||||
#include "cutscene.hh"
|
#include "cutscene.hh"
|
||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
class SceneManager {
|
class SceneManager {
|
||||||
@@ -27,6 +28,10 @@ class SceneManager {
|
|||||||
void InitializeScene(uint8_t* splashpackData);
|
void InitializeScene(uint8_t* splashpackData);
|
||||||
void GameTick(psyqo::GPU &gpu);
|
void GameTick(psyqo::GPU &gpu);
|
||||||
|
|
||||||
|
// Font access (set from main.cpp after uploadSystemFont)
|
||||||
|
static void SetFont(psyqo::Font<>* font) { s_font = font; }
|
||||||
|
static psyqo::Font<>* GetFont() { return s_font; }
|
||||||
|
|
||||||
// Trigger event callbacks (called by CollisionSystem)
|
// Trigger event callbacks (called by CollisionSystem)
|
||||||
void fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
void fireTriggerEnter(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
||||||
void fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
void fireTriggerStay(uint16_t triggerObjIdx, uint16_t otherObjIdx);
|
||||||
@@ -119,6 +124,9 @@ class SceneManager {
|
|||||||
int m_cutsceneCount = 0;
|
int m_cutsceneCount = 0;
|
||||||
CutscenePlayer m_cutscenePlayer;
|
CutscenePlayer m_cutscenePlayer;
|
||||||
|
|
||||||
|
// UI system (v13+)
|
||||||
|
UISystem m_uiSystem;
|
||||||
|
|
||||||
psxsplash::Controls m_controls;
|
psxsplash::Controls m_controls;
|
||||||
|
|
||||||
psxsplash::Camera m_currentCamera;
|
psxsplash::Camera m_currentCamera;
|
||||||
@@ -141,6 +149,9 @@ class SceneManager {
|
|||||||
|
|
||||||
bool freecam = false;
|
bool freecam = false;
|
||||||
|
|
||||||
|
// Static font pointer (set from main.cpp)
|
||||||
|
static psyqo::Font<>* s_font;
|
||||||
|
|
||||||
// Scene transition state
|
// Scene transition state
|
||||||
int m_currentSceneIndex = 0;
|
int m_currentSceneIndex = 0;
|
||||||
int m_pendingSceneIndex = -1; // -1 = no pending load
|
int m_pendingSceneIndex = -1; // -1 = no pending load
|
||||||
|
|||||||
@@ -83,8 +83,13 @@ struct SPLASHPACKFileHeader {
|
|||||||
uint16_t cutsceneCount;
|
uint16_t cutsceneCount;
|
||||||
uint16_t reserved_cs;
|
uint16_t reserved_cs;
|
||||||
uint32_t cutsceneTableOffset;
|
uint32_t cutsceneTableOffset;
|
||||||
|
// Version 13 additions (UI system + fonts):
|
||||||
|
uint16_t uiCanvasCount;
|
||||||
|
uint8_t uiFontCount;
|
||||||
|
uint8_t uiPad13;
|
||||||
|
uint32_t uiTableOffset;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(SPLASHPACKFileHeader) == 104, "SPLASHPACKFileHeader must be 104 bytes");
|
static_assert(sizeof(SPLASHPACKFileHeader) == 112, "SPLASHPACKFileHeader must be 112 bytes");
|
||||||
|
|
||||||
struct SPLASHPACKTextureAtlas {
|
struct SPLASHPACKTextureAtlas {
|
||||||
uint32_t polygonsOffset;
|
uint32_t polygonsOffset;
|
||||||
@@ -104,7 +109,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null");
|
psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null");
|
||||||
psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data);
|
psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast<psxsplash::SPLASHPACKFileHeader *>(data);
|
||||||
psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic");
|
psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic");
|
||||||
psyqo::Kernel::assert(header->version >= 12, "Splashpack version too old (need v12+): re-export from SplashEdit");
|
psyqo::Kernel::assert(header->version >= 13, "Splashpack version too old (need v13+): re-export from SplashEdit");
|
||||||
|
|
||||||
setup.playerStartPosition = header->playerStartPos;
|
setup.playerStartPosition = header->playerStartPos;
|
||||||
setup.playerStartRotation = header->playerStartRot;
|
setup.playerStartRotation = header->playerStartRot;
|
||||||
@@ -354,15 +359,23 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
? reinterpret_cast<CutsceneKeyframe*>(data + kfOff)
|
? reinterpret_cast<CutsceneKeyframe*>(data + kfOff)
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
// Resolve target object by name
|
// Resolve target object by name (or store UI name for later resolution)
|
||||||
track.target = nullptr;
|
track.target = nullptr;
|
||||||
|
track.uiHandle = -1;
|
||||||
if (objNameLen > 0 && objNameOff != 0) {
|
if (objNameLen > 0 && objNameOff != 0) {
|
||||||
const char* objName = reinterpret_cast<const char*>(data + objNameOff);
|
const char* objName = reinterpret_cast<const char*>(data + objNameOff);
|
||||||
for (size_t oi = 0; oi < setup.objectNames.size(); oi++) {
|
bool isUITrack = static_cast<uint8_t>(track.trackType) >= 5;
|
||||||
if (setup.objectNames[oi] &&
|
if (isUITrack) {
|
||||||
sp_streq(setup.objectNames[oi], objName)) {
|
// Store the raw name pointer temporarily in target
|
||||||
track.target = setup.objects[oi];
|
// (will be resolved to uiHandle later by scenemanager)
|
||||||
break;
|
track.target = reinterpret_cast<GameObject*>(const_cast<char*>(objName));
|
||||||
|
} else {
|
||||||
|
for (size_t oi = 0; oi < setup.objectNames.size(); oi++) {
|
||||||
|
if (setup.objectNames[oi] &&
|
||||||
|
sp_streq(setup.objectNames[oi], objName)) {
|
||||||
|
track.target = setup.objects[oi];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If not found, target stays nullptr — track will be skipped at runtime
|
// If not found, target stays nullptr — track will be skipped at runtime
|
||||||
@@ -374,6 +387,7 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
cs.tracks[ti].keyframeCount = 0;
|
cs.tracks[ti].keyframeCount = 0;
|
||||||
cs.tracks[ti].keyframes = nullptr;
|
cs.tracks[ti].keyframes = nullptr;
|
||||||
cs.tracks[ti].target = nullptr;
|
cs.tracks[ti].target = nullptr;
|
||||||
|
cs.tracks[ti].uiHandle = -1;
|
||||||
cs.tracks[ti].initialValues[0] = 0;
|
cs.tracks[ti].initialValues[0] = 0;
|
||||||
cs.tracks[ti].initialValues[1] = 0;
|
cs.tracks[ti].initialValues[1] = 0;
|
||||||
cs.tracks[ti].initialValues[2] = 0;
|
cs.tracks[ti].initialValues[2] = 0;
|
||||||
@@ -383,6 +397,13 @@ void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read UI canvas data (version 13+)
|
||||||
|
if (header->version >= 13) {
|
||||||
|
setup.uiCanvasCount = header->uiCanvasCount;
|
||||||
|
setup.uiFontCount = header->uiFontCount;
|
||||||
|
setup.uiTableOffset = header->uiTableOffset;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace psxsplash
|
} // namespace psxsplash
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include "audiomanager.hh"
|
#include "audiomanager.hh"
|
||||||
#include "interactable.hh"
|
#include "interactable.hh"
|
||||||
#include "cutscene.hh"
|
#include "cutscene.hh"
|
||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
namespace psxsplash {
|
namespace psxsplash {
|
||||||
|
|
||||||
@@ -90,6 +91,11 @@ struct SplashpackSceneSetup {
|
|||||||
// Cutscenes (version 12+)
|
// Cutscenes (version 12+)
|
||||||
Cutscene loadedCutscenes[MAX_CUTSCENES];
|
Cutscene loadedCutscenes[MAX_CUTSCENES];
|
||||||
int cutsceneCount = 0;
|
int cutsceneCount = 0;
|
||||||
|
|
||||||
|
// UI system (v13+)
|
||||||
|
uint16_t uiCanvasCount = 0;
|
||||||
|
uint8_t uiFontCount = 0;
|
||||||
|
uint32_t uiTableOffset = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SplashPackLoader {
|
class SplashPackLoader {
|
||||||
|
|||||||
581
src/uisystem.cpp
Normal file
581
src/uisystem.cpp
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
#include "uisystem.hh"
|
||||||
|
|
||||||
|
#include <psyqo/kernel.hh>
|
||||||
|
#include <psyqo/primitives/common.hh>
|
||||||
|
#include <psyqo/primitives/misc.hh>
|
||||||
|
#include <psyqo/primitives/rectangles.hh>
|
||||||
|
#include <psyqo/primitives/triangles.hh>
|
||||||
|
|
||||||
|
namespace psxsplash {
|
||||||
|
|
||||||
|
// Bare-metal string compare (no libc)
|
||||||
|
static bool ui_streq(const char* a, const char* b) {
|
||||||
|
while (*a && *b) {
|
||||||
|
if (*a++ != *b++) return false;
|
||||||
|
}
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Init
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void UISystem::init(psyqo::Font<>& systemFont) {
|
||||||
|
m_systemFont = &systemFont;
|
||||||
|
m_canvasCount = 0;
|
||||||
|
m_elementCount = 0;
|
||||||
|
m_pendingTextCount = 0;
|
||||||
|
m_fontCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Load from splashpack (zero-copy, pointer fixup)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void UISystem::loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
|
||||||
|
uint8_t fontCount, uint32_t tableOffset) {
|
||||||
|
if (tableOffset == 0) return;
|
||||||
|
|
||||||
|
uint8_t* ptr = data + tableOffset;
|
||||||
|
|
||||||
|
// ── Parse font descriptors (16 bytes each, before canvas data) ──
|
||||||
|
if (fontCount > UI_MAX_FONTS - 1) fontCount = UI_MAX_FONTS - 1;
|
||||||
|
m_fontCount = fontCount;
|
||||||
|
for (int fi = 0; fi < fontCount; fi++) {
|
||||||
|
UIFontDesc& fd = m_fontDescs[fi];
|
||||||
|
fd.glyphW = ptr[0];
|
||||||
|
fd.glyphH = ptr[1];
|
||||||
|
fd.vramX = *reinterpret_cast<uint16_t*>(ptr + 2);
|
||||||
|
fd.vramY = *reinterpret_cast<uint16_t*>(ptr + 4);
|
||||||
|
fd.textureH = *reinterpret_cast<uint16_t*>(ptr + 6);
|
||||||
|
uint32_t dataOff = *reinterpret_cast<uint32_t*>(ptr + 8);
|
||||||
|
fd.pixelDataSize = *reinterpret_cast<uint32_t*>(ptr + 12);
|
||||||
|
fd.pixelData = (dataOff != 0) ? (data + dataOff) : nullptr;
|
||||||
|
ptr += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Parse canvas descriptors ──
|
||||||
|
if (canvasCount == 0) return;
|
||||||
|
if (canvasCount > UI_MAX_CANVASES) canvasCount = UI_MAX_CANVASES;
|
||||||
|
|
||||||
|
// Canvas descriptor table: 12 bytes per entry
|
||||||
|
// struct { uint32_t dataOffset; uint8_t nameLen; uint8_t sortOrder;
|
||||||
|
// uint8_t elementCount; uint8_t flags; uint32_t nameOffset; }
|
||||||
|
uint8_t* tablePtr = ptr; // starts right after font descriptors
|
||||||
|
m_canvasCount = canvasCount;
|
||||||
|
m_elementCount = 0;
|
||||||
|
|
||||||
|
for (int ci = 0; ci < canvasCount; ci++) {
|
||||||
|
uint32_t dataOffset = *reinterpret_cast<uint32_t*>(tablePtr); tablePtr += 4;
|
||||||
|
uint8_t nameLen = *tablePtr++;
|
||||||
|
uint8_t sortOrder = *tablePtr++;
|
||||||
|
uint8_t elementCount = *tablePtr++;
|
||||||
|
uint8_t flags = *tablePtr++;
|
||||||
|
uint32_t nameOffset = *reinterpret_cast<uint32_t*>(tablePtr); tablePtr += 4;
|
||||||
|
|
||||||
|
UICanvas& cv = m_canvases[ci];
|
||||||
|
cv.name = (nameLen > 0 && nameOffset != 0)
|
||||||
|
? reinterpret_cast<const char*>(data + nameOffset)
|
||||||
|
: "";
|
||||||
|
cv.visible = (flags & 0x01) != 0;
|
||||||
|
cv.sortOrder = sortOrder;
|
||||||
|
cv.elements = &m_elements[m_elementCount];
|
||||||
|
|
||||||
|
// Cap element count against pool
|
||||||
|
if (m_elementCount + elementCount > UI_MAX_ELEMENTS)
|
||||||
|
elementCount = (uint8_t)(UI_MAX_ELEMENTS - m_elementCount);
|
||||||
|
cv.elementCount = elementCount;
|
||||||
|
|
||||||
|
// Parse element array (48 bytes per entry)
|
||||||
|
uint8_t* elemPtr = data + dataOffset;
|
||||||
|
for (int ei = 0; ei < elementCount; ei++) {
|
||||||
|
UIElement& el = m_elements[m_elementCount++];
|
||||||
|
|
||||||
|
// Identity (8 bytes)
|
||||||
|
el.type = static_cast<UIElementType>(*elemPtr++);
|
||||||
|
uint8_t eFlags = *elemPtr++;
|
||||||
|
el.visible = (eFlags & 0x01) != 0;
|
||||||
|
uint8_t eNameLen = *elemPtr++;
|
||||||
|
elemPtr++; // pad0
|
||||||
|
uint32_t eNameOff = *reinterpret_cast<uint32_t*>(elemPtr); elemPtr += 4;
|
||||||
|
el.name = (eNameLen > 0 && eNameOff != 0)
|
||||||
|
? reinterpret_cast<const char*>(data + eNameOff)
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// Layout (8 bytes)
|
||||||
|
el.x = *reinterpret_cast<int16_t*>(elemPtr); elemPtr += 2;
|
||||||
|
el.y = *reinterpret_cast<int16_t*>(elemPtr); elemPtr += 2;
|
||||||
|
el.w = *reinterpret_cast<int16_t*>(elemPtr); elemPtr += 2;
|
||||||
|
el.h = *reinterpret_cast<int16_t*>(elemPtr); elemPtr += 2;
|
||||||
|
|
||||||
|
// Anchors (4 bytes)
|
||||||
|
el.anchorMinX = *elemPtr++;
|
||||||
|
el.anchorMinY = *elemPtr++;
|
||||||
|
el.anchorMaxX = *elemPtr++;
|
||||||
|
el.anchorMaxY = *elemPtr++;
|
||||||
|
|
||||||
|
// Primary color (4 bytes)
|
||||||
|
el.colorR = *elemPtr++;
|
||||||
|
el.colorG = *elemPtr++;
|
||||||
|
el.colorB = *elemPtr++;
|
||||||
|
elemPtr++; // pad1
|
||||||
|
|
||||||
|
// Type-specific data (16 bytes)
|
||||||
|
uint8_t* typeData = elemPtr;
|
||||||
|
elemPtr += 16;
|
||||||
|
|
||||||
|
// Initialize union to zero
|
||||||
|
for (int i = 0; i < (int)sizeof(UIImageData); i++)
|
||||||
|
reinterpret_cast<uint8_t*>(&el.image)[i] = 0;
|
||||||
|
|
||||||
|
switch (el.type) {
|
||||||
|
case UIElementType::Image:
|
||||||
|
el.image.texpageX = typeData[0];
|
||||||
|
el.image.texpageY = typeData[1];
|
||||||
|
el.image.clutX = *reinterpret_cast<uint16_t*>(&typeData[2]);
|
||||||
|
el.image.clutY = *reinterpret_cast<uint16_t*>(&typeData[4]);
|
||||||
|
el.image.u0 = typeData[6];
|
||||||
|
el.image.v0 = typeData[7];
|
||||||
|
el.image.u1 = typeData[8];
|
||||||
|
el.image.v1 = typeData[9];
|
||||||
|
el.image.bitDepth = typeData[10];
|
||||||
|
break;
|
||||||
|
case UIElementType::Progress:
|
||||||
|
el.progress.bgR = typeData[0];
|
||||||
|
el.progress.bgG = typeData[1];
|
||||||
|
el.progress.bgB = typeData[2];
|
||||||
|
el.progress.value = typeData[3];
|
||||||
|
break;
|
||||||
|
case UIElementType::Text:
|
||||||
|
el.textData.fontIndex = typeData[0]; // 0=system, 1+=custom
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text content offset (8 bytes)
|
||||||
|
uint32_t textOff = *reinterpret_cast<uint32_t*>(elemPtr); elemPtr += 4;
|
||||||
|
elemPtr += 4; // pad2
|
||||||
|
|
||||||
|
// Initialize text buffer
|
||||||
|
el.textBuf[0] = '\0';
|
||||||
|
if (el.type == UIElementType::Text && textOff != 0) {
|
||||||
|
const char* src = reinterpret_cast<const char*>(data + textOff);
|
||||||
|
int ti = 0;
|
||||||
|
while (ti < UI_TEXT_BUF - 1 && src[ti] != '\0') {
|
||||||
|
el.textBuf[ti] = src[ti];
|
||||||
|
ti++;
|
||||||
|
}
|
||||||
|
el.textBuf[ti] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insertion sort canvases by sortOrder (ascending = back-to-front)
|
||||||
|
for (int i = 1; i < m_canvasCount; i++) {
|
||||||
|
UICanvas tmp = m_canvases[i];
|
||||||
|
int j = i - 1;
|
||||||
|
while (j >= 0 && m_canvases[j].sortOrder > tmp.sortOrder) {
|
||||||
|
m_canvases[j + 1] = m_canvases[j];
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
m_canvases[j + 1] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Layout resolution
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void UISystem::resolveLayout(const UIElement& el,
|
||||||
|
int16_t& outX, int16_t& outY,
|
||||||
|
int16_t& outW, int16_t& outH) const {
|
||||||
|
// Anchor gives the origin point in screen space (8.8 fixed → pixel)
|
||||||
|
int ax = ((int)el.anchorMinX * VRAM_RES_WIDTH) >> 8;
|
||||||
|
int ay = ((int)el.anchorMinY * VRAM_RES_HEIGHT) >> 8;
|
||||||
|
outX = (int16_t)(ax + el.x);
|
||||||
|
outY = (int16_t)(ay + el.y);
|
||||||
|
|
||||||
|
// Stretch: anchorMax != anchorMin means width/height is determined by span + offset
|
||||||
|
if (el.anchorMaxX != el.anchorMinX) {
|
||||||
|
int bx = ((int)el.anchorMaxX * VRAM_RES_WIDTH) >> 8;
|
||||||
|
outW = (int16_t)(bx - ax + el.w);
|
||||||
|
} else {
|
||||||
|
outW = el.w;
|
||||||
|
}
|
||||||
|
if (el.anchorMaxY != el.anchorMinY) {
|
||||||
|
int by = ((int)el.anchorMaxY * VRAM_RES_HEIGHT) >> 8;
|
||||||
|
outH = (int16_t)(by - ay + el.h);
|
||||||
|
} else {
|
||||||
|
outH = el.h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to screen bounds (never draw outside the framebuffer)
|
||||||
|
if (outX < 0) { outW += outX; outX = 0; }
|
||||||
|
if (outY < 0) { outH += outY; outY = 0; }
|
||||||
|
if (outW <= 0) outW = 1;
|
||||||
|
if (outH <= 0) outH = 1;
|
||||||
|
if (outX + outW > VRAM_RES_WIDTH) outW = (int16_t)(VRAM_RES_WIDTH - outX);
|
||||||
|
if (outY + outH > VRAM_RES_HEIGHT) outH = (int16_t)(VRAM_RES_HEIGHT - outY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TPage construction for UI images
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
psyqo::PrimPieces::TPageAttr UISystem::makeTPage(const UIImageData& img) {
|
||||||
|
psyqo::PrimPieces::TPageAttr tpage;
|
||||||
|
tpage.setPageX(img.texpageX);
|
||||||
|
tpage.setPageY(img.texpageY);
|
||||||
|
// Color mode from bitDepth: 0→Tex4Bits, 1→Tex8Bits, 2→Tex16Bits
|
||||||
|
switch (img.bitDepth) {
|
||||||
|
case 0:
|
||||||
|
tpage.set(psyqo::Prim::TPageAttr::Tex4Bits);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
tpage.set(psyqo::Prim::TPageAttr::Tex8Bits);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
default:
|
||||||
|
tpage.set(psyqo::Prim::TPageAttr::Tex16Bits);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tpage.setDithering(false); // UI doesn't need dithering
|
||||||
|
return tpage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Render a single element into the OT
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void UISystem::renderElement(UIElement& el,
|
||||||
|
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||||
|
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc) {
|
||||||
|
int16_t x, y, w, h;
|
||||||
|
resolveLayout(el, x, y, w, h);
|
||||||
|
|
||||||
|
switch (el.type) {
|
||||||
|
case UIElementType::Box: {
|
||||||
|
auto& frag = balloc.allocateFragment<psyqo::Prim::Rectangle>();
|
||||||
|
frag.primitive.setColor(psyqo::Color{.r = el.colorR, .g = el.colorG, .b = el.colorB});
|
||||||
|
frag.primitive.position = {.x = x, .y = y};
|
||||||
|
frag.primitive.size = {.x = w, .y = h};
|
||||||
|
frag.primitive.setOpaque();
|
||||||
|
ot.insert(frag, 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UIElementType::Progress: {
|
||||||
|
// Background: full rect
|
||||||
|
auto& bgFrag = balloc.allocateFragment<psyqo::Prim::Rectangle>();
|
||||||
|
bgFrag.primitive.setColor(psyqo::Color{.r = el.progress.bgR, .g = el.progress.bgG, .b = el.progress.bgB});
|
||||||
|
bgFrag.primitive.position = {.x = x, .y = y};
|
||||||
|
bgFrag.primitive.size = {.x = w, .y = h};
|
||||||
|
bgFrag.primitive.setOpaque();
|
||||||
|
ot.insert(bgFrag, 1);
|
||||||
|
|
||||||
|
// Fill: partial width
|
||||||
|
int fillW = (int)el.progress.value * w / 100;
|
||||||
|
if (fillW < 0) fillW = 0;
|
||||||
|
if (fillW > w) fillW = w;
|
||||||
|
if (fillW > 0) {
|
||||||
|
auto& fillFrag = balloc.allocateFragment<psyqo::Prim::Rectangle>();
|
||||||
|
fillFrag.primitive.setColor(psyqo::Color{.r = el.colorR, .g = el.colorG, .b = el.colorB});
|
||||||
|
fillFrag.primitive.position = {.x = x, .y = y};
|
||||||
|
fillFrag.primitive.size = {.x = (int16_t)fillW, .y = h};
|
||||||
|
fillFrag.primitive.setOpaque();
|
||||||
|
ot.insert(fillFrag, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UIElementType::Image: {
|
||||||
|
psyqo::PrimPieces::TPageAttr tpage = makeTPage(el.image);
|
||||||
|
psyqo::PrimPieces::ClutIndex clut(el.image.clutX, el.image.clutY);
|
||||||
|
psyqo::Color tint = {.r = el.colorR, .g = el.colorG, .b = el.colorB};
|
||||||
|
|
||||||
|
// Triangle 0: top-left, top-right, bottom-left
|
||||||
|
{
|
||||||
|
auto& tri = balloc.allocateFragment<psyqo::Prim::GouraudTexturedTriangle>();
|
||||||
|
tri.primitive.pointA.x = x; tri.primitive.pointA.y = y;
|
||||||
|
tri.primitive.pointB.x = x + w; tri.primitive.pointB.y = y;
|
||||||
|
tri.primitive.pointC.x = x; tri.primitive.pointC.y = y + h;
|
||||||
|
tri.primitive.uvA.u = el.image.u0; tri.primitive.uvA.v = el.image.v0;
|
||||||
|
tri.primitive.uvB.u = el.image.u1; tri.primitive.uvB.v = el.image.v0;
|
||||||
|
tri.primitive.uvC.u = el.image.u0; tri.primitive.uvC.v = el.image.v1;
|
||||||
|
tri.primitive.tpage = tpage;
|
||||||
|
tri.primitive.clutIndex = clut;
|
||||||
|
tri.primitive.setColorA(tint);
|
||||||
|
tri.primitive.setColorB(tint);
|
||||||
|
tri.primitive.setColorC(tint);
|
||||||
|
tri.primitive.setOpaque();
|
||||||
|
ot.insert(tri, 0);
|
||||||
|
}
|
||||||
|
// Triangle 1: top-right, bottom-right, bottom-left
|
||||||
|
{
|
||||||
|
auto& tri = balloc.allocateFragment<psyqo::Prim::GouraudTexturedTriangle>();
|
||||||
|
tri.primitive.pointA.x = x + w; tri.primitive.pointA.y = y;
|
||||||
|
tri.primitive.pointB.x = x + w; tri.primitive.pointB.y = y + h;
|
||||||
|
tri.primitive.pointC.x = x; tri.primitive.pointC.y = y + h;
|
||||||
|
tri.primitive.uvA.u = el.image.u1; tri.primitive.uvA.v = el.image.v0;
|
||||||
|
tri.primitive.uvB.u = el.image.u1; tri.primitive.uvB.v = el.image.v1;
|
||||||
|
tri.primitive.uvC.u = el.image.u0; tri.primitive.uvC.v = el.image.v1;
|
||||||
|
tri.primitive.tpage = tpage;
|
||||||
|
tri.primitive.clutIndex = clut;
|
||||||
|
tri.primitive.setColorA(tint);
|
||||||
|
tri.primitive.setColorB(tint);
|
||||||
|
tri.primitive.setColorC(tint);
|
||||||
|
tri.primitive.setOpaque();
|
||||||
|
ot.insert(tri, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case UIElementType::Text: {
|
||||||
|
// Queue text for phase 2 (after gpu.chain)
|
||||||
|
if (m_pendingTextCount < UI_MAX_ELEMENTS) {
|
||||||
|
uint8_t fi = (el.type == UIElementType::Text) ? el.textData.fontIndex : 0;
|
||||||
|
m_pendingTexts[m_pendingTextCount++] = {
|
||||||
|
x, y,
|
||||||
|
el.colorR, el.colorG, el.colorB,
|
||||||
|
fi,
|
||||||
|
el.textBuf
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Render phases
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void UISystem::renderOT(psyqo::GPU& gpu,
|
||||||
|
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||||
|
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc) {
|
||||||
|
m_pendingTextCount = 0;
|
||||||
|
|
||||||
|
// Canvases are pre-sorted by sortOrder (ascending = back first).
|
||||||
|
// Higher-sortOrder canvases insert at OT 0 later, appearing on top.
|
||||||
|
for (int i = 0; i < m_canvasCount; i++) {
|
||||||
|
UICanvas& cv = m_canvases[i];
|
||||||
|
if (!cv.visible) continue;
|
||||||
|
for (int j = 0; j < cv.elementCount; j++) {
|
||||||
|
UIElement& el = cv.elements[j];
|
||||||
|
if (!el.visible) continue;
|
||||||
|
renderElement(el, ot, balloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::renderText(psyqo::GPU& gpu) {
|
||||||
|
for (int i = 0; i < m_pendingTextCount; i++) {
|
||||||
|
auto& pt = m_pendingTexts[i];
|
||||||
|
psyqo::FontBase* font = resolveFont(pt.fontIndex);
|
||||||
|
if (!font) continue;
|
||||||
|
font->chainprintf(gpu,
|
||||||
|
{{.x = pt.x, .y = pt.y}},
|
||||||
|
{{.r = pt.r, .g = pt.g, .b = pt.b}},
|
||||||
|
"%s", pt.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Font support
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
psyqo::FontBase* UISystem::resolveFont(uint8_t fontIndex) {
|
||||||
|
if (fontIndex == 0 || fontIndex > m_fontCount) return m_systemFont;
|
||||||
|
return &m_customFonts[fontIndex - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::uploadFonts(psyqo::GPU& gpu) {
|
||||||
|
for (int i = 0; i < m_fontCount; i++) {
|
||||||
|
UIFontDesc& fd = m_fontDescs[i];
|
||||||
|
if (!fd.pixelData || fd.pixelDataSize == 0) continue;
|
||||||
|
|
||||||
|
// Upload 4bpp texture to VRAM
|
||||||
|
// 4bpp 256px wide = 64 VRAM hwords wide
|
||||||
|
Renderer::GetInstance().VramUpload(
|
||||||
|
reinterpret_cast<const uint16_t*>(fd.pixelData),
|
||||||
|
(int16_t)fd.vramX, (int16_t)fd.vramY,
|
||||||
|
64, (int16_t)fd.textureH);
|
||||||
|
|
||||||
|
// Initialize the Font<2> instance for this custom font
|
||||||
|
m_customFonts[i].initialize(gpu,
|
||||||
|
{{.x = (int16_t)fd.vramX, .y = (int16_t)fd.vramY}},
|
||||||
|
{{.x = (int16_t)fd.glyphW, .y = (int16_t)fd.glyphH}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Canvas API
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
int UISystem::findCanvas(const char* name) const {
|
||||||
|
if (!name) return -1;
|
||||||
|
for (int i = 0; i < m_canvasCount; i++) {
|
||||||
|
if (m_canvases[i].name && ui_streq(m_canvases[i].name, name))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setCanvasVisible(int idx, bool v) {
|
||||||
|
if (idx >= 0 && idx < m_canvasCount)
|
||||||
|
m_canvases[idx].visible = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISystem::isCanvasVisible(int idx) const {
|
||||||
|
if (idx >= 0 && idx < m_canvasCount)
|
||||||
|
return m_canvases[idx].visible;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Element API
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
int UISystem::findElement(int canvasIdx, const char* name) const {
|
||||||
|
if (canvasIdx < 0 || canvasIdx >= m_canvasCount || !name) return -1;
|
||||||
|
const UICanvas& cv = m_canvases[canvasIdx];
|
||||||
|
for (int i = 0; i < cv.elementCount; i++) {
|
||||||
|
if (cv.elements[i].name && ui_streq(cv.elements[i].name, name)) {
|
||||||
|
// Return flat handle: index into m_elements
|
||||||
|
int handle = (int)(cv.elements + i - m_elements);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setElementVisible(int handle, bool v) {
|
||||||
|
if (handle >= 0 && handle < m_elementCount)
|
||||||
|
m_elements[handle].visible = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UISystem::isElementVisible(int handle) const {
|
||||||
|
if (handle >= 0 && handle < m_elementCount)
|
||||||
|
return m_elements[handle].visible;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setText(int handle, const char* text) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
UIElement& el = m_elements[handle];
|
||||||
|
if (el.type != UIElementType::Text) return;
|
||||||
|
if (!text) { el.textBuf[0] = '\0'; return; }
|
||||||
|
int i = 0;
|
||||||
|
while (i < UI_TEXT_BUF - 1 && text[i] != '\0') {
|
||||||
|
el.textBuf[i] = text[i];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
el.textBuf[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* UISystem::getText(int handle) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return "";
|
||||||
|
const UIElement& el = m_elements[handle];
|
||||||
|
if (el.type != UIElementType::Text) return "";
|
||||||
|
return el.textBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setProgress(int handle, uint8_t value) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
UIElement& el = m_elements[handle];
|
||||||
|
if (el.type != UIElementType::Progress) return;
|
||||||
|
if (value > 100) value = 100;
|
||||||
|
el.progress.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setColor(int handle, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
m_elements[handle].colorR = r;
|
||||||
|
m_elements[handle].colorG = g;
|
||||||
|
m_elements[handle].colorB = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::getColor(int handle, uint8_t& r, uint8_t& g, uint8_t& b) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) { r = g = b = 0; return; }
|
||||||
|
r = m_elements[handle].colorR;
|
||||||
|
g = m_elements[handle].colorG;
|
||||||
|
b = m_elements[handle].colorB;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setPosition(int handle, int16_t x, int16_t y) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
UIElement& el = m_elements[handle];
|
||||||
|
el.x = x;
|
||||||
|
el.y = y;
|
||||||
|
// Zero out anchors to make position absolute
|
||||||
|
el.anchorMinX = 0;
|
||||||
|
el.anchorMinY = 0;
|
||||||
|
el.anchorMaxX = 0;
|
||||||
|
el.anchorMaxY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::getPosition(int handle, int16_t& x, int16_t& y) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) { x = y = 0; return; }
|
||||||
|
// Resolve full layout to return actual screen position
|
||||||
|
int16_t rx, ry, rw, rh;
|
||||||
|
resolveLayout(m_elements[handle], rx, ry, rw, rh);
|
||||||
|
x = rx;
|
||||||
|
y = ry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setSize(int handle, int16_t w, int16_t h) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
m_elements[handle].w = w;
|
||||||
|
m_elements[handle].h = h;
|
||||||
|
// Clear stretch anchors so size is explicit
|
||||||
|
m_elements[handle].anchorMaxX = m_elements[handle].anchorMinX;
|
||||||
|
m_elements[handle].anchorMaxY = m_elements[handle].anchorMinY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::getSize(int handle, int16_t& w, int16_t& h) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) { w = h = 0; return; }
|
||||||
|
int16_t rx, ry, rw, rh;
|
||||||
|
resolveLayout(m_elements[handle], rx, ry, rw, rh);
|
||||||
|
w = rw;
|
||||||
|
h = rh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UISystem::setProgressColors(int handle, uint8_t bgR, uint8_t bgG, uint8_t bgB,
|
||||||
|
uint8_t fillR, uint8_t fillG, uint8_t fillB) {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return;
|
||||||
|
UIElement& el = m_elements[handle];
|
||||||
|
if (el.type != UIElementType::Progress) return;
|
||||||
|
el.progress.bgR = bgR;
|
||||||
|
el.progress.bgG = bgG;
|
||||||
|
el.progress.bgB = bgB;
|
||||||
|
el.colorR = fillR;
|
||||||
|
el.colorG = fillG;
|
||||||
|
el.colorB = fillB;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t UISystem::getProgress(int handle) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return 0;
|
||||||
|
const UIElement& el = m_elements[handle];
|
||||||
|
if (el.type != UIElementType::Progress) return 0;
|
||||||
|
return el.progress.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIElementType UISystem::getElementType(int handle) const {
|
||||||
|
if (handle < 0 || handle >= m_elementCount) return UIElementType::Box;
|
||||||
|
return m_elements[handle].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UISystem::getCanvasElementCount(int canvasIdx) const {
|
||||||
|
if (canvasIdx < 0 || canvasIdx >= m_canvasCount) return 0;
|
||||||
|
return m_canvases[canvasIdx].elementCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UISystem::getCanvasElementHandle(int canvasIdx, int elementIndex) const {
|
||||||
|
if (canvasIdx < 0 || canvasIdx >= m_canvasCount) return -1;
|
||||||
|
const UICanvas& cv = m_canvases[canvasIdx];
|
||||||
|
if (elementIndex < 0 || elementIndex >= cv.elementCount) return -1;
|
||||||
|
return (int)(cv.elements + elementIndex - m_elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace psxsplash
|
||||||
151
src/uisystem.hh
Normal file
151
src/uisystem.hh
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "vram_config.h"
|
||||||
|
#include "renderer.hh"
|
||||||
|
#include <psyqo/font.hh>
|
||||||
|
#include <psyqo/gpu.hh>
|
||||||
|
#include <psyqo/primitives/common.hh>
|
||||||
|
#include <psyqo/primitives/misc.hh>
|
||||||
|
#include <psyqo/primitives/rectangles.hh>
|
||||||
|
#include <psyqo/primitives/triangles.hh>
|
||||||
|
|
||||||
|
namespace psxsplash {
|
||||||
|
|
||||||
|
static constexpr int UI_MAX_CANVASES = 16;
|
||||||
|
static constexpr int UI_MAX_ELEMENTS = 128;
|
||||||
|
static constexpr int UI_TEXT_BUF = 64;
|
||||||
|
static constexpr int UI_MAX_FONTS = 4; // 0 = system font, 1-3 = custom
|
||||||
|
|
||||||
|
enum class UIElementType : uint8_t {
|
||||||
|
Image = 0,
|
||||||
|
Box = 1,
|
||||||
|
Text = 2,
|
||||||
|
Progress = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIImageData {
|
||||||
|
uint8_t texpageX, texpageY;
|
||||||
|
uint16_t clutX, clutY;
|
||||||
|
uint8_t u0, v0, u1, v1;
|
||||||
|
uint8_t bitDepth; // 0=4bit, 1=8bit, 2=16bit
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIProgressData {
|
||||||
|
uint8_t bgR, bgG, bgB;
|
||||||
|
uint8_t value; // 0-100, mutable
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UITextData {
|
||||||
|
uint8_t fontIndex; // 0 = system font, 1+ = custom font
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UIElement {
|
||||||
|
UIElementType type;
|
||||||
|
bool visible;
|
||||||
|
const char* name; // points into splashpack data
|
||||||
|
int16_t x, y, w, h; // pixel rect / offset in PS1 screen space
|
||||||
|
uint8_t anchorMinX, anchorMinY, anchorMaxX, anchorMaxY; // 8.8 fixed
|
||||||
|
uint8_t colorR, colorG, colorB;
|
||||||
|
union { UIImageData image; UIProgressData progress; UITextData textData; };
|
||||||
|
char textBuf[UI_TEXT_BUF]; // UIText: mutable, starts with default
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UICanvas {
|
||||||
|
const char* name;
|
||||||
|
bool visible;
|
||||||
|
uint8_t sortOrder;
|
||||||
|
UIElement* elements; // slice into m_elements pool
|
||||||
|
uint8_t elementCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Font descriptor loaded from the splashpack (for VRAM upload).
|
||||||
|
struct UIFontDesc {
|
||||||
|
uint8_t glyphW, glyphH;
|
||||||
|
uint16_t vramX, vramY;
|
||||||
|
uint16_t textureH;
|
||||||
|
const uint8_t* pixelData; // raw 4bpp, points into splashpack
|
||||||
|
uint32_t pixelDataSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
class UISystem {
|
||||||
|
public:
|
||||||
|
void init(psyqo::Font<>& systemFont);
|
||||||
|
|
||||||
|
/// Called from SplashPackLoader after splashpack is loaded.
|
||||||
|
/// Parses font descriptors (before canvas data) and canvas/element tables.
|
||||||
|
void loadFromSplashpack(uint8_t* data, uint16_t canvasCount,
|
||||||
|
uint8_t fontCount, uint32_t tableOffset);
|
||||||
|
|
||||||
|
/// Upload custom font textures to VRAM and initialize Font<> instances.
|
||||||
|
/// Must be called AFTER loadFromSplashpack and BEFORE first render.
|
||||||
|
void uploadFonts(psyqo::GPU& gpu);
|
||||||
|
|
||||||
|
// Phase 1: Insert OT primitives for boxes, images, progress bars.
|
||||||
|
// Called BEFORE gpu.chain(ot) from inside the renderer.
|
||||||
|
void renderOT(psyqo::GPU& gpu,
|
||||||
|
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||||
|
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc);
|
||||||
|
|
||||||
|
// Phase 2: Emit text via psyqo font chaining.
|
||||||
|
// Called AFTER gpu.chain(ot).
|
||||||
|
void renderText(psyqo::GPU& gpu);
|
||||||
|
|
||||||
|
// Canvas API
|
||||||
|
int findCanvas(const char* name) const;
|
||||||
|
void setCanvasVisible(int idx, bool v);
|
||||||
|
bool isCanvasVisible(int idx) const;
|
||||||
|
|
||||||
|
// Element API — returns flat handle into m_elements, or -1
|
||||||
|
int findElement(int canvasIdx, const char* name) const;
|
||||||
|
void setElementVisible(int handle, bool v);
|
||||||
|
bool isElementVisible(int handle) const;
|
||||||
|
void setText(int handle, const char* text);
|
||||||
|
const char* getText(int handle) const;
|
||||||
|
void setProgress(int handle, uint8_t value);
|
||||||
|
void setColor(int handle, uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
void getColor(int handle, uint8_t& r, uint8_t& g, uint8_t& b) const;
|
||||||
|
void setPosition(int handle, int16_t x, int16_t y);
|
||||||
|
void getPosition(int handle, int16_t& x, int16_t& y) const;
|
||||||
|
void setSize(int handle, int16_t w, int16_t h);
|
||||||
|
void getSize(int handle, int16_t& w, int16_t& h) const;
|
||||||
|
void setProgressColors(int handle, uint8_t bgR, uint8_t bgG, uint8_t bgB,
|
||||||
|
uint8_t fillR, uint8_t fillG, uint8_t fillB);
|
||||||
|
uint8_t getProgress(int handle) const;
|
||||||
|
UIElementType getElementType(int handle) const;
|
||||||
|
int getCanvasElementCount(int canvasIdx) const;
|
||||||
|
int getCanvasElementHandle(int canvasIdx, int elementIndex) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
psyqo::Font<>* m_systemFont = nullptr;
|
||||||
|
|
||||||
|
// Custom fonts: up to 3, each with 4 fragments for DMA chaining
|
||||||
|
psyqo::Font<4> m_customFonts[UI_MAX_FONTS - 1];
|
||||||
|
UIFontDesc m_fontDescs[UI_MAX_FONTS - 1]; // descriptors from splashpack
|
||||||
|
int m_fontCount = 0; // number of custom fonts (0-3)
|
||||||
|
|
||||||
|
UICanvas m_canvases[UI_MAX_CANVASES];
|
||||||
|
UIElement m_elements[UI_MAX_ELEMENTS];
|
||||||
|
int m_canvasCount = 0;
|
||||||
|
int m_elementCount = 0;
|
||||||
|
|
||||||
|
// Per-frame pending text list (filled during renderOT, flushed in renderText)
|
||||||
|
struct PendingText { int16_t x, y; uint8_t r, g, b; uint8_t fontIndex; const char* text; };
|
||||||
|
PendingText m_pendingTexts[UI_MAX_ELEMENTS];
|
||||||
|
int m_pendingTextCount = 0;
|
||||||
|
|
||||||
|
/// Resolve which Font to use for a given fontIndex.
|
||||||
|
psyqo::FontBase* resolveFont(uint8_t fontIndex);
|
||||||
|
|
||||||
|
void resolveLayout(const UIElement& el,
|
||||||
|
int16_t& outX, int16_t& outY,
|
||||||
|
int16_t& outW, int16_t& outH) const;
|
||||||
|
|
||||||
|
void renderElement(UIElement& el,
|
||||||
|
psyqo::OrderingTable<Renderer::ORDERING_TABLE_SIZE>& ot,
|
||||||
|
psyqo::BumpAllocator<Renderer::BUMP_ALLOCATOR_SIZE>& balloc);
|
||||||
|
|
||||||
|
static psyqo::PrimPieces::TPageAttr makeTPage(const UIImageData& img);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace psxsplash
|
||||||
Reference in New Issue
Block a user