Lua chat callbacks + better font styling

golly gee whiz!! i hope i didn't fuck something up
This commit is contained in:
Kae 2024-04-22 06:07:59 +10:00
parent d5f5fb5ddf
commit ca1426eabc
63 changed files with 658 additions and 513 deletions

View File

@ -1,28 +1,43 @@
{
"nametag" : {
"showMasterNames" : true,
"fontDirectives" : "?border=1;222;2224",
// border is double here as it otherwise fades out near the end
// (could just the end to opaque too, but then diagonals are more jaggy)
"textStyle" : { "backDirectives" : "?border=1;222;2224?border=1;222;2224" },
"inspectOpacityRate" : 0.15,
"movementThreshold" : 0.5,
"offset" : [0, 13]
},
"font" : {
"defaultDirectives" : "",
"defaultFont" : ""
"specialDamageBar" : {
"nameStyle" : {}
},
"cursorTooltip" : {
"font" : ""
"textStyle" : {},
"buttonTextStyle" : {
"shadow" : "black"
},
"labelTextStyle" : {},
"paneTextStyle" : {},
"textBoxTextStyle" : {},
"itemSlotTextStyle" : {
"backDirectives" : "?border=1;444;4444"
},
"teamBarNameStyle" : {},
"cursorTooltip" : { "textStyle" : {} },
"debugFont" : "iosevka-semibold",
"debugFontSize" : 7,
"debugFontDirectives" : "?border=1;111a;1114",
"debugTextStyle" : {
"font" : "iosevka-semibold",
"fontSize" : 7,
"backDirectives" : "?border=2;111a;1114"
},
"debugSpatialClearTime" : 0.0,
"debugOffset" : [80, 130],
// Change planet name to support the new internal string formatting.
"planetNameFormatString" : "- {} -",
"planetNameDirectives" : "?border=4;000;000",
"planetTextStyle" : {
"backDirectives" : "?border=6;000;000",
"fontSize" : 24
},
"buttonClickSound" : [ "/sfx/interface/button/click.wav" ],
"buttonReleaseSound" : [ "/sfx/interface/button/release.wav" ],

View File

@ -1,10 +1,8 @@
{
"config" : {
"lineHeight" : 1,
"font" : {
"directives" : "?border=1;111a;1114",
"padding" : [1, 1] // Padding to prevent border clipping at the edges of the log canvas while keeping compat with mods that patch the canvas size
},
"padding" : [1, 1],
"textStyle" : { "backDirectives" : "?border=1;111a;1114" },
"colors" : {
"local" : "^white;",
"party" : "^blue;",

View File

@ -1,4 +1,7 @@
{
"movementThreshold" : 0.5,
"bubbleOffset" : [0, 1.875]
"movementThreshold" : 0.5,
"bubbleOffset" : [0, 1.875],
"textStyle" : {
"backDirectives" : "?border=1;000a;0000?border=1;000a;0004"
}
}

View File

@ -427,8 +427,6 @@ void Assets::queueJsons(StringSet const& paths) const {
}
ImageConstPtr Assets::image(AssetPath const& path) const {
validatePath(path, true, true);
return as<ImageData>(getAsset(AssetId{AssetType::Image, path}))->image;
}
@ -956,7 +954,7 @@ Json Assets::checkPatchArray(String const& path, AssetSourcePtr const& source, J
Json Assets::readJson(String const& path) const {
ByteArray streamData = read(path);
try {
Json result = inputUtf8Json(streamData.begin(), streamData.end(), false);
Json result = inputUtf8Json(streamData.begin(), streamData.end(), JsonParseType::Top);
for (auto const& pair : m_files.get(path).patchSources) {
auto& patchPath = pair.first;
auto& patchSource = pair.second;
@ -973,7 +971,7 @@ Json Assets::readJson(String const& path) const {
if (newResult)
result = std::move(newResult);
} else {
auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false);
auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), JsonParseType::Top);
if (patchJson.isType(Json::Type::Array)) {
auto patchData = patchJson.toArray();
try {
@ -1131,6 +1129,7 @@ shared_ptr<Assets::AssetData> Assets::loadJson(AssetPath const& path) const {
}
shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
validatePath(path, true, true);
if (!path.directives.empty()) {
shared_ptr<ImageData> source =
as<ImageData>(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}}));

View File

@ -667,6 +667,7 @@ void ClientApplication::changeState(MainAppState newState) {
m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get()));
m_universeClient->setLuaCallbacks("chat", LuaBindings::makeChatCallbacks(m_mainInterface.get(), m_universeClient.get()));
m_universeClient->startLua();
m_mainMixer->setWorldPainter(m_worldPainter);

View File

@ -162,7 +162,7 @@ std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
rhs.directives.forEach([&](auto const& entry, Directives const& directives) {
os << "?";
os << entry.string(*directives.shared);
os << entry.string(*directives);
});
return os;

View File

@ -42,6 +42,8 @@ bool Directives::Shared::empty() const {
return entries.empty();
}
Directives::Shared::Shared() {}
Directives::Shared::Shared(List<Entry>&& givenEntries, String&& givenString, StringView givenPrefix) {
entries = std::move(givenEntries);
string = std::move(givenString);
@ -74,7 +76,7 @@ Directives::Directives(Directives const& directives) {
Directives::~Directives() {}
Directives& Directives::operator=(String const& s) {
if (shared && shared->string == s)
if (m_shared && m_shared->string == s)
return *this;
parse(String(s));
@ -82,7 +84,7 @@ Directives& Directives::operator=(String const& s) {
}
Directives& Directives::operator=(String&& s) {
if (shared && shared->string == s) {
if (m_shared && m_shared->string == s) {
s.clear();
return *this;
}
@ -92,7 +94,7 @@ Directives& Directives::operator=(String&& s) {
}
Directives& Directives::operator=(const char* s) {
if (shared && shared->string.utf8().compare(s) == 0)
if (m_shared && m_shared->string.utf8().compare(s) == 0)
return *this;
parse(s);
@ -100,27 +102,29 @@ Directives& Directives::operator=(const char* s) {
}
Directives& Directives::operator=(Directives&& other) noexcept {
shared = std::move(other.shared);
m_shared = std::move(other.m_shared);
return *this;
}
Directives& Directives::operator=(Directives const& other) {
shared = other.shared;
m_shared = other.m_shared;
return *this;
}
void Directives::loadOperations() const {
if (!shared)
if (!m_shared)
return;
MutexLocker lock(shared->mutex, true);
for (auto& entry : shared->entries)
entry.loadOperation(*shared);
MutexLocker locker(m_shared->mutex, false);
if (!m_shared.unique())
locker.lock();
for (auto& entry : m_shared->entries)
entry.loadOperation(*m_shared);
}
void Directives::parse(String&& directives) {
if (directives.empty()) {
shared.reset();
m_shared.reset();
return;
}
@ -147,46 +151,46 @@ void Directives::parse(String&& directives) {
});
if (entries.empty() && !prefix.empty()) {
shared.reset();
m_shared.reset();
return;
}
shared = std::make_shared<Shared const>(std::move(entries), std::move(directives), prefix);
m_shared = std::make_shared<Shared const>(std::move(entries), std::move(directives), prefix);
if (view.utf8().size() < 1000) { // Pre-load short enough directives
for (auto& entry : shared->entries)
entry.loadOperation(*shared);
for (auto& entry : m_shared->entries)
entry.loadOperation(*m_shared);
}
}
String Directives::string() const {
if (!shared)
if (!m_shared)
return "";
else
return shared->string;
return m_shared->string;
}
StringView Directives::prefix() const {
if (!shared)
if (!m_shared)
return "";
else
return shared->prefix;
return m_shared->prefix;
}
String const* Directives::stringPtr() const {
if (!shared)
if (!m_shared)
return nullptr;
else
return &shared->string;
return &m_shared->string;
}
String Directives::buildString() const {
if (shared) {
String built = shared->prefix;
if (m_shared) {
String built = m_shared->prefix;
for (auto& entry : shared->entries) {
for (auto& entry : m_shared->entries) {
built += "?";
built += entry.string(*shared);
built += entry.string(*m_shared);
}
return built;
@ -197,23 +201,25 @@ String Directives::buildString() const {
String& Directives::addToString(String& out) const {
if (!empty())
out += shared->string;
out += m_shared->string;
return out;
}
size_t Directives::hash() const {
return shared ? shared->hash : 0;
return m_shared ? m_shared->hash : 0;
}
size_t Directives::size() const {
return shared ? shared->entries.size() : 0;
return m_shared ? m_shared->entries.size() : 0;
}
bool Directives::empty() const {
return !shared || shared->empty();
return !m_shared || m_shared->empty();
}
Directives::operator bool() const { return !empty(); }
Directives::operator bool() const {
return !empty();
}
bool Directives::equals(Directives const& other) const {
return hash() == other.hash();
@ -243,7 +249,7 @@ DataStream& operator>>(DataStream& ds, Directives& directives) {
DataStream& operator<<(DataStream & ds, Directives const& directives) {
if (directives)
ds.write(directives.shared->string);
ds.write(directives->string);
else
ds.write(String());
@ -326,21 +332,22 @@ String DirectivesGroup::toString() const {
}
void DirectivesGroup::addToString(String& string) const {
for (auto& directives : m_directives)
if (directives.shared) {
auto& dirString = directives.shared->string;
for (auto& directives : m_directives) {
if (directives) {
auto& dirString = directives->string;
if (!dirString.empty()) {
if (dirString.utf8().front() != '?')
string += "?";
string += dirString;
}
}
}
}
void DirectivesGroup::forEach(DirectivesCallback callback) const {
for (auto& directives : m_directives) {
if (directives.shared) {
for (auto& entry : directives.shared->entries)
if (directives) {
for (auto& entry : directives->entries)
callback(entry, directives);
}
}
@ -348,8 +355,8 @@ void DirectivesGroup::forEach(DirectivesCallback callback) const {
bool DirectivesGroup::forEachAbortable(AbortableDirectivesCallback callback) const {
for (auto& directives : m_directives) {
if (directives.shared) {
for (auto& entry : directives.shared->entries) {
if (directives) {
for (auto& entry : directives->entries) {
if (!callback(entry, directives))
return false;
}
@ -367,7 +374,7 @@ Image DirectivesGroup::applyNewImage(Image const& image) const {
void DirectivesGroup::applyExistingImage(Image& image) const {
forEach([&](auto const& entry, Directives const& directives) {
ImageOperation const& operation = entry.loadOperation(*directives.shared);
ImageOperation const& operation = entry.loadOperation(*directives);
if (auto error = operation.ptr<ErrorImageOperation>())
std::rethrow_exception(error->exception);
else

View File

@ -36,6 +36,7 @@ public:
mutable Mutex mutex;
bool empty() const;
Shared();
Shared(List<Entry>&& givenEntries, String&& givenString, StringView givenPrefix);
};
@ -65,6 +66,9 @@ public:
bool empty() const;
operator bool() const;
Shared const& operator*() const;
Shared const* operator->() const;
bool equals(Directives const& other) const;
bool equals(String const& string) const;
@ -78,7 +82,7 @@ public:
//friend bool operator==(Directives const& directives, String const& string);
//friend bool operator==(String const& string, Directives const& directives);
std::shared_ptr<Shared const> shared;
std::shared_ptr<Shared const> m_shared;
};
class DirectivesGroup {
@ -122,7 +126,6 @@ private:
size_t m_count;
};
template <>
struct hash<DirectivesGroup> {
size_t operator()(DirectivesGroup const& s) const;
@ -130,4 +133,13 @@ struct hash<DirectivesGroup> {
typedef DirectivesGroup ImageDirectives;
inline Directives::Shared const& Directives::operator*() const {
return *m_shared;
}
inline Directives::Shared const* Directives::operator->() const {
if (!m_shared)
throw DirectivesException("Directives::operator-> nullptr");
return m_shared.get();
}
}

View File

@ -65,12 +65,12 @@ bool CommaElement::operator==(CommaElement const&) const {
FormattedJson FormattedJson::parse(String const& string) {
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
string.begin(), string.end(), true);
string.begin(), string.end(), JsonParseType::Value);
}
FormattedJson FormattedJson::parseJson(String const& string) {
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
string.begin(), string.end(), false);
string.begin(), string.end(), JsonParseType::Top);
}
FormattedJson FormattedJson::ofType(Json::Type type) {

View File

@ -105,11 +105,15 @@ Json Json::ofType(Type t) {
}
Json Json::parse(String const& string) {
return inputUtf32Json<String::const_iterator>(string.begin(), string.end(), true);
return inputUtf32Json<String::const_iterator>(string.begin(), string.end(), JsonParseType::Value);
}
Json Json::parseSequence(String const& sequence) {
return inputUtf32Json<String::const_iterator>(sequence.begin(), sequence.end(), JsonParseType::Sequence);
}
Json Json::parseJson(String const& json) {
return inputUtf32Json<String::const_iterator>(json.begin(), json.end(), false);
return inputUtf32Json<String::const_iterator>(json.begin(), json.end(), JsonParseType::Top);
}
Json::Json() {}

View File

@ -51,6 +51,9 @@ public:
// Parses JSON or JSON sub-type
static Json parse(String const& string);
// Parses JSON sequence
static Json parseSequence(String const& sequence);
// Parses JSON object or array only (the only top level types allowed by
// JSON)
static Json parseJson(String const& json);

View File

@ -50,7 +50,7 @@ public:
};
template <typename InputIterator>
Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) {
Json inputUtf8Json(InputIterator begin, InputIterator end, JsonParseType parseType) {
typedef U8ToU32Iterator<InputIterator> Utf32Input;
typedef JsonParser<Utf32Input> Parser;
@ -58,7 +58,7 @@ Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) {
Parser parser(stream);
Utf32Input wbegin(begin);
Utf32Input wend(end);
Utf32Input pend = parser.parse(wbegin, wend, fragment);
Utf32Input pend = parser.parse(wbegin, wend, parseType);
if (parser.error())
throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column()));
@ -77,11 +77,11 @@ void outputUtf8Json(Json const& val, OutputIterator out, int pretty, bool sort)
}
template <typename InputIterator, typename Stream = JsonBuilderStream, typename Jsonlike = Json>
Jsonlike inputUtf32Json(InputIterator begin, InputIterator end, bool fragment) {
Jsonlike inputUtf32Json(InputIterator begin, InputIterator end, JsonParseType parseType) {
Stream stream;
JsonParser<InputIterator> parser(stream);
InputIterator pend = parser.parse(begin, end, fragment);
InputIterator pend = parser.parse(begin, end, parseType);
if (parser.error()) {
throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column()));

View File

@ -27,6 +27,12 @@ struct JsonStream {
virtual void putComma() = 0;
};
enum class JsonParseType : uint8_t {
Top, // Top-level Object or Array
Value, // Any singular Json value
Sequence // Like an array, but without needing the [] or commas.
};
// Will parse JSON and output to a given JsonStream. Parses an *extension* to
// the JSON format that includes comments.
template <typename InputIterator>
@ -39,15 +45,17 @@ public:
// Does not throw. On error, returned iterator will not be equal to end, and
// error() will be non-null. Set fragment to true to parse any JSON type
// rather than just object or array.
InputIterator parse(InputIterator begin, InputIterator end, bool fragment = false) {
InputIterator parse(InputIterator begin, InputIterator end, JsonParseType parseType = JsonParseType::Top) {
init(begin, end);
try {
white();
if (fragment)
value();
else
if (parseType == JsonParseType::Top)
top();
else if (parseType == JsonParseType::Value)
value();
else if (parseType == JsonParseType::Sequence)
sequence();
white();
} catch (ParsingException const&) {
}
@ -190,6 +198,56 @@ private:
}
}
void sequence() {
m_stream.beginArray();
while (true) {
if (isSpace(m_char)) {
next();
continue;
} else if (m_char == '{')
object();
else if (m_char == '[')
array();
else if (m_char == '"')
string();
else if (m_char == '-')
number();
else if (m_char == 0)
break;
else {
auto begin = m_current;
if (m_char >= '0' && m_char <= '9') {
number();
if (m_error.empty()) {
next();
continue;
}
}
m_error.clear();
m_current = begin;
if (m_char == 't' || m_char == 'f' || m_char == 'n') {
word();
if (m_error.empty()) {
next();
continue;
}
}
m_error.clear();
m_current = begin;
// well, shit. do a simple string parse until we hit whitespace
// no fancy things like \n, do a proper string if you want that
CharArray str;
do {
str += m_char;
next();
} while (m_char && !isSpace(m_char));
m_stream.putString(str.c_str(), str.length());
}
next();
}
m_stream.endArray();
}
void string() {
CharArray s = parseString();
m_stream.putString(s.c_str(), s.length());

View File

@ -60,6 +60,10 @@ LuaInt LuaTable::rawLength() const {
return engine().tableLength(true, handleIndex());
}
void LuaCallbacks::copyCallback(String srcName, String dstName) {
m_callbacks.set(dstName, m_callbacks.get(srcName));
}
LuaCallbacks& LuaCallbacks::merge(LuaCallbacks const& callbacks) {
try {
for (auto const& pair : callbacks.m_callbacks)

View File

@ -267,6 +267,8 @@ LuaValue const LuaNil = LuaValue();
class LuaCallbacks {
public:
void copyCallback(String srcName, String dstName);
template <typename Function>
void registerCallback(String name, Function&& func);

View File

@ -1,9 +1,35 @@
#include "StarText.hpp"
#include "StarJsonExtra.hpp"
#include <regex>
namespace Star {
TextStyle::TextStyle(Json const& config) : TextStyle() {
if (config.isType(Json::Type::String))
font = config.toString();
else
loadJson(config);
}
TextStyle& TextStyle::loadJson(Json const& config) {
if (!config)
return *this;
lineSpacing = config.getFloat("lineSpacing", lineSpacing);
if (auto jColor = config.opt("color"))
color = jsonToColor(*jColor).toRgba();
if (auto jShadow = config.opt("shadow"))
shadow = jsonToColor(*jShadow).toRgba();
fontSize = config.getUInt("fontSize", fontSize);
if (auto jFont = config.optString("font"))
font = *jFont;
if (auto jDirectives = config.optString("directives"))
directives = *jDirectives;
if (auto jBackDirectives = config.optString("backDirectives"))
backDirectives = *jBackDirectives;
return *this;
}
namespace Text {
static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc));
String stripEscapeCodes(String const& s) {

View File

@ -2,9 +2,29 @@
#include "StarString.hpp"
#include "StarStringView.hpp"
#include "StarVector.hpp"
#include "StarDirectives.hpp"
#include "StarJson.hpp"
namespace Star {
unsigned const DefaultFontSize = 8;
float const DefaultLineSpacing = 1.3f;
struct TextStyle {
float lineSpacing = DefaultLineSpacing;
Vec4B color = Vec4B::filled(255);
Vec4B shadow = Vec4B::filled(0);
unsigned fontSize = DefaultFontSize;
String font = "";
Directives directives;
Directives backDirectives;
TextStyle() = default;
TextStyle(Json const& config);
TextStyle& loadJson(Json const& config);
};
namespace Text {
unsigned char const StartEsc = '\x1b';
unsigned char const EndEsc = ';';

View File

@ -118,12 +118,14 @@ LuaCallbacks LuaBindings::makeUtilityCallbacks() {
callbacks.registerCallback("print", UtilityCallbacks::print);
callbacks.registerCallback("interpolateSinEase", UtilityCallbacks::interpolateSinEase);
callbacks.registerCallback("replaceTags", UtilityCallbacks::replaceTags);
callbacks.registerCallback("jsonParse", [](String const& json) { return Json::parse(json); });
callbacks.registerCallback("parseJsonSequence", [](String const& json) { return Json::parseSequence(json); });
callbacks.registerCallback("jsonMerge", [](Json const& a, Json const& b) { return jsonMerge(a, b); });
callbacks.registerCallback("jsonQuery", [](Json const& json, String const& path, Json const& def) { return json.query(path, def); });
callbacks.registerCallback("makeRandomSource", [](Maybe<uint64_t> seed) { return seed ? RandomSource(*seed) : RandomSource(); });
callbacks.registerCallback("makePerlinSource", [](Json const& config) { return PerlinF(config); });
callbacks.copyCallback("parseJson", "jsonFromString"); // SE compat
auto hash64LuaValues = [](LuaVariadic<LuaValue> const& values) -> uint64_t {
XXHash64 hash;

View File

@ -83,9 +83,7 @@ ActionBar::ActionBar(MainInterfacePaneManager* paneManager, PlayerPtr player) {
TextPositioning countPosition = {jsonToVec2F(m_config.get("countMidAnchor")), HorizontalAnchor::HMidAnchor};
customBarLeft->setCountPosition(countPosition);
customBarLeft->setCountFontMode(FontMode::Shadow);
customBarRight->setCountPosition(countPosition);
customBarRight->setCountFontMode(FontMode::Shadow);
m_customBarWidgets.append({customBarLeft, customBarRight, customBarLeftOverlay, customBarRightOverlay});
}

View File

@ -20,32 +20,30 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) {
m_historyOffset = 0;
auto assets = Root::singleton().assets();
auto config = assets->json("/interface/chat/chat.config:config");
m_timeChatLastActive = Time::monotonicMilliseconds();
auto fontConfig = assets->json("/interface/chat/chat.config:config.font");
m_fontSize = fontConfig.getInt("baseSize");
m_fontDirectives = fontConfig.queryString("directives", "");
m_font = fontConfig.queryString("type", "");
m_chatLineHeight = assets->json("/interface/chat/chat.config:config.lineHeight").toFloat();
m_chatVisTime = assets->json("/interface/chat/chat.config:config.visTime").toFloat();
m_fadeRate = assets->json("/interface/chat/chat.config:config.fadeRate").toDouble();
m_chatHistoryLimit = assets->json("/interface/chat/chat.config:config.chatHistoryLimit").toInt();
m_chatTextStyle = config.get("textStyle");
m_chatTextStyle.lineSpacing = config.get("lineHeight").toFloat();
m_chatVisTime = config.get("visTime").toFloat();
m_fadeRate = config.get("fadeRate").toDouble();
m_chatHistoryLimit = config.get("chatHistoryLimit").toInt();
m_portraitTextOffset = jsonToVec2I(assets->json("/interface/chat/chat.config:config.portraitTextOffset"));
m_portraitImageOffset = jsonToVec2I(assets->json("/interface/chat/chat.config:config.portraitImageOffset"));
m_portraitScale = assets->json("/interface/chat/chat.config:config.portraitScale").toFloat();
m_portraitVerticalMargin = assets->json("/interface/chat/chat.config:config.portraitVerticalMargin").toFloat();
m_portraitBackground = assets->json("/interface/chat/chat.config:config.portraitBackground").toString();
m_portraitTextOffset = jsonToVec2I(config.get("portraitTextOffset"));
m_portraitImageOffset = jsonToVec2I(config.get("portraitImageOffset"));
m_portraitScale = config.get("portraitScale").toFloat();
m_portraitVerticalMargin = config.get("portraitVerticalMargin").toFloat();
m_portraitBackground = config.get("portraitBackground").toString();
m_bodyHeight = assets->json("/interface/chat/chat.config:config.bodyHeight").toInt();
m_expandedBodyHeight = assets->json("/interface/chat/chat.config:config.expandedBodyHeight").toInt();
m_bodyHeight = config.get("bodyHeight").toInt();
m_expandedBodyHeight = config.get("expandedBodyHeight").toInt();
m_colorCodes[MessageContext::Local] = assets->json("/interface/chat/chat.config:config.colors.local").toString();
m_colorCodes[MessageContext::Party] = assets->json("/interface/chat/chat.config:config.colors.party").toString();
m_colorCodes[MessageContext::Broadcast] = assets->json("/interface/chat/chat.config:config.colors.broadcast").toString();
m_colorCodes[MessageContext::Whisper] = assets->json("/interface/chat/chat.config:config.colors.whisper").toString();
m_colorCodes[MessageContext::CommandResult] = assets->json("/interface/chat/chat.config:config.colors.commandResult").toString();
m_colorCodes[MessageContext::RadioMessage] = assets->json("/interface/chat/chat.config:config.colors.radioMessage").toString();
m_colorCodes[MessageContext::World] = assets->json("/interface/chat/chat.config:config.colors.world").toString();
m_colorCodes[MessageContext::Local] = config.query("colors.local").toString();
m_colorCodes[MessageContext::Party] = config.query("colors.party").toString();
m_colorCodes[MessageContext::Broadcast] = config.query("colors.broadcast").toString();
m_colorCodes[MessageContext::Whisper] = config.query("colors.whisper").toString();
m_colorCodes[MessageContext::CommandResult] = config.query("colors.commandResult").toString();
m_colorCodes[MessageContext::RadioMessage] = config.query("colors.radioMessage").toString();
m_colorCodes[MessageContext::World] = config.query("colors.world").toString();
GuiReader reader;
@ -72,7 +70,7 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) {
m_say = fetchChild<LabelWidget>("say");
m_chatLog = fetchChild<CanvasWidget>("chatLog");
if (auto logPadding = fontConfig.optQuery("padding")) {
if (auto logPadding = config.optQuery("padding")) {
m_chatLogPadding = jsonToVec2I(logPadding.get());
m_chatLog->setSize(m_chatLog->size() + m_chatLogPadding * 2);
m_chatLog->setPosition(m_chatLog->position() - m_chatLogPadding);
@ -133,8 +131,8 @@ String Chat::currentChat() const {
return m_textBox->getText();
}
void Chat::setCurrentChat(String const& chat) {
m_textBox->setText(chat);
bool Chat::setCurrentChat(String const& chat, bool moveCursor) {
return m_textBox->setText(chat, true, moveCursor);
}
void Chat::clearCurrentChat() {
@ -179,8 +177,7 @@ void Chat::addMessages(List<ChatReceivedMessage> const& messages, bool showPane)
if (message.portrait.empty())
wrapWidth = m_chatLog->size()[0] - m_chatLogPadding[0] * 2;
guiContext.setFont(m_font);
guiContext.setFontSize(m_fontSize);
guiContext.setTextStyle(m_chatTextStyle);
StringList lines;
if (message.fromNick != "" && message.portrait == "")
lines = guiContext.wrapInterfaceText(strf("<{}> {}", message.fromNick, message.text), wrapWidth);
@ -239,10 +236,10 @@ void Chat::renderImpl() {
int messageIndex = -m_historyOffset;
GuiContext& guiContext = GuiContext::singleton();
guiContext.setFont(m_font);
guiContext.setFontSize(m_fontSize);
guiContext.setLineSpacing(m_chatLineHeight);
for (auto message : m_receivedMessages) {
float lineHeight = m_chatTextStyle.lineSpacing;
float fontSize = m_chatTextStyle.fontSize;
guiContext.setTextStyle(m_chatTextStyle);
for (auto& message : m_receivedMessages) {
if (!m_modeFilter.empty() && !m_modeFilter.contains(message.mode))
continue;
@ -260,7 +257,7 @@ void Chat::renderImpl() {
String messageString = channelColorCode + message.text;
float messageHeight = 0;
float lineHeightMargin = + ((m_chatLineHeight * m_fontSize) - m_fontSize);
float lineHeightMargin = ((lineHeight * fontSize) - fontSize);
unsigned wrapWidth = m_chatLog->size()[0] - m_chatLogPadding[0] * 2;
if (message.portrait != "") {
@ -274,13 +271,14 @@ void Chat::renderImpl() {
m_chatLog->drawImage(m_portraitBackground, Vec2F(imagePosition), 1.0f, fade);
m_chatLog->drawImage(message.portrait, Vec2F(imagePosition + m_portraitImageOffset), m_portraitScale, fade);
tp.pos += Vec2F(0, floor(messageHeight / 2));
m_chatLog->drawText(messageString, tp, m_fontSize, fade, FontMode::Normal, m_chatLineHeight, m_font, m_fontDirectives);
m_chatTextStyle.color = fade;
m_chatLog->drawText(messageString, tp, m_chatTextStyle);
} else {
TextPositioning tp = {Vec2F(chatMin), HorizontalAnchor::LeftAnchor, VerticalAnchor::BottomAnchor, wrapWidth};
messageHeight = guiContext.determineInterfaceTextSize(messageString, tp).size()[1] + lineHeightMargin;
m_chatLog->drawText(messageString, tp, m_fontSize, fade, FontMode::Normal, m_chatLineHeight, m_font, m_fontDirectives);
m_chatTextStyle.color = fade;
m_chatLog->drawText(messageString, tp, m_chatTextStyle);
}
chatMin[1] += ceil(messageHeight);

View File

@ -32,7 +32,7 @@ public:
void addHistory(String const& chat);
String currentChat() const;
void setCurrentChat(String const& chat);
bool setCurrentChat(String const& chat, bool moveCursor = false);
void clearCurrentChat();
ChatSendMode sendMode() const;
@ -66,10 +66,7 @@ private:
int64_t m_timeChatLastActive;
float m_chatVisTime;
float m_fadeRate;
unsigned m_fontSize;
String m_fontDirectives;
String m_font;
float m_chatLineHeight;
TextStyle m_chatTextStyle;
unsigned m_chatHistoryLimit;
int m_historyOffset;

View File

@ -20,9 +20,8 @@ ChatBubbleManager::ChatBubbleManager()
auto jsonData = assets->json("/interface/windowconfig/chatbubbles.config");
m_color = jsonToColor(jsonData.get("textColor"));
m_fontSize = jsonData.getInt("fontSize");
m_textStyle.color = jsonToColor(jsonData.get("textColor")).toRgba();
m_textStyle.loadJson(jsonData.get("textStyle"));
m_textPadding = jsonToVec2F(jsonData.get("textPadding"));
m_zoom = jsonData.getInt("textZoom");
@ -196,9 +195,7 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
// bother me so bad if it weren't so fucking easy to do right.
// yea I agree
m_guiContext->setFontSize(m_fontSize, m_zoom);
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setDefaultFont();
m_guiContext->setTextStyle(m_textStyle);
auto result = m_guiContext->determineTextSize(sayAction.text, m_textTemplate);
float textWidth = result.width() / m_zoom + m_textPadding[0];
float textHeight = result.height() / m_zoom + m_textPadding[1];
@ -252,14 +249,17 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
float verticalShift = (partSize * innerTiles[1] - textMultiLineShift) * 0.5f + textMultiLineShift;
Vec2F position = Vec2F(horizontalCenter, verticalShift);
List<BubbleText> bubbleTexts;
auto fontSize = config.getUInt("fontSize", m_fontSize);
auto color = config.opt("color").apply(jsonToColor).value(m_color);
bubbleTexts.append(make_tuple(sayAction.text, fontSize, color.toRgba(), true, position));
TextStyle textStyle = m_textStyle;
textStyle.fontSize = config.getUInt("fontSize", textStyle.fontSize);
if (auto jColor = config.opt("color"))
textStyle.color = jsonToColor(*jColor).toRgba();
textStyle.loadJson(config.get("style", Json()));
bubbleTexts.append(make_tuple(sayAction.text, textStyle, true, position));
for (auto& backgroundImage : backgroundImages)
get<1>(backgroundImage) += Vec2F(-horizontalCenter, partSize);
for (auto& bubbleText : bubbleTexts)
get<4>(bubbleText) += Vec2F(-horizontalCenter, partSize);
get<3>(bubbleText) += Vec2F(-horizontalCenter, partSize);
auto pos = m_camera.worldToScreen(sayAction.position + m_bubbleOffset);
RectF boundBox = fold(backgroundImages, RectF::null(), [pos, this](RectF const& boundBox, BubbleImage const& bubbleImage) {
@ -272,7 +272,7 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
m_bubbles.filter([&sayAction](BubbleState<Bubble> const&, Bubble const& bubble) { return bubble.entity != sayAction.entity; });
m_bubbles.addBubble(pos, boundBox, std::move(bubble), m_interBubbleMargin * m_zoom);
oldBubbles.sort([](BubbleState<Bubble> const& a, BubbleState<Bubble> const& b) { return a.contents.age < b.contents.age; });
for (auto bubble : oldBubbles.slice(0, m_maxMessagePerEntity - 1))
for (auto& bubble : oldBubbles.slice(0, m_maxMessagePerEntity - 1))
m_bubbles.addBubble(bubble.idealDestination, bubble.boundBox, bubble.contents, 0);
} else if (action.is<PortraitChatAction>()) {
@ -286,12 +286,12 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
backgroundImages.append(make_tuple(m_portraitMoreImage, Vec2F(m_portraitMorePosition)));
backgroundImages.append(make_tuple(portraitAction.portrait, Vec2F(m_portraitPosition)));
List<BubbleText> bubbleTexts;
bubbleTexts.append(make_tuple(portraitAction.text, m_fontSize, m_color.toRgba(), false, Vec2F(m_portraitTextPosition)));
bubbleTexts.append(make_tuple(portraitAction.text, m_textStyle, false, Vec2F(m_portraitTextPosition)));
for (auto& backgroundImage : backgroundImages)
get<1>(backgroundImage) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0);
for (auto& bubbleText : bubbleTexts)
get<4>(bubbleText) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0);
get<3>(bubbleText) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0);
m_portraitBubbles.prepend({
portraitAction.entity,
@ -321,28 +321,19 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
RectF ChatBubbleManager::bubbleImageRect(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio) {
auto imgMetadata = Root::singleton().imageMetadataDatabase();
auto image = get<0>(bubbleImage);
auto& image = get<0>(bubbleImage);
return RectF::withSize(screenPos + get<1>(bubbleImage) * pixelRatio, Vec2F(imgMetadata->imageSize(image)) * pixelRatio);
}
void ChatBubbleManager::drawBubbleImage(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio, int alpha) {
auto image = get<0>(bubbleImage);
auto& image = get<0>(bubbleImage);
auto offset = get<1>(bubbleImage) * pixelRatio;
m_guiContext->drawQuad(image, screenPos + offset, pixelRatio, {255, 255, 255, alpha});
}
void ChatBubbleManager::drawBubbleText(Vec2F screenPos, BubbleText const& bubbleText, int pixelRatio, int alpha, bool isPortrait) {
Vec4B const& baseColor = get<2>(bubbleText);
// use the alpha as a blend value for the text colour as pulled from data.
Vec4B const& displayColor = Vec4B(baseColor[0], baseColor[1], baseColor[2], (baseColor[3] * alpha) / 255);
m_guiContext->setDefaultFont();
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setFontColor(displayColor);
m_guiContext->setFontSize(get<1>(bubbleText), m_zoom);
auto offset = get<4>(bubbleText) * pixelRatio;
m_guiContext->setTextStyle(get<1>(bubbleText), m_zoom);
auto offset = get<3>(bubbleText) * pixelRatio;
TextPositioning tp = isPortrait ? m_portraitTextTemplate : m_textTemplate;
tp.pos = screenPos + offset;

View File

@ -26,7 +26,7 @@ public:
private:
typedef tuple<String, Vec2F> BubbleImage;
typedef tuple<String, unsigned, Vec4B, bool, Vec2F> BubbleText;
typedef tuple<String, TextStyle, bool, Vec2F> BubbleText;
struct Bubble {
EntityId entity;
@ -64,8 +64,7 @@ private:
TextPositioning m_textTemplate;
TextPositioning m_portraitTextTemplate;
Color m_color;
int m_fontSize;
TextStyle m_textStyle;
Vec2F m_textPadding;
BubbleSeparator<Bubble> m_bubbles;

View File

@ -45,8 +45,9 @@ void Cinematic::load(Json const& definition) {
panel->animationFrames = panelDefinition.getInt("animationFrames", std::numeric_limits<int>::max());
panel->text = panelDefinition.getString("text", "");
panel->textPosition = TextPositioning(panelDefinition.get("textPosition", JsonObject()));
panel->fontColor = panelDefinition.opt("fontColor").apply(jsonToVec4B).value(Vec4B(255, 255, 255, 255));
panel->fontSize = panelDefinition.getUInt("fontSize", 8);
panel->textStyle = panelDefinition.get("textStyle", Json());
panel->textStyle.color = panelDefinition.opt("fontColor").apply(jsonToVec4B).value(panel->textStyle.color);
panel->textStyle.fontSize = panelDefinition.getUInt("fontSize", panel->textStyle.fontSize);
panel->avatar = panelDefinition.getString("avatar", "");
panel->startTime = panelDefinition.getFloat("startTime", 0);
panel->endTime = panelDefinition.getFloat("endTime", 0);
@ -208,8 +209,9 @@ void Cinematic::render() {
}
}
if (!panel->text.empty()) {
textPainter->setFontSize(floor(panel->fontSize * drawableScale));
auto fontColor = panel->fontColor;
textPainter->setTextStyle(panel->textStyle);
textPainter->setFontSize(floor(panel->textStyle.fontSize * drawableScale));
auto fontColor = panel->textStyle.color;
fontColor[3] *= values.alpha;
textPainter->setFontColor(fontColor);
Vec2F position = (m_offset + values.position + Vec2F(panel->textPosition.pos)) * drawableScale + drawableTranslation;

View File

@ -62,8 +62,7 @@ private:
int animationFrames;
String text;
TextPositioning textPosition;
Vec4B fontColor;
unsigned fontSize;
TextStyle textStyle;
List<KeyFrame> keyFrames;
float startTime;
float endTime;

View File

@ -91,7 +91,7 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
}
} else {
auto player = m_universeClient->mainPlayer();
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments }))
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), "/" + command, { allArguments }))
result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
else
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);

View File

@ -4,6 +4,9 @@
#include "StarLuaGameConverters.hpp"
#include "StarMainInterface.hpp"
#include "StarGuiContext.hpp"
#include "StarChat.hpp"
#include "StarUniverseClient.hpp"
#include "StarClientCommandProcessor.hpp"
namespace Star {
@ -42,4 +45,49 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) {
return callbacks;
}
LuaCallbacks LuaBindings::makeChatCallbacks(MainInterface* mainInterface, UniverseClient* client) {
LuaCallbacks callbacks;
auto chat = as<Chat>(mainInterface->paneManager()->registeredPane(MainInterfacePanes::Chat).get());
callbacks.registerCallback("send", [chat, client](String const& message, Maybe<String> modeName, Maybe<bool> speak) {
auto sendMode = modeName ? ChatSendModeNames.getLeft(*modeName) : ChatSendMode::Broadcast;
client->sendChat(message, sendMode, speak);
});
// just for SE compat - this shoulda been a utility callback :moyai:
callbacks.registerCallback("parseArguments", [](String const& args) {
return Json::parseSequence(args);
});
callbacks.registerCallback("command", [mainInterface](String const& command) -> StringList {
return mainInterface->commandProcessor()->handleCommand(command);
});
callbacks.registerCallback("addMessage", [client, chat](String const& text, Maybe<Json> config) {
ChatReceivedMessage message({MessageContext::Mode::CommandResult, ""}, client->clientContext()->connectionId(), "", text);
if (config) {
if (auto mode = config->optString("mode"))
message.context.mode = MessageContextModeNames.getLeft(*mode);
if (auto channelName = config->optString("channelName"))
message.context.channelName = std::move(*channelName);
if (auto portrait = config->optString("portrait"))
message.portrait = std::move(*portrait);
if (auto fromNick = config->optString("fromNick"))
message.fromNick = std::move(*fromNick);
}
chat->addMessages({std::move(message)}, config ? config->getBool("showPane", true) : true);
});
callbacks.registerCallback("input", [chat]() -> String {
return chat->currentChat();
});
callbacks.registerCallback("setInput", [chat](String const& text, Maybe<bool> moveCursor) -> bool {
return chat->setCurrentChat(text, moveCursor.value(false));
});
return callbacks;
}
}

View File

@ -5,9 +5,11 @@
namespace Star {
STAR_CLASS(MainInterface);
STAR_CLASS(UniverseClient);
namespace LuaBindings {
LuaCallbacks makeInterfaceCallbacks(MainInterface* mainInterface);
LuaCallbacks makeChatCallbacks(MainInterface* mainInterface, UniverseClient* client);
}
}

View File

@ -160,10 +160,8 @@ MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter,
auto planetName = make_shared<Pane>();
m_planetText = make_shared<LabelWidget>();
m_planetText->setFontSize(m_config->planetNameFontSize);
m_planetText->setFontMode(FontMode::Normal);
m_planetText->setTextStyle(m_config->planetNameTextStyle);
m_planetText->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor);
m_planetText->setDirectives(m_config->planetNameDirectives);
planetName->disableScissoring();
planetName->setPosition(m_config->planetNameOffset);
planetName->setAnchor(PaneAnchor::Center);
@ -810,9 +808,7 @@ void MainInterface::renderInWorldElements() {
if (m_disableHud)
return;
m_guiContext->setDefaultFont();
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setFontColor(Vec4B::filled(255));
m_guiContext->clearTextStyle();
m_questIndicatorPainter->render();
m_nameplatePainter->render();
m_chatBubbleManager->render();
@ -822,9 +818,7 @@ void MainInterface::render() {
if (m_disableHud)
return;
m_guiContext->setDefaultFont();
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setFontColor(Vec4B::filled(255));
m_guiContext->clearTextStyle();
renderBreath();
renderMessages();
renderMonsterHealthBar();
@ -950,6 +944,10 @@ CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignore
return canvas;
}
ClientCommandProcessorPtr MainInterface::commandProcessor() const {
return m_clientCommandProcessor;
}
// For when the player swaps characters. We need to completely reload ScriptPanes,
// because a lot of ScriptPanes do not expect the character to suddenly change and may break or spill data over.
void MainInterface::takeScriptPanes(List<ScriptPaneInfo>& out) {
@ -1084,9 +1082,7 @@ void MainInterface::renderMessages() {
m_guiContext->drawQuad(m_config->messageTextContainer,
RectF::withCenter(backgroundTextCenterPos, Vec2F(imgMetadata->imageSize(m_config->messageTextContainer) * interfaceScale())));
m_guiContext->setFont(m_config->font);
m_guiContext->setFontSize(m_config->fontSize);
m_guiContext->setFontColor(Color::White.toRgba());
m_guiContext->setTextStyle(m_config->textStyle);
m_guiContext->renderText(message->message, {messageTextOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor});
}
}
@ -1112,9 +1108,7 @@ void MainInterface::renderMonsterHealthBar() {
m_guiContext->drawQuad(container, RectF::withCenter(backgroundCenterPos + offset, Vec2F(imgMetadata->imageSize(container) * interfaceScale())));
auto nameTextOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.nameTextOffset")) * interfaceScale();
m_guiContext->setFont(m_config->font);
m_guiContext->setFontSize(m_config->fontSize);
m_guiContext->setFontColor(Color::White.toRgba());
m_guiContext->setTextStyle(m_config->textStyle);
m_guiContext->renderText(showDamageEntity->name(), backgroundCenterPos + nameTextOffset);
auto empty = assets->json("/interface.config:monsterHealth.progressEmpty").toString();
@ -1176,11 +1170,11 @@ void MainInterface::renderSpecialDamageBar() {
m_guiContext->drawQuad(fill, RectF::withSize(bottomCenter + fillOffset, size * interfaceScale()));
auto nameOffset = jsonToVec2F(barConfig.get("nameOffset")) * interfaceScale();
m_guiContext->setFontColor(jsonToColor(barConfig.get("nameColor")).toRgba());
m_guiContext->setFontColor(jsonToColor(barConfig.get("nameStyle")).toRgba());
m_guiContext->setFontSize(barConfig.getUInt("nameSize"));
m_guiContext->setFontProcessingDirectives(barConfig.getString("nameDirectives"));
m_guiContext->renderText(target->name(), TextPositioning(bottomCenter + nameOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor));
m_guiContext->setFontProcessingDirectives("");
m_guiContext->clearTextStyle();
}
}
@ -1341,12 +1335,8 @@ void MainInterface::renderDebug() {
if (m_clientCommandProcessor->debugHudEnabled()) {
auto assets = Root::singleton().assets();
m_guiContext->setFontSize(m_config->debugFontSize);
m_guiContext->setFont(m_config->debugFont);
m_guiContext->setTextStyle(m_config->debugTextStyle);
m_guiContext->setLineSpacing(0.5f);
m_guiContext->setFontProcessingDirectives(m_config->debugFontDirectives);
m_guiContext->setFontColor(Color::White.toRgba());
m_guiContext->setFontMode(FontMode::Normal);
bool clearMap = m_debugMapClearTimer.wrapTick();
auto logMapValues = LogMap::getValues();
@ -1358,7 +1348,7 @@ void MainInterface::renderDebug() {
int counter = 0;
for (auto const& pair : logMapValues) {
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * counter++) };
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->textStyle.fontSize * interfaceScale() * counter++) };
String& text = formatted.emplace_back(strf("{}^lightgray;:^green,set; {}", pair.first, pair.second));
m_debugTextRect.combine(m_guiContext->determineTextSize(text, positioning).padded(m_config->debugBackgroundPad));
}
@ -1373,15 +1363,9 @@ void MainInterface::renderDebug() {
m_debugTextRect = RectF::null();
for (size_t index = 0; index != formatted.size(); ++index) {
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * index) };
TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->textStyle.fontSize * interfaceScale() * index) };
m_guiContext->renderText(formatted[index], positioning);
}
m_guiContext->setFontSize(8);
m_guiContext->setDefaultFont();
m_guiContext->setDefaultLineSpacing();
m_guiContext->setFontColor(Vec4B::filled(255));
m_guiContext->setFontProcessingDirectives("");
}
auto const& camera = m_worldPainter->camera();
@ -1413,7 +1397,7 @@ void MainInterface::renderDebug() {
m_guiContext->drawLine(position + Vec2F(2, -2), position + Vec2F(-2, -2), point.color, 1);
}
m_guiContext->setFontSize(m_config->debugFontSize);
m_guiContext->setTextStyle(m_config->debugTextStyle);
for (auto const& logText : SpatialLogger::getText("world", clearSpatial)) {
m_guiContext->setFontColor(logText.color);
@ -1424,7 +1408,7 @@ void MainInterface::renderDebug() {
m_guiContext->setFontColor(logText.color);
m_guiContext->renderText(logText.text.utf8Ptr(), logText.position);
}
m_guiContext->setFontColor(Vec4B::filled(255));
m_guiContext->clearTextStyle();
}
void MainInterface::updateCursor() {
@ -1483,24 +1467,26 @@ void MainInterface::renderCursor() {
auto assets = Root::singleton().assets();
auto imgDb = Root::singleton().imageMetadataDatabase();
auto backgroundImage = assets->json("/interface.config:cursorTooltip.background").toString();
auto rawCursorOffset = jsonToVec2I(assets->json("/interface.config:cursorTooltip.offset"));
auto config = assets->json("/interface.config:cursorTooltip");
auto backgroundImage = config.getString("background");
auto rawCursorOffset = jsonToVec2I(config.get("offset"));
Vec2I tooltipSize = Vec2I(imgDb->imageSize(backgroundImage)) * interfaceScale();
Vec2I cursorOffset = (Vec2I{0, -m_cursor.size().y()} + rawCursorOffset) * cursorScale;
Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset;
size_t fontSize = assets->json("/interface.config:cursorTooltip.fontSize").toUInt();
String font = assets->json("/interface.config:cursorTooltip.font").toString();
Vec4B fontColor = jsonToColor(assets->json("/interface.config:cursorTooltip.color")).toRgba();
TextStyle textStyle = config.get("textStyle");
size_t fontSize = config.get("fontSize").toUInt();
Vec4B fontColor = jsonToColor(config.get("color")).toRgba();
m_guiContext->drawQuad(backgroundImage, Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), 0), interfaceScale());
m_guiContext->setTextStyle(textStyle);
m_guiContext->setFontSize(fontSize);
m_guiContext->setFontColor(fontColor);
m_guiContext->setFont(font);
m_guiContext->renderText(*m_cursorTooltip,
TextPositioning(Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), tooltipSize.y()) / 2,
HorizontalAnchor::HMidAnchor,
VerticalAnchor::VMidAnchor));
m_guiContext->clearTextStyle();
}
m_cursorItem->setPosition(m_cursorScreenPos / interfaceScale() + m_config->inventoryItemMouseOffset);

View File

@ -119,6 +119,8 @@ public:
CanvasWidgetPtr fetchCanvas(String const& canvasName, bool ignoreInterfaceScale = false);
ClientCommandProcessorPtr commandProcessor() const;
struct ScriptPaneInfo {
ScriptPanePtr scriptPane;
Json config;

View File

@ -39,8 +39,7 @@ MainInterfaceConfigPtr MainInterfaceConfig::loadFromAssets() {
auto config = make_shared<MainInterfaceConfig>();
config->fontSize = assets->json("/interface.config:font.baseSize").toInt();
config->font = assets->json("/interface.config:font.defaultFont").toString();
config->textStyle = assets->json("/interface.config:textStyle");
config->inventoryImage = assets->json("/interface.config:mainBar.inventory.base").toString();
config->inventoryImageHover = assets->json("/interface.config:mainBar.inventory.hover").toString();
config->inventoryImageGlow = assets->json("/interface.config:mainBar.inventory.glow").toString();
@ -130,17 +129,14 @@ MainInterfaceConfigPtr MainInterfaceConfig::loadFromAssets() {
config->planetNameTime = assets->json("/interface.config:planetNameTime").toFloat();
config->planetNameFadeTime = assets->json("/interface.config:planetNameFadeTime").toFloat();
config->planetNameFormatString = assets->json("/interface.config:planetNameFormatString").toString();
config->planetNameFontSize = assets->json("/interface.config:font.planetSize").toInt();
config->planetNameDirectives = assets->json("/interface.config:planetNameDirectives").toString();
config->planetNameTextStyle = assets->json("/interface.config:planetTextStyle");
config->planetNameOffset = jsonToVec2I(assets->json("/interface.config:planetTextOffset"));
config->renderVirtualCursor = assets->json("/interface.config:renderVirtualCursor").toBool();
config->cursorItemSlot = assets->json("/interface.config:cursorItemSlot");
config->debugOffset = jsonToVec2I(assets->json("/interface.config:debugOffset"));
config->debugFontSize = assets->json("/interface.config:debugFontSize").toUInt();
config->debugFont = assets->json("/interface.config:debugFont").toString();
config->debugFontDirectives = assets->json("/interface.config:debugFontDirectives").toString();
config->debugTextStyle = assets->json("/interface.config:debugTextStyle");
config->debugSpatialClearTime = assets->json("/interface.config:debugSpatialClearTime").toFloat();
config->debugMapClearTime = assets->json("/interface.config:debugMapClearTime").toFloat();
config->debugBackgroundColor = jsonToColor(assets->json("/interface.config:debugBackgroundColor"));

View File

@ -5,6 +5,7 @@
#include "StarBiMap.hpp"
#include "StarRegisteredPaneManager.hpp"
#include "StarAnimation.hpp"
#include "StarText.hpp"
namespace Star {
@ -43,8 +44,7 @@ typedef RegisteredPaneManager<MainInterfacePanes> MainInterfacePaneManager;
struct MainInterfaceConfig {
static MainInterfaceConfigPtr loadFromAssets();
unsigned fontSize;
String font;
TextStyle textStyle;
String inventoryImage;
String inventoryImageHover;
@ -136,17 +136,14 @@ struct MainInterfaceConfig {
float planetNameTime;
float planetNameFadeTime;
String planetNameFormatString;
unsigned planetNameFontSize;
String planetNameDirectives;
TextStyle planetNameTextStyle;
Vec2I planetNameOffset;
bool renderVirtualCursor;
Json cursorItemSlot;
Vec2I debugOffset;
unsigned debugFontSize;
String debugFont;
String debugFontDirectives;
TextStyle debugTextStyle;
float debugSpatialClearTime;
float debugMapClearTime;
Color debugBackgroundColor;

View File

@ -15,14 +15,11 @@ NameplatePainter::NameplatePainter() {
m_opacityRate = nametagConfig.getFloat("opacityRate");
m_inspectOpacityRate = nametagConfig.queryFloat("inspectOpacityRate", m_opacityRate);
m_offset = jsonToVec2F(nametagConfig.get("offset"));
m_font = nametagConfig.queryString("font", "");
m_fontDirectives = nametagConfig.queryString("fontDirectives", "");
m_fontSize = nametagConfig.getFloat("fontSize");
m_statusFont = nametagConfig.queryString("font", m_font);
m_statusFontDirectives = nametagConfig.queryString("fontDirectives", m_fontDirectives);
m_statusFontSize = nametagConfig.queryFloat("statusFontSize", m_fontSize);
Json textStyle = nametagConfig.get("textStyle");
m_textStyle = textStyle;
m_statusTextStyle = nametagConfig.get("statusTextStyle", textStyle);
m_statusOffset = jsonToVec2F(nametagConfig.get("statusOffset"));
m_statusColor = jsonToColor(nametagConfig.get("statusColor"));
m_statusTextStyle.color = jsonToColor(nametagConfig.get("statusColor")).toRgba();
m_opacityBoost = nametagConfig.getFloat("opacityBoost");
m_nametags.setTweenFactor(nametagConfig.getFloat("tweenFactor"));
m_nametags.setMovementThreshold(nametagConfig.getFloat("movementThreshold"));
@ -80,31 +77,18 @@ void NameplatePainter::render() {
if (nametag.opacity == 0.0f)
return;
context.setFont(m_font);
context.setFontProcessingDirectives(m_fontDirectives);
context.setFontSize(m_fontSize);
auto& setStyle = context.setTextStyle(m_textStyle);
auto color = Color::rgb(nametag.color);
color.setAlphaF(nametag.opacity);
context.setFontColor(color.toRgba());
context.setFontMode(FontMode::Normal);
setStyle.color = color.toRgba();
context.renderText(nametag.name, namePosition(bubble.currentPosition));
if (nametag.statusText) {
auto statusColor = m_statusColor;
statusColor.setAlphaF(nametag.opacity);
context.setFontColor(statusColor.toRgba());
context.setFontSize(m_statusFontSize);
context.setFontProcessingDirectives(m_statusFontDirectives);
context.setFont(m_statusFont);
context.setTextStyle(m_statusTextStyle).color[3] *= nametag.opacity;
context.renderText(*nametag.statusText, statusPosition(bubble.currentPosition));
}
context.setDefaultFont();
context.setFontProcessingDirectives("");
context.clearTextStyle();
});
}
@ -121,16 +105,13 @@ TextPositioning NameplatePainter::statusPosition(Vec2F bubblePosition) const {
RectF NameplatePainter::determineBoundBox(Vec2F bubblePosition, Nametag const& nametag) const {
auto& context = GuiContext::singleton();
context.setFontSize(m_fontSize);
context.setFontProcessingDirectives(m_fontDirectives);
context.setFont(m_font);
context.setTextStyle(m_textStyle);
RectF nametagBox = context.determineTextSize(nametag.name, namePosition(bubblePosition));
if (nametag.statusText) {
context.setFontSize(m_statusFontSize);
context.setFontProcessingDirectives(m_statusFontDirectives);
context.setFont(m_statusFont);
context.setTextStyle(m_statusTextStyle);
nametagBox.combine(context.determineTextSize(*nametag.statusText, statusPosition(bubblePosition)));
}
context.clearTextStyle();
return nametagBox;
}

View File

@ -34,14 +34,9 @@ private:
float m_opacityRate;
float m_inspectOpacityRate;
Vec2F m_offset;
String m_font;
String m_statusFont;
String m_fontDirectives;
String m_statusFontDirectives;
float m_fontSize;
float m_statusFontSize;
Vec2F m_statusOffset;
Color m_statusColor;
TextStyle m_textStyle;
TextStyle m_statusTextStyle;
float m_opacityBoost;
WorldCamera m_camera;

View File

@ -29,7 +29,8 @@ TeamBar::TeamBar(MainInterface* mainInterface, UniverseClientPtr client) {
m_teamInvitation = make_shared<TeamInvitation>(this);
m_teamMemberMenu = make_shared<TeamMemberMenu>(this);
m_nameFontSize = assets->json("/interface.config:font.nameSize").toInt();
m_nameStyle = assets->json("/interface.config:teamBarNameStyle");
m_nameStyle.fontSize = assets->json("/interface.config:font.nameSize").toInt();
m_nameOffset = jsonToVec2F(assets->json("/interface.config:nameOffset"));
GuiReader reader;

View File

@ -93,7 +93,7 @@ private:
GuiContext* m_guiContext;
int m_nameFontSize;
TextStyle m_nameStyle;
Vec2F m_nameOffset;
TeamInvitePtr m_teamInvite;

View File

@ -96,4 +96,12 @@ ByteArray ClientContext::writeUpdate() {
return m_rpc->send();
}
void ClientContext::setConnectionId(ConnectionId connectionId) {
m_connectionId = connectionId;
}
ConnectionId ClientContext::connectionId() const {
return m_connectionId;
}
}

View File

@ -43,9 +43,13 @@ public:
void readUpdate(ByteArray data);
ByteArray writeUpdate();
void setConnectionId(ConnectionId connectionId);
ConnectionId connectionId() const;
private:
Uuid m_serverUuid;
Uuid m_playerUuid;
ConnectionId m_connectionId = 0;
JsonRpcPtr m_rpc;

View File

@ -1382,11 +1382,11 @@ pair<Vec2F, Directives> Humanoid::extractScaleFromDirectives(Directives const& d
size_t totalLength = 0;
Maybe<Vec2F> scale;
for (auto& entry : directives.shared->entries) {
auto string = entry.string(*directives.shared);
for (auto& entry : directives->entries) {
auto string = entry.string(*directives);
const ScaleImageOperation* op = nullptr;
if (string.beginsWith("scalenearest") && string.utf8().find("skip") == NPos)
op = entry.loadOperation(*directives.shared).ptr<ScaleImageOperation>();
op = entry.loadOperation(*directives).ptr<ScaleImageOperation>();
if (op)
scale = scale.value(Vec2F::filled(1.f)).piecewiseMultiply(op->scale);

View File

@ -132,7 +132,7 @@ AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) {
operation.is<ScanLinesImageOperation>() ||
operation.is<SetColorImageOperation>())) {
filtered += "?";
filtered += entry.string(*directives.shared);
filtered += entry.string(*directives);
}
});

View File

@ -62,7 +62,7 @@ void ParallaxLayer::addImageDirectives(Directives const& newDirectives) {
if (directives) {
String dirString = directives.string();
auto& newString = newDirectives.shared->string;
auto& newString = newDirectives->string;
if (!newString.empty()) {
if (newString.utf8().front() != '?')
dirString += "?";

View File

@ -440,8 +440,8 @@ bool UniverseClient::flying() const {
return false;
}
void UniverseClient::sendChat(String const& text, ChatSendMode sendMode) {
if (!text.beginsWith("/"))
void UniverseClient::sendChat(String const& text, ChatSendMode sendMode, Maybe<bool> speak) {
if (speak.value(!text.beginsWith("/")))
m_mainPlayer->addChatMessage(text);
m_connection->pushSingle(make_shared<ChatSendPacket>(text, sendMode));
}

View File

@ -80,7 +80,7 @@ public:
SkyConstPtr currentSky() const;
bool flying() const;
void sendChat(String const& text, ChatSendMode sendMode);
void sendChat(String const& text, ChatSendMode sendMode, Maybe<bool> speak = {});
List<ChatReceivedMessage> pullChatMessages();
uint16_t players();

View File

@ -1734,6 +1734,7 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) {
m_entityUpdateTimer = GameTimer(m_interpolationTracker.entityUpdateDelta());
m_clientId = startPacket.clientId;
m_mainPlayer->clientContext()->setConnectionId(startPacket.clientId);
auto entitySpace = connectionEntitySpace(startPacket.clientId);
m_worldTemplate = make_shared<WorldTemplate>(startPacket.templateData);
m_entityMap = make_shared<EntityMap>(m_worldTemplate->size(), entitySpace.first, entitySpace.second);

View File

@ -47,11 +47,12 @@ const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Cha
m_font->setPixelSize(size);
auto pair = m_font->render(c);
Image& image = pair.first;
if (processingDirectives && *processingDirectives) {
if (processingDirectives) {
try {
Directives const& directives = *processingDirectives;
Vec2F preSize = Vec2F(image.size());
for (auto& entry : processingDirectives->shared->entries)
for (auto& entry : directives->entries)
processImageOperation(entry.operation, image);
res.first->second.offset = (preSize - Vec2F(image.size())) / 2;

View File

@ -1,7 +1,5 @@
#include "StarTextPainter.hpp"
#include "StarJsonExtra.hpp"
#include "StarText.hpp"
#include <regex>
namespace Star {
@ -40,34 +38,42 @@ TextPositioning TextPositioning::translated(Vec2F translation) const {
TextPainter::TextPainter(RendererPtr renderer, TextureGroupPtr textureGroup)
: m_renderer(renderer),
m_fontTextureGroup(textureGroup),
m_fontSize(8),
m_lineSpacing(1.30f),
m_renderSettings({FontMode::Normal, Vec4B::filled(255), "hobo", ""}) {
m_defaultRenderSettings(),
m_renderSettings(),
m_savedRenderSettings() {
reloadFonts();
m_reloadTracker = make_shared<TrackerListener>();
Root::singleton().registerReloadListener(m_reloadTracker);
}
RectF TextPainter::renderText(StringView s, TextPositioning const& position) {
RectF rect;
if (position.charLimit) {
unsigned charLimit = *position.charLimit;
return doRenderText(s, position, true, &charLimit);
rect = doRenderText(s, position, true, &charLimit);
} else {
return doRenderText(s, position, true, nullptr);
rect = doRenderText(s, position, true, nullptr);
}
renderPrimitives();
return rect;
}
RectF TextPainter::renderLine(StringView s, TextPositioning const& position) {
RectF rect;
if (position.charLimit) {
unsigned charLimit = *position.charLimit;
return doRenderLine(s, position, true, &charLimit);
rect = doRenderLine(s, position, true, &charLimit);
} else {
return doRenderLine(s, position, true, nullptr);
rect = doRenderLine(s, position, true, nullptr);
}
renderPrimitives();
return rect;
}
RectF TextPainter::renderGlyph(String::Char c, TextPositioning const& position) {
return doRenderGlyph(c, position, true);
auto rect = doRenderGlyph(c, position, true);
renderPrimitives();
return rect;
}
RectF TextPainter::determineTextSize(StringView s, TextPositioning const& position) {
@ -83,34 +89,36 @@ RectF TextPainter::determineGlyphSize(String::Char c, TextPositioning const& pos
}
int TextPainter::glyphWidth(String::Char c) {
return m_fontTextureGroup.glyphWidth(c, m_fontSize);
return m_fontTextureGroup.glyphWidth(c, m_renderSettings.fontSize);
}
int TextPainter::stringWidth(StringView s) {
int TextPainter::stringWidth(StringView s, unsigned charLimit) {
if (s.empty())
return 0;
String font = m_renderSettings.font, setFont = font;
m_fontTextureGroup.switchFont(font);
int width = 0;
Text::CommandsCallback commandsCallback = [&](StringView commands) {
commands.forEachSplitView(",", [&](StringView command, size_t, size_t) {
if (command == "reset")
if (command == "reset") {
m_fontTextureGroup.switchFont(font = setFont);
else if (command == "set")
} else if (command == "set") {
setFont = font;
else if (command.beginsWith("font="))
} else if (command.beginsWith("font=")) {
m_fontTextureGroup.switchFont(font = command.substr(5));
}
});
return true;
};
int width = 0;
Text::TextCallback textCallback = [&](StringView text) {
for (String::Char c : text)
for (String::Char c : text) {
width += glyphWidth(c);
if (charLimit && --charLimit == 0)
return false;
}
return true;
};
@ -122,7 +130,7 @@ int TextPainter::stringWidth(StringView s) {
bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) {
String font = m_renderSettings.font, setFont = font;
m_fontTextureGroup.switchFont(font);
int lines = 0;
unsigned lines = 0;
auto it = text.begin();
auto end = text.end();
@ -149,14 +157,15 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
if (escIt != end) {
if (character == Text::EndEsc) {
StringView inner = slice(escIt, it);
StringView inner = slice(++escIt, it);
inner.forEachSplitView(",", [&](StringView command, size_t, size_t) {
if (command == "reset")
if (command == "reset") {
m_fontTextureGroup.switchFont(font = setFont);
else if (command == "set")
} else if (command == "set") {
setFont = font;
else if (command.beginsWith("font="))
} else if (command.beginsWith("font=")) {
m_fontTextureGroup.switchFont(font = command.substr(5));
}
});
escIt = end;
}
@ -177,7 +186,6 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
splitIt = end;
} else {
int charWidth = glyphWidth(character);
// is it a place where we might want to split the line ?
if (character == ' ' || character == '\t') {
splitIt = it;
@ -220,36 +228,20 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
List<StringView> TextPainter::wrapTextViews(StringView s, Maybe<unsigned> wrapWidth) {
List<StringView> views = {};
bool active = false;
StringView current;
int lastLine = 0;
auto addText = [&active, &current](StringView text) {
// Merge views if they are adjacent
if (active && current.utf8Ptr() + current.utf8Size() == text.utf8Ptr())
current = StringView(current.utf8Ptr(), current.utf8Size() + text.utf8Size());
else
current = text;
active = true;
};
TextPainter::WrapTextCallback textCallback = [&](StringView text, int line) {
if (lastLine != line) {
views.push_back(current);
lastLine = line;
active = false;
auto last = views.end();
unsigned curLine = 0;
TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) {
if (line == curLine && last != views.end() && last->end() == text.begin()) {
*last = StringView(last->utf8Ptr(), last->utf8Size() + text.utf8Size());
} else {
last = views.insert(views.end(), text);
curLine = line;
}
addText(text);
return true;
};
processWrapText(s, wrapWidth.ptr(), textCallback);
if (active)
views.push_back(current);
return views;
}
@ -258,12 +250,11 @@ StringList TextPainter::wrapText(StringView s, Maybe<unsigned> wrapWidth) {
String current;
int lastLine = 0;
TextPainter::WrapTextCallback textCallback = [&](StringView text, int line) {
TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) {
if (lastLine != line) {
result.append(std::move(current));
lastLine = line;
}
current += text;
return true;
};
@ -277,40 +268,45 @@ StringList TextPainter::wrapText(StringView s, Maybe<unsigned> wrapWidth) {
};
unsigned TextPainter::fontSize() const {
return m_fontSize;
return m_renderSettings.fontSize;
}
void TextPainter::setFontSize(unsigned size) {
m_fontSize = size;
m_renderSettings.fontSize = size;
}
void TextPainter::setLineSpacing(float lineSpacing) {
m_lineSpacing = lineSpacing;
m_renderSettings.lineSpacing = lineSpacing;
}
void TextPainter::setMode(FontMode mode) {
m_renderSettings.mode = mode;
m_renderSettings.shadow = fontModeToColor(mode).toRgba();
}
void TextPainter::setFontColor(Vec4B color) {
m_renderSettings.color = std::move(color);
}
void TextPainter::setProcessingDirectives(StringView directives) {
m_renderSettings.directives = String(directives);
if (m_renderSettings.directives) {
m_renderSettings.directives.loadOperations();
for (auto& entry : m_renderSettings.directives.shared->entries) {
if (auto border = entry.operation.ptr<BorderImageOperation>())
border->includeTransparent = true;
}
}
void TextPainter::setProcessingDirectives(StringView directives, bool back) {
Directives& target = back ? m_renderSettings.backDirectives : m_renderSettings.directives;
modifyDirectives(target = String(directives));
}
void TextPainter::setFont(String const& font) {
m_renderSettings.font = font;
}
TextStyle& TextPainter::setTextStyle(TextStyle const& textStyle) {
TextStyle& style = m_renderSettings = textStyle;
modifyDirectives(style.directives);
modifyDirectives(style.backDirectives);
return style;
}
void TextPainter::clearTextStyle() {
m_renderSettings = m_defaultRenderSettings;
}
void TextPainter::addFont(FontPtr const& font, String const& name) {
m_fontTextureGroup.addFont(font, name);
}
@ -348,16 +344,19 @@ void TextPainter::applyCommands(StringView unsplitCommands) {
m_renderSettings = m_savedRenderSettings;
} else if (command == "set") {
m_savedRenderSettings = m_renderSettings;
} else if (command == "shadow") {
m_renderSettings.mode = (FontMode)((int)m_renderSettings.mode | (int)FontMode::Shadow);
} else if (command.beginsWith("shadow")) {
if (command.utf8Size() == 6)
m_renderSettings.shadow = Color::Black.toRgba();
else if (command[6] == '=')
m_renderSettings.shadow = Color(command.substr(7)).toRgba();
} else if (command == "noshadow") {
m_renderSettings.mode = (FontMode)((int)m_renderSettings.mode & (-1 ^ (int)FontMode::Shadow));
m_renderSettings.shadow = Color::Clear.toRgba();
} else if (command.beginsWith("font=")) {
m_renderSettings.font = command.substr(5);
} else if (command.beginsWith("directives=")) {
// Honestly this is really stupid but I just couldn't help myself
// Should probably limit in the future
setProcessingDirectives(command.substr(11));
} else if (command.beginsWith("backdirectives=")) {
setProcessingDirectives(command.substr(15), true);
} else {
// expects both #... sequences and plain old color names.
Color c = Color(command);
@ -370,6 +369,16 @@ void TextPainter::applyCommands(StringView unsplitCommands) {
});
}
void TextPainter::modifyDirectives(Directives& directives) {
if (directives) {
directives.loadOperations();
for (auto& entry : directives->entries) {
if (auto border = entry.operation.ptr<BorderImageOperation>())
border->includeTransparent = true;
}
}
}
RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit) {
Vec2F pos = position.pos;
if (s.empty())
@ -377,26 +386,23 @@ RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, b
List<StringView> lines = wrapTextViews(s, position.wrapWidth);
int height = (lines.size() - 1) * m_lineSpacing * m_fontSize + m_fontSize;
RenderSettings backupRenderSettings = m_renderSettings;
m_savedRenderSettings = m_renderSettings;
TextStyle backup = m_savedRenderSettings = m_renderSettings;
int height = (lines.size() - 1) * backup.lineSpacing * backup.fontSize + backup.fontSize;
if (position.vAnchor == VerticalAnchor::BottomAnchor)
pos[1] += (height - m_fontSize);
pos[1] += (height - backup.fontSize);
else if (position.vAnchor == VerticalAnchor::VMidAnchor)
pos[1] += (height - m_fontSize) / 2;
pos[1] += (height - backup.fontSize) / 2;
RectF bounds = RectF::withSize(pos, Vec2F());
for (auto& i : lines) {
bounds.combine(doRenderLine(i, { pos, position.hAnchor, position.vAnchor }, reallyRender, charLimit));
pos[1] -= m_fontSize * m_lineSpacing;
pos[1] -= m_renderSettings.fontSize * m_renderSettings.lineSpacing;
if (charLimit && *charLimit == 0)
break;
}
m_renderSettings = std::move(backupRenderSettings);
m_renderSettings = std::move(backup);
return bounds;
}
@ -425,9 +431,8 @@ RectF TextPainter::doRenderLine(StringView text, TextPositioning const& position
if (*charLimit == 0)
return false;
else
--* charLimit;
--*charLimit;
}
RectF glyphBounds = doRenderGlyph(c, pos, reallyRender);
bounds.combine(glyphBounds);
pos.pos[0] += glyphBounds.width();
@ -461,41 +466,58 @@ RectF TextPainter::doRenderGlyph(String::Char c, TextPositioning const& position
float vOffset = 0;
if (position.vAnchor == VerticalAnchor::VMidAnchor)
vOffset = -floor((float)m_fontSize / 2);
vOffset = -floor((float)m_renderSettings.fontSize / 2);
else if (position.vAnchor == VerticalAnchor::TopAnchor)
vOffset = -(float)m_fontSize;
vOffset = -(float)m_renderSettings.fontSize;
Directives* directives = m_renderSettings.directives ? &m_renderSettings.directives : nullptr;
Vec2F pos = position.pos + Vec2F(hOffset, vOffset);
if (reallyRender) {
if ((int)m_renderSettings.mode & (int)FontMode::Shadow) {
Color shadow = Color::Black;
uint8_t alphaU = m_renderSettings.color[3];
bool hasShadow = m_renderSettings.shadow[3] > 0;
bool hasBackDirectives = m_renderSettings.backDirectives;
if (hasShadow) {
//Kae: unlike vanilla we draw only one shadow glyph instead of two, so i'm tweaking the alpha here
Vec4B shadow = m_renderSettings.shadow;
uint8_t alphaU = m_renderSettings.color[3] * byteToFloat(shadow[3]);
if (alphaU != 255) {
float alpha = byteToFloat(alphaU);
shadow.setAlpha(floatToByte(alpha * (1.5f - 0.5f * alpha)));
shadow[3] = floatToByte(alpha * (1.5f - 0.5f * alpha));
}
else
shadow.setAlpha(alphaU);
shadow[3] = alphaU;
//Kae: Draw only one shadow glyph instead of stacking two, alpha modified to appear perceptually the same as vanilla
renderGlyph(c, position.pos + Vec2F(hOffset, vOffset - 2), m_fontSize, 1, shadow.toRgba(), directives);
Directives const* shadowDirectives = hasBackDirectives ? &m_renderSettings.backDirectives : directives;
renderGlyph(c, pos + Vec2F(0, -2), m_shadowPrimitives, m_renderSettings.fontSize, 1, shadow, shadowDirectives);
}
if (hasBackDirectives)
renderGlyph(c, pos, m_backPrimitives, m_renderSettings.fontSize, 1, m_renderSettings.color, &m_renderSettings.backDirectives);
renderGlyph(c, position.pos + Vec2F(hOffset, vOffset), m_fontSize, 1, m_renderSettings.color, directives);
auto& output = (hasShadow || hasBackDirectives) ? m_frontPrimitives : m_renderer->immediatePrimitives();
renderGlyph(c, pos, output, m_renderSettings.fontSize, 1, m_renderSettings.color, directives);
}
return RectF::withSize(position.pos + Vec2F(hOffset, vOffset), {(float)width, (int)m_fontSize});
return RectF::withSize(pos, {(float)width, (int)m_renderSettings.fontSize});
}
void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, unsigned fontSize,
void TextPainter::renderPrimitives() {
auto& destination = m_renderer->immediatePrimitives();
std::move(std::begin(m_shadowPrimitives), std::end(m_shadowPrimitives), std::back_inserter(destination));
m_shadowPrimitives.clear();
std::move(std::begin(m_backPrimitives), std::end(m_backPrimitives), std::back_inserter(destination));
m_backPrimitives.clear();
std::move(std::begin(m_frontPrimitives), std::end(m_frontPrimitives), std::back_inserter(destination));
m_frontPrimitives.clear();
}
void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize,
float scale, Vec4B const& color, Directives const* processingDirectives) {
if (!fontSize)
return;
const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives);
Vec2F offset = glyphTexture.offset * scale;
m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t<RenderQuad>(), glyphTexture.texture, Vec2F::round(screenPos + offset), scale, color, 0.0f);
out.emplace_back(std::in_place_type_t<RenderQuad>(), glyphTexture.texture, Vec2F::round(screenPos + offset), scale, color, 0.0f);
}
FontPtr TextPainter::loadFont(String const& fontPath, Maybe<String> fontName) {

View File

@ -4,17 +4,21 @@
#include "StarAnchorTypes.hpp"
#include "StarRoot.hpp"
#include "StarStringView.hpp"
#include "StarText.hpp"
namespace Star {
STAR_CLASS(TextPainter);
enum class FontMode {
// deprecated in favor of explicit shadow color
enum class FontMode : uint8_t {
Normal,
Shadow
};
float const DefaultLineSpacing = 1.3f;
inline Color const& fontModeToColor(FontMode mode) {
return mode == FontMode::Shadow ? Color::Black : Color::Clear;
}
STAR_CLASS(TextPainter);
struct TextPositioning {
TextPositioning();
@ -52,10 +56,10 @@ public:
RectF determineGlyphSize(String::Char c, TextPositioning const& position);
int glyphWidth(String::Char c);
int stringWidth(StringView s);
int stringWidth(StringView s, unsigned charLimit = 0);
typedef function<bool(StringView, int)> WrapTextCallback;
typedef function<bool(StringView, unsigned)> WrapTextCallback;
bool processWrapText(StringView s, unsigned* wrapWidth, WrapTextCallback textFunc);
List<StringView> wrapTextViews(StringView s, Maybe<unsigned> wrapWidth);
@ -66,39 +70,36 @@ public:
void setLineSpacing(float lineSpacing);
void setMode(FontMode mode);
void setFontColor(Vec4B color);
void setProcessingDirectives(StringView directives);
void setProcessingDirectives(StringView directives, bool back = false);
void setFont(String const& font);
TextStyle& setTextStyle(TextStyle const& textStyle);
void clearTextStyle();
void addFont(FontPtr const& font, String const& name);
void reloadFonts();
void cleanup(int64_t textureTimeout);
void applyCommands(StringView unsplitCommands);
private:
struct RenderSettings {
FontMode mode;
Vec4B color;
String font;
Directives directives;
};
void modifyDirectives(Directives& directives);
RectF doRenderText(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit);
RectF doRenderLine(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit);
RectF doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender);
void renderGlyph(String::Char c, Vec2F const& screenPos, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives = nullptr);
void renderPrimitives();
void renderGlyph(String::Char c, Vec2F const& screenPos, List<RenderPrimitive>& out, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives = nullptr);
static FontPtr loadFont(String const& fontPath, Maybe<String> fontName = {});
RendererPtr m_renderer;
List<RenderPrimitive> m_shadowPrimitives;
List<RenderPrimitive> m_backPrimitives;
List<RenderPrimitive> m_frontPrimitives;
FontTextureGroup m_fontTextureGroup;
unsigned m_fontSize;
float m_lineSpacing;
RenderSettings m_renderSettings;
RenderSettings m_savedRenderSettings;
TextStyle m_defaultRenderSettings;
TextStyle m_renderSettings;
TextStyle m_savedRenderSettings;
String m_nonRenderedCharacters;
TrackerListenerPtr m_reloadTracker;
};

View File

@ -24,14 +24,11 @@ ButtonWidget::ButtonWidget() {
auto interfaceConfig = assets->json("/interface.config");
m_pressedOffset = jsonToVec2I(interfaceConfig.get("buttonPressedOffset"));
m_fontSize = interfaceConfig.query("font.buttonSize").toInt();
m_fontDirectives = interfaceConfig.queryString("font.defaultDirectives", "");
m_font = interfaceConfig.query("font.defaultFont").toString();
m_clickSounds = jsonToStringList(assets->json("/interface.config:buttonClickSound"));
m_releaseSounds = jsonToStringList(assets->json("/interface.config:buttonReleaseSound"));
m_hoverSounds = jsonToStringList(assets->json("/interface.config:buttonHoverSound"));
m_hoverOffSounds = jsonToStringList(assets->json("/interface.config:buttonHoverOffSound"));
m_textStyle = interfaceConfig.get("buttonTextStyle");
m_clickSounds = jsonToStringList(interfaceConfig.get("buttonClickSound"));
m_releaseSounds = jsonToStringList(interfaceConfig.get("buttonReleaseSound"));
m_hoverSounds = jsonToStringList(interfaceConfig.get("buttonHoverSound"));
m_hoverOffSounds = jsonToStringList(interfaceConfig.get("buttonHoverOffSound"));
}
ButtonWidget::ButtonWidget(WidgetCallbackFunc callback,
@ -95,19 +92,15 @@ void ButtonWidget::renderImpl() {
if (!m_text.empty()) {
auto& guiContext = GuiContext::singleton();
guiContext.setFontProcessingDirectives(m_fontDirectives);
guiContext.setFontSize(m_fontSize);
guiContext.setFont(m_font);
guiContext.setTextStyle(m_textStyle);
if (m_disabled)
guiContext.setFontColor(m_fontColorDisabled.toRgba());
else if (m_fontColorChecked && m_checked)
guiContext.setFontColor(m_fontColorChecked.value().toRgba());
else
guiContext.setFontColor(m_fontColor.toRgba());
guiContext.setFontMode(FontMode::Shadow);
guiContext.renderInterfaceText(m_text, {textPosition, m_hTextAnchor, VerticalAnchor::VMidAnchor});
guiContext.setFontMode(FontMode::Normal);
guiContext.setFontProcessingDirectives("");
guiContext.clearTextStyle();
}
}
@ -323,11 +316,11 @@ void ButtonWidget::setText(String const& text) {
}
void ButtonWidget::setFontSize(int size) {
m_fontSize = size;
m_textStyle.fontSize = size;
}
void ButtonWidget::setFontDirectives(String directives) {
m_fontDirectives = directives;
m_textStyle.directives = directives;
}
void ButtonWidget::setTextOffset(Vec2I textOffset) {
@ -339,7 +332,7 @@ void ButtonWidget::setTextAlign(HorizontalAnchor hAnchor) {
}
void ButtonWidget::setFontColor(Color color) {
m_fontColor = color;
m_textStyle.color = (m_fontColor = color).toRgba();
}
void ButtonWidget::setFontColorDisabled(Color color) {

View File

@ -121,9 +121,7 @@ protected:
Vec2I m_pressedOffset;
Vec2U m_buttonBoundSize;
int m_fontSize;
String m_font;
String m_fontDirectives;
TextStyle m_textStyle;
String m_text;
Vec2I m_textOffset;

View File

@ -68,7 +68,18 @@ void CanvasWidget::drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangl
}
void CanvasWidget::drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String font, String processingDirectives) {
m_renderOps.append(make_tuple(std::move(s), std::move(position), fontSize, color, mode, lineSpacing, std::move(font), std::move(processingDirectives)));
TextStyle style;
style.fontSize = fontSize;
style.color = color;
style.shadow = fontModeToColor(mode).toRgba();
style.lineSpacing = lineSpacing;
style.font = font;
style.directives = processingDirectives;
m_renderOps.append(make_tuple(std::move(s), std::move(position), std::move(style)));
}
void CanvasWidget::drawText(String s, TextPositioning position, TextStyle style) {
m_renderOps.append(make_tuple(std::move(s), std::move(position), std::move(style)));
}
Vec2I CanvasWidget::mousePosition() const {
@ -145,7 +156,7 @@ void CanvasWidget::renderImpl() {
if (auto args = op.ptr<TrianglesOp>())
tupleUnpackFunction(bind(&CanvasWidget::renderTriangles, this, renderingOffset, _1, _2), *args);
if (auto args = op.ptr<TextOp>())
tupleUnpackFunction(bind(&CanvasWidget::renderText, this, renderingOffset, _1, _2, _3, _4, _5, _6, _7, _8), *args);
tupleUnpackFunction(bind(&CanvasWidget::renderText, this, renderingOffset, _1, _2, _3), *args);
}
}
@ -256,14 +267,9 @@ void CanvasWidget::renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2
context.drawInterfaceTriangles(translated, color);
}
void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives) {
void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, TextStyle const& style) {
auto& context = GuiContext::singleton();
context.setFontProcessingDirectives(directives);
context.setFontSize(fontSize, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
context.setFontColor(color);
context.setFontMode(mode);
context.setFont(font);
context.setLineSpacing(lineSpacing);
context.setTextStyle(style, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
TextPositioning translatedPosition = position;
translatedPosition.pos += renderingOffset;
@ -272,10 +278,7 @@ void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, Tex
else
context.renderInterfaceText(s, translatedPosition);
context.setDefaultLineSpacing();
context.setDefaultFont();
context.setFontMode(FontMode::Normal);
context.setFontProcessingDirectives("");
context.clearTextStyle();
}
}

View File

@ -67,6 +67,7 @@ public:
void drawTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& poly, Vec4B const& color = Vec4B(255, 255, 255, 255));
void drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color = Vec4B(255, 255, 255, 255), FontMode mode = FontMode::Normal, float lineSpacing = Star::DefaultLineSpacing, String font = "", String processingDirectives = "");
void drawText(String s, TextPositioning position, TextStyle style);
protected:
void renderImpl() override;
@ -79,7 +80,7 @@ protected:
void renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color);
void renderPoly(Vec2F const& renderingOffset, PolyF poly, Vec4B const& color, float lineWidth);
void renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
void renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives);
void renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, TextStyle const& style);
private:
bool m_ignoreInterfaceScale;
@ -97,7 +98,7 @@ private:
typedef tuple<Vec2F, Vec2F, Vec4B, float> LineOp;
typedef tuple<PolyF, Vec4B, float> PolyOp;
typedef tuple<List<tuple<Vec2F, Vec2F, Vec2F>>, Vec4B> TrianglesOp;
typedef tuple<String, TextPositioning, unsigned, Vec4B, FontMode, float, String, String> TextOp;
typedef tuple<String, TextPositioning, TextStyle> TextOp;
typedef MVariant<RectOp, ImageOp, ImageRectOp, DrawableOp, TiledImageOp, LineOp, PolyOp, TrianglesOp, TextOp> RenderOp;
List<RenderOp> m_renderOps;

View File

@ -9,8 +9,8 @@ namespace Star {
FuelWidget::FuelWidget() {
auto assets = Root::singleton().assets();
m_fontSize = assets->json("/interface.config:font.buttonSize").toInt();
m_font = assets->json("/interface.config:font.defaultFont").toString();
m_textStyle.fontSize = assets->json("/interface.config:font.buttonSize").toInt();
m_textStyle.loadJson(assets->json("/interface.config:textStyle"));
m_fuelLevel = 0;
m_maxLevel = 0;
@ -72,8 +72,7 @@ void FuelWidget::renderImpl() {
context()->drawInterfaceQuad("/interface/fuel/fuelgaugemarkings.png", shift(0, 1, entireTex), shift(0, 1, entirePosition));
auto& guiContext = GuiContext::singleton();
guiContext.setFontSize(m_fontSize);
guiContext.setFont(m_font);
guiContext.setTextStyle(m_textStyle);
if (m_potential != 0) {
guiContext.setFontColor(Color::White.toRgba());
} else if (m_fuelLevel == 0) {

View File

@ -30,8 +30,7 @@ protected:
float m_pingTimeout;
unsigned m_fontSize;
String m_font;
TextStyle m_textStyle;
private:
};

View File

@ -377,6 +377,20 @@ void GuiContext::setDefaultFont() {
textPainter()->setFont("");
}
TextStyle& GuiContext::setTextStyle(TextStyle const& textStyle, int pixelRatio) {
TextStyle& setStyle = textPainter()->setTextStyle(textStyle);
setStyle.fontSize *= pixelRatio;
return setStyle;
}
TextStyle& GuiContext::setTextStyle(TextStyle const& textStyle) {
return setTextStyle(textStyle, interfaceScale());
}
void GuiContext::clearTextStyle() {
textPainter()->clearTextStyle();
}
void GuiContext::setLineSpacing(float lineSpacing) {
textPainter()->setLineSpacing(lineSpacing);
}

View File

@ -106,6 +106,9 @@ public:
void setFontProcessingDirectives(String const& directives);
void setFont(String const& font);
void setDefaultFont();
TextStyle& setTextStyle(TextStyle const& textStyle, int pixelRatio);
TextStyle& setTextStyle(TextStyle const& textStyle);
void clearTextStyle();
void setLineSpacing(float lineSpacing);
void setDefaultLineSpacing();

View File

@ -14,16 +14,13 @@ ItemSlotWidget::ItemSlotWidget(ItemPtr const& item, String const& backingImage)
: m_item(item), m_backingImage(backingImage) {
m_drawBackingImageWhenFull = false;
m_drawBackingImageWhenEmpty = true;
m_fontSize = 0;
m_progress = 1;
auto assets = Root::singleton().assets();
auto interfaceConfig = assets->json("/interface.config");
m_countPosition = TextPositioning(jsonToVec2F(interfaceConfig.get("itemCountRightAnchor")), HorizontalAnchor::RightAnchor);
m_countFontMode = FontMode::Normal;
m_fontSize = interfaceConfig.query("font.itemSize").toInt();
m_font = interfaceConfig.query("font.defaultFont").toString();
m_fontColor = Color::rgb(jsonToVec3B(interfaceConfig.query("font.defaultColor")));
m_textStyle = interfaceConfig.get("itemSlotTextStyle");
m_itemDraggableArea = jsonToRectI(interfaceConfig.get("itemDraggableArea"));
m_durabilityOffset = jsonToVec2I(interfaceConfig.get("itemIconDurabilityOffset"));
@ -192,13 +189,10 @@ void ItemSlotWidget::renderImpl() {
context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:{}", frame)), Vec2F(screenPosition()));
if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something
context()->setFont(m_font);
context()->setFontSize(m_fontSize);
context()->setFontColor(m_fontColor.toRgba());
context()->setTextStyle(m_textStyle);
context()->setFontMode(m_countFontMode);
context()->renderInterfaceText(toString(m_item->count()), m_countPosition.translated(Vec2F(screenPosition())));
context()->setFontMode(FontMode::Normal);
context()->setDefaultFont();
context()->clearTextStyle();
}
} else if (m_drawBackingImageWhenEmpty && m_backingImage != "") {

View File

@ -56,9 +56,7 @@ private:
Vec2I m_durabilityOffset;
RectI m_itemDraggableArea;
int m_fontSize;
String m_font;
Color m_fontColor;
TextStyle m_textStyle;
WidgetCallbackFunc m_callback;
WidgetCallbackFunc m_rightClickCallback;

View File

@ -10,17 +10,14 @@ LabelWidget::LabelWidget(String text,
VerticalAnchor const& vAnchor,
Maybe<unsigned> wrapWidth,
Maybe<float> lineSpacing)
: m_fontMode(FontMode::Normal),
m_color(color),
m_hAnchor(hAnchor),
: m_hAnchor(hAnchor),
m_vAnchor(vAnchor),
m_wrapWidth(std::move(wrapWidth)),
m_lineSpacing(std::move(lineSpacing)) {
m_wrapWidth(std::move(wrapWidth)) {
auto assets = Root::singleton().assets();
auto fontConfig = assets->json("/interface.config:font");
m_fontSize = fontConfig.getInt("baseSize");
m_processingDirectives = fontConfig.getString("defaultDirectives");
m_font = fontConfig.queryString("defaultFont", "");
m_style = assets->json("/interface.config:labelTextStyle");
m_style.color = color.toRgba();
if (lineSpacing)
m_style.lineSpacing = *lineSpacing;
setText(std::move(text));
}
@ -38,16 +35,16 @@ void LabelWidget::setText(String newText) {
}
void LabelWidget::setFontSize(int fontSize) {
m_fontSize = fontSize;
m_style.fontSize = fontSize;
updateTextRegion();
}
void LabelWidget::setFontMode(FontMode fontMode) {
m_fontMode = fontMode;
m_style.shadow = fontModeToColor(fontMode).toRgba();
}
void LabelWidget::setColor(Color newColor) {
m_color = std::move(newColor);
m_style.color = newColor.toRgba();
}
void LabelWidget::setAnchor(HorizontalAnchor hAnchor, VerticalAnchor vAnchor) {
@ -62,12 +59,12 @@ void LabelWidget::setWrapWidth(Maybe<unsigned> wrapWidth) {
}
void LabelWidget::setLineSpacing(Maybe<float> lineSpacing) {
m_lineSpacing = std::move(lineSpacing);
m_style.lineSpacing = lineSpacing.value(DefaultLineSpacing);
updateTextRegion();
}
void LabelWidget::setDirectives(String const& directives) {
m_processingDirectives = directives;
m_style.directives = directives;
updateTextRegion();
}
@ -76,6 +73,12 @@ void LabelWidget::setTextCharLimit(Maybe<unsigned> charLimit) {
updateTextRegion();
}
void LabelWidget::setTextStyle(TextStyle const& textStyle) {
m_style = textStyle;
updateTextRegion();
}
RectI LabelWidget::relativeBoundRect() const {
return RectI(m_textRegion).translated(relativePosition());
}
@ -85,41 +88,14 @@ RectI LabelWidget::getScissorRect() const {
}
void LabelWidget::renderImpl() {
context()->setFont(m_font);
context()->setFontSize(m_fontSize);
context()->setFontMode(m_fontMode);
context()->setFontColor(m_color.toRgba());
context()->setFontProcessingDirectives(m_processingDirectives);
if (m_lineSpacing)
context()->setLineSpacing(*m_lineSpacing);
else
context()->setDefaultLineSpacing();
context()->setTextStyle(m_style);
context()->renderInterfaceText(m_text, {Vec2F(screenPosition()), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit});
context()->setDefaultFont();
context()->setFontMode(FontMode::Normal);
context()->setFontProcessingDirectives("");
context()->setDefaultLineSpacing();
}
void LabelWidget::updateTextRegion() {
context()->setFontSize(m_fontSize);
context()->setFontColor(m_color.toRgba());
context()->setFontProcessingDirectives(m_processingDirectives);
context()->setFont(m_font);
if (m_lineSpacing)
context()->setLineSpacing(*m_lineSpacing);
else
context()->setDefaultLineSpacing();
context()->setTextStyle(m_style);
m_textRegion = RectI(context()->determineInterfaceTextSize(m_text, {Vec2F(), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit}));
setSize(m_textRegion.size());
context()->setDefaultFont();
context()->setFontProcessingDirectives("");
context()->setDefaultLineSpacing();
}
}

View File

@ -25,6 +25,7 @@ public:
void setLineSpacing(Maybe<float> lineSpacing);
void setDirectives(String const& directives);
void setTextCharLimit(Maybe<unsigned> charLimit);
void setTextStyle(TextStyle const& style);
RectI relativeBoundRect() const override;
@ -36,13 +37,9 @@ private:
void updateTextRegion();
String m_text;
int m_fontSize;
FontMode m_fontMode;
Color m_color;
TextStyle m_style;
HorizontalAnchor m_hAnchor;
VerticalAnchor m_vAnchor;
String m_processingDirectives;
String m_font;
Maybe<unsigned> m_wrapWidth;
Maybe<float> m_lineSpacing;
Maybe<unsigned> m_textCharLimit;

View File

@ -32,8 +32,7 @@ Pane::Pane() {
m_hasDisplayed = false;
auto assets = Root::singleton().assets();
m_fontSize = assets->json("/interface.config:font.baseSize").toInt();
m_font = assets->json("/interface.config:font.defaultFont").toString();
m_textStyle = assets->json("/interface.config:paneTextStyle");
m_iconOffset = jsonToVec2I(assets->json("/interface.config:paneIconOffset"));
m_titleOffset = jsonToVec2I(assets->json("/interface.config:paneTitleOffset"));
m_subTitleOffset = jsonToVec2I(assets->json("/interface.config:paneSubTitleOffset"));
@ -436,15 +435,13 @@ void Pane::renderImpl() {
m_context->resetInterfaceScissorRect();
}
m_context->setFont(m_font);
m_context->setFontSize(m_fontSize);
m_context->setTextStyle(m_textStyle);
m_context->setFontColor(m_titleColor.toRgba());
m_context->setFontMode(FontMode::Shadow);
m_context->renderInterfaceText(m_title, {headerPos + Vec2F(m_titleOffset)});
m_context->setFontColor(m_subTitleColor.toRgba());
m_context->renderInterfaceText(m_subTitle, {headerPos + Vec2F(m_subTitleOffset)});
m_context->setFontMode(FontMode::Normal);
m_context->setDefaultFont();
m_context->clearTextStyle();
}
}

View File

@ -117,8 +117,7 @@ protected:
WidgetPtr m_icon;
String m_title;
String m_subTitle;
String m_font;
unsigned m_fontSize;
TextStyle m_textStyle;
Vec2I m_iconOffset;
Vec2I m_titleOffset;
Vec2I m_subTitleOffset;

View File

@ -23,14 +23,11 @@ TextBoxWidget::TextBoxWidget(String const& startingText, String const& hint, Wid
m_overfillMode = true;
m_maxWidth = assets->json("/interface.config:textBoxDefaultWidth").toInt();
auto fontConfig = assets->json("/interface.config:font");
m_fontSize = fontConfig.getInt("baseSize");
m_processingDirectives = fontConfig.getString("defaultDirectives");
m_font = fontConfig.queryString("defaultFont", "");
m_color = Color::rgb(jsonToVec3B(fontConfig.getArray("defaultColor")));
auto fontConfig = assets->json("/interface.config:textBoxTextStyle");
m_textStyle = fontConfig;
// Meh, padding is hard-coded here
setSize({m_maxWidth + 6, m_fontSize + 2});
setSize({m_maxWidth + 6, m_textStyle.fontSize + 2});
m_cursorHoriz = jsonToVec2I(assets->json("/interface.config:textboxCursorHorizontal"));
m_cursorVert = jsonToVec2I(assets->json("/interface.config:textboxCursorVertical"));
}
@ -48,20 +45,17 @@ void TextBoxWidget::renderImpl() {
else if (m_hAnchor == HorizontalAnchor::RightAnchor)
pos += Vec2F(size()[0], 0);
context()->setFont(m_font);
context()->setTextStyle(m_textStyle);
if ((m_maxWidth != -1) && m_overfillMode) {
context()->setFontSize(m_fontSize);
int shift = std::max(0, getCursorOffset() - m_maxWidth);
pos += Vec2F(-shift, 0);
}
context()->setFontSize(m_fontSize);
context()->setFontProcessingDirectives(m_processingDirectives);
if (m_text.empty()) {
context()->setFontColor(m_color.mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba());
context()->setFontColor(Color::rgba(m_textStyle.color).mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba());
context()->renderInterfaceText(m_hint, {pos, m_hAnchor, m_vAnchor});
} else {
context()->setFontColor(m_color.mix(Color::rgbf(0, 0, 1), blueRate).toRgba());
context()->setFontColor(Color::rgba(m_textStyle.color).mix(Color::rgbf(0, 0, 1), blueRate).toRgba());
if (m_textHidden) {
String hiddenText('*', m_text.length());
context()->renderInterfaceText(hiddenText, { pos, m_hAnchor, m_vAnchor });
@ -69,22 +63,21 @@ void TextBoxWidget::renderImpl() {
else
context()->renderInterfaceText(m_text, { pos, m_hAnchor, m_vAnchor });
}
context()->setDefaultFont();
context()->setFontProcessingDirectives("");
context()->setFontColor(Vec4B::filled(255));
context()->clearTextStyle();
if (hasFocus()) {
// render cursor
float cc = 0.6f + 0.4f * std::sin((double)Time::monotonicMilliseconds() / 300.0);
Color cursorColor = Color::rgbf(cc, cc, cc);
float fontSize = m_textStyle.fontSize;
context()->drawInterfaceLine(
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[1]),
pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[1]),
cursorColor.toRgba());
context()->drawInterfaceLine(
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[0], m_fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[1], m_fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[0], fontSize * m_cursorVert[0]),
pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[1], fontSize * m_cursorVert[0]),
cursorColor.toRgba());
}
@ -94,8 +87,7 @@ void TextBoxWidget::renderImpl() {
int TextBoxWidget::getCursorOffset() { // horizontal only
float scale;
context()->setFont(m_font);
context()->setFontSize(m_fontSize);
context()->setTextStyle(m_textStyle);
if (m_hAnchor == HorizontalAnchor::LeftAnchor) {
scale = 1.0;
} else if (m_hAnchor == HorizontalAnchor::HMidAnchor) {
@ -158,7 +150,7 @@ String const& TextBoxWidget::getText() const {
return m_text;
}
bool TextBoxWidget::setText(String const& text, bool callback) {
bool TextBoxWidget::setText(String const& text, bool callback, bool moveCursor) {
if (m_text == text)
return true;
@ -166,7 +158,10 @@ bool TextBoxWidget::setText(String const& text, bool callback) {
return false;
m_text = text;
m_cursorOffset = m_text.size();
size_t size = m_text.size();
if (moveCursor || m_cursorOffset > size)
m_cursorOffset = size;
m_repeatCode = SpecialRepeatKeyCodes::None;
if (callback)
m_callback(this);
@ -190,20 +185,20 @@ void TextBoxWidget::setRegex(String const& regex) {
}
void TextBoxWidget::setColor(Color const& color) {
m_color = color;
m_textStyle.color = color.toRgba();
}
void TextBoxWidget::setDirectives(String const& directives) {
m_processingDirectives = directives;
m_textStyle.directives = directives;
}
void TextBoxWidget::setFontSize(int fontSize) {
m_fontSize = fontSize;
m_textStyle.fontSize = fontSize;
}
void TextBoxWidget::setMaxWidth(int maxWidth) {
m_maxWidth = maxWidth;
setSize({m_maxWidth + 6, m_fontSize + 2});
setSize({m_maxWidth + 6, m_textStyle.fontSize + 2});
}
void TextBoxWidget::setOverfillMode(bool overtype) {
@ -426,8 +421,7 @@ bool TextBoxWidget::newTextValid(String const& text) const {
if (!text.regexMatch(m_regex))
return false;
if ((m_maxWidth != -1) && !m_overfillMode) {
context()->setFont(m_font);
context()->setFontSize(m_fontSize);
context()->setTextStyle(m_textStyle);
return context()->stringInterfaceWidth(text) <= m_maxWidth;
}
return true;

View File

@ -14,7 +14,7 @@ public:
virtual void update(float dt) override;
String const& getText() const;
bool setText(String const& text, bool callback = true);
bool setText(String const& text, bool callback = true, bool moveCursor = true);
bool getHidden() const;
void setHidden(bool hidden);
@ -64,10 +64,7 @@ private:
String m_regex;
HorizontalAnchor m_hAnchor;
VerticalAnchor m_vAnchor;
Color m_color;
String m_processingDirectives;
String m_font;
int m_fontSize;
TextStyle m_textStyle;
int m_maxWidth;
int m_cursorOffset;
bool m_isHover;