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,29 +1,44 @@
{ {
"nametag" : { "nametag" : {
"showMasterNames" : true, "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, "inspectOpacityRate" : 0.15,
"movementThreshold" : 0.5, "movementThreshold" : 0.5,
"offset" : [0, 13] "offset" : [0, 13]
}, },
"font" : { "specialDamageBar" : {
"defaultDirectives" : "", "nameStyle" : {}
"defaultFont" : ""
}, },
"cursorTooltip" : { "textStyle" : {},
"font" : "" "buttonTextStyle" : {
"shadow" : "black"
}, },
"labelTextStyle" : {},
"paneTextStyle" : {},
"textBoxTextStyle" : {},
"itemSlotTextStyle" : {
"backDirectives" : "?border=1;444;4444"
},
"teamBarNameStyle" : {},
"cursorTooltip" : { "textStyle" : {} },
"debugFont" : "iosevka-semibold", "debugTextStyle" : {
"debugFontSize" : 7, "font" : "iosevka-semibold",
"debugFontDirectives" : "?border=1;111a;1114", "fontSize" : 7,
"backDirectives" : "?border=2;111a;1114"
},
"debugSpatialClearTime" : 0.0, "debugSpatialClearTime" : 0.0,
"debugOffset" : [80, 130], "debugOffset" : [80, 130],
// Change planet name to support the new internal string formatting. // Change planet name to support the new internal string formatting.
"planetNameFormatString" : "- {} -", "planetNameFormatString" : "- {} -",
"planetNameDirectives" : "?border=4;000;000", "planetTextStyle" : {
"backDirectives" : "?border=6;000;000",
"fontSize" : 24
},
"buttonClickSound" : [ "/sfx/interface/button/click.wav" ], "buttonClickSound" : [ "/sfx/interface/button/click.wav" ],
"buttonReleaseSound" : [ "/sfx/interface/button/release.wav" ], "buttonReleaseSound" : [ "/sfx/interface/button/release.wav" ],
"buttonHoverSound" : [ "/sfx/interface/button/hover.wav" ], "buttonHoverSound" : [ "/sfx/interface/button/hover.wav" ],

View File

@ -1,10 +1,8 @@
{ {
"config" : { "config" : {
"lineHeight" : 1, "lineHeight" : 1,
"font" : { "padding" : [1, 1],
"directives" : "?border=1;111a;1114", "textStyle" : { "backDirectives" : "?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
},
"colors" : { "colors" : {
"local" : "^white;", "local" : "^white;",
"party" : "^blue;", "party" : "^blue;",

View File

@ -1,4 +1,7 @@
{ {
"movementThreshold" : 0.5, "movementThreshold" : 0.5,
"bubbleOffset" : [0, 1.875] "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 { ImageConstPtr Assets::image(AssetPath const& path) const {
validatePath(path, true, true);
return as<ImageData>(getAsset(AssetId{AssetType::Image, path}))->image; 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 { Json Assets::readJson(String const& path) const {
ByteArray streamData = read(path); ByteArray streamData = read(path);
try { 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) { for (auto const& pair : m_files.get(path).patchSources) {
auto& patchPath = pair.first; auto& patchPath = pair.first;
auto& patchSource = pair.second; auto& patchSource = pair.second;
@ -973,7 +971,7 @@ Json Assets::readJson(String const& path) const {
if (newResult) if (newResult)
result = std::move(newResult); result = std::move(newResult);
} else { } else {
auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), JsonParseType::Top);
if (patchJson.isType(Json::Type::Array)) { if (patchJson.isType(Json::Type::Array)) {
auto patchData = patchJson.toArray(); auto patchData = patchJson.toArray();
try { 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 { shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
validatePath(path, true, true);
if (!path.directives.empty()) { if (!path.directives.empty()) {
shared_ptr<ImageData> source = shared_ptr<ImageData> source =
as<ImageData>(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}})); 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_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); 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_universeClient->startLua();
m_mainMixer->setWorldPainter(m_worldPainter); 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) { rhs.directives.forEach([&](auto const& entry, Directives const& directives) {
os << "?"; os << "?";
os << entry.string(*directives.shared); os << entry.string(*directives);
}); });
return os; return os;

View File

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

View File

@ -36,6 +36,7 @@ public:
mutable Mutex mutex; mutable Mutex mutex;
bool empty() const; bool empty() const;
Shared();
Shared(List<Entry>&& givenEntries, String&& givenString, StringView givenPrefix); Shared(List<Entry>&& givenEntries, String&& givenString, StringView givenPrefix);
}; };
@ -65,6 +66,9 @@ public:
bool empty() const; bool empty() const;
operator bool() const; operator bool() const;
Shared const& operator*() const;
Shared const* operator->() const;
bool equals(Directives const& other) const; bool equals(Directives const& other) const;
bool equals(String const& string) const; bool equals(String const& string) const;
@ -78,7 +82,7 @@ public:
//friend bool operator==(Directives const& directives, String const& string); //friend bool operator==(Directives const& directives, String const& string);
//friend bool operator==(String const& string, Directives const& directives); //friend bool operator==(String const& string, Directives const& directives);
std::shared_ptr<Shared const> shared; std::shared_ptr<Shared const> m_shared;
}; };
class DirectivesGroup { class DirectivesGroup {
@ -122,7 +126,6 @@ private:
size_t m_count; size_t m_count;
}; };
template <> template <>
struct hash<DirectivesGroup> { struct hash<DirectivesGroup> {
size_t operator()(DirectivesGroup const& s) const; size_t operator()(DirectivesGroup const& s) const;
@ -130,4 +133,13 @@ struct hash<DirectivesGroup> {
typedef DirectivesGroup ImageDirectives; 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) { FormattedJson FormattedJson::parse(String const& string) {
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>( return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
string.begin(), string.end(), true); string.begin(), string.end(), JsonParseType::Value);
} }
FormattedJson FormattedJson::parseJson(String const& string) { FormattedJson FormattedJson::parseJson(String const& string) {
return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>( return inputUtf32Json<String::const_iterator, FormattedJsonBuilderStream, FormattedJson>(
string.begin(), string.end(), false); string.begin(), string.end(), JsonParseType::Top);
} }
FormattedJson FormattedJson::ofType(Json::Type type) { FormattedJson FormattedJson::ofType(Json::Type type) {

View File

@ -105,11 +105,15 @@ Json Json::ofType(Type t) {
} }
Json Json::parse(String const& string) { 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) { 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() {} Json::Json() {}

View File

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

View File

@ -50,7 +50,7 @@ public:
}; };
template <typename InputIterator> template <typename InputIterator>
Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) { Json inputUtf8Json(InputIterator begin, InputIterator end, JsonParseType parseType) {
typedef U8ToU32Iterator<InputIterator> Utf32Input; typedef U8ToU32Iterator<InputIterator> Utf32Input;
typedef JsonParser<Utf32Input> Parser; typedef JsonParser<Utf32Input> Parser;
@ -58,7 +58,7 @@ Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) {
Parser parser(stream); Parser parser(stream);
Utf32Input wbegin(begin); Utf32Input wbegin(begin);
Utf32Input wend(end); Utf32Input wend(end);
Utf32Input pend = parser.parse(wbegin, wend, fragment); Utf32Input pend = parser.parse(wbegin, wend, parseType);
if (parser.error()) if (parser.error())
throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column())); 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> 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; Stream stream;
JsonParser<InputIterator> parser(stream); JsonParser<InputIterator> parser(stream);
InputIterator pend = parser.parse(begin, end, fragment); InputIterator pend = parser.parse(begin, end, parseType);
if (parser.error()) { if (parser.error()) {
throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column())); 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; 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 // Will parse JSON and output to a given JsonStream. Parses an *extension* to
// the JSON format that includes comments. // the JSON format that includes comments.
template <typename InputIterator> template <typename InputIterator>
@ -39,15 +45,17 @@ public:
// Does not throw. On error, returned iterator will not be equal to end, and // 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 // error() will be non-null. Set fragment to true to parse any JSON type
// rather than just object or array. // 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); init(begin, end);
try { try {
white(); white();
if (fragment) if (parseType == JsonParseType::Top)
value();
else
top(); top();
else if (parseType == JsonParseType::Value)
value();
else if (parseType == JsonParseType::Sequence)
sequence();
white(); white();
} catch (ParsingException const&) { } 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() { void string() {
CharArray s = parseString(); CharArray s = parseString();
m_stream.putString(s.c_str(), s.length()); m_stream.putString(s.c_str(), s.length());

View File

@ -60,6 +60,10 @@ LuaInt LuaTable::rawLength() const {
return engine().tableLength(true, handleIndex()); 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) { LuaCallbacks& LuaCallbacks::merge(LuaCallbacks const& callbacks) {
try { try {
for (auto const& pair : callbacks.m_callbacks) for (auto const& pair : callbacks.m_callbacks)

View File

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

View File

@ -1,9 +1,35 @@
#include "StarText.hpp" #include "StarText.hpp"
#include "StarJsonExtra.hpp"
#include <regex> #include <regex>
namespace Star { 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 { namespace Text {
static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc)); static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc));
String stripEscapeCodes(String const& s) { String stripEscapeCodes(String const& s) {

View File

@ -2,9 +2,29 @@
#include "StarString.hpp" #include "StarString.hpp"
#include "StarStringView.hpp" #include "StarStringView.hpp"
#include "StarVector.hpp"
#include "StarDirectives.hpp"
#include "StarJson.hpp"
namespace Star { 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 { namespace Text {
unsigned char const StartEsc = '\x1b'; unsigned char const StartEsc = '\x1b';
unsigned char const EndEsc = ';'; unsigned char const EndEsc = ';';

View File

@ -118,12 +118,14 @@ LuaCallbacks LuaBindings::makeUtilityCallbacks() {
callbacks.registerCallback("print", UtilityCallbacks::print); callbacks.registerCallback("print", UtilityCallbacks::print);
callbacks.registerCallback("interpolateSinEase", UtilityCallbacks::interpolateSinEase); callbacks.registerCallback("interpolateSinEase", UtilityCallbacks::interpolateSinEase);
callbacks.registerCallback("replaceTags", UtilityCallbacks::replaceTags); 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("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("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("makeRandomSource", [](Maybe<uint64_t> seed) { return seed ? RandomSource(*seed) : RandomSource(); });
callbacks.registerCallback("makePerlinSource", [](Json const& config) { return PerlinF(config); }); callbacks.registerCallback("makePerlinSource", [](Json const& config) { return PerlinF(config); });
callbacks.copyCallback("parseJson", "jsonFromString"); // SE compat
auto hash64LuaValues = [](LuaVariadic<LuaValue> const& values) -> uint64_t { auto hash64LuaValues = [](LuaVariadic<LuaValue> const& values) -> uint64_t {
XXHash64 hash; XXHash64 hash;

View File

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

View File

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

View File

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

View File

@ -20,9 +20,8 @@ ChatBubbleManager::ChatBubbleManager()
auto jsonData = assets->json("/interface/windowconfig/chatbubbles.config"); auto jsonData = assets->json("/interface/windowconfig/chatbubbles.config");
m_color = jsonToColor(jsonData.get("textColor")); m_textStyle.color = jsonToColor(jsonData.get("textColor")).toRgba();
m_textStyle.loadJson(jsonData.get("textStyle"));
m_fontSize = jsonData.getInt("fontSize");
m_textPadding = jsonToVec2F(jsonData.get("textPadding")); m_textPadding = jsonToVec2F(jsonData.get("textPadding"));
m_zoom = jsonData.getInt("textZoom"); 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. // bother me so bad if it weren't so fucking easy to do right.
// yea I agree // yea I agree
m_guiContext->setFontSize(m_fontSize, m_zoom); m_guiContext->setTextStyle(m_textStyle);
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setDefaultFont();
auto result = m_guiContext->determineTextSize(sayAction.text, m_textTemplate); auto result = m_guiContext->determineTextSize(sayAction.text, m_textTemplate);
float textWidth = result.width() / m_zoom + m_textPadding[0]; float textWidth = result.width() / m_zoom + m_textPadding[0];
float textHeight = result.height() / m_zoom + m_textPadding[1]; 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; float verticalShift = (partSize * innerTiles[1] - textMultiLineShift) * 0.5f + textMultiLineShift;
Vec2F position = Vec2F(horizontalCenter, verticalShift); Vec2F position = Vec2F(horizontalCenter, verticalShift);
List<BubbleText> bubbleTexts; List<BubbleText> bubbleTexts;
auto fontSize = config.getUInt("fontSize", m_fontSize); TextStyle textStyle = m_textStyle;
auto color = config.opt("color").apply(jsonToColor).value(m_color); textStyle.fontSize = config.getUInt("fontSize", textStyle.fontSize);
bubbleTexts.append(make_tuple(sayAction.text, fontSize, color.toRgba(), true, position)); 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) for (auto& backgroundImage : backgroundImages)
get<1>(backgroundImage) += Vec2F(-horizontalCenter, partSize); get<1>(backgroundImage) += Vec2F(-horizontalCenter, partSize);
for (auto& bubbleText : bubbleTexts) 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); auto pos = m_camera.worldToScreen(sayAction.position + m_bubbleOffset);
RectF boundBox = fold(backgroundImages, RectF::null(), [pos, this](RectF const& boundBox, BubbleImage const& bubbleImage) { 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.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); 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; }); 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); m_bubbles.addBubble(bubble.idealDestination, bubble.boundBox, bubble.contents, 0);
} else if (action.is<PortraitChatAction>()) { } 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(m_portraitMoreImage, Vec2F(m_portraitMorePosition)));
backgroundImages.append(make_tuple(portraitAction.portrait, Vec2F(m_portraitPosition))); backgroundImages.append(make_tuple(portraitAction.portrait, Vec2F(m_portraitPosition)));
List<BubbleText> bubbleTexts; 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) for (auto& backgroundImage : backgroundImages)
get<1>(backgroundImage) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0); get<1>(backgroundImage) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0);
for (auto& bubbleText : bubbleTexts) 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({ m_portraitBubbles.prepend({
portraitAction.entity, portraitAction.entity,
@ -321,28 +321,19 @@ void ChatBubbleManager::addChatActions(List<ChatAction> chatActions, bool silent
RectF ChatBubbleManager::bubbleImageRect(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio) { RectF ChatBubbleManager::bubbleImageRect(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio) {
auto imgMetadata = Root::singleton().imageMetadataDatabase(); 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); 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) { 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; auto offset = get<1>(bubbleImage) * pixelRatio;
m_guiContext->drawQuad(image, screenPos + offset, pixelRatio, {255, 255, 255, alpha}); 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) { void ChatBubbleManager::drawBubbleText(Vec2F screenPos, BubbleText const& bubbleText, int pixelRatio, int alpha, bool isPortrait) {
Vec4B const& baseColor = get<2>(bubbleText); m_guiContext->setTextStyle(get<1>(bubbleText), m_zoom);
auto offset = get<3>(bubbleText) * pixelRatio;
// 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;
TextPositioning tp = isPortrait ? m_portraitTextTemplate : m_textTemplate; TextPositioning tp = isPortrait ? m_portraitTextTemplate : m_textTemplate;
tp.pos = screenPos + offset; tp.pos = screenPos + offset;

View File

@ -26,7 +26,7 @@ public:
private: private:
typedef tuple<String, Vec2F> BubbleImage; typedef tuple<String, Vec2F> BubbleImage;
typedef tuple<String, unsigned, Vec4B, bool, Vec2F> BubbleText; typedef tuple<String, TextStyle, bool, Vec2F> BubbleText;
struct Bubble { struct Bubble {
EntityId entity; EntityId entity;
@ -64,8 +64,7 @@ private:
TextPositioning m_textTemplate; TextPositioning m_textTemplate;
TextPositioning m_portraitTextTemplate; TextPositioning m_portraitTextTemplate;
Color m_color; TextStyle m_textStyle;
int m_fontSize;
Vec2F m_textPadding; Vec2F m_textPadding;
BubbleSeparator<Bubble> m_bubbles; 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->animationFrames = panelDefinition.getInt("animationFrames", std::numeric_limits<int>::max());
panel->text = panelDefinition.getString("text", ""); panel->text = panelDefinition.getString("text", "");
panel->textPosition = TextPositioning(panelDefinition.get("textPosition", JsonObject())); panel->textPosition = TextPositioning(panelDefinition.get("textPosition", JsonObject()));
panel->fontColor = panelDefinition.opt("fontColor").apply(jsonToVec4B).value(Vec4B(255, 255, 255, 255)); panel->textStyle = panelDefinition.get("textStyle", Json());
panel->fontSize = panelDefinition.getUInt("fontSize", 8); 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->avatar = panelDefinition.getString("avatar", "");
panel->startTime = panelDefinition.getFloat("startTime", 0); panel->startTime = panelDefinition.getFloat("startTime", 0);
panel->endTime = panelDefinition.getFloat("endTime", 0); panel->endTime = panelDefinition.getFloat("endTime", 0);
@ -208,8 +209,9 @@ void Cinematic::render() {
} }
} }
if (!panel->text.empty()) { if (!panel->text.empty()) {
textPainter->setFontSize(floor(panel->fontSize * drawableScale)); textPainter->setTextStyle(panel->textStyle);
auto fontColor = panel->fontColor; textPainter->setFontSize(floor(panel->textStyle.fontSize * drawableScale));
auto fontColor = panel->textStyle.color;
fontColor[3] *= values.alpha; fontColor[3] *= values.alpha;
textPainter->setFontColor(fontColor); textPainter->setFontColor(fontColor);
Vec2F position = (m_offset + values.position + Vec2F(panel->textPosition.pos)) * drawableScale + drawableTranslation; Vec2F position = (m_offset + values.position + Vec2F(panel->textPosition.pos)) * drawableScale + drawableTranslation;

View File

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

View File

@ -91,7 +91,7 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
} }
} else { } else {
auto player = m_universeClient->mainPlayer(); 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)); result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
else else
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);

View File

@ -4,6 +4,9 @@
#include "StarLuaGameConverters.hpp" #include "StarLuaGameConverters.hpp"
#include "StarMainInterface.hpp" #include "StarMainInterface.hpp"
#include "StarGuiContext.hpp" #include "StarGuiContext.hpp"
#include "StarChat.hpp"
#include "StarUniverseClient.hpp"
#include "StarClientCommandProcessor.hpp"
namespace Star { namespace Star {
@ -42,4 +45,49 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) {
return callbacks; 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 { namespace Star {
STAR_CLASS(MainInterface); STAR_CLASS(MainInterface);
STAR_CLASS(UniverseClient);
namespace LuaBindings { namespace LuaBindings {
LuaCallbacks makeInterfaceCallbacks(MainInterface* mainInterface); 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>(); auto planetName = make_shared<Pane>();
m_planetText = make_shared<LabelWidget>(); m_planetText = make_shared<LabelWidget>();
m_planetText->setFontSize(m_config->planetNameFontSize); m_planetText->setTextStyle(m_config->planetNameTextStyle);
m_planetText->setFontMode(FontMode::Normal);
m_planetText->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor); m_planetText->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor);
m_planetText->setDirectives(m_config->planetNameDirectives);
planetName->disableScissoring(); planetName->disableScissoring();
planetName->setPosition(m_config->planetNameOffset); planetName->setPosition(m_config->planetNameOffset);
planetName->setAnchor(PaneAnchor::Center); planetName->setAnchor(PaneAnchor::Center);
@ -810,9 +808,7 @@ void MainInterface::renderInWorldElements() {
if (m_disableHud) if (m_disableHud)
return; return;
m_guiContext->setDefaultFont(); m_guiContext->clearTextStyle();
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setFontColor(Vec4B::filled(255));
m_questIndicatorPainter->render(); m_questIndicatorPainter->render();
m_nameplatePainter->render(); m_nameplatePainter->render();
m_chatBubbleManager->render(); m_chatBubbleManager->render();
@ -822,9 +818,7 @@ void MainInterface::render() {
if (m_disableHud) if (m_disableHud)
return; return;
m_guiContext->setDefaultFont(); m_guiContext->clearTextStyle();
m_guiContext->setFontProcessingDirectives("");
m_guiContext->setFontColor(Vec4B::filled(255));
renderBreath(); renderBreath();
renderMessages(); renderMessages();
renderMonsterHealthBar(); renderMonsterHealthBar();
@ -950,6 +944,10 @@ CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignore
return canvas; return canvas;
} }
ClientCommandProcessorPtr MainInterface::commandProcessor() const {
return m_clientCommandProcessor;
}
// For when the player swaps characters. We need to completely reload ScriptPanes, // 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. // 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) { void MainInterface::takeScriptPanes(List<ScriptPaneInfo>& out) {
@ -1084,9 +1082,7 @@ void MainInterface::renderMessages() {
m_guiContext->drawQuad(m_config->messageTextContainer, m_guiContext->drawQuad(m_config->messageTextContainer,
RectF::withCenter(backgroundTextCenterPos, Vec2F(imgMetadata->imageSize(m_config->messageTextContainer) * interfaceScale()))); RectF::withCenter(backgroundTextCenterPos, Vec2F(imgMetadata->imageSize(m_config->messageTextContainer) * interfaceScale())));
m_guiContext->setFont(m_config->font); m_guiContext->setTextStyle(m_config->textStyle);
m_guiContext->setFontSize(m_config->fontSize);
m_guiContext->setFontColor(Color::White.toRgba());
m_guiContext->renderText(message->message, {messageTextOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor}); 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()))); m_guiContext->drawQuad(container, RectF::withCenter(backgroundCenterPos + offset, Vec2F(imgMetadata->imageSize(container) * interfaceScale())));
auto nameTextOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.nameTextOffset")) * interfaceScale(); auto nameTextOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.nameTextOffset")) * interfaceScale();
m_guiContext->setFont(m_config->font); m_guiContext->setTextStyle(m_config->textStyle);
m_guiContext->setFontSize(m_config->fontSize);
m_guiContext->setFontColor(Color::White.toRgba());
m_guiContext->renderText(showDamageEntity->name(), backgroundCenterPos + nameTextOffset); m_guiContext->renderText(showDamageEntity->name(), backgroundCenterPos + nameTextOffset);
auto empty = assets->json("/interface.config:monsterHealth.progressEmpty").toString(); 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())); m_guiContext->drawQuad(fill, RectF::withSize(bottomCenter + fillOffset, size * interfaceScale()));
auto nameOffset = jsonToVec2F(barConfig.get("nameOffset")) * 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->setFontSize(barConfig.getUInt("nameSize"));
m_guiContext->setFontProcessingDirectives(barConfig.getString("nameDirectives")); m_guiContext->setFontProcessingDirectives(barConfig.getString("nameDirectives"));
m_guiContext->renderText(target->name(), TextPositioning(bottomCenter + nameOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor)); 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()) { if (m_clientCommandProcessor->debugHudEnabled()) {
auto assets = Root::singleton().assets(); auto assets = Root::singleton().assets();
m_guiContext->setFontSize(m_config->debugFontSize); m_guiContext->setTextStyle(m_config->debugTextStyle);
m_guiContext->setFont(m_config->debugFont);
m_guiContext->setLineSpacing(0.5f); 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(); bool clearMap = m_debugMapClearTimer.wrapTick();
auto logMapValues = LogMap::getValues(); auto logMapValues = LogMap::getValues();
@ -1358,7 +1348,7 @@ void MainInterface::renderDebug() {
int counter = 0; int counter = 0;
for (auto const& pair : logMapValues) { 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)); 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)); m_debugTextRect.combine(m_guiContext->determineTextSize(text, positioning).padded(m_config->debugBackgroundPad));
} }
@ -1373,15 +1363,9 @@ void MainInterface::renderDebug() {
m_debugTextRect = RectF::null(); m_debugTextRect = RectF::null();
for (size_t index = 0; index != formatted.size(); ++index) { 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->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(); 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->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)) { for (auto const& logText : SpatialLogger::getText("world", clearSpatial)) {
m_guiContext->setFontColor(logText.color); m_guiContext->setFontColor(logText.color);
@ -1424,7 +1408,7 @@ void MainInterface::renderDebug() {
m_guiContext->setFontColor(logText.color); m_guiContext->setFontColor(logText.color);
m_guiContext->renderText(logText.text.utf8Ptr(), logText.position); m_guiContext->renderText(logText.text.utf8Ptr(), logText.position);
} }
m_guiContext->setFontColor(Vec4B::filled(255)); m_guiContext->clearTextStyle();
} }
void MainInterface::updateCursor() { void MainInterface::updateCursor() {
@ -1483,24 +1467,26 @@ void MainInterface::renderCursor() {
auto assets = Root::singleton().assets(); auto assets = Root::singleton().assets();
auto imgDb = Root::singleton().imageMetadataDatabase(); auto imgDb = Root::singleton().imageMetadataDatabase();
auto backgroundImage = assets->json("/interface.config:cursorTooltip.background").toString(); auto config = assets->json("/interface.config:cursorTooltip");
auto rawCursorOffset = jsonToVec2I(assets->json("/interface.config:cursorTooltip.offset")); auto backgroundImage = config.getString("background");
auto rawCursorOffset = jsonToVec2I(config.get("offset"));
Vec2I tooltipSize = Vec2I(imgDb->imageSize(backgroundImage)) * interfaceScale(); Vec2I tooltipSize = Vec2I(imgDb->imageSize(backgroundImage)) * interfaceScale();
Vec2I cursorOffset = (Vec2I{0, -m_cursor.size().y()} + rawCursorOffset) * cursorScale; Vec2I cursorOffset = (Vec2I{0, -m_cursor.size().y()} + rawCursorOffset) * cursorScale;
Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset; Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset;
size_t fontSize = assets->json("/interface.config:cursorTooltip.fontSize").toUInt(); TextStyle textStyle = config.get("textStyle");
String font = assets->json("/interface.config:cursorTooltip.font").toString(); size_t fontSize = config.get("fontSize").toUInt();
Vec4B fontColor = jsonToColor(assets->json("/interface.config:cursorTooltip.color")).toRgba(); Vec4B fontColor = jsonToColor(config.get("color")).toRgba();
m_guiContext->drawQuad(backgroundImage, Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), 0), interfaceScale()); m_guiContext->drawQuad(backgroundImage, Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), 0), interfaceScale());
m_guiContext->setTextStyle(textStyle);
m_guiContext->setFontSize(fontSize); m_guiContext->setFontSize(fontSize);
m_guiContext->setFontColor(fontColor); m_guiContext->setFontColor(fontColor);
m_guiContext->setFont(font);
m_guiContext->renderText(*m_cursorTooltip, m_guiContext->renderText(*m_cursorTooltip,
TextPositioning(Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), tooltipSize.y()) / 2, TextPositioning(Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), tooltipSize.y()) / 2,
HorizontalAnchor::HMidAnchor, HorizontalAnchor::HMidAnchor,
VerticalAnchor::VMidAnchor)); VerticalAnchor::VMidAnchor));
m_guiContext->clearTextStyle();
} }
m_cursorItem->setPosition(m_cursorScreenPos / interfaceScale() + m_config->inventoryItemMouseOffset); m_cursorItem->setPosition(m_cursorScreenPos / interfaceScale() + m_config->inventoryItemMouseOffset);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,8 @@ TeamBar::TeamBar(MainInterface* mainInterface, UniverseClientPtr client) {
m_teamInvitation = make_shared<TeamInvitation>(this); m_teamInvitation = make_shared<TeamInvitation>(this);
m_teamMemberMenu = make_shared<TeamMemberMenu>(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")); m_nameOffset = jsonToVec2F(assets->json("/interface.config:nameOffset"));
GuiReader reader; GuiReader reader;

View File

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

View File

@ -96,4 +96,12 @@ ByteArray ClientContext::writeUpdate() {
return m_rpc->send(); 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); void readUpdate(ByteArray data);
ByteArray writeUpdate(); ByteArray writeUpdate();
void setConnectionId(ConnectionId connectionId);
ConnectionId connectionId() const;
private: private:
Uuid m_serverUuid; Uuid m_serverUuid;
Uuid m_playerUuid; Uuid m_playerUuid;
ConnectionId m_connectionId = 0;
JsonRpcPtr m_rpc; JsonRpcPtr m_rpc;

View File

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

View File

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

View File

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

View File

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

View File

@ -1734,6 +1734,7 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) {
m_entityUpdateTimer = GameTimer(m_interpolationTracker.entityUpdateDelta()); m_entityUpdateTimer = GameTimer(m_interpolationTracker.entityUpdateDelta());
m_clientId = startPacket.clientId; m_clientId = startPacket.clientId;
m_mainPlayer->clientContext()->setConnectionId(startPacket.clientId);
auto entitySpace = connectionEntitySpace(startPacket.clientId); auto entitySpace = connectionEntitySpace(startPacket.clientId);
m_worldTemplate = make_shared<WorldTemplate>(startPacket.templateData); m_worldTemplate = make_shared<WorldTemplate>(startPacket.templateData);
m_entityMap = make_shared<EntityMap>(m_worldTemplate->size(), entitySpace.first, entitySpace.second); 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); m_font->setPixelSize(size);
auto pair = m_font->render(c); auto pair = m_font->render(c);
Image& image = pair.first; Image& image = pair.first;
if (processingDirectives && *processingDirectives) { if (processingDirectives) {
try { try {
Directives const& directives = *processingDirectives;
Vec2F preSize = Vec2F(image.size()); Vec2F preSize = Vec2F(image.size());
for (auto& entry : processingDirectives->shared->entries) for (auto& entry : directives->entries)
processImageOperation(entry.operation, image); processImageOperation(entry.operation, image);
res.first->second.offset = (preSize - Vec2F(image.size())) / 2; res.first->second.offset = (preSize - Vec2F(image.size())) / 2;

View File

@ -1,7 +1,5 @@
#include "StarTextPainter.hpp" #include "StarTextPainter.hpp"
#include "StarJsonExtra.hpp" #include "StarJsonExtra.hpp"
#include "StarText.hpp"
#include <regex> #include <regex>
namespace Star { namespace Star {
@ -40,34 +38,42 @@ TextPositioning TextPositioning::translated(Vec2F translation) const {
TextPainter::TextPainter(RendererPtr renderer, TextureGroupPtr textureGroup) TextPainter::TextPainter(RendererPtr renderer, TextureGroupPtr textureGroup)
: m_renderer(renderer), : m_renderer(renderer),
m_fontTextureGroup(textureGroup), m_fontTextureGroup(textureGroup),
m_fontSize(8), m_defaultRenderSettings(),
m_lineSpacing(1.30f), m_renderSettings(),
m_renderSettings({FontMode::Normal, Vec4B::filled(255), "hobo", ""}) { m_savedRenderSettings() {
reloadFonts(); reloadFonts();
m_reloadTracker = make_shared<TrackerListener>(); m_reloadTracker = make_shared<TrackerListener>();
Root::singleton().registerReloadListener(m_reloadTracker); Root::singleton().registerReloadListener(m_reloadTracker);
} }
RectF TextPainter::renderText(StringView s, TextPositioning const& position) { RectF TextPainter::renderText(StringView s, TextPositioning const& position) {
RectF rect;
if (position.charLimit) { if (position.charLimit) {
unsigned charLimit = *position.charLimit; unsigned charLimit = *position.charLimit;
return doRenderText(s, position, true, &charLimit); rect = doRenderText(s, position, true, &charLimit);
} else { } 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 TextPainter::renderLine(StringView s, TextPositioning const& position) {
RectF rect;
if (position.charLimit) { if (position.charLimit) {
unsigned charLimit = *position.charLimit; unsigned charLimit = *position.charLimit;
return doRenderLine(s, position, true, &charLimit); rect = doRenderLine(s, position, true, &charLimit);
} else { } 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) { 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) { 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) { 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()) if (s.empty())
return 0; return 0;
String font = m_renderSettings.font, setFont = font; String font = m_renderSettings.font, setFont = font;
m_fontTextureGroup.switchFont(font); m_fontTextureGroup.switchFont(font);
int width = 0;
Text::CommandsCallback commandsCallback = [&](StringView commands) { Text::CommandsCallback commandsCallback = [&](StringView commands) {
commands.forEachSplitView(",", [&](StringView command, size_t, size_t) { commands.forEachSplitView(",", [&](StringView command, size_t, size_t) {
if (command == "reset") if (command == "reset") {
m_fontTextureGroup.switchFont(font = setFont); m_fontTextureGroup.switchFont(font = setFont);
else if (command == "set") } else if (command == "set") {
setFont = font; setFont = font;
else if (command.beginsWith("font=")) } else if (command.beginsWith("font=")) {
m_fontTextureGroup.switchFont(font = command.substr(5)); m_fontTextureGroup.switchFont(font = command.substr(5));
}
}); });
return true; return true;
}; };
int width = 0;
Text::TextCallback textCallback = [&](StringView text) { Text::TextCallback textCallback = [&](StringView text) {
for (String::Char c : text) for (String::Char c : text) {
width += glyphWidth(c); width += glyphWidth(c);
if (charLimit && --charLimit == 0)
return false;
}
return true; return true;
}; };
@ -122,7 +130,7 @@ int TextPainter::stringWidth(StringView s) {
bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) { bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) {
String font = m_renderSettings.font, setFont = font; String font = m_renderSettings.font, setFont = font;
m_fontTextureGroup.switchFont(font); m_fontTextureGroup.switchFont(font);
int lines = 0; unsigned lines = 0;
auto it = text.begin(); auto it = text.begin();
auto end = text.end(); auto end = text.end();
@ -149,14 +157,15 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
if (escIt != end) { if (escIt != end) {
if (character == Text::EndEsc) { if (character == Text::EndEsc) {
StringView inner = slice(escIt, it); StringView inner = slice(++escIt, it);
inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { inner.forEachSplitView(",", [&](StringView command, size_t, size_t) {
if (command == "reset") if (command == "reset") {
m_fontTextureGroup.switchFont(font = setFont); m_fontTextureGroup.switchFont(font = setFont);
else if (command == "set") } else if (command == "set") {
setFont = font; setFont = font;
else if (command.beginsWith("font=")) } else if (command.beginsWith("font=")) {
m_fontTextureGroup.switchFont(font = command.substr(5)); m_fontTextureGroup.switchFont(font = command.substr(5));
}
}); });
escIt = end; escIt = end;
} }
@ -177,7 +186,6 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText
splitIt = end; splitIt = end;
} else { } else {
int charWidth = glyphWidth(character); int charWidth = glyphWidth(character);
// is it a place where we might want to split the line ? // is it a place where we might want to split the line ?
if (character == ' ' || character == '\t') { if (character == ' ' || character == '\t') {
splitIt = it; 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> TextPainter::wrapTextViews(StringView s, Maybe<unsigned> wrapWidth) {
List<StringView> views = {}; List<StringView> views = {};
auto last = views.end();
bool active = false; unsigned curLine = 0;
StringView current; TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) {
int lastLine = 0; if (line == curLine && last != views.end() && last->end() == text.begin()) {
*last = StringView(last->utf8Ptr(), last->utf8Size() + text.utf8Size());
auto addText = [&active, &current](StringView text) { } else {
// Merge views if they are adjacent last = views.insert(views.end(), text);
if (active && current.utf8Ptr() + current.utf8Size() == text.utf8Ptr()) curLine = line;
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;
} }
addText(text);
return true; return true;
}; };
processWrapText(s, wrapWidth.ptr(), textCallback); processWrapText(s, wrapWidth.ptr(), textCallback);
if (active)
views.push_back(current);
return views; return views;
} }
@ -258,12 +250,11 @@ StringList TextPainter::wrapText(StringView s, Maybe<unsigned> wrapWidth) {
String current; String current;
int lastLine = 0; int lastLine = 0;
TextPainter::WrapTextCallback textCallback = [&](StringView text, int line) { TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) {
if (lastLine != line) { if (lastLine != line) {
result.append(std::move(current)); result.append(std::move(current));
lastLine = line; lastLine = line;
} }
current += text; current += text;
return true; return true;
}; };
@ -277,40 +268,45 @@ StringList TextPainter::wrapText(StringView s, Maybe<unsigned> wrapWidth) {
}; };
unsigned TextPainter::fontSize() const { unsigned TextPainter::fontSize() const {
return m_fontSize; return m_renderSettings.fontSize;
} }
void TextPainter::setFontSize(unsigned size) { void TextPainter::setFontSize(unsigned size) {
m_fontSize = size; m_renderSettings.fontSize = size;
} }
void TextPainter::setLineSpacing(float lineSpacing) { void TextPainter::setLineSpacing(float lineSpacing) {
m_lineSpacing = lineSpacing; m_renderSettings.lineSpacing = lineSpacing;
} }
void TextPainter::setMode(FontMode mode) { void TextPainter::setMode(FontMode mode) {
m_renderSettings.mode = mode; m_renderSettings.shadow = fontModeToColor(mode).toRgba();
} }
void TextPainter::setFontColor(Vec4B color) { void TextPainter::setFontColor(Vec4B color) {
m_renderSettings.color = std::move(color); m_renderSettings.color = std::move(color);
} }
void TextPainter::setProcessingDirectives(StringView directives) { void TextPainter::setProcessingDirectives(StringView directives, bool back) {
m_renderSettings.directives = String(directives); Directives& target = back ? m_renderSettings.backDirectives : m_renderSettings.directives;
if (m_renderSettings.directives) { modifyDirectives(target = String(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::setFont(String const& font) { void TextPainter::setFont(String const& font) {
m_renderSettings.font = 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) { void TextPainter::addFont(FontPtr const& font, String const& name) {
m_fontTextureGroup.addFont(font, name); m_fontTextureGroup.addFont(font, name);
} }
@ -348,16 +344,19 @@ void TextPainter::applyCommands(StringView unsplitCommands) {
m_renderSettings = m_savedRenderSettings; m_renderSettings = m_savedRenderSettings;
} else if (command == "set") { } else if (command == "set") {
m_savedRenderSettings = m_renderSettings; m_savedRenderSettings = m_renderSettings;
} else if (command == "shadow") { } else if (command.beginsWith("shadow")) {
m_renderSettings.mode = (FontMode)((int)m_renderSettings.mode | (int)FontMode::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") { } 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=")) { } else if (command.beginsWith("font=")) {
m_renderSettings.font = command.substr(5); m_renderSettings.font = command.substr(5);
} else if (command.beginsWith("directives=")) { } 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)); setProcessingDirectives(command.substr(11));
} else if (command.beginsWith("backdirectives=")) {
setProcessingDirectives(command.substr(15), true);
} else { } else {
// expects both #... sequences and plain old color names. // expects both #... sequences and plain old color names.
Color c = Color(command); 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) { RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit) {
Vec2F pos = position.pos; Vec2F pos = position.pos;
if (s.empty()) if (s.empty())
@ -377,26 +386,23 @@ RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, b
List<StringView> lines = wrapTextViews(s, position.wrapWidth); List<StringView> lines = wrapTextViews(s, position.wrapWidth);
int height = (lines.size() - 1) * m_lineSpacing * m_fontSize + m_fontSize; TextStyle backup = m_savedRenderSettings = m_renderSettings;
int height = (lines.size() - 1) * backup.lineSpacing * backup.fontSize + backup.fontSize;
RenderSettings backupRenderSettings = m_renderSettings;
m_savedRenderSettings = m_renderSettings;
if (position.vAnchor == VerticalAnchor::BottomAnchor) if (position.vAnchor == VerticalAnchor::BottomAnchor)
pos[1] += (height - m_fontSize); pos[1] += (height - backup.fontSize);
else if (position.vAnchor == VerticalAnchor::VMidAnchor) else if (position.vAnchor == VerticalAnchor::VMidAnchor)
pos[1] += (height - m_fontSize) / 2; pos[1] += (height - backup.fontSize) / 2;
RectF bounds = RectF::withSize(pos, Vec2F()); RectF bounds = RectF::withSize(pos, Vec2F());
for (auto& i : lines) { for (auto& i : lines) {
bounds.combine(doRenderLine(i, { pos, position.hAnchor, position.vAnchor }, reallyRender, charLimit)); 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) if (charLimit && *charLimit == 0)
break; break;
} }
m_renderSettings = std::move(backupRenderSettings); m_renderSettings = std::move(backup);
return bounds; return bounds;
} }
@ -425,9 +431,8 @@ RectF TextPainter::doRenderLine(StringView text, TextPositioning const& position
if (*charLimit == 0) if (*charLimit == 0)
return false; return false;
else else
--* charLimit; --*charLimit;
} }
RectF glyphBounds = doRenderGlyph(c, pos, reallyRender); RectF glyphBounds = doRenderGlyph(c, pos, reallyRender);
bounds.combine(glyphBounds); bounds.combine(glyphBounds);
pos.pos[0] += glyphBounds.width(); pos.pos[0] += glyphBounds.width();
@ -461,41 +466,58 @@ RectF TextPainter::doRenderGlyph(String::Char c, TextPositioning const& position
float vOffset = 0; float vOffset = 0;
if (position.vAnchor == VerticalAnchor::VMidAnchor) if (position.vAnchor == VerticalAnchor::VMidAnchor)
vOffset = -floor((float)m_fontSize / 2); vOffset = -floor((float)m_renderSettings.fontSize / 2);
else if (position.vAnchor == VerticalAnchor::TopAnchor) else if (position.vAnchor == VerticalAnchor::TopAnchor)
vOffset = -(float)m_fontSize; vOffset = -(float)m_renderSettings.fontSize;
Directives* directives = m_renderSettings.directives ? &m_renderSettings.directives : nullptr; Directives* directives = m_renderSettings.directives ? &m_renderSettings.directives : nullptr;
Vec2F pos = position.pos + Vec2F(hOffset, vOffset);
if (reallyRender) { if (reallyRender) {
if ((int)m_renderSettings.mode & (int)FontMode::Shadow) { bool hasShadow = m_renderSettings.shadow[3] > 0;
Color shadow = Color::Black; bool hasBackDirectives = m_renderSettings.backDirectives;
uint8_t alphaU = m_renderSettings.color[3]; 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) { if (alphaU != 255) {
float alpha = byteToFloat(alphaU); float alpha = byteToFloat(alphaU);
shadow.setAlpha(floatToByte(alpha * (1.5f - 0.5f * alpha))); shadow[3] = floatToByte(alpha * (1.5f - 0.5f * alpha));
} }
else 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 Directives const* shadowDirectives = hasBackDirectives ? &m_renderSettings.backDirectives : directives;
renderGlyph(c, position.pos + Vec2F(hOffset, vOffset - 2), m_fontSize, 1, shadow.toRgba(), 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) { float scale, Vec4B const& color, Directives const* processingDirectives) {
if (!fontSize) if (!fontSize)
return; return;
const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives); const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives);
Vec2F offset = glyphTexture.offset * scale; 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) { FontPtr TextPainter::loadFont(String const& fontPath, Maybe<String> fontName) {

View File

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

View File

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

View File

@ -121,9 +121,7 @@ protected:
Vec2I m_pressedOffset; Vec2I m_pressedOffset;
Vec2U m_buttonBoundSize; Vec2U m_buttonBoundSize;
int m_fontSize; TextStyle m_textStyle;
String m_font;
String m_fontDirectives;
String m_text; String m_text;
Vec2I m_textOffset; 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) { 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 { Vec2I CanvasWidget::mousePosition() const {
@ -145,7 +156,7 @@ void CanvasWidget::renderImpl() {
if (auto args = op.ptr<TrianglesOp>()) if (auto args = op.ptr<TrianglesOp>())
tupleUnpackFunction(bind(&CanvasWidget::renderTriangles, this, renderingOffset, _1, _2), *args); tupleUnpackFunction(bind(&CanvasWidget::renderTriangles, this, renderingOffset, _1, _2), *args);
if (auto args = op.ptr<TextOp>()) 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); 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(); auto& context = GuiContext::singleton();
context.setFontProcessingDirectives(directives); context.setTextStyle(style, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
context.setFontSize(fontSize, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
context.setFontColor(color);
context.setFontMode(mode);
context.setFont(font);
context.setLineSpacing(lineSpacing);
TextPositioning translatedPosition = position; TextPositioning translatedPosition = position;
translatedPosition.pos += renderingOffset; translatedPosition.pos += renderingOffset;
@ -272,10 +278,7 @@ void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, Tex
else else
context.renderInterfaceText(s, translatedPosition); context.renderInterfaceText(s, translatedPosition);
context.setDefaultLineSpacing(); context.clearTextStyle();
context.setDefaultFont();
context.setFontMode(FontMode::Normal);
context.setFontProcessingDirectives("");
} }
} }

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 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, 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: protected:
void renderImpl() override; void renderImpl() override;
@ -79,7 +80,7 @@ protected:
void renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color); void renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color);
void renderPoly(Vec2F const& renderingOffset, PolyF poly, Vec4B const& color, float lineWidth); 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 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: private:
bool m_ignoreInterfaceScale; bool m_ignoreInterfaceScale;
@ -97,7 +98,7 @@ private:
typedef tuple<Vec2F, Vec2F, Vec4B, float> LineOp; typedef tuple<Vec2F, Vec2F, Vec4B, float> LineOp;
typedef tuple<PolyF, Vec4B, float> PolyOp; typedef tuple<PolyF, Vec4B, float> PolyOp;
typedef tuple<List<tuple<Vec2F, Vec2F, Vec2F>>, Vec4B> TrianglesOp; 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; typedef MVariant<RectOp, ImageOp, ImageRectOp, DrawableOp, TiledImageOp, LineOp, PolyOp, TrianglesOp, TextOp> RenderOp;
List<RenderOp> m_renderOps; List<RenderOp> m_renderOps;

View File

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

View File

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

View File

@ -377,6 +377,20 @@ void GuiContext::setDefaultFont() {
textPainter()->setFont(""); 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) { void GuiContext::setLineSpacing(float lineSpacing) {
textPainter()->setLineSpacing(lineSpacing); textPainter()->setLineSpacing(lineSpacing);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ public:
virtual void update(float dt) override; virtual void update(float dt) override;
String const& getText() const; 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; bool getHidden() const;
void setHidden(bool hidden); void setHidden(bool hidden);
@ -64,10 +64,7 @@ private:
String m_regex; String m_regex;
HorizontalAnchor m_hAnchor; HorizontalAnchor m_hAnchor;
VerticalAnchor m_vAnchor; VerticalAnchor m_vAnchor;
Color m_color; TextStyle m_textStyle;
String m_processingDirectives;
String m_font;
int m_fontSize;
int m_maxWidth; int m_maxWidth;
int m_cursorOffset; int m_cursorOffset;
bool m_isHover; bool m_isHover;