#include <string>
#include <sstream>
#include <vector>
#include "Dialog.h"
#include "ResourceLoader.h"
#include "Json.h"
#include "Lua.h"
#include "Log.h"
using namespace GameUi;
bool Rectangle::isInside(Point pt) const
{
return (pt.x >= x && pt.x <= (x+w) && pt.y >= y && pt.y <= (y+h));
}
uint8_t DrawItems::getTextureIdForAsset(const char* assetName)
{
static uint8_t nextId = 255;//0;
static std::map<std::string,uint8_t> g_cachedTextureIds;
if (g_cachedTextureIds.count(assetName))
return g_cachedTextureIds[assetName];
nextId++;
fprintf(stderr, "tex[%i] => %s\n", nextId, assetName);
g_cachedTextureIds[assetName] = nextId;
TextureLoadJob job = { std::string(assetName), nextId };
loadTexturesQueue.emplace_back(job);
return nextId;
}
void DrawItems::addQuad(Rectangle r, Rectangle tex, Color col, uint8_t texId)
{
int t[2][6] = { { 0, 0, 1, 1, 1, 0 }, { 0, 1, 1, 1, 0, 0 } };
for (int i = 0; i < 6; i++)
m_items.push_back(UiVertex{int16_t(r.x+t[0][i]*r.w), int16_t(r.y+t[1][i]*r.h), col.r,col.g,col.b,col.a,
uint8_t(tex.x+t[0][i]*tex.w), uint8_t(tex.y+t[1][i]*tex.h), texId});
}
void DrawItems::addIcon(int x, int y, char icon, Color col, int scale)
{
if (icon >= 0 && icon < 24)
{
int u = icon % 12;
int v = icon / 12;
v += 4;
addQuad(Rectangle{x, y, 20*scale, 19*scale}, Rectangle{u*20, v*19, 20, 19}, col);
}
}
void DrawItems::addChar(int x, int y, char ch, Color col, int scale)
{
if ( ch >= 32 && ch < 127 ) {
ch -= 32;
int u = ch % 24;
int v = ch / 24;
addQuad(Rectangle{x, y, 10*scale, 19*scale}, Rectangle{u*10, v*19, 10, 19}, col);
}
}
static void ReplaceStringInPlace(std::string& subject, const std::string& search, const std::string& replace)
{
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
}
// Currently only does a specific type of variable expansion, but idea is it could do more
std::string UiControl::formattedText(DrawItems& drawItems, std::string text, ...)
{
std::string txt = text;
for(auto const &var : drawItems.m_variables)
ReplaceStringInPlace(txt, "${" + var.first + "}", var.second);
return txt;
}
void DrawItems::addString(int x, int y, const char* a_text, Color col, int scale)
{
for (int i = 0; a_text[i]; i++)
addChar(x + 10*scale*i, y, a_text[i], col, scale);
}
void DrawItems::addContrastString(int x, int y, const char* a_text, int scale)
{
std::string str = UiControl::formattedText(*this, a_text);
int offs[5][3] = { {0,0,0}, {-1,1,0}, {-1,2,0}, {1,3,0}, {0,0,0xffffff} };
for (int i = 0; i < 5; i++)
addString(x+offs[i][0], y+offs[i][1], str.c_str(), offs[i][2] | 0xff000000, scale);
}
void DrawItems::add9Slice(Rectangle r, Rectangle t, int radius, Color col)
{
int tx[4] = { t.x, t.x+int(t.w*0.333333f), t.x+int(t.w*0.666666f), t.x+t.w };
int ty[4] = { t.y, t.y+int(t.h*0.333333f), t.y+int(t.h*0.666666f), t.y+t.h };
int x[4] = { r.x, r.x + radius, r.x+r.w - radius, r.x+r.w };
int y[4] = { r.y, r.y + radius, r.y+r.h - radius, r.y+r.h };
for (int j = 0; j < 3; j++)
for (int i = 0; i < 3; i++)
addQuad(Rectangle{x[i], y[j], x[i+1]-x[i], y[j+1]-y[j]},
Rectangle{tx[i], ty[j], tx[i+1]-tx[i], ty[j+1]-ty[j]}, col);
}
bool UiControl::handleMouse(MouseState ms)
{
m_mouseState = ms;
bool inside = m_geometry.isInside(ms.pos);
bool lastDown = m_mouseDownLast;
m_mouseDownLast = ms.down;
bool insideLast = m_mouseInsideLast;
m_mouseInsideLast = inside;
bool buttonStateChange = lastDown != ms.down;
bool insideStateChange = insideLast != inside;
if (buttonStateChange && !ms.down && m_mouseDownInside) {
m_mouseDownInside = false;
onMouseRelease();
if (inside)
onMouseClicked();
return true;
}
if (buttonStateChange && ms.down && inside)
{
m_mouseDownInside = true;
onMousePress();
return true;
}
if (insideStateChange) {
if (inside)
onMouseEnter();
else
onMouseLeave();
return false;
}
onMouseMove();
return false;
}
extern std::vector<std::string> split(const std::string &s, char delim);
void UiControl::genericDispatch(const char* event)
{
if (m_ui && m_actionMap.count(event))
for (auto action : m_actionMap[event])
m_ui->DialogRedirect(action);
}
// Potentially this lives in game code
void Ui::DialogRedirect(std::string redirect)
{
printf("redirecting to %s\n", redirect.c_str());
std::vector<std::string> parts = split(redirect, ':');
if (!parts.size() || parts[0] == "")
return;
if (parts[0] == "close" || parts[0] == "pop") {
printf("closing current\n");
closeCurrent();
} else if (parts[0] == "push") {
printf("pushing %s\n", parts[1].c_str());
if (m_dialogMap.count(parts[1])) {
Dialog* d = m_dialogMap[parts[1]];
pushDialog(d);
}
} else if (parts[0] == "exec") {
Log(LL_Info, "LUA", "executing lua function: %s\n", parts[1].c_str());
m_luaVM.CallLua(parts[1].c_str());
} else if (parts[0] == "goto") {
printf("going to %s\n", parts[1].c_str());
if (m_dialogMap.count(parts[1])) {
Dialog* d = m_dialogMap[parts[1]];
closeCurrent();
pushDialog(d);
}
}
}
AnimationParameters AnimationParametersFromJson(ConstJsonValueRef animParams)
{
AnimationParameters params;
params.m_event = animParams["event"]("show");
params.m_parameter = animParams["parameter"]("x");
LookupCurveFunction(animParams["curve"]("linear"), params.m_curve);
//params.m_curve = animParams["curve"].GetString();
params.m_timeScale = animParams["timeScale"](1000);
params.m_timeOffset = animParams["timeOffset"](0);
//params.m_loop = animParams["loop"].GetString();
params.m_t0 = animParams["t0"](0);
params.m_t1 = animParams["t1"](1);
return params;
}
void Ui::genericDispatch(const char* event)
{
if (m_gameFlowSteps.count(event))
for (auto action : m_gameFlowSteps[event])
DialogRedirect(action);
}
UiControl* ControlFromJson(ConstJsonValueRef control, LuaVM& m_luaVM)
{
UiControl* newControl = nullptr;
std::string childType = control["type"]("null");
int x = control["x"](0);
int y = control["y"](0);
int w = control["w"](0);
int h = control["h"](0);
int r = control["r"](0);
int g = control["g"](0);
int b = control["b"](0);
int a = control["a"](0);
uint32_t col = (a << 24) | (b << 16) | (g << 8) | r;
const char* text = control["text"]("default");
AnimationList animationList;
for (auto a : control["animations"])
animationList.push_back(AnimationParametersFromJson(a));
ActionMap actionMap;
for (auto actions : control["actions"])
for (auto action : actions)
actionMap[actions.GetName()].push_back(action("null"));
if (childType == "Script") {
m_luaVM.LoadFile(control["lua"]("error.lua"));
} else if (childType == "Label") {
newControl = new Label(x, y, control["align"](0), text);
} else if (childType == "Button") {
newControl = new Button(x, y, text);
} else if (childType == "ToolButton") {
newControl = new ToolButton(x, y, control["icon"](0));
} else if (childType == "CheckBox") {
newControl = new CheckBox(x, y, text, control["binding"]("checkState"));
} else if (childType == "Slider") {
newControl = new Slider(x, y, h, control["binding"]("sliderValue"));
} else if (childType == "Frame") {
newControl = new Frame(x, y, w, h, text);
} else if (childType == "Panel") {
newControl = new Panel(w, h, col);// control["title"]("Title"), control["icon"](0));
} else if (childType == "Rectangle") {
newControl = new RectangleShape(x, y, w, h, col);
} else if (childType == "Image") {
newControl = new ImageShape(x, y, control["asset"]("null"));
}
if (newControl) {
newControl->m_actionMap = actionMap;
newControl->m_animationList = animationList;
//newControl->m_ui = this; // no good doing it here, as dialog creates a control
//newDialog->m_controls.push_back(newControl);
}
else
{
if (childType != "Script")
Log(LL_Error, "UI", "Bad control in UI json, %s", childType.c_str());
}
return newControl;
}
void Ui::LoadDialogsFromAsset(const char* assetName)
{
LoadFileAsync(assetName, true, [this](Resource* res)
{
Document jsonDoc;
if (!res->data.data() || jsonDoc.Parse((char*)res->data.data()).HasParseError()) {
Log(LL_Error, "UI", "json parse error of ui json\n");
return;
}
for (auto actions : ConstJsonValueRef(jsonDoc["actions"]))
for (auto action : actions)
m_gameFlowSteps[actions.GetName()].push_back(action("null"));
// This is here to support reloading the dialog map (goes back to same screen again)
std::string oldDialog = "";
if (m_stack.size() && m_stack.back()) {
oldDialog = m_stack.back()->m_name;
}
for (auto dialog : m_dialogMap)
delete dialog.second;
m_dialogMap.clear();
for (auto di : ConstJsonValueRef(jsonDoc["dialogs"]))
{
Dialog *newDialog = new Dialog();
for (auto bg : di["background"]) {
UiControl *newBg = ControlFromJson(bg, m_luaVM);
if (newBg) {
newBg->m_ui = this;
newDialog->m_background.push_back(newBg);
}
}
for (auto view : di["views"]) {
View *newView = new View(view["x"](0), view["y"](0), view["w"](0), view["h"](0), view["view"]("blank"));
newDialog->m_views.push_back(newView);
}
for (auto control : di["controls"]) {
UiControl *newControl = ControlFromJson(control, m_luaVM);
if (newControl) {
newDialog->m_controls.push_back(newControl);
}
}
for (UiControl* ctrl : newDialog->m_controls)
ctrl->m_ui = this; // doing here ensures all child controls have m_ui set
// probably a better way to do this during construction
ActionMap actionMap;
for (auto actions : di["actions"])
for (auto action : actions)
actionMap[actions.GetName()].push_back(action("null"));
//printf("adding %s to dialog map\n", di->name.GetString());
newDialog->m_actionMap = actionMap;
newDialog->m_stage = di["stage"]("blank");
newDialog->m_name = di.GetName();
m_dialogMap[di.GetName()] = newDialog;
}
if (oldDialog == "")
{
genericDispatch("onLaunch");
}
else
{
if (m_dialogMap.count(oldDialog)) {
pop();
pushDialog(m_dialogMap[oldDialog]);
} else {
printf("didn't find old dialog: %s\n", oldDialog.c_str());
}
}
});
}