Files
WickedEngine/WickedEngine/wiBacklog.cpp
T
Turánszki János 1c38d93304 Editor gui v2 (#843)
2024-05-17 08:04:05 +02:00

476 lines
11 KiB
C++

#include "wiBacklog.h"
#include "wiMath.h"
#include "wiResourceManager.h"
#include "wiTextureHelper.h"
#include "wiSpinLock.h"
#include "wiFont.h"
#include "wiSpriteFont.h"
#include "wiImage.h"
#include "wiLua.h"
#include "wiInput.h"
#include "wiPlatform.h"
#include "wiHelper.h"
#include "wiGUI.h"
#include <mutex>
#include <deque>
#include <limits>
#include <thread>
#include <iostream>
using namespace wi::graphics;
namespace wi::backlog
{
bool enabled = false;
bool was_ever_enabled = enabled;
struct LogEntry
{
std::string text;
LogLevel level = LogLevel::Default;
};
std::deque<LogEntry> entries;
std::deque<LogEntry> history;
const float speed = 4000.0f;
const size_t deletefromline = 500;
float pos = 5;
float scroll = 0;
int historyPos = 0;
wi::font::Params font_params;
wi::SpinLock logLock;
Texture backgroundTex;
bool refitscroll = false;
wi::gui::TextInputField inputField;
wi::gui::Button toggleButton;
bool locked = false;
bool blockLuaExec = false;
LogLevel logLevel = LogLevel::Default;
LogLevel unseen = LogLevel::None;
std::string getTextWithoutLock()
{
std::string retval;
for (auto& x : entries)
{
retval += x.text;
}
return retval;
}
void write_logfile()
{
std::string filename = wi::helper::GetTempDirectoryPath() + "wiBacklog.txt";
std::string text = getText(); // will lock mutex
wi::helper::FileWrite(filename, (const uint8_t*)text.c_str(), text.length());
}
// The logwriter object will automatically write out the backlog to the temp folder when it's destroyed
// Should happen on application exit
struct LogWriter
{
~LogWriter()
{
write_logfile();
}
} logwriter;
void Toggle()
{
enabled = !enabled;
was_ever_enabled = true;
}
void Scroll(float dir)
{
scroll += dir;
}
void Update(const wi::Canvas& canvas, float dt)
{
if (!locked)
{
if (wi::input::Press(wi::input::KEYBOARD_BUTTON_HOME))
{
Toggle();
}
if (isActive())
{
if (wi::input::Press(wi::input::KEYBOARD_BUTTON_UP))
{
historyPrev();
}
if (wi::input::Press(wi::input::KEYBOARD_BUTTON_DOWN))
{
historyNext();
}
if (wi::input::Down(wi::input::KEYBOARD_BUTTON_PAGEUP))
{
Scroll(1000.0f * dt);
}
if (wi::input::Down(wi::input::KEYBOARD_BUTTON_PAGEDOWN))
{
Scroll(-1000.0f * dt);
}
Scroll(wi::input::GetPointer().z * 20);
static bool created = false;
if (!created)
{
created = true;
inputField.Create("");
inputField.SetCancelInputEnabled(false);
inputField.OnInputAccepted([](wi::gui::EventArgs args) {
historyPos = 0;
post(args.sValue);
LogEntry entry;
entry.text = args.sValue;
entry.level = LogLevel::Default;
history.push_back(entry);
if (history.size() > deletefromline)
{
history.pop_front();
}
if (!blockLuaExec)
{
wi::lua::RunText(args.sValue);
}
else
{
post("Lua execution is disabled", LogLevel::Error);
}
inputField.SetText("");
});
wi::Color theme_color_idle = wi::Color(30, 40, 60, 200);
wi::Color theme_color_focus = wi::Color(70, 150, 170, 220);
wi::Color theme_color_active = wi::Color::White();
wi::Color theme_color_deactivating = wi::Color::lerp(theme_color_focus, wi::Color::White(), 0.5f);
inputField.SetColor(theme_color_idle); // all states the same, it's gonna be always active anyway
inputField.font.params.color = wi::Color(160, 240, 250, 255);
inputField.font.params.shadowColor = wi::Color::Transparent();
toggleButton.Create("V");
toggleButton.OnClick([](wi::gui::EventArgs args) {
Toggle();
});
toggleButton.SetColor(theme_color_idle, wi::gui::IDLE);
toggleButton.SetColor(theme_color_focus, wi::gui::FOCUS);
toggleButton.SetColor(theme_color_active, wi::gui::ACTIVE);
toggleButton.SetColor(theme_color_deactivating, wi::gui::DEACTIVATING);
toggleButton.SetShadowRadius(5);
toggleButton.SetShadowColor(wi::Color(80, 140, 180, 100));
toggleButton.font.params.color = wi::Color(160, 240, 250, 255);
toggleButton.font.params.rotation = XM_PI;
toggleButton.font.params.size = 24;
toggleButton.font.params.scaling = 3;
toggleButton.font.params.shadowColor = wi::Color::Transparent();
for (int i = 0; i < arraysize(toggleButton.sprites); ++i)
{
toggleButton.sprites[i].params.enableCornerRounding();
toggleButton.sprites[i].params.corners_rounding[2].radius = 50;
}
}
if (inputField.GetState() != wi::gui::ACTIVE)
{
inputField.SetAsActive();
}
}
else
{
inputField.Deactivate();
}
}
if (enabled)
{
pos += speed * dt;
}
else
{
pos -= speed * dt;
}
pos = wi::math::Clamp(pos, -canvas.GetLogicalHeight(), 0);
inputField.SetSize(XMFLOAT2(canvas.GetLogicalWidth() - 40, 20));
inputField.SetPos(XMFLOAT2(20, canvas.GetLogicalHeight() - 40 + pos));
inputField.Update(canvas, dt);
toggleButton.SetSize(XMFLOAT2(100, 100));
toggleButton.SetPos(XMFLOAT2(canvas.GetLogicalWidth() - toggleButton.GetSize().x - 20, 20 + pos));
toggleButton.Update(canvas, dt);
}
void Draw(
const wi::Canvas& canvas,
CommandList cmd,
ColorSpace colorspace
)
{
if (!was_ever_enabled)
return;
if (pos <= -canvas.GetLogicalHeight())
return;
GraphicsDevice* device = GetDevice();
device->EventBegin("Backlog", cmd);
if (!backgroundTex.IsValid())
{
const uint8_t colorData[] = { 0, 0, 43, 200, 43, 31, 141, 223 };
wi::texturehelper::CreateTexture(backgroundTex, colorData, 1, 2);
device->SetName(&backgroundTex, "wi::backlog::backgroundTex");
}
wi::image::Params fx = wi::image::Params((float)canvas.GetLogicalWidth(), (float)canvas.GetLogicalHeight());
fx.pos = XMFLOAT3(0, pos, 0);
fx.opacity = wi::math::Lerp(1, 0, -pos / canvas.GetLogicalHeight());
if (colorspace != ColorSpace::SRGB)
{
fx.enableLinearOutputMapping(9);
}
wi::image::Draw(&backgroundTex, fx, cmd);
wi::image::Params inputbg;
inputbg.color = wi::Color(80, 140, 180, 200);
inputbg.pos = inputField.translation;
inputbg.pos.x -= 8;
inputbg.pos.y -= 8;
inputbg.siz = inputField.GetSize();
inputbg.siz.x += 16;
inputbg.siz.y += 16;
inputbg.enableCornerRounding();
inputbg.corners_rounding[0].radius = 10;
inputbg.corners_rounding[1].radius = 10;
inputbg.corners_rounding[2].radius = 10;
inputbg.corners_rounding[3].radius = 10;
if (colorspace != ColorSpace::SRGB)
{
inputbg.enableLinearOutputMapping(9);
}
wi::image::Draw(nullptr, inputbg, cmd);
if (colorspace != ColorSpace::SRGB)
{
inputField.sprites[inputField.GetState()].params.enableLinearOutputMapping(9);
inputField.font.params.enableLinearOutputMapping(9);
toggleButton.sprites[inputField.GetState()].params.enableLinearOutputMapping(9);
toggleButton.font.params.enableLinearOutputMapping(9);
}
inputField.Render(canvas, cmd);
Rect rect;
rect.left = 0;
rect.right = (int32_t)canvas.GetPhysicalWidth();
rect.top = 0;
rect.bottom = (int32_t)canvas.GetPhysicalHeight();
device->BindScissorRects(1, &rect, cmd);
toggleButton.Render(canvas, cmd);
rect.bottom = int32_t(canvas.LogicalToPhysical(inputField.GetPos().y - 15));
device->BindScissorRects(1, &rect, cmd);
DrawOutputText(canvas, cmd, colorspace);
rect.left = 0;
rect.right = std::numeric_limits<int>::max();
rect.top = 0;
rect.bottom = std::numeric_limits<int>::max();
device->BindScissorRects(1, &rect, cmd);
device->EventEnd(cmd);
}
void DrawOutputText(
const wi::Canvas& canvas,
CommandList cmd,
ColorSpace colorspace
)
{
std::scoped_lock lock(logLock);
wi::font::SetCanvas(canvas); // always set here as it can be called from outside...
wi::font::Params params = font_params;
params.cursor = {};
if (refitscroll)
{
float textheight = wi::font::TextHeight(getTextWithoutLock(), params);
float limit = canvas.GetLogicalHeight() - 50;
if (scroll + textheight > limit)
{
scroll = limit - textheight;
}
refitscroll = false;
}
params.posX = 5;
params.posY = pos + scroll;
params.h_wrap = canvas.GetLogicalWidth() - params.posX;
if (colorspace != ColorSpace::SRGB)
{
params.enableLinearOutputMapping(9);
}
for (auto& x : entries)
{
switch (x.level)
{
case LogLevel::Warning:
params.color = wi::Color::Warning();
break;
case LogLevel::Error:
params.color = wi::Color::Error();
break;
default:
params.color = font_params.color;
break;
}
params.cursor = wi::font::Draw(x.text, params, cmd);
}
unseen = LogLevel::None;
}
std::string getText()
{
std::scoped_lock lock(logLock);
return getTextWithoutLock();
}
void clear()
{
std::scoped_lock lock(logLock);
entries.clear();
scroll = 0;
}
void post(const std::string& input, LogLevel level)
{
if (logLevel > level)
{
return;
}
// This is explicitly scoped for scoped_lock!
{
std::scoped_lock lock(logLock);
std::string str;
switch (level)
{
default:
case LogLevel::Default:
str = "";
break;
case LogLevel::Warning:
str = "[Warning] ";
break;
case LogLevel::Error:
str = "[Error] ";
break;
}
str += input;
str += '\n';
LogEntry entry;
entry.text = str;
entry.level = level;
entries.push_back(entry);
if (entries.size() > deletefromline)
{
entries.pop_front();
}
refitscroll = true;
switch (level)
{
default:
case LogLevel::Default:
wi::helper::DebugOut(str, wi::helper::DebugLevel::Normal);
break;
case LogLevel::Warning:
wi::helper::DebugOut(str, wi::helper::DebugLevel::Warning);
break;
case LogLevel::Error:
wi::helper::DebugOut(str, wi::helper::DebugLevel::Error);
break;
}
unseen = std::max(unseen, level);
// lock released on block end
}
if (level >= LogLevel::Error)
{
write_logfile(); // will lock mutex
}
}
void historyPrev()
{
std::scoped_lock lock(logLock);
if (!history.empty())
{
inputField.SetText(history[history.size() - 1 - historyPos].text);
inputField.SetAsActive();
if ((size_t)historyPos < history.size() - 1)
{
historyPos++;
}
}
}
void historyNext()
{
std::scoped_lock lock(logLock);
if (!history.empty())
{
if (historyPos > 0)
{
historyPos--;
}
inputField.SetText(history[history.size() - 1 - historyPos].text);
inputField.SetAsActive();
}
}
void setBackground(Texture* texture)
{
backgroundTex = *texture;
}
void setFontSize(int value)
{
font_params.size = value;
}
void setFontRowspacing(float value)
{
font_params.spacingY = value;
}
void setFontColor(wi::Color color)
{
font_params.color = color;
}
bool isActive() { return enabled; }
void Lock()
{
locked = true;
enabled = false;
}
void Unlock()
{
locked = false;
}
void BlockLuaExecution()
{
blockLuaExec = true;
}
void UnblockLuaExecution()
{
blockLuaExec = false;
}
void SetLogLevel(LogLevel newLevel)
{
logLevel = newLevel;
}
LogLevel GetUnseenLogLevelMax()
{
return unseen;
}
}