From ca1426eabc873f781eb0dd389d45634b7d183250 Mon Sep 17 00:00:00 2001 From: Kae <80987908+Novaenia@users.noreply.github.com> Date: Mon, 22 Apr 2024 06:07:59 +1000 Subject: [PATCH] Lua chat callbacks + better font styling golly gee whiz!! i hope i didn't fuck something up --- assets/opensb/interface.config.patch | 37 +++- .../opensb/interface/chat/chat.config.patch | 6 +- .../windowconfig/chatbubbles.config.patch | 7 +- source/base/StarAssets.cpp | 7 +- source/client/StarClientApplication.cpp | 1 + source/core/StarAssetPath.cpp | 2 +- source/core/StarDirectives.cpp | 83 +++---- source/core/StarDirectives.hpp | 16 +- source/core/StarFormattedJson.cpp | 4 +- source/core/StarJson.cpp | 8 +- source/core/StarJson.hpp | 3 + source/core/StarJsonBuilder.hpp | 8 +- source/core/StarJsonParser.hpp | 66 +++++- source/core/StarLua.cpp | 4 + source/core/StarLua.hpp | 2 + source/core/StarText.cpp | 28 ++- source/core/StarText.hpp | 22 +- .../core/scripting/StarUtilityLuaBindings.cpp | 4 +- source/frontend/StarActionBar.cpp | 2 - source/frontend/StarChat.cpp | 68 +++--- source/frontend/StarChat.hpp | 7 +- source/frontend/StarChatBubbleManager.cpp | 43 ++-- source/frontend/StarChatBubbleManager.hpp | 5 +- source/frontend/StarCinematic.cpp | 10 +- source/frontend/StarCinematic.hpp | 3 +- .../frontend/StarClientCommandProcessor.cpp | 2 +- source/frontend/StarInterfaceLuaBindings.cpp | 48 ++++ source/frontend/StarInterfaceLuaBindings.hpp | 2 + source/frontend/StarMainInterface.cpp | 62 ++---- source/frontend/StarMainInterface.hpp | 2 + source/frontend/StarMainInterfaceTypes.cpp | 10 +- source/frontend/StarMainInterfaceTypes.hpp | 11 +- source/frontend/StarNameplatePainter.cpp | 41 +--- source/frontend/StarNameplatePainter.hpp | 9 +- source/frontend/StarTeamBar.cpp | 3 +- source/frontend/StarTeamBar.hpp | 2 +- source/game/StarClientContext.cpp | 8 + source/game/StarClientContext.hpp | 4 + source/game/StarHumanoid.cpp | 6 +- source/game/StarImageMetadataDatabase.cpp | 2 +- source/game/StarParallax.cpp | 2 +- source/game/StarUniverseClient.cpp | 4 +- source/game/StarUniverseClient.hpp | 2 +- source/game/StarWorldClient.cpp | 1 + source/rendering/StarFontTextureGroup.cpp | 5 +- source/rendering/StarTextPainter.cpp | 208 ++++++++++-------- source/rendering/StarTextPainter.hpp | 43 ++-- source/windowing/StarButtonWidget.cpp | 27 +-- source/windowing/StarButtonWidget.hpp | 4 +- source/windowing/StarCanvasWidget.cpp | 29 +-- source/windowing/StarCanvasWidget.hpp | 5 +- source/windowing/StarFuelWidget.cpp | 7 +- source/windowing/StarFuelWidget.hpp | 3 +- source/windowing/StarGuiContext.cpp | 14 ++ source/windowing/StarGuiContext.hpp | 3 + source/windowing/StarItemSlotWidget.cpp | 12 +- source/windowing/StarItemSlotWidget.hpp | 4 +- source/windowing/StarLabelWidget.cpp | 62 ++---- source/windowing/StarLabelWidget.hpp | 7 +- source/windowing/StarPane.cpp | 9 +- source/windowing/StarPane.hpp | 3 +- source/windowing/StarTextBoxWidget.cpp | 52 ++--- source/windowing/StarTextBoxWidget.hpp | 7 +- 63 files changed, 658 insertions(+), 513 deletions(-) diff --git a/assets/opensb/interface.config.patch b/assets/opensb/interface.config.patch index 5e71a20..d798609 100644 --- a/assets/opensb/interface.config.patch +++ b/assets/opensb/interface.config.patch @@ -1,29 +1,44 @@ { "nametag" : { "showMasterNames" : true, - "fontDirectives" : "?border=1;222;2224", + // border is double here as it otherwise fades out near the end + // (could just the end to opaque too, but then diagonals are more jaggy) + "textStyle" : { "backDirectives" : "?border=1;222;2224?border=1;222;2224" }, "inspectOpacityRate" : 0.15, "movementThreshold" : 0.5, "offset" : [0, 13] }, - "font" : { - "defaultDirectives" : "", - "defaultFont" : "" + "specialDamageBar" : { + "nameStyle" : {} }, - "cursorTooltip" : { - "font" : "" + "textStyle" : {}, + "buttonTextStyle" : { + "shadow" : "black" }, + "labelTextStyle" : {}, + "paneTextStyle" : {}, + "textBoxTextStyle" : {}, + "itemSlotTextStyle" : { + "backDirectives" : "?border=1;444;4444" + }, + "teamBarNameStyle" : {}, + "cursorTooltip" : { "textStyle" : {} }, - "debugFont" : "iosevka-semibold", - "debugFontSize" : 7, - "debugFontDirectives" : "?border=1;111a;1114", + "debugTextStyle" : { + "font" : "iosevka-semibold", + "fontSize" : 7, + "backDirectives" : "?border=2;111a;1114" + }, "debugSpatialClearTime" : 0.0, "debugOffset" : [80, 130], // Change planet name to support the new internal string formatting. "planetNameFormatString" : "- {} -", - "planetNameDirectives" : "?border=4;000;000", - + "planetTextStyle" : { + "backDirectives" : "?border=6;000;000", + "fontSize" : 24 + }, + "buttonClickSound" : [ "/sfx/interface/button/click.wav" ], "buttonReleaseSound" : [ "/sfx/interface/button/release.wav" ], "buttonHoverSound" : [ "/sfx/interface/button/hover.wav" ], diff --git a/assets/opensb/interface/chat/chat.config.patch b/assets/opensb/interface/chat/chat.config.patch index 52dc90d..d71b88d 100644 --- a/assets/opensb/interface/chat/chat.config.patch +++ b/assets/opensb/interface/chat/chat.config.patch @@ -1,10 +1,8 @@ { "config" : { "lineHeight" : 1, - "font" : { - "directives" : "?border=1;111a;1114", - "padding" : [1, 1] // Padding to prevent border clipping at the edges of the log canvas while keeping compat with mods that patch the canvas size - }, + "padding" : [1, 1], + "textStyle" : { "backDirectives" : "?border=1;111a;1114" }, "colors" : { "local" : "^white;", "party" : "^blue;", diff --git a/assets/opensb/interface/windowconfig/chatbubbles.config.patch b/assets/opensb/interface/windowconfig/chatbubbles.config.patch index c3da49d..3c0fda8 100644 --- a/assets/opensb/interface/windowconfig/chatbubbles.config.patch +++ b/assets/opensb/interface/windowconfig/chatbubbles.config.patch @@ -1,4 +1,7 @@ { - "movementThreshold" : 0.5, - "bubbleOffset" : [0, 1.875] + "movementThreshold" : 0.5, + "bubbleOffset" : [0, 1.875], + "textStyle" : { + "backDirectives" : "?border=1;000a;0000?border=1;000a;0004" + } } \ No newline at end of file diff --git a/source/base/StarAssets.cpp b/source/base/StarAssets.cpp index 357fca0..f2582b1 100644 --- a/source/base/StarAssets.cpp +++ b/source/base/StarAssets.cpp @@ -427,8 +427,6 @@ void Assets::queueJsons(StringSet const& paths) const { } ImageConstPtr Assets::image(AssetPath const& path) const { - validatePath(path, true, true); - return as(getAsset(AssetId{AssetType::Image, path}))->image; } @@ -956,7 +954,7 @@ Json Assets::checkPatchArray(String const& path, AssetSourcePtr const& source, J Json Assets::readJson(String const& path) const { ByteArray streamData = read(path); try { - Json result = inputUtf8Json(streamData.begin(), streamData.end(), false); + Json result = inputUtf8Json(streamData.begin(), streamData.end(), JsonParseType::Top); for (auto const& pair : m_files.get(path).patchSources) { auto& patchPath = pair.first; auto& patchSource = pair.second; @@ -973,7 +971,7 @@ Json Assets::readJson(String const& path) const { if (newResult) result = std::move(newResult); } else { - auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), false); + auto patchJson = inputUtf8Json(patchStream.begin(), patchStream.end(), JsonParseType::Top); if (patchJson.isType(Json::Type::Array)) { auto patchData = patchJson.toArray(); try { @@ -1131,6 +1129,7 @@ shared_ptr Assets::loadJson(AssetPath const& path) const { } shared_ptr Assets::loadImage(AssetPath const& path) const { + validatePath(path, true, true); if (!path.directives.empty()) { shared_ptr source = as(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}})); diff --git a/source/client/StarClientApplication.cpp b/source/client/StarClientApplication.cpp index 82d18cf..1a74132 100644 --- a/source/client/StarClientApplication.cpp +++ b/source/client/StarClientApplication.cpp @@ -667,6 +667,7 @@ void ClientApplication::changeState(MainAppState newState) { m_mainInterface = make_shared(m_universeClient, m_worldPainter, m_cinematicOverlay); m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); + m_universeClient->setLuaCallbacks("chat", LuaBindings::makeChatCallbacks(m_mainInterface.get(), m_universeClient.get())); m_universeClient->startLua(); m_mainMixer->setWorldPainter(m_worldPainter); diff --git a/source/core/StarAssetPath.cpp b/source/core/StarAssetPath.cpp index e54a139..23743d9 100644 --- a/source/core/StarAssetPath.cpp +++ b/source/core/StarAssetPath.cpp @@ -162,7 +162,7 @@ std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) { rhs.directives.forEach([&](auto const& entry, Directives const& directives) { os << "?"; - os << entry.string(*directives.shared); + os << entry.string(*directives); }); return os; diff --git a/source/core/StarDirectives.cpp b/source/core/StarDirectives.cpp index 4770200..89985a0 100644 --- a/source/core/StarDirectives.cpp +++ b/source/core/StarDirectives.cpp @@ -42,6 +42,8 @@ bool Directives::Shared::empty() const { return entries.empty(); } +Directives::Shared::Shared() {} + Directives::Shared::Shared(List&& givenEntries, String&& givenString, StringView givenPrefix) { entries = std::move(givenEntries); string = std::move(givenString); @@ -74,7 +76,7 @@ Directives::Directives(Directives const& directives) { Directives::~Directives() {} Directives& Directives::operator=(String const& s) { - if (shared && shared->string == s) + if (m_shared && m_shared->string == s) return *this; parse(String(s)); @@ -82,7 +84,7 @@ Directives& Directives::operator=(String const& s) { } Directives& Directives::operator=(String&& s) { - if (shared && shared->string == s) { + if (m_shared && m_shared->string == s) { s.clear(); return *this; } @@ -92,7 +94,7 @@ Directives& Directives::operator=(String&& s) { } Directives& Directives::operator=(const char* s) { - if (shared && shared->string.utf8().compare(s) == 0) + if (m_shared && m_shared->string.utf8().compare(s) == 0) return *this; parse(s); @@ -100,27 +102,29 @@ Directives& Directives::operator=(const char* s) { } Directives& Directives::operator=(Directives&& other) noexcept { - shared = std::move(other.shared); + m_shared = std::move(other.m_shared); return *this; } Directives& Directives::operator=(Directives const& other) { - shared = other.shared; + m_shared = other.m_shared; return *this; } void Directives::loadOperations() const { - if (!shared) + if (!m_shared) return; - MutexLocker lock(shared->mutex, true); - for (auto& entry : shared->entries) - entry.loadOperation(*shared); + MutexLocker locker(m_shared->mutex, false); + if (!m_shared.unique()) + locker.lock(); + for (auto& entry : m_shared->entries) + entry.loadOperation(*m_shared); } void Directives::parse(String&& directives) { if (directives.empty()) { - shared.reset(); + m_shared.reset(); return; } @@ -147,46 +151,46 @@ void Directives::parse(String&& directives) { }); if (entries.empty() && !prefix.empty()) { - shared.reset(); + m_shared.reset(); return; } - shared = std::make_shared(std::move(entries), std::move(directives), prefix); + m_shared = std::make_shared(std::move(entries), std::move(directives), prefix); if (view.utf8().size() < 1000) { // Pre-load short enough directives - for (auto& entry : shared->entries) - entry.loadOperation(*shared); + for (auto& entry : m_shared->entries) + entry.loadOperation(*m_shared); } } String Directives::string() const { - if (!shared) + if (!m_shared) return ""; else - return shared->string; + return m_shared->string; } StringView Directives::prefix() const { - if (!shared) + if (!m_shared) return ""; else - return shared->prefix; + return m_shared->prefix; } String const* Directives::stringPtr() const { - if (!shared) + if (!m_shared) return nullptr; else - return &shared->string; + return &m_shared->string; } String Directives::buildString() const { - if (shared) { - String built = shared->prefix; + if (m_shared) { + String built = m_shared->prefix; - for (auto& entry : shared->entries) { + for (auto& entry : m_shared->entries) { built += "?"; - built += entry.string(*shared); + built += entry.string(*m_shared); } return built; @@ -197,23 +201,25 @@ String Directives::buildString() const { String& Directives::addToString(String& out) const { if (!empty()) - out += shared->string; + out += m_shared->string; return out; } size_t Directives::hash() const { - return shared ? shared->hash : 0; + return m_shared ? m_shared->hash : 0; } size_t Directives::size() const { - return shared ? shared->entries.size() : 0; + return m_shared ? m_shared->entries.size() : 0; } bool Directives::empty() const { - return !shared || shared->empty(); + return !m_shared || m_shared->empty(); } -Directives::operator bool() const { return !empty(); } +Directives::operator bool() const { + return !empty(); +} bool Directives::equals(Directives const& other) const { return hash() == other.hash(); @@ -243,7 +249,7 @@ DataStream& operator>>(DataStream& ds, Directives& directives) { DataStream& operator<<(DataStream & ds, Directives const& directives) { if (directives) - ds.write(directives.shared->string); + ds.write(directives->string); else ds.write(String()); @@ -326,21 +332,22 @@ String DirectivesGroup::toString() const { } void DirectivesGroup::addToString(String& string) const { - for (auto& directives : m_directives) - if (directives.shared) { - auto& dirString = directives.shared->string; + for (auto& directives : m_directives) { + if (directives) { + auto& dirString = directives->string; if (!dirString.empty()) { if (dirString.utf8().front() != '?') string += "?"; string += dirString; } } + } } void DirectivesGroup::forEach(DirectivesCallback callback) const { for (auto& directives : m_directives) { - if (directives.shared) { - for (auto& entry : directives.shared->entries) + if (directives) { + for (auto& entry : directives->entries) callback(entry, directives); } } @@ -348,8 +355,8 @@ void DirectivesGroup::forEach(DirectivesCallback callback) const { bool DirectivesGroup::forEachAbortable(AbortableDirectivesCallback callback) const { for (auto& directives : m_directives) { - if (directives.shared) { - for (auto& entry : directives.shared->entries) { + if (directives) { + for (auto& entry : directives->entries) { if (!callback(entry, directives)) return false; } @@ -367,7 +374,7 @@ Image DirectivesGroup::applyNewImage(Image const& image) const { void DirectivesGroup::applyExistingImage(Image& image) const { forEach([&](auto const& entry, Directives const& directives) { - ImageOperation const& operation = entry.loadOperation(*directives.shared); + ImageOperation const& operation = entry.loadOperation(*directives); if (auto error = operation.ptr()) std::rethrow_exception(error->exception); else diff --git a/source/core/StarDirectives.hpp b/source/core/StarDirectives.hpp index e272915..fa10a33 100644 --- a/source/core/StarDirectives.hpp +++ b/source/core/StarDirectives.hpp @@ -36,6 +36,7 @@ public: mutable Mutex mutex; bool empty() const; + Shared(); Shared(List&& givenEntries, String&& givenString, StringView givenPrefix); }; @@ -65,6 +66,9 @@ public: bool empty() const; operator bool() const; + Shared const& operator*() const; + Shared const* operator->() const; + bool equals(Directives const& other) const; bool equals(String const& string) const; @@ -78,7 +82,7 @@ public: //friend bool operator==(Directives const& directives, String const& string); //friend bool operator==(String const& string, Directives const& directives); - std::shared_ptr shared; + std::shared_ptr m_shared; }; class DirectivesGroup { @@ -122,7 +126,6 @@ private: size_t m_count; }; - template <> struct hash { size_t operator()(DirectivesGroup const& s) const; @@ -130,4 +133,13 @@ struct hash { 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(); +} + } diff --git a/source/core/StarFormattedJson.cpp b/source/core/StarFormattedJson.cpp index d7bd8c7..a770ac6 100644 --- a/source/core/StarFormattedJson.cpp +++ b/source/core/StarFormattedJson.cpp @@ -65,12 +65,12 @@ bool CommaElement::operator==(CommaElement const&) const { FormattedJson FormattedJson::parse(String const& string) { return inputUtf32Json( - string.begin(), string.end(), true); + string.begin(), string.end(), JsonParseType::Value); } FormattedJson FormattedJson::parseJson(String const& string) { return inputUtf32Json( - string.begin(), string.end(), false); + string.begin(), string.end(), JsonParseType::Top); } FormattedJson FormattedJson::ofType(Json::Type type) { diff --git a/source/core/StarJson.cpp b/source/core/StarJson.cpp index 04ac544..7cf1cda 100644 --- a/source/core/StarJson.cpp +++ b/source/core/StarJson.cpp @@ -105,11 +105,15 @@ Json Json::ofType(Type t) { } Json Json::parse(String const& string) { - return inputUtf32Json(string.begin(), string.end(), true); + return inputUtf32Json(string.begin(), string.end(), JsonParseType::Value); +} + +Json Json::parseSequence(String const& sequence) { + return inputUtf32Json(sequence.begin(), sequence.end(), JsonParseType::Sequence); } Json Json::parseJson(String const& json) { - return inputUtf32Json(json.begin(), json.end(), false); + return inputUtf32Json(json.begin(), json.end(), JsonParseType::Top); } Json::Json() {} diff --git a/source/core/StarJson.hpp b/source/core/StarJson.hpp index a533e47..b58b9a0 100644 --- a/source/core/StarJson.hpp +++ b/source/core/StarJson.hpp @@ -51,6 +51,9 @@ public: // Parses JSON or JSON sub-type static Json parse(String const& string); + // Parses JSON sequence + static Json parseSequence(String const& sequence); + // Parses JSON object or array only (the only top level types allowed by // JSON) static Json parseJson(String const& json); diff --git a/source/core/StarJsonBuilder.hpp b/source/core/StarJsonBuilder.hpp index ea0df46..46f192c 100644 --- a/source/core/StarJsonBuilder.hpp +++ b/source/core/StarJsonBuilder.hpp @@ -50,7 +50,7 @@ public: }; template -Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) { +Json inputUtf8Json(InputIterator begin, InputIterator end, JsonParseType parseType) { typedef U8ToU32Iterator Utf32Input; typedef JsonParser Parser; @@ -58,7 +58,7 @@ Json inputUtf8Json(InputIterator begin, InputIterator end, bool fragment) { Parser parser(stream); Utf32Input wbegin(begin); Utf32Input wend(end); - Utf32Input pend = parser.parse(wbegin, wend, fragment); + Utf32Input pend = parser.parse(wbegin, wend, parseType); if (parser.error()) throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column())); @@ -77,11 +77,11 @@ void outputUtf8Json(Json const& val, OutputIterator out, int pretty, bool sort) } template -Jsonlike inputUtf32Json(InputIterator begin, InputIterator end, bool fragment) { +Jsonlike inputUtf32Json(InputIterator begin, InputIterator end, JsonParseType parseType) { Stream stream; JsonParser parser(stream); - InputIterator pend = parser.parse(begin, end, fragment); + InputIterator pend = parser.parse(begin, end, parseType); if (parser.error()) { throw JsonParsingException(strf("Error parsing json: {} at {}:{}", parser.error(), parser.line(), parser.column())); diff --git a/source/core/StarJsonParser.hpp b/source/core/StarJsonParser.hpp index d814edb..0bf7443 100644 --- a/source/core/StarJsonParser.hpp +++ b/source/core/StarJsonParser.hpp @@ -27,6 +27,12 @@ struct JsonStream { virtual void putComma() = 0; }; +enum class JsonParseType : uint8_t { + Top, // Top-level Object or Array + Value, // Any singular Json value + Sequence // Like an array, but without needing the [] or commas. +}; + // Will parse JSON and output to a given JsonStream. Parses an *extension* to // the JSON format that includes comments. template @@ -39,15 +45,17 @@ public: // Does not throw. On error, returned iterator will not be equal to end, and // error() will be non-null. Set fragment to true to parse any JSON type // rather than just object or array. - InputIterator parse(InputIterator begin, InputIterator end, bool fragment = false) { + InputIterator parse(InputIterator begin, InputIterator end, JsonParseType parseType = JsonParseType::Top) { init(begin, end); try { white(); - if (fragment) - value(); - else + if (parseType == JsonParseType::Top) top(); + else if (parseType == JsonParseType::Value) + value(); + else if (parseType == JsonParseType::Sequence) + sequence(); white(); } catch (ParsingException const&) { } @@ -190,6 +198,56 @@ private: } } + void sequence() { + m_stream.beginArray(); + while (true) { + if (isSpace(m_char)) { + next(); + continue; + } else if (m_char == '{') + object(); + else if (m_char == '[') + array(); + else if (m_char == '"') + string(); + else if (m_char == '-') + number(); + else if (m_char == 0) + break; + else { + auto begin = m_current; + if (m_char >= '0' && m_char <= '9') { + number(); + if (m_error.empty()) { + next(); + continue; + } + } + m_error.clear(); + m_current = begin; + if (m_char == 't' || m_char == 'f' || m_char == 'n') { + word(); + if (m_error.empty()) { + next(); + continue; + } + } + m_error.clear(); + m_current = begin; + // well, shit. do a simple string parse until we hit whitespace + // no fancy things like \n, do a proper string if you want that + CharArray str; + do { + str += m_char; + next(); + } while (m_char && !isSpace(m_char)); + m_stream.putString(str.c_str(), str.length()); + } + next(); + } + m_stream.endArray(); + } + void string() { CharArray s = parseString(); m_stream.putString(s.c_str(), s.length()); diff --git a/source/core/StarLua.cpp b/source/core/StarLua.cpp index 73955f2..5a2effa 100644 --- a/source/core/StarLua.cpp +++ b/source/core/StarLua.cpp @@ -60,6 +60,10 @@ LuaInt LuaTable::rawLength() const { return engine().tableLength(true, handleIndex()); } +void LuaCallbacks::copyCallback(String srcName, String dstName) { + m_callbacks.set(dstName, m_callbacks.get(srcName)); +} + LuaCallbacks& LuaCallbacks::merge(LuaCallbacks const& callbacks) { try { for (auto const& pair : callbacks.m_callbacks) diff --git a/source/core/StarLua.hpp b/source/core/StarLua.hpp index 09e81e6..57afb98 100644 --- a/source/core/StarLua.hpp +++ b/source/core/StarLua.hpp @@ -267,6 +267,8 @@ LuaValue const LuaNil = LuaValue(); class LuaCallbacks { public: + void copyCallback(String srcName, String dstName); + template void registerCallback(String name, Function&& func); diff --git a/source/core/StarText.cpp b/source/core/StarText.cpp index 11fca27..86c9f25 100644 --- a/source/core/StarText.cpp +++ b/source/core/StarText.cpp @@ -1,9 +1,35 @@ #include "StarText.hpp" - +#include "StarJsonExtra.hpp" #include namespace Star { +TextStyle::TextStyle(Json const& config) : TextStyle() { + if (config.isType(Json::Type::String)) + font = config.toString(); + else + loadJson(config); +} +TextStyle& TextStyle::loadJson(Json const& config) { + if (!config) + return *this; + + lineSpacing = config.getFloat("lineSpacing", lineSpacing); + if (auto jColor = config.opt("color")) + color = jsonToColor(*jColor).toRgba(); + if (auto jShadow = config.opt("shadow")) + shadow = jsonToColor(*jShadow).toRgba(); + fontSize = config.getUInt("fontSize", fontSize); + if (auto jFont = config.optString("font")) + font = *jFont; + if (auto jDirectives = config.optString("directives")) + directives = *jDirectives; + if (auto jBackDirectives = config.optString("backDirectives")) + backDirectives = *jBackDirectives; + + return *this; +} + namespace Text { static auto stripEscapeRegex = std::regex(strf("\\{:c}[^;]*{:c}", CmdEsc, EndEsc)); String stripEscapeCodes(String const& s) { diff --git a/source/core/StarText.hpp b/source/core/StarText.hpp index 4f6a97a..ff6880c 100644 --- a/source/core/StarText.hpp +++ b/source/core/StarText.hpp @@ -2,9 +2,29 @@ #include "StarString.hpp" #include "StarStringView.hpp" +#include "StarVector.hpp" +#include "StarDirectives.hpp" +#include "StarJson.hpp" namespace Star { - + +unsigned const DefaultFontSize = 8; +float const DefaultLineSpacing = 1.3f; + +struct TextStyle { + float lineSpacing = DefaultLineSpacing; + Vec4B color = Vec4B::filled(255); + Vec4B shadow = Vec4B::filled(0); + unsigned fontSize = DefaultFontSize; + String font = ""; + Directives directives; + Directives backDirectives; + + TextStyle() = default; + TextStyle(Json const& config); + TextStyle& loadJson(Json const& config); +}; + namespace Text { unsigned char const StartEsc = '\x1b'; unsigned char const EndEsc = ';'; diff --git a/source/core/scripting/StarUtilityLuaBindings.cpp b/source/core/scripting/StarUtilityLuaBindings.cpp index 5ab6e19..98ad239 100644 --- a/source/core/scripting/StarUtilityLuaBindings.cpp +++ b/source/core/scripting/StarUtilityLuaBindings.cpp @@ -118,12 +118,14 @@ LuaCallbacks LuaBindings::makeUtilityCallbacks() { callbacks.registerCallback("print", UtilityCallbacks::print); callbacks.registerCallback("interpolateSinEase", UtilityCallbacks::interpolateSinEase); callbacks.registerCallback("replaceTags", UtilityCallbacks::replaceTags); - callbacks.registerCallback("jsonParse", [](String const& json) { return Json::parse(json); }); + callbacks.registerCallback("parseJsonSequence", [](String const& json) { return Json::parseSequence(json); }); callbacks.registerCallback("jsonMerge", [](Json const& a, Json const& b) { return jsonMerge(a, b); }); callbacks.registerCallback("jsonQuery", [](Json const& json, String const& path, Json const& def) { return json.query(path, def); }); callbacks.registerCallback("makeRandomSource", [](Maybe seed) { return seed ? RandomSource(*seed) : RandomSource(); }); callbacks.registerCallback("makePerlinSource", [](Json const& config) { return PerlinF(config); }); + callbacks.copyCallback("parseJson", "jsonFromString"); // SE compat + auto hash64LuaValues = [](LuaVariadic const& values) -> uint64_t { XXHash64 hash; diff --git a/source/frontend/StarActionBar.cpp b/source/frontend/StarActionBar.cpp index fb08e3b..9f78626 100644 --- a/source/frontend/StarActionBar.cpp +++ b/source/frontend/StarActionBar.cpp @@ -83,9 +83,7 @@ ActionBar::ActionBar(MainInterfacePaneManager* paneManager, PlayerPtr player) { TextPositioning countPosition = {jsonToVec2F(m_config.get("countMidAnchor")), HorizontalAnchor::HMidAnchor}; customBarLeft->setCountPosition(countPosition); - customBarLeft->setCountFontMode(FontMode::Shadow); customBarRight->setCountPosition(countPosition); - customBarRight->setCountFontMode(FontMode::Shadow); m_customBarWidgets.append({customBarLeft, customBarRight, customBarLeftOverlay, customBarRightOverlay}); } diff --git a/source/frontend/StarChat.cpp b/source/frontend/StarChat.cpp index 688bf4a..d780d23 100644 --- a/source/frontend/StarChat.cpp +++ b/source/frontend/StarChat.cpp @@ -20,32 +20,30 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) { m_historyOffset = 0; auto assets = Root::singleton().assets(); + auto config = assets->json("/interface/chat/chat.config:config"); m_timeChatLastActive = Time::monotonicMilliseconds(); - auto fontConfig = assets->json("/interface/chat/chat.config:config.font"); - m_fontSize = fontConfig.getInt("baseSize"); - m_fontDirectives = fontConfig.queryString("directives", ""); - m_font = fontConfig.queryString("type", ""); - m_chatLineHeight = assets->json("/interface/chat/chat.config:config.lineHeight").toFloat(); - m_chatVisTime = assets->json("/interface/chat/chat.config:config.visTime").toFloat(); - m_fadeRate = assets->json("/interface/chat/chat.config:config.fadeRate").toDouble(); - m_chatHistoryLimit = assets->json("/interface/chat/chat.config:config.chatHistoryLimit").toInt(); + m_chatTextStyle = config.get("textStyle"); + m_chatTextStyle.lineSpacing = config.get("lineHeight").toFloat(); + m_chatVisTime = config.get("visTime").toFloat(); + m_fadeRate = config.get("fadeRate").toDouble(); + m_chatHistoryLimit = config.get("chatHistoryLimit").toInt(); - m_portraitTextOffset = jsonToVec2I(assets->json("/interface/chat/chat.config:config.portraitTextOffset")); - m_portraitImageOffset = jsonToVec2I(assets->json("/interface/chat/chat.config:config.portraitImageOffset")); - m_portraitScale = assets->json("/interface/chat/chat.config:config.portraitScale").toFloat(); - m_portraitVerticalMargin = assets->json("/interface/chat/chat.config:config.portraitVerticalMargin").toFloat(); - m_portraitBackground = assets->json("/interface/chat/chat.config:config.portraitBackground").toString(); + m_portraitTextOffset = jsonToVec2I(config.get("portraitTextOffset")); + m_portraitImageOffset = jsonToVec2I(config.get("portraitImageOffset")); + m_portraitScale = config.get("portraitScale").toFloat(); + m_portraitVerticalMargin = config.get("portraitVerticalMargin").toFloat(); + m_portraitBackground = config.get("portraitBackground").toString(); - m_bodyHeight = assets->json("/interface/chat/chat.config:config.bodyHeight").toInt(); - m_expandedBodyHeight = assets->json("/interface/chat/chat.config:config.expandedBodyHeight").toInt(); + m_bodyHeight = config.get("bodyHeight").toInt(); + m_expandedBodyHeight = config.get("expandedBodyHeight").toInt(); - m_colorCodes[MessageContext::Local] = assets->json("/interface/chat/chat.config:config.colors.local").toString(); - m_colorCodes[MessageContext::Party] = assets->json("/interface/chat/chat.config:config.colors.party").toString(); - m_colorCodes[MessageContext::Broadcast] = assets->json("/interface/chat/chat.config:config.colors.broadcast").toString(); - m_colorCodes[MessageContext::Whisper] = assets->json("/interface/chat/chat.config:config.colors.whisper").toString(); - m_colorCodes[MessageContext::CommandResult] = assets->json("/interface/chat/chat.config:config.colors.commandResult").toString(); - m_colorCodes[MessageContext::RadioMessage] = assets->json("/interface/chat/chat.config:config.colors.radioMessage").toString(); - m_colorCodes[MessageContext::World] = assets->json("/interface/chat/chat.config:config.colors.world").toString(); + m_colorCodes[MessageContext::Local] = config.query("colors.local").toString(); + m_colorCodes[MessageContext::Party] = config.query("colors.party").toString(); + m_colorCodes[MessageContext::Broadcast] = config.query("colors.broadcast").toString(); + m_colorCodes[MessageContext::Whisper] = config.query("colors.whisper").toString(); + m_colorCodes[MessageContext::CommandResult] = config.query("colors.commandResult").toString(); + m_colorCodes[MessageContext::RadioMessage] = config.query("colors.radioMessage").toString(); + m_colorCodes[MessageContext::World] = config.query("colors.world").toString(); GuiReader reader; @@ -72,7 +70,7 @@ Chat::Chat(UniverseClientPtr client) : m_client(client) { m_say = fetchChild("say"); m_chatLog = fetchChild("chatLog"); - if (auto logPadding = fontConfig.optQuery("padding")) { + if (auto logPadding = config.optQuery("padding")) { m_chatLogPadding = jsonToVec2I(logPadding.get()); m_chatLog->setSize(m_chatLog->size() + m_chatLogPadding * 2); m_chatLog->setPosition(m_chatLog->position() - m_chatLogPadding); @@ -133,8 +131,8 @@ String Chat::currentChat() const { return m_textBox->getText(); } -void Chat::setCurrentChat(String const& chat) { - m_textBox->setText(chat); +bool Chat::setCurrentChat(String const& chat, bool moveCursor) { + return m_textBox->setText(chat, true, moveCursor); } void Chat::clearCurrentChat() { @@ -179,8 +177,7 @@ void Chat::addMessages(List const& messages, bool showPane) if (message.portrait.empty()) wrapWidth = m_chatLog->size()[0] - m_chatLogPadding[0] * 2; - guiContext.setFont(m_font); - guiContext.setFontSize(m_fontSize); + guiContext.setTextStyle(m_chatTextStyle); StringList lines; if (message.fromNick != "" && message.portrait == "") lines = guiContext.wrapInterfaceText(strf("<{}> {}", message.fromNick, message.text), wrapWidth); @@ -239,10 +236,10 @@ void Chat::renderImpl() { int messageIndex = -m_historyOffset; GuiContext& guiContext = GuiContext::singleton(); - guiContext.setFont(m_font); - guiContext.setFontSize(m_fontSize); - guiContext.setLineSpacing(m_chatLineHeight); - for (auto message : m_receivedMessages) { + float lineHeight = m_chatTextStyle.lineSpacing; + float fontSize = m_chatTextStyle.fontSize; + guiContext.setTextStyle(m_chatTextStyle); + for (auto& message : m_receivedMessages) { if (!m_modeFilter.empty() && !m_modeFilter.contains(message.mode)) continue; @@ -260,7 +257,7 @@ void Chat::renderImpl() { String messageString = channelColorCode + message.text; float messageHeight = 0; - float lineHeightMargin = + ((m_chatLineHeight * m_fontSize) - m_fontSize); + float lineHeightMargin = ((lineHeight * fontSize) - fontSize); unsigned wrapWidth = m_chatLog->size()[0] - m_chatLogPadding[0] * 2; if (message.portrait != "") { @@ -274,13 +271,14 @@ void Chat::renderImpl() { m_chatLog->drawImage(m_portraitBackground, Vec2F(imagePosition), 1.0f, fade); m_chatLog->drawImage(message.portrait, Vec2F(imagePosition + m_portraitImageOffset), m_portraitScale, fade); tp.pos += Vec2F(0, floor(messageHeight / 2)); - m_chatLog->drawText(messageString, tp, m_fontSize, fade, FontMode::Normal, m_chatLineHeight, m_font, m_fontDirectives); + m_chatTextStyle.color = fade; + m_chatLog->drawText(messageString, tp, m_chatTextStyle); } else { TextPositioning tp = {Vec2F(chatMin), HorizontalAnchor::LeftAnchor, VerticalAnchor::BottomAnchor, wrapWidth}; messageHeight = guiContext.determineInterfaceTextSize(messageString, tp).size()[1] + lineHeightMargin; - - m_chatLog->drawText(messageString, tp, m_fontSize, fade, FontMode::Normal, m_chatLineHeight, m_font, m_fontDirectives); + m_chatTextStyle.color = fade; + m_chatLog->drawText(messageString, tp, m_chatTextStyle); } chatMin[1] += ceil(messageHeight); diff --git a/source/frontend/StarChat.hpp b/source/frontend/StarChat.hpp index 28117ff..80cf683 100644 --- a/source/frontend/StarChat.hpp +++ b/source/frontend/StarChat.hpp @@ -32,7 +32,7 @@ public: void addHistory(String const& chat); String currentChat() const; - void setCurrentChat(String const& chat); + bool setCurrentChat(String const& chat, bool moveCursor = false); void clearCurrentChat(); ChatSendMode sendMode() const; @@ -66,10 +66,7 @@ private: int64_t m_timeChatLastActive; float m_chatVisTime; float m_fadeRate; - unsigned m_fontSize; - String m_fontDirectives; - String m_font; - float m_chatLineHeight; + TextStyle m_chatTextStyle; unsigned m_chatHistoryLimit; int m_historyOffset; diff --git a/source/frontend/StarChatBubbleManager.cpp b/source/frontend/StarChatBubbleManager.cpp index b08e292..d837de7 100644 --- a/source/frontend/StarChatBubbleManager.cpp +++ b/source/frontend/StarChatBubbleManager.cpp @@ -20,9 +20,8 @@ ChatBubbleManager::ChatBubbleManager() auto jsonData = assets->json("/interface/windowconfig/chatbubbles.config"); - m_color = jsonToColor(jsonData.get("textColor")); - - m_fontSize = jsonData.getInt("fontSize"); + m_textStyle.color = jsonToColor(jsonData.get("textColor")).toRgba(); + m_textStyle.loadJson(jsonData.get("textStyle")); m_textPadding = jsonToVec2F(jsonData.get("textPadding")); m_zoom = jsonData.getInt("textZoom"); @@ -196,9 +195,7 @@ void ChatBubbleManager::addChatActions(List chatActions, bool silent // bother me so bad if it weren't so fucking easy to do right. // yea I agree - m_guiContext->setFontSize(m_fontSize, m_zoom); - m_guiContext->setFontProcessingDirectives(""); - m_guiContext->setDefaultFont(); + m_guiContext->setTextStyle(m_textStyle); auto result = m_guiContext->determineTextSize(sayAction.text, m_textTemplate); float textWidth = result.width() / m_zoom + m_textPadding[0]; float textHeight = result.height() / m_zoom + m_textPadding[1]; @@ -252,14 +249,17 @@ void ChatBubbleManager::addChatActions(List chatActions, bool silent float verticalShift = (partSize * innerTiles[1] - textMultiLineShift) * 0.5f + textMultiLineShift; Vec2F position = Vec2F(horizontalCenter, verticalShift); List bubbleTexts; - auto fontSize = config.getUInt("fontSize", m_fontSize); - auto color = config.opt("color").apply(jsonToColor).value(m_color); - bubbleTexts.append(make_tuple(sayAction.text, fontSize, color.toRgba(), true, position)); + TextStyle textStyle = m_textStyle; + textStyle.fontSize = config.getUInt("fontSize", textStyle.fontSize); + if (auto jColor = config.opt("color")) + textStyle.color = jsonToColor(*jColor).toRgba(); + textStyle.loadJson(config.get("style", Json())); + bubbleTexts.append(make_tuple(sayAction.text, textStyle, true, position)); for (auto& backgroundImage : backgroundImages) get<1>(backgroundImage) += Vec2F(-horizontalCenter, partSize); for (auto& bubbleText : bubbleTexts) - get<4>(bubbleText) += Vec2F(-horizontalCenter, partSize); + get<3>(bubbleText) += Vec2F(-horizontalCenter, partSize); auto pos = m_camera.worldToScreen(sayAction.position + m_bubbleOffset); RectF boundBox = fold(backgroundImages, RectF::null(), [pos, this](RectF const& boundBox, BubbleImage const& bubbleImage) { @@ -272,7 +272,7 @@ void ChatBubbleManager::addChatActions(List chatActions, bool silent m_bubbles.filter([&sayAction](BubbleState const&, Bubble const& bubble) { return bubble.entity != sayAction.entity; }); m_bubbles.addBubble(pos, boundBox, std::move(bubble), m_interBubbleMargin * m_zoom); oldBubbles.sort([](BubbleState const& a, BubbleState const& b) { return a.contents.age < b.contents.age; }); - for (auto bubble : oldBubbles.slice(0, m_maxMessagePerEntity - 1)) + for (auto& bubble : oldBubbles.slice(0, m_maxMessagePerEntity - 1)) m_bubbles.addBubble(bubble.idealDestination, bubble.boundBox, bubble.contents, 0); } else if (action.is()) { @@ -286,12 +286,12 @@ void ChatBubbleManager::addChatActions(List chatActions, bool silent backgroundImages.append(make_tuple(m_portraitMoreImage, Vec2F(m_portraitMorePosition))); backgroundImages.append(make_tuple(portraitAction.portrait, Vec2F(m_portraitPosition))); List bubbleTexts; - bubbleTexts.append(make_tuple(portraitAction.text, m_fontSize, m_color.toRgba(), false, Vec2F(m_portraitTextPosition))); + bubbleTexts.append(make_tuple(portraitAction.text, m_textStyle, false, Vec2F(m_portraitTextPosition))); for (auto& backgroundImage : backgroundImages) get<1>(backgroundImage) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0); for (auto& bubbleText : bubbleTexts) - get<4>(bubbleText) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0); + get<3>(bubbleText) += Vec2F(-m_portraitBackgroundSize[0] / 2, 0); m_portraitBubbles.prepend({ portraitAction.entity, @@ -321,28 +321,19 @@ void ChatBubbleManager::addChatActions(List chatActions, bool silent RectF ChatBubbleManager::bubbleImageRect(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio) { auto imgMetadata = Root::singleton().imageMetadataDatabase(); - auto image = get<0>(bubbleImage); + auto& image = get<0>(bubbleImage); return RectF::withSize(screenPos + get<1>(bubbleImage) * pixelRatio, Vec2F(imgMetadata->imageSize(image)) * pixelRatio); } void ChatBubbleManager::drawBubbleImage(Vec2F screenPos, BubbleImage const& bubbleImage, int pixelRatio, int alpha) { - auto image = get<0>(bubbleImage); + auto& image = get<0>(bubbleImage); auto offset = get<1>(bubbleImage) * pixelRatio; m_guiContext->drawQuad(image, screenPos + offset, pixelRatio, {255, 255, 255, alpha}); } void ChatBubbleManager::drawBubbleText(Vec2F screenPos, BubbleText const& bubbleText, int pixelRatio, int alpha, bool isPortrait) { - Vec4B const& baseColor = get<2>(bubbleText); - - // use the alpha as a blend value for the text colour as pulled from data. - Vec4B const& displayColor = Vec4B(baseColor[0], baseColor[1], baseColor[2], (baseColor[3] * alpha) / 255); - - m_guiContext->setDefaultFont(); - m_guiContext->setFontProcessingDirectives(""); - m_guiContext->setFontColor(displayColor); - m_guiContext->setFontSize(get<1>(bubbleText), m_zoom); - - auto offset = get<4>(bubbleText) * pixelRatio; + m_guiContext->setTextStyle(get<1>(bubbleText), m_zoom); + auto offset = get<3>(bubbleText) * pixelRatio; TextPositioning tp = isPortrait ? m_portraitTextTemplate : m_textTemplate; tp.pos = screenPos + offset; diff --git a/source/frontend/StarChatBubbleManager.hpp b/source/frontend/StarChatBubbleManager.hpp index cf46fcc..42b58db 100644 --- a/source/frontend/StarChatBubbleManager.hpp +++ b/source/frontend/StarChatBubbleManager.hpp @@ -26,7 +26,7 @@ public: private: typedef tuple BubbleImage; - typedef tuple BubbleText; + typedef tuple BubbleText; struct Bubble { EntityId entity; @@ -64,8 +64,7 @@ private: TextPositioning m_textTemplate; TextPositioning m_portraitTextTemplate; - Color m_color; - int m_fontSize; + TextStyle m_textStyle; Vec2F m_textPadding; BubbleSeparator m_bubbles; diff --git a/source/frontend/StarCinematic.cpp b/source/frontend/StarCinematic.cpp index abd638d..c2dbd20 100644 --- a/source/frontend/StarCinematic.cpp +++ b/source/frontend/StarCinematic.cpp @@ -45,8 +45,9 @@ void Cinematic::load(Json const& definition) { panel->animationFrames = panelDefinition.getInt("animationFrames", std::numeric_limits::max()); panel->text = panelDefinition.getString("text", ""); panel->textPosition = TextPositioning(panelDefinition.get("textPosition", JsonObject())); - panel->fontColor = panelDefinition.opt("fontColor").apply(jsonToVec4B).value(Vec4B(255, 255, 255, 255)); - panel->fontSize = panelDefinition.getUInt("fontSize", 8); + panel->textStyle = panelDefinition.get("textStyle", Json()); + panel->textStyle.color = panelDefinition.opt("fontColor").apply(jsonToVec4B).value(panel->textStyle.color); + panel->textStyle.fontSize = panelDefinition.getUInt("fontSize", panel->textStyle.fontSize); panel->avatar = panelDefinition.getString("avatar", ""); panel->startTime = panelDefinition.getFloat("startTime", 0); panel->endTime = panelDefinition.getFloat("endTime", 0); @@ -208,8 +209,9 @@ void Cinematic::render() { } } if (!panel->text.empty()) { - textPainter->setFontSize(floor(panel->fontSize * drawableScale)); - auto fontColor = panel->fontColor; + textPainter->setTextStyle(panel->textStyle); + textPainter->setFontSize(floor(panel->textStyle.fontSize * drawableScale)); + auto fontColor = panel->textStyle.color; fontColor[3] *= values.alpha; textPainter->setFontColor(fontColor); Vec2F position = (m_offset + values.position + Vec2F(panel->textPosition.pos)) * drawableScale + drawableTranslation; diff --git a/source/frontend/StarCinematic.hpp b/source/frontend/StarCinematic.hpp index 15ff2cd..b2e3c81 100644 --- a/source/frontend/StarCinematic.hpp +++ b/source/frontend/StarCinematic.hpp @@ -62,8 +62,7 @@ private: int animationFrames; String text; TextPositioning textPosition; - Vec4B fontColor; - unsigned fontSize; + TextStyle textStyle; List keyFrames; float startTime; float endTime; diff --git a/source/frontend/StarClientCommandProcessor.cpp b/source/frontend/StarClientCommandProcessor.cpp index 52882d9..def1b43 100644 --- a/source/frontend/StarClientCommandProcessor.cpp +++ b/source/frontend/StarClientCommandProcessor.cpp @@ -91,7 +91,7 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) { } } else { auto player = m_universeClient->mainPlayer(); - if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments })) + if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), "/" + command, { allArguments })) result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true)); else m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); diff --git a/source/frontend/StarInterfaceLuaBindings.cpp b/source/frontend/StarInterfaceLuaBindings.cpp index a023a71..9e0bb2e 100644 --- a/source/frontend/StarInterfaceLuaBindings.cpp +++ b/source/frontend/StarInterfaceLuaBindings.cpp @@ -4,6 +4,9 @@ #include "StarLuaGameConverters.hpp" #include "StarMainInterface.hpp" #include "StarGuiContext.hpp" +#include "StarChat.hpp" +#include "StarUniverseClient.hpp" +#include "StarClientCommandProcessor.hpp" namespace Star { @@ -42,4 +45,49 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) { return callbacks; } +LuaCallbacks LuaBindings::makeChatCallbacks(MainInterface* mainInterface, UniverseClient* client) { + LuaCallbacks callbacks; + + auto chat = as(mainInterface->paneManager()->registeredPane(MainInterfacePanes::Chat).get()); + + callbacks.registerCallback("send", [chat, client](String const& message, Maybe modeName, Maybe 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 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 moveCursor) -> bool { + return chat->setCurrentChat(text, moveCursor.value(false)); + }); + + return callbacks; +} + } diff --git a/source/frontend/StarInterfaceLuaBindings.hpp b/source/frontend/StarInterfaceLuaBindings.hpp index 539cceb..2973f18 100644 --- a/source/frontend/StarInterfaceLuaBindings.hpp +++ b/source/frontend/StarInterfaceLuaBindings.hpp @@ -5,9 +5,11 @@ namespace Star { STAR_CLASS(MainInterface); +STAR_CLASS(UniverseClient); namespace LuaBindings { LuaCallbacks makeInterfaceCallbacks(MainInterface* mainInterface); + LuaCallbacks makeChatCallbacks(MainInterface* mainInterface, UniverseClient* client); } } diff --git a/source/frontend/StarMainInterface.cpp b/source/frontend/StarMainInterface.cpp index 4303a28..ece00a5 100644 --- a/source/frontend/StarMainInterface.cpp +++ b/source/frontend/StarMainInterface.cpp @@ -160,10 +160,8 @@ MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, auto planetName = make_shared(); m_planetText = make_shared(); - m_planetText->setFontSize(m_config->planetNameFontSize); - m_planetText->setFontMode(FontMode::Normal); + m_planetText->setTextStyle(m_config->planetNameTextStyle); m_planetText->setAnchor(HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor); - m_planetText->setDirectives(m_config->planetNameDirectives); planetName->disableScissoring(); planetName->setPosition(m_config->planetNameOffset); planetName->setAnchor(PaneAnchor::Center); @@ -810,9 +808,7 @@ void MainInterface::renderInWorldElements() { if (m_disableHud) return; - m_guiContext->setDefaultFont(); - m_guiContext->setFontProcessingDirectives(""); - m_guiContext->setFontColor(Vec4B::filled(255)); + m_guiContext->clearTextStyle(); m_questIndicatorPainter->render(); m_nameplatePainter->render(); m_chatBubbleManager->render(); @@ -822,9 +818,7 @@ void MainInterface::render() { if (m_disableHud) return; - m_guiContext->setDefaultFont(); - m_guiContext->setFontProcessingDirectives(""); - m_guiContext->setFontColor(Vec4B::filled(255)); + m_guiContext->clearTextStyle(); renderBreath(); renderMessages(); renderMonsterHealthBar(); @@ -950,6 +944,10 @@ CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignore return canvas; } +ClientCommandProcessorPtr MainInterface::commandProcessor() const { + return m_clientCommandProcessor; +} + // For when the player swaps characters. We need to completely reload ScriptPanes, // because a lot of ScriptPanes do not expect the character to suddenly change and may break or spill data over. void MainInterface::takeScriptPanes(List& out) { @@ -1084,9 +1082,7 @@ void MainInterface::renderMessages() { m_guiContext->drawQuad(m_config->messageTextContainer, RectF::withCenter(backgroundTextCenterPos, Vec2F(imgMetadata->imageSize(m_config->messageTextContainer) * interfaceScale()))); - m_guiContext->setFont(m_config->font); - m_guiContext->setFontSize(m_config->fontSize); - m_guiContext->setFontColor(Color::White.toRgba()); + m_guiContext->setTextStyle(m_config->textStyle); m_guiContext->renderText(message->message, {messageTextOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor}); } } @@ -1112,9 +1108,7 @@ void MainInterface::renderMonsterHealthBar() { m_guiContext->drawQuad(container, RectF::withCenter(backgroundCenterPos + offset, Vec2F(imgMetadata->imageSize(container) * interfaceScale()))); auto nameTextOffset = jsonToVec2F(assets->json("/interface.config:monsterHealth.nameTextOffset")) * interfaceScale(); - m_guiContext->setFont(m_config->font); - m_guiContext->setFontSize(m_config->fontSize); - m_guiContext->setFontColor(Color::White.toRgba()); + m_guiContext->setTextStyle(m_config->textStyle); m_guiContext->renderText(showDamageEntity->name(), backgroundCenterPos + nameTextOffset); auto empty = assets->json("/interface.config:monsterHealth.progressEmpty").toString(); @@ -1176,11 +1170,11 @@ void MainInterface::renderSpecialDamageBar() { m_guiContext->drawQuad(fill, RectF::withSize(bottomCenter + fillOffset, size * interfaceScale())); auto nameOffset = jsonToVec2F(barConfig.get("nameOffset")) * interfaceScale(); - m_guiContext->setFontColor(jsonToColor(barConfig.get("nameColor")).toRgba()); + m_guiContext->setFontColor(jsonToColor(barConfig.get("nameStyle")).toRgba()); m_guiContext->setFontSize(barConfig.getUInt("nameSize")); m_guiContext->setFontProcessingDirectives(barConfig.getString("nameDirectives")); m_guiContext->renderText(target->name(), TextPositioning(bottomCenter + nameOffset, HorizontalAnchor::HMidAnchor, VerticalAnchor::BottomAnchor)); - m_guiContext->setFontProcessingDirectives(""); + m_guiContext->clearTextStyle(); } } @@ -1341,12 +1335,8 @@ void MainInterface::renderDebug() { if (m_clientCommandProcessor->debugHudEnabled()) { auto assets = Root::singleton().assets(); - m_guiContext->setFontSize(m_config->debugFontSize); - m_guiContext->setFont(m_config->debugFont); + m_guiContext->setTextStyle(m_config->debugTextStyle); m_guiContext->setLineSpacing(0.5f); - m_guiContext->setFontProcessingDirectives(m_config->debugFontDirectives); - m_guiContext->setFontColor(Color::White.toRgba()); - m_guiContext->setFontMode(FontMode::Normal); bool clearMap = m_debugMapClearTimer.wrapTick(); auto logMapValues = LogMap::getValues(); @@ -1358,7 +1348,7 @@ void MainInterface::renderDebug() { int counter = 0; for (auto const& pair : logMapValues) { - TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * counter++) }; + TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->textStyle.fontSize * interfaceScale() * counter++) }; String& text = formatted.emplace_back(strf("{}^lightgray;:^green,set; {}", pair.first, pair.second)); m_debugTextRect.combine(m_guiContext->determineTextSize(text, positioning).padded(m_config->debugBackgroundPad)); } @@ -1373,15 +1363,9 @@ void MainInterface::renderDebug() { m_debugTextRect = RectF::null(); for (size_t index = 0; index != formatted.size(); ++index) { - TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->fontSize * interfaceScale() * index) }; + TextPositioning positioning = { Vec2F(m_config->debugOffset[0], windowHeight() - m_config->debugOffset[1] - m_config->textStyle.fontSize * interfaceScale() * index) }; m_guiContext->renderText(formatted[index], positioning); } - - m_guiContext->setFontSize(8); - m_guiContext->setDefaultFont(); - m_guiContext->setDefaultLineSpacing(); - m_guiContext->setFontColor(Vec4B::filled(255)); - m_guiContext->setFontProcessingDirectives(""); } auto const& camera = m_worldPainter->camera(); @@ -1413,7 +1397,7 @@ void MainInterface::renderDebug() { m_guiContext->drawLine(position + Vec2F(2, -2), position + Vec2F(-2, -2), point.color, 1); } - m_guiContext->setFontSize(m_config->debugFontSize); + m_guiContext->setTextStyle(m_config->debugTextStyle); for (auto const& logText : SpatialLogger::getText("world", clearSpatial)) { m_guiContext->setFontColor(logText.color); @@ -1424,7 +1408,7 @@ void MainInterface::renderDebug() { m_guiContext->setFontColor(logText.color); m_guiContext->renderText(logText.text.utf8Ptr(), logText.position); } - m_guiContext->setFontColor(Vec4B::filled(255)); + m_guiContext->clearTextStyle(); } void MainInterface::updateCursor() { @@ -1483,24 +1467,26 @@ void MainInterface::renderCursor() { auto assets = Root::singleton().assets(); auto imgDb = Root::singleton().imageMetadataDatabase(); - auto backgroundImage = assets->json("/interface.config:cursorTooltip.background").toString(); - auto rawCursorOffset = jsonToVec2I(assets->json("/interface.config:cursorTooltip.offset")); + auto config = assets->json("/interface.config:cursorTooltip"); + auto backgroundImage = config.getString("background"); + auto rawCursorOffset = jsonToVec2I(config.get("offset")); Vec2I tooltipSize = Vec2I(imgDb->imageSize(backgroundImage)) * interfaceScale(); Vec2I cursorOffset = (Vec2I{0, -m_cursor.size().y()} + rawCursorOffset) * cursorScale; Vec2I tooltipOffset = m_cursorScreenPos + cursorOffset; - size_t fontSize = assets->json("/interface.config:cursorTooltip.fontSize").toUInt(); - String font = assets->json("/interface.config:cursorTooltip.font").toString(); - Vec4B fontColor = jsonToColor(assets->json("/interface.config:cursorTooltip.color")).toRgba(); + TextStyle textStyle = config.get("textStyle"); + size_t fontSize = config.get("fontSize").toUInt(); + Vec4B fontColor = jsonToColor(config.get("color")).toRgba(); m_guiContext->drawQuad(backgroundImage, Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), 0), interfaceScale()); + m_guiContext->setTextStyle(textStyle); m_guiContext->setFontSize(fontSize); m_guiContext->setFontColor(fontColor); - m_guiContext->setFont(font); m_guiContext->renderText(*m_cursorTooltip, TextPositioning(Vec2F(tooltipOffset) + Vec2F(-tooltipSize.x(), tooltipSize.y()) / 2, HorizontalAnchor::HMidAnchor, VerticalAnchor::VMidAnchor)); + m_guiContext->clearTextStyle(); } m_cursorItem->setPosition(m_cursorScreenPos / interfaceScale() + m_config->inventoryItemMouseOffset); diff --git a/source/frontend/StarMainInterface.hpp b/source/frontend/StarMainInterface.hpp index 0f1487e..ec79f68 100644 --- a/source/frontend/StarMainInterface.hpp +++ b/source/frontend/StarMainInterface.hpp @@ -119,6 +119,8 @@ public: CanvasWidgetPtr fetchCanvas(String const& canvasName, bool ignoreInterfaceScale = false); + ClientCommandProcessorPtr commandProcessor() const; + struct ScriptPaneInfo { ScriptPanePtr scriptPane; Json config; diff --git a/source/frontend/StarMainInterfaceTypes.cpp b/source/frontend/StarMainInterfaceTypes.cpp index 1d67292..957b69d 100644 --- a/source/frontend/StarMainInterfaceTypes.cpp +++ b/source/frontend/StarMainInterfaceTypes.cpp @@ -39,8 +39,7 @@ MainInterfaceConfigPtr MainInterfaceConfig::loadFromAssets() { auto config = make_shared(); - config->fontSize = assets->json("/interface.config:font.baseSize").toInt(); - config->font = assets->json("/interface.config:font.defaultFont").toString(); + config->textStyle = assets->json("/interface.config:textStyle"); config->inventoryImage = assets->json("/interface.config:mainBar.inventory.base").toString(); config->inventoryImageHover = assets->json("/interface.config:mainBar.inventory.hover").toString(); config->inventoryImageGlow = assets->json("/interface.config:mainBar.inventory.glow").toString(); @@ -130,17 +129,14 @@ MainInterfaceConfigPtr MainInterfaceConfig::loadFromAssets() { config->planetNameTime = assets->json("/interface.config:planetNameTime").toFloat(); config->planetNameFadeTime = assets->json("/interface.config:planetNameFadeTime").toFloat(); config->planetNameFormatString = assets->json("/interface.config:planetNameFormatString").toString(); - config->planetNameFontSize = assets->json("/interface.config:font.planetSize").toInt(); - config->planetNameDirectives = assets->json("/interface.config:planetNameDirectives").toString(); + config->planetNameTextStyle = assets->json("/interface.config:planetTextStyle"); config->planetNameOffset = jsonToVec2I(assets->json("/interface.config:planetTextOffset")); config->renderVirtualCursor = assets->json("/interface.config:renderVirtualCursor").toBool(); config->cursorItemSlot = assets->json("/interface.config:cursorItemSlot"); config->debugOffset = jsonToVec2I(assets->json("/interface.config:debugOffset")); - config->debugFontSize = assets->json("/interface.config:debugFontSize").toUInt(); - config->debugFont = assets->json("/interface.config:debugFont").toString(); - config->debugFontDirectives = assets->json("/interface.config:debugFontDirectives").toString(); + config->debugTextStyle = assets->json("/interface.config:debugTextStyle"); config->debugSpatialClearTime = assets->json("/interface.config:debugSpatialClearTime").toFloat(); config->debugMapClearTime = assets->json("/interface.config:debugMapClearTime").toFloat(); config->debugBackgroundColor = jsonToColor(assets->json("/interface.config:debugBackgroundColor")); diff --git a/source/frontend/StarMainInterfaceTypes.hpp b/source/frontend/StarMainInterfaceTypes.hpp index 6b0bca3..b5f228a 100644 --- a/source/frontend/StarMainInterfaceTypes.hpp +++ b/source/frontend/StarMainInterfaceTypes.hpp @@ -5,6 +5,7 @@ #include "StarBiMap.hpp" #include "StarRegisteredPaneManager.hpp" #include "StarAnimation.hpp" +#include "StarText.hpp" namespace Star { @@ -43,8 +44,7 @@ typedef RegisteredPaneManager MainInterfacePaneManager; struct MainInterfaceConfig { static MainInterfaceConfigPtr loadFromAssets(); - unsigned fontSize; - String font; + TextStyle textStyle; String inventoryImage; String inventoryImageHover; @@ -136,17 +136,14 @@ struct MainInterfaceConfig { float planetNameTime; float planetNameFadeTime; String planetNameFormatString; - unsigned planetNameFontSize; - String planetNameDirectives; + TextStyle planetNameTextStyle; Vec2I planetNameOffset; bool renderVirtualCursor; Json cursorItemSlot; Vec2I debugOffset; - unsigned debugFontSize; - String debugFont; - String debugFontDirectives; + TextStyle debugTextStyle; float debugSpatialClearTime; float debugMapClearTime; Color debugBackgroundColor; diff --git a/source/frontend/StarNameplatePainter.cpp b/source/frontend/StarNameplatePainter.cpp index 4637232..0666af0 100644 --- a/source/frontend/StarNameplatePainter.cpp +++ b/source/frontend/StarNameplatePainter.cpp @@ -15,14 +15,11 @@ NameplatePainter::NameplatePainter() { m_opacityRate = nametagConfig.getFloat("opacityRate"); m_inspectOpacityRate = nametagConfig.queryFloat("inspectOpacityRate", m_opacityRate); m_offset = jsonToVec2F(nametagConfig.get("offset")); - m_font = nametagConfig.queryString("font", ""); - m_fontDirectives = nametagConfig.queryString("fontDirectives", ""); - m_fontSize = nametagConfig.getFloat("fontSize"); - m_statusFont = nametagConfig.queryString("font", m_font); - m_statusFontDirectives = nametagConfig.queryString("fontDirectives", m_fontDirectives); - m_statusFontSize = nametagConfig.queryFloat("statusFontSize", m_fontSize); + Json textStyle = nametagConfig.get("textStyle"); + m_textStyle = textStyle; + m_statusTextStyle = nametagConfig.get("statusTextStyle", textStyle); m_statusOffset = jsonToVec2F(nametagConfig.get("statusOffset")); - m_statusColor = jsonToColor(nametagConfig.get("statusColor")); + m_statusTextStyle.color = jsonToColor(nametagConfig.get("statusColor")).toRgba(); m_opacityBoost = nametagConfig.getFloat("opacityBoost"); m_nametags.setTweenFactor(nametagConfig.getFloat("tweenFactor")); m_nametags.setMovementThreshold(nametagConfig.getFloat("movementThreshold")); @@ -80,31 +77,18 @@ void NameplatePainter::render() { if (nametag.opacity == 0.0f) return; - context.setFont(m_font); - context.setFontProcessingDirectives(m_fontDirectives); - context.setFontSize(m_fontSize); - + auto& setStyle = context.setTextStyle(m_textStyle); auto color = Color::rgb(nametag.color); color.setAlphaF(nametag.opacity); - context.setFontColor(color.toRgba()); - context.setFontMode(FontMode::Normal); + setStyle.color = color.toRgba(); context.renderText(nametag.name, namePosition(bubble.currentPosition)); if (nametag.statusText) { - auto statusColor = m_statusColor; - statusColor.setAlphaF(nametag.opacity); - context.setFontColor(statusColor.toRgba()); - - context.setFontSize(m_statusFontSize); - context.setFontProcessingDirectives(m_statusFontDirectives); - context.setFont(m_statusFont); - + context.setTextStyle(m_statusTextStyle).color[3] *= nametag.opacity; context.renderText(*nametag.statusText, statusPosition(bubble.currentPosition)); } - - context.setDefaultFont(); - context.setFontProcessingDirectives(""); + context.clearTextStyle(); }); } @@ -121,16 +105,13 @@ TextPositioning NameplatePainter::statusPosition(Vec2F bubblePosition) const { RectF NameplatePainter::determineBoundBox(Vec2F bubblePosition, Nametag const& nametag) const { auto& context = GuiContext::singleton(); - context.setFontSize(m_fontSize); - context.setFontProcessingDirectives(m_fontDirectives); - context.setFont(m_font); + context.setTextStyle(m_textStyle); RectF nametagBox = context.determineTextSize(nametag.name, namePosition(bubblePosition)); if (nametag.statusText) { - context.setFontSize(m_statusFontSize); - context.setFontProcessingDirectives(m_statusFontDirectives); - context.setFont(m_statusFont); + context.setTextStyle(m_statusTextStyle); nametagBox.combine(context.determineTextSize(*nametag.statusText, statusPosition(bubblePosition))); } + context.clearTextStyle(); return nametagBox; } diff --git a/source/frontend/StarNameplatePainter.hpp b/source/frontend/StarNameplatePainter.hpp index d895cfb..06aa187 100644 --- a/source/frontend/StarNameplatePainter.hpp +++ b/source/frontend/StarNameplatePainter.hpp @@ -34,14 +34,9 @@ private: float m_opacityRate; float m_inspectOpacityRate; Vec2F m_offset; - String m_font; - String m_statusFont; - String m_fontDirectives; - String m_statusFontDirectives; - float m_fontSize; - float m_statusFontSize; Vec2F m_statusOffset; - Color m_statusColor; + TextStyle m_textStyle; + TextStyle m_statusTextStyle; float m_opacityBoost; WorldCamera m_camera; diff --git a/source/frontend/StarTeamBar.cpp b/source/frontend/StarTeamBar.cpp index 767bb91..8a8f956 100644 --- a/source/frontend/StarTeamBar.cpp +++ b/source/frontend/StarTeamBar.cpp @@ -29,7 +29,8 @@ TeamBar::TeamBar(MainInterface* mainInterface, UniverseClientPtr client) { m_teamInvitation = make_shared(this); m_teamMemberMenu = make_shared(this); - m_nameFontSize = assets->json("/interface.config:font.nameSize").toInt(); + m_nameStyle = assets->json("/interface.config:teamBarNameStyle"); + m_nameStyle.fontSize = assets->json("/interface.config:font.nameSize").toInt(); m_nameOffset = jsonToVec2F(assets->json("/interface.config:nameOffset")); GuiReader reader; diff --git a/source/frontend/StarTeamBar.hpp b/source/frontend/StarTeamBar.hpp index 9ebbb50..ca50452 100644 --- a/source/frontend/StarTeamBar.hpp +++ b/source/frontend/StarTeamBar.hpp @@ -93,7 +93,7 @@ private: GuiContext* m_guiContext; - int m_nameFontSize; + TextStyle m_nameStyle; Vec2F m_nameOffset; TeamInvitePtr m_teamInvite; diff --git a/source/game/StarClientContext.cpp b/source/game/StarClientContext.cpp index e6fd89a..a91c40e 100644 --- a/source/game/StarClientContext.cpp +++ b/source/game/StarClientContext.cpp @@ -96,4 +96,12 @@ ByteArray ClientContext::writeUpdate() { return m_rpc->send(); } +void ClientContext::setConnectionId(ConnectionId connectionId) { + m_connectionId = connectionId; +} + +ConnectionId ClientContext::connectionId() const { + return m_connectionId; +} + } diff --git a/source/game/StarClientContext.hpp b/source/game/StarClientContext.hpp index 1bad18d..5cdd727 100644 --- a/source/game/StarClientContext.hpp +++ b/source/game/StarClientContext.hpp @@ -43,9 +43,13 @@ public: void readUpdate(ByteArray data); ByteArray writeUpdate(); + void setConnectionId(ConnectionId connectionId); + ConnectionId connectionId() const; + private: Uuid m_serverUuid; Uuid m_playerUuid; + ConnectionId m_connectionId = 0; JsonRpcPtr m_rpc; diff --git a/source/game/StarHumanoid.cpp b/source/game/StarHumanoid.cpp index 418e88d..a18dd3f 100644 --- a/source/game/StarHumanoid.cpp +++ b/source/game/StarHumanoid.cpp @@ -1382,11 +1382,11 @@ pair Humanoid::extractScaleFromDirectives(Directives const& d size_t totalLength = 0; Maybe scale; - for (auto& entry : directives.shared->entries) { - auto string = entry.string(*directives.shared); + for (auto& entry : directives->entries) { + auto string = entry.string(*directives); const ScaleImageOperation* op = nullptr; if (string.beginsWith("scalenearest") && string.utf8().find("skip") == NPos) - op = entry.loadOperation(*directives.shared).ptr(); + op = entry.loadOperation(*directives).ptr(); if (op) scale = scale.value(Vec2F::filled(1.f)).piecewiseMultiply(op->scale); diff --git a/source/game/StarImageMetadataDatabase.cpp b/source/game/StarImageMetadataDatabase.cpp index b3a3983..bf84e51 100644 --- a/source/game/StarImageMetadataDatabase.cpp +++ b/source/game/StarImageMetadataDatabase.cpp @@ -132,7 +132,7 @@ AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) { operation.is() || operation.is())) { filtered += "?"; - filtered += entry.string(*directives.shared); + filtered += entry.string(*directives); } }); diff --git a/source/game/StarParallax.cpp b/source/game/StarParallax.cpp index 2670699..d537b2c 100644 --- a/source/game/StarParallax.cpp +++ b/source/game/StarParallax.cpp @@ -62,7 +62,7 @@ void ParallaxLayer::addImageDirectives(Directives const& newDirectives) { if (directives) { String dirString = directives.string(); - auto& newString = newDirectives.shared->string; + auto& newString = newDirectives->string; if (!newString.empty()) { if (newString.utf8().front() != '?') dirString += "?"; diff --git a/source/game/StarUniverseClient.cpp b/source/game/StarUniverseClient.cpp index adc6bef..136fd7b 100644 --- a/source/game/StarUniverseClient.cpp +++ b/source/game/StarUniverseClient.cpp @@ -440,8 +440,8 @@ bool UniverseClient::flying() const { return false; } -void UniverseClient::sendChat(String const& text, ChatSendMode sendMode) { - if (!text.beginsWith("/")) +void UniverseClient::sendChat(String const& text, ChatSendMode sendMode, Maybe speak) { + if (speak.value(!text.beginsWith("/"))) m_mainPlayer->addChatMessage(text); m_connection->pushSingle(make_shared(text, sendMode)); } diff --git a/source/game/StarUniverseClient.hpp b/source/game/StarUniverseClient.hpp index 16457c7..985124a 100644 --- a/source/game/StarUniverseClient.hpp +++ b/source/game/StarUniverseClient.hpp @@ -80,7 +80,7 @@ public: SkyConstPtr currentSky() const; bool flying() const; - void sendChat(String const& text, ChatSendMode sendMode); + void sendChat(String const& text, ChatSendMode sendMode, Maybe speak = {}); List pullChatMessages(); uint16_t players(); diff --git a/source/game/StarWorldClient.cpp b/source/game/StarWorldClient.cpp index eaea2b2..5f7e028 100644 --- a/source/game/StarWorldClient.cpp +++ b/source/game/StarWorldClient.cpp @@ -1734,6 +1734,7 @@ void WorldClient::initWorld(WorldStartPacket const& startPacket) { m_entityUpdateTimer = GameTimer(m_interpolationTracker.entityUpdateDelta()); m_clientId = startPacket.clientId; + m_mainPlayer->clientContext()->setConnectionId(startPacket.clientId); auto entitySpace = connectionEntitySpace(startPacket.clientId); m_worldTemplate = make_shared(startPacket.templateData); m_entityMap = make_shared(m_worldTemplate->size(), entitySpace.first, entitySpace.second); diff --git a/source/rendering/StarFontTextureGroup.cpp b/source/rendering/StarFontTextureGroup.cpp index e8f701a..fb3a107 100644 --- a/source/rendering/StarFontTextureGroup.cpp +++ b/source/rendering/StarFontTextureGroup.cpp @@ -47,11 +47,12 @@ const FontTextureGroup::GlyphTexture& FontTextureGroup::glyphTexture(String::Cha m_font->setPixelSize(size); auto pair = m_font->render(c); Image& image = pair.first; - if (processingDirectives && *processingDirectives) { + if (processingDirectives) { try { + Directives const& directives = *processingDirectives; Vec2F preSize = Vec2F(image.size()); - for (auto& entry : processingDirectives->shared->entries) + for (auto& entry : directives->entries) processImageOperation(entry.operation, image); res.first->second.offset = (preSize - Vec2F(image.size())) / 2; diff --git a/source/rendering/StarTextPainter.cpp b/source/rendering/StarTextPainter.cpp index 8e21488..8198969 100644 --- a/source/rendering/StarTextPainter.cpp +++ b/source/rendering/StarTextPainter.cpp @@ -1,7 +1,5 @@ #include "StarTextPainter.hpp" #include "StarJsonExtra.hpp" -#include "StarText.hpp" - #include namespace Star { @@ -40,34 +38,42 @@ TextPositioning TextPositioning::translated(Vec2F translation) const { TextPainter::TextPainter(RendererPtr renderer, TextureGroupPtr textureGroup) : m_renderer(renderer), m_fontTextureGroup(textureGroup), - m_fontSize(8), - m_lineSpacing(1.30f), - m_renderSettings({FontMode::Normal, Vec4B::filled(255), "hobo", ""}) { + m_defaultRenderSettings(), + m_renderSettings(), + m_savedRenderSettings() { reloadFonts(); m_reloadTracker = make_shared(); Root::singleton().registerReloadListener(m_reloadTracker); } RectF TextPainter::renderText(StringView s, TextPositioning const& position) { + RectF rect; if (position.charLimit) { unsigned charLimit = *position.charLimit; - return doRenderText(s, position, true, &charLimit); + rect = doRenderText(s, position, true, &charLimit); } else { - return doRenderText(s, position, true, nullptr); + rect = doRenderText(s, position, true, nullptr); } + renderPrimitives(); + return rect; } RectF TextPainter::renderLine(StringView s, TextPositioning const& position) { + RectF rect; if (position.charLimit) { unsigned charLimit = *position.charLimit; - return doRenderLine(s, position, true, &charLimit); + rect = doRenderLine(s, position, true, &charLimit); } else { - return doRenderLine(s, position, true, nullptr); + rect = doRenderLine(s, position, true, nullptr); } + renderPrimitives(); + return rect; } RectF TextPainter::renderGlyph(String::Char c, TextPositioning const& position) { - return doRenderGlyph(c, position, true); + auto rect = doRenderGlyph(c, position, true); + renderPrimitives(); + return rect; } RectF TextPainter::determineTextSize(StringView s, TextPositioning const& position) { @@ -83,34 +89,36 @@ RectF TextPainter::determineGlyphSize(String::Char c, TextPositioning const& pos } int TextPainter::glyphWidth(String::Char c) { - return m_fontTextureGroup.glyphWidth(c, m_fontSize); + return m_fontTextureGroup.glyphWidth(c, m_renderSettings.fontSize); } -int TextPainter::stringWidth(StringView s) { +int TextPainter::stringWidth(StringView s, unsigned charLimit) { if (s.empty()) return 0; String font = m_renderSettings.font, setFont = font; m_fontTextureGroup.switchFont(font); - int width = 0; - Text::CommandsCallback commandsCallback = [&](StringView commands) { commands.forEachSplitView(",", [&](StringView command, size_t, size_t) { - if (command == "reset") + if (command == "reset") { m_fontTextureGroup.switchFont(font = setFont); - else if (command == "set") + } else if (command == "set") { setFont = font; - else if (command.beginsWith("font=")) + } else if (command.beginsWith("font=")) { m_fontTextureGroup.switchFont(font = command.substr(5)); + } }); return true; }; + int width = 0; Text::TextCallback textCallback = [&](StringView text) { - for (String::Char c : text) + for (String::Char c : text) { width += glyphWidth(c); - + if (charLimit && --charLimit == 0) + return false; + } return true; }; @@ -122,7 +130,7 @@ int TextPainter::stringWidth(StringView s) { bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapTextCallback textFunc) { String font = m_renderSettings.font, setFont = font; m_fontTextureGroup.switchFont(font); - int lines = 0; + unsigned lines = 0; auto it = text.begin(); auto end = text.end(); @@ -149,14 +157,15 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText if (escIt != end) { if (character == Text::EndEsc) { - StringView inner = slice(escIt, it); + StringView inner = slice(++escIt, it); inner.forEachSplitView(",", [&](StringView command, size_t, size_t) { - if (command == "reset") + if (command == "reset") { m_fontTextureGroup.switchFont(font = setFont); - else if (command == "set") + } else if (command == "set") { setFont = font; - else if (command.beginsWith("font=")) + } else if (command.beginsWith("font=")) { m_fontTextureGroup.switchFont(font = command.substr(5)); + } }); escIt = end; } @@ -177,7 +186,6 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText splitIt = end; } else { int charWidth = glyphWidth(character); - // is it a place where we might want to split the line ? if (character == ' ' || character == '\t') { splitIt = it; @@ -220,36 +228,20 @@ bool TextPainter::processWrapText(StringView text, unsigned* wrapWidth, WrapText List TextPainter::wrapTextViews(StringView s, Maybe wrapWidth) { List views = {}; - - bool active = false; - StringView current; - int lastLine = 0; - - auto addText = [&active, ¤t](StringView text) { - // Merge views if they are adjacent - if (active && current.utf8Ptr() + current.utf8Size() == text.utf8Ptr()) - current = StringView(current.utf8Ptr(), current.utf8Size() + text.utf8Size()); - else - current = text; - active = true; - }; - - TextPainter::WrapTextCallback textCallback = [&](StringView text, int line) { - if (lastLine != line) { - views.push_back(current); - lastLine = line; - active = false; + auto last = views.end(); + unsigned curLine = 0; + TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) { + if (line == curLine && last != views.end() && last->end() == text.begin()) { + *last = StringView(last->utf8Ptr(), last->utf8Size() + text.utf8Size()); + } else { + last = views.insert(views.end(), text); + curLine = line; } - - addText(text); return true; }; processWrapText(s, wrapWidth.ptr(), textCallback); - if (active) - views.push_back(current); - return views; } @@ -258,12 +250,11 @@ StringList TextPainter::wrapText(StringView s, Maybe wrapWidth) { String current; int lastLine = 0; - TextPainter::WrapTextCallback textCallback = [&](StringView text, int line) { + TextPainter::WrapTextCallback textCallback = [&](StringView text, unsigned line) { if (lastLine != line) { result.append(std::move(current)); lastLine = line; } - current += text; return true; }; @@ -277,40 +268,45 @@ StringList TextPainter::wrapText(StringView s, Maybe wrapWidth) { }; unsigned TextPainter::fontSize() const { - return m_fontSize; + return m_renderSettings.fontSize; } void TextPainter::setFontSize(unsigned size) { - m_fontSize = size; + m_renderSettings.fontSize = size; } void TextPainter::setLineSpacing(float lineSpacing) { - m_lineSpacing = lineSpacing; + m_renderSettings.lineSpacing = lineSpacing; } void TextPainter::setMode(FontMode mode) { - m_renderSettings.mode = mode; + m_renderSettings.shadow = fontModeToColor(mode).toRgba(); } void TextPainter::setFontColor(Vec4B color) { m_renderSettings.color = std::move(color); } -void TextPainter::setProcessingDirectives(StringView directives) { - m_renderSettings.directives = String(directives); - if (m_renderSettings.directives) { - m_renderSettings.directives.loadOperations(); - for (auto& entry : m_renderSettings.directives.shared->entries) { - if (auto border = entry.operation.ptr()) - border->includeTransparent = true; - } - } +void TextPainter::setProcessingDirectives(StringView directives, bool back) { + Directives& target = back ? m_renderSettings.backDirectives : m_renderSettings.directives; + modifyDirectives(target = String(directives)); } void TextPainter::setFont(String const& font) { m_renderSettings.font = font; } +TextStyle& TextPainter::setTextStyle(TextStyle const& textStyle) { + TextStyle& style = m_renderSettings = textStyle; + modifyDirectives(style.directives); + modifyDirectives(style.backDirectives); + return style; +} + +void TextPainter::clearTextStyle() { + m_renderSettings = m_defaultRenderSettings; +} + void TextPainter::addFont(FontPtr const& font, String const& name) { m_fontTextureGroup.addFont(font, name); } @@ -348,16 +344,19 @@ void TextPainter::applyCommands(StringView unsplitCommands) { m_renderSettings = m_savedRenderSettings; } else if (command == "set") { m_savedRenderSettings = m_renderSettings; - } else if (command == "shadow") { - m_renderSettings.mode = (FontMode)((int)m_renderSettings.mode | (int)FontMode::Shadow); + } else if (command.beginsWith("shadow")) { + if (command.utf8Size() == 6) + m_renderSettings.shadow = Color::Black.toRgba(); + else if (command[6] == '=') + m_renderSettings.shadow = Color(command.substr(7)).toRgba(); } else if (command == "noshadow") { - m_renderSettings.mode = (FontMode)((int)m_renderSettings.mode & (-1 ^ (int)FontMode::Shadow)); + m_renderSettings.shadow = Color::Clear.toRgba(); } else if (command.beginsWith("font=")) { m_renderSettings.font = command.substr(5); } else if (command.beginsWith("directives=")) { - // Honestly this is really stupid but I just couldn't help myself - // Should probably limit in the future setProcessingDirectives(command.substr(11)); + } else if (command.beginsWith("backdirectives=")) { + setProcessingDirectives(command.substr(15), true); } else { // expects both #... sequences and plain old color names. Color c = Color(command); @@ -370,6 +369,16 @@ void TextPainter::applyCommands(StringView unsplitCommands) { }); } +void TextPainter::modifyDirectives(Directives& directives) { + if (directives) { + directives.loadOperations(); + for (auto& entry : directives->entries) { + if (auto border = entry.operation.ptr()) + border->includeTransparent = true; + } + } +} + RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit) { Vec2F pos = position.pos; if (s.empty()) @@ -377,26 +386,23 @@ RectF TextPainter::doRenderText(StringView s, TextPositioning const& position, b List lines = wrapTextViews(s, position.wrapWidth); - int height = (lines.size() - 1) * m_lineSpacing * m_fontSize + m_fontSize; - - RenderSettings backupRenderSettings = m_renderSettings; - m_savedRenderSettings = m_renderSettings; - + TextStyle backup = m_savedRenderSettings = m_renderSettings; + int height = (lines.size() - 1) * backup.lineSpacing * backup.fontSize + backup.fontSize; if (position.vAnchor == VerticalAnchor::BottomAnchor) - pos[1] += (height - m_fontSize); + pos[1] += (height - backup.fontSize); else if (position.vAnchor == VerticalAnchor::VMidAnchor) - pos[1] += (height - m_fontSize) / 2; + pos[1] += (height - backup.fontSize) / 2; RectF bounds = RectF::withSize(pos, Vec2F()); for (auto& i : lines) { bounds.combine(doRenderLine(i, { pos, position.hAnchor, position.vAnchor }, reallyRender, charLimit)); - pos[1] -= m_fontSize * m_lineSpacing; + pos[1] -= m_renderSettings.fontSize * m_renderSettings.lineSpacing; if (charLimit && *charLimit == 0) break; } - m_renderSettings = std::move(backupRenderSettings); + m_renderSettings = std::move(backup); return bounds; } @@ -425,9 +431,8 @@ RectF TextPainter::doRenderLine(StringView text, TextPositioning const& position if (*charLimit == 0) return false; else - --* charLimit; + --*charLimit; } - RectF glyphBounds = doRenderGlyph(c, pos, reallyRender); bounds.combine(glyphBounds); pos.pos[0] += glyphBounds.width(); @@ -461,41 +466,58 @@ RectF TextPainter::doRenderGlyph(String::Char c, TextPositioning const& position float vOffset = 0; if (position.vAnchor == VerticalAnchor::VMidAnchor) - vOffset = -floor((float)m_fontSize / 2); + vOffset = -floor((float)m_renderSettings.fontSize / 2); else if (position.vAnchor == VerticalAnchor::TopAnchor) - vOffset = -(float)m_fontSize; + vOffset = -(float)m_renderSettings.fontSize; Directives* directives = m_renderSettings.directives ? &m_renderSettings.directives : nullptr; + Vec2F pos = position.pos + Vec2F(hOffset, vOffset); if (reallyRender) { - if ((int)m_renderSettings.mode & (int)FontMode::Shadow) { - Color shadow = Color::Black; - uint8_t alphaU = m_renderSettings.color[3]; + bool hasShadow = m_renderSettings.shadow[3] > 0; + bool hasBackDirectives = m_renderSettings.backDirectives; + if (hasShadow) { + //Kae: unlike vanilla we draw only one shadow glyph instead of two, so i'm tweaking the alpha here + Vec4B shadow = m_renderSettings.shadow; + uint8_t alphaU = m_renderSettings.color[3] * byteToFloat(shadow[3]); if (alphaU != 255) { float alpha = byteToFloat(alphaU); - shadow.setAlpha(floatToByte(alpha * (1.5f - 0.5f * alpha))); + shadow[3] = floatToByte(alpha * (1.5f - 0.5f * alpha)); } else - shadow.setAlpha(alphaU); + shadow[3] = alphaU; - //Kae: Draw only one shadow glyph instead of stacking two, alpha modified to appear perceptually the same as vanilla - renderGlyph(c, position.pos + Vec2F(hOffset, vOffset - 2), m_fontSize, 1, shadow.toRgba(), directives); + Directives const* shadowDirectives = hasBackDirectives ? &m_renderSettings.backDirectives : directives; + renderGlyph(c, pos + Vec2F(0, -2), m_shadowPrimitives, m_renderSettings.fontSize, 1, shadow, shadowDirectives); } + if (hasBackDirectives) + renderGlyph(c, pos, m_backPrimitives, m_renderSettings.fontSize, 1, m_renderSettings.color, &m_renderSettings.backDirectives); - renderGlyph(c, position.pos + Vec2F(hOffset, vOffset), m_fontSize, 1, m_renderSettings.color, directives); + auto& output = (hasShadow || hasBackDirectives) ? m_frontPrimitives : m_renderer->immediatePrimitives(); + renderGlyph(c, pos, output, m_renderSettings.fontSize, 1, m_renderSettings.color, directives); } - return RectF::withSize(position.pos + Vec2F(hOffset, vOffset), {(float)width, (int)m_fontSize}); + return RectF::withSize(pos, {(float)width, (int)m_renderSettings.fontSize}); } -void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, unsigned fontSize, +void TextPainter::renderPrimitives() { + auto& destination = m_renderer->immediatePrimitives(); + std::move(std::begin(m_shadowPrimitives), std::end(m_shadowPrimitives), std::back_inserter(destination)); + m_shadowPrimitives.clear(); + std::move(std::begin(m_backPrimitives), std::end(m_backPrimitives), std::back_inserter(destination)); + m_backPrimitives.clear(); + std::move(std::begin(m_frontPrimitives), std::end(m_frontPrimitives), std::back_inserter(destination)); + m_frontPrimitives.clear(); +} + +void TextPainter::renderGlyph(String::Char c, Vec2F const& screenPos, List& out, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives) { if (!fontSize) return; const FontTextureGroup::GlyphTexture& glyphTexture = m_fontTextureGroup.glyphTexture(c, fontSize, processingDirectives); Vec2F offset = glyphTexture.offset * scale; - m_renderer->immediatePrimitives().emplace_back(std::in_place_type_t(), glyphTexture.texture, Vec2F::round(screenPos + offset), scale, color, 0.0f); + out.emplace_back(std::in_place_type_t(), glyphTexture.texture, Vec2F::round(screenPos + offset), scale, color, 0.0f); } FontPtr TextPainter::loadFont(String const& fontPath, Maybe fontName) { diff --git a/source/rendering/StarTextPainter.hpp b/source/rendering/StarTextPainter.hpp index a0db304..bff3a64 100644 --- a/source/rendering/StarTextPainter.hpp +++ b/source/rendering/StarTextPainter.hpp @@ -4,17 +4,21 @@ #include "StarAnchorTypes.hpp" #include "StarRoot.hpp" #include "StarStringView.hpp" +#include "StarText.hpp" namespace Star { -STAR_CLASS(TextPainter); - -enum class FontMode { +// deprecated in favor of explicit shadow color +enum class FontMode : uint8_t { Normal, Shadow }; -float const DefaultLineSpacing = 1.3f; +inline Color const& fontModeToColor(FontMode mode) { + return mode == FontMode::Shadow ? Color::Black : Color::Clear; +} + +STAR_CLASS(TextPainter); struct TextPositioning { TextPositioning(); @@ -52,10 +56,10 @@ public: RectF determineGlyphSize(String::Char c, TextPositioning const& position); int glyphWidth(String::Char c); - int stringWidth(StringView s); + int stringWidth(StringView s, unsigned charLimit = 0); - typedef function WrapTextCallback; + typedef function WrapTextCallback; bool processWrapText(StringView s, unsigned* wrapWidth, WrapTextCallback textFunc); List wrapTextViews(StringView s, Maybe wrapWidth); @@ -66,39 +70,36 @@ public: void setLineSpacing(float lineSpacing); void setMode(FontMode mode); void setFontColor(Vec4B color); - void setProcessingDirectives(StringView directives); + void setProcessingDirectives(StringView directives, bool back = false); void setFont(String const& font); + TextStyle& setTextStyle(TextStyle const& textStyle); + void clearTextStyle(); void addFont(FontPtr const& font, String const& name); void reloadFonts(); void cleanup(int64_t textureTimeout); void applyCommands(StringView unsplitCommands); private: - struct RenderSettings { - FontMode mode; - Vec4B color; - String font; - Directives directives; - }; - + void modifyDirectives(Directives& directives); RectF doRenderText(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit); RectF doRenderLine(StringView s, TextPositioning const& position, bool reallyRender, unsigned* charLimit); RectF doRenderGlyph(String::Char c, TextPositioning const& position, bool reallyRender); - void renderGlyph(String::Char c, Vec2F const& screenPos, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives = nullptr); + void renderPrimitives(); + void renderGlyph(String::Char c, Vec2F const& screenPos, List& out, unsigned fontSize, float scale, Vec4B const& color, Directives const* processingDirectives = nullptr); static FontPtr loadFont(String const& fontPath, Maybe fontName = {}); RendererPtr m_renderer; + List m_shadowPrimitives; + List m_backPrimitives; + List m_frontPrimitives; FontTextureGroup m_fontTextureGroup; - unsigned m_fontSize; - float m_lineSpacing; - - RenderSettings m_renderSettings; - RenderSettings m_savedRenderSettings; + TextStyle m_defaultRenderSettings; + TextStyle m_renderSettings; + TextStyle m_savedRenderSettings; String m_nonRenderedCharacters; - TrackerListenerPtr m_reloadTracker; }; diff --git a/source/windowing/StarButtonWidget.cpp b/source/windowing/StarButtonWidget.cpp index a8c8a85..258ac55 100644 --- a/source/windowing/StarButtonWidget.cpp +++ b/source/windowing/StarButtonWidget.cpp @@ -24,14 +24,11 @@ ButtonWidget::ButtonWidget() { auto interfaceConfig = assets->json("/interface.config"); m_pressedOffset = jsonToVec2I(interfaceConfig.get("buttonPressedOffset")); - m_fontSize = interfaceConfig.query("font.buttonSize").toInt(); - m_fontDirectives = interfaceConfig.queryString("font.defaultDirectives", ""); - m_font = interfaceConfig.query("font.defaultFont").toString(); - - m_clickSounds = jsonToStringList(assets->json("/interface.config:buttonClickSound")); - m_releaseSounds = jsonToStringList(assets->json("/interface.config:buttonReleaseSound")); - m_hoverSounds = jsonToStringList(assets->json("/interface.config:buttonHoverSound")); - m_hoverOffSounds = jsonToStringList(assets->json("/interface.config:buttonHoverOffSound")); + m_textStyle = interfaceConfig.get("buttonTextStyle"); + m_clickSounds = jsonToStringList(interfaceConfig.get("buttonClickSound")); + m_releaseSounds = jsonToStringList(interfaceConfig.get("buttonReleaseSound")); + m_hoverSounds = jsonToStringList(interfaceConfig.get("buttonHoverSound")); + m_hoverOffSounds = jsonToStringList(interfaceConfig.get("buttonHoverOffSound")); } ButtonWidget::ButtonWidget(WidgetCallbackFunc callback, @@ -95,19 +92,15 @@ void ButtonWidget::renderImpl() { if (!m_text.empty()) { auto& guiContext = GuiContext::singleton(); - guiContext.setFontProcessingDirectives(m_fontDirectives); - guiContext.setFontSize(m_fontSize); - guiContext.setFont(m_font); + guiContext.setTextStyle(m_textStyle); if (m_disabled) guiContext.setFontColor(m_fontColorDisabled.toRgba()); else if (m_fontColorChecked && m_checked) guiContext.setFontColor(m_fontColorChecked.value().toRgba()); else guiContext.setFontColor(m_fontColor.toRgba()); - guiContext.setFontMode(FontMode::Shadow); guiContext.renderInterfaceText(m_text, {textPosition, m_hTextAnchor, VerticalAnchor::VMidAnchor}); - guiContext.setFontMode(FontMode::Normal); - guiContext.setFontProcessingDirectives(""); + guiContext.clearTextStyle(); } } @@ -323,11 +316,11 @@ void ButtonWidget::setText(String const& text) { } void ButtonWidget::setFontSize(int size) { - m_fontSize = size; + m_textStyle.fontSize = size; } void ButtonWidget::setFontDirectives(String directives) { - m_fontDirectives = directives; + m_textStyle.directives = directives; } void ButtonWidget::setTextOffset(Vec2I textOffset) { @@ -339,7 +332,7 @@ void ButtonWidget::setTextAlign(HorizontalAnchor hAnchor) { } void ButtonWidget::setFontColor(Color color) { - m_fontColor = color; + m_textStyle.color = (m_fontColor = color).toRgba(); } void ButtonWidget::setFontColorDisabled(Color color) { diff --git a/source/windowing/StarButtonWidget.hpp b/source/windowing/StarButtonWidget.hpp index 69335d8..bace69b 100644 --- a/source/windowing/StarButtonWidget.hpp +++ b/source/windowing/StarButtonWidget.hpp @@ -121,9 +121,7 @@ protected: Vec2I m_pressedOffset; Vec2U m_buttonBoundSize; - int m_fontSize; - String m_font; - String m_fontDirectives; + TextStyle m_textStyle; String m_text; Vec2I m_textOffset; diff --git a/source/windowing/StarCanvasWidget.cpp b/source/windowing/StarCanvasWidget.cpp index 16ec994..b4a3cbf 100644 --- a/source/windowing/StarCanvasWidget.cpp +++ b/source/windowing/StarCanvasWidget.cpp @@ -68,7 +68,18 @@ void CanvasWidget::drawTriangles(List> const& triangl } void CanvasWidget::drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String font, String processingDirectives) { - m_renderOps.append(make_tuple(std::move(s), std::move(position), fontSize, color, mode, lineSpacing, std::move(font), std::move(processingDirectives))); + TextStyle style; + style.fontSize = fontSize; + style.color = color; + style.shadow = fontModeToColor(mode).toRgba(); + style.lineSpacing = lineSpacing; + style.font = font; + style.directives = processingDirectives; + m_renderOps.append(make_tuple(std::move(s), std::move(position), std::move(style))); +} + +void CanvasWidget::drawText(String s, TextPositioning position, TextStyle style) { + m_renderOps.append(make_tuple(std::move(s), std::move(position), std::move(style))); } Vec2I CanvasWidget::mousePosition() const { @@ -145,7 +156,7 @@ void CanvasWidget::renderImpl() { if (auto args = op.ptr()) tupleUnpackFunction(bind(&CanvasWidget::renderTriangles, this, renderingOffset, _1, _2), *args); if (auto args = op.ptr()) - 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> const& poly, Vec4B const& color = Vec4B(255, 255, 255, 255)); void drawText(String s, TextPositioning position, unsigned fontSize, Vec4B const& color = Vec4B(255, 255, 255, 255), FontMode mode = FontMode::Normal, float lineSpacing = Star::DefaultLineSpacing, String font = "", String processingDirectives = ""); + void drawText(String s, TextPositioning position, TextStyle style); protected: void renderImpl() override; @@ -79,7 +80,7 @@ protected: void renderRect(Vec2F const& renderingOffset, RectF const& coords, Vec4B const& color); void renderPoly(Vec2F const& renderingOffset, PolyF poly, Vec4B const& color, float lineWidth); void renderTriangles(Vec2F const& renderingOffset, List> const& triangles, Vec4B const& color); - void renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives); + void renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, TextStyle const& style); private: bool m_ignoreInterfaceScale; @@ -97,7 +98,7 @@ private: typedef tuple LineOp; typedef tuple PolyOp; typedef tuple>, Vec4B> TrianglesOp; - typedef tuple TextOp; + typedef tuple TextOp; typedef MVariant RenderOp; List m_renderOps; diff --git a/source/windowing/StarFuelWidget.cpp b/source/windowing/StarFuelWidget.cpp index d7028f8..8f2d93d 100644 --- a/source/windowing/StarFuelWidget.cpp +++ b/source/windowing/StarFuelWidget.cpp @@ -9,8 +9,8 @@ namespace Star { FuelWidget::FuelWidget() { auto assets = Root::singleton().assets(); - m_fontSize = assets->json("/interface.config:font.buttonSize").toInt(); - m_font = assets->json("/interface.config:font.defaultFont").toString(); + m_textStyle.fontSize = assets->json("/interface.config:font.buttonSize").toInt(); + m_textStyle.loadJson(assets->json("/interface.config:textStyle")); m_fuelLevel = 0; m_maxLevel = 0; @@ -72,8 +72,7 @@ void FuelWidget::renderImpl() { context()->drawInterfaceQuad("/interface/fuel/fuelgaugemarkings.png", shift(0, 1, entireTex), shift(0, 1, entirePosition)); auto& guiContext = GuiContext::singleton(); - guiContext.setFontSize(m_fontSize); - guiContext.setFont(m_font); + guiContext.setTextStyle(m_textStyle); if (m_potential != 0) { guiContext.setFontColor(Color::White.toRgba()); } else if (m_fuelLevel == 0) { diff --git a/source/windowing/StarFuelWidget.hpp b/source/windowing/StarFuelWidget.hpp index c057b9a..21f0f6c 100644 --- a/source/windowing/StarFuelWidget.hpp +++ b/source/windowing/StarFuelWidget.hpp @@ -30,8 +30,7 @@ protected: float m_pingTimeout; - unsigned m_fontSize; - String m_font; + TextStyle m_textStyle; private: }; diff --git a/source/windowing/StarGuiContext.cpp b/source/windowing/StarGuiContext.cpp index d1be94a..492f08e 100644 --- a/source/windowing/StarGuiContext.cpp +++ b/source/windowing/StarGuiContext.cpp @@ -377,6 +377,20 @@ void GuiContext::setDefaultFont() { textPainter()->setFont(""); } +TextStyle& GuiContext::setTextStyle(TextStyle const& textStyle, int pixelRatio) { + TextStyle& setStyle = textPainter()->setTextStyle(textStyle); + setStyle.fontSize *= pixelRatio; + return setStyle; +} + +TextStyle& GuiContext::setTextStyle(TextStyle const& textStyle) { + return setTextStyle(textStyle, interfaceScale()); +} + +void GuiContext::clearTextStyle() { + textPainter()->clearTextStyle(); +} + void GuiContext::setLineSpacing(float lineSpacing) { textPainter()->setLineSpacing(lineSpacing); } diff --git a/source/windowing/StarGuiContext.hpp b/source/windowing/StarGuiContext.hpp index ef14619..cb0051c 100644 --- a/source/windowing/StarGuiContext.hpp +++ b/source/windowing/StarGuiContext.hpp @@ -106,6 +106,9 @@ public: void setFontProcessingDirectives(String const& directives); void setFont(String const& font); void setDefaultFont(); + TextStyle& setTextStyle(TextStyle const& textStyle, int pixelRatio); + TextStyle& setTextStyle(TextStyle const& textStyle); + void clearTextStyle(); void setLineSpacing(float lineSpacing); void setDefaultLineSpacing(); diff --git a/source/windowing/StarItemSlotWidget.cpp b/source/windowing/StarItemSlotWidget.cpp index 93d95d3..4ba3425 100644 --- a/source/windowing/StarItemSlotWidget.cpp +++ b/source/windowing/StarItemSlotWidget.cpp @@ -14,16 +14,13 @@ ItemSlotWidget::ItemSlotWidget(ItemPtr const& item, String const& backingImage) : m_item(item), m_backingImage(backingImage) { m_drawBackingImageWhenFull = false; m_drawBackingImageWhenEmpty = true; - m_fontSize = 0; m_progress = 1; auto assets = Root::singleton().assets(); auto interfaceConfig = assets->json("/interface.config"); m_countPosition = TextPositioning(jsonToVec2F(interfaceConfig.get("itemCountRightAnchor")), HorizontalAnchor::RightAnchor); m_countFontMode = FontMode::Normal; - m_fontSize = interfaceConfig.query("font.itemSize").toInt(); - m_font = interfaceConfig.query("font.defaultFont").toString(); - m_fontColor = Color::rgb(jsonToVec3B(interfaceConfig.query("font.defaultColor"))); + m_textStyle = interfaceConfig.get("itemSlotTextStyle"); m_itemDraggableArea = jsonToRectI(interfaceConfig.get("itemDraggableArea")); m_durabilityOffset = jsonToVec2I(interfaceConfig.get("itemIconDurabilityOffset")); @@ -192,13 +189,10 @@ void ItemSlotWidget::renderImpl() { context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:{}", frame)), Vec2F(screenPosition())); if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something - context()->setFont(m_font); - context()->setFontSize(m_fontSize); - context()->setFontColor(m_fontColor.toRgba()); + context()->setTextStyle(m_textStyle); context()->setFontMode(m_countFontMode); context()->renderInterfaceText(toString(m_item->count()), m_countPosition.translated(Vec2F(screenPosition()))); - context()->setFontMode(FontMode::Normal); - context()->setDefaultFont(); + context()->clearTextStyle(); } } else if (m_drawBackingImageWhenEmpty && m_backingImage != "") { diff --git a/source/windowing/StarItemSlotWidget.hpp b/source/windowing/StarItemSlotWidget.hpp index aaa4c9c..3827048 100644 --- a/source/windowing/StarItemSlotWidget.hpp +++ b/source/windowing/StarItemSlotWidget.hpp @@ -56,9 +56,7 @@ private: Vec2I m_durabilityOffset; RectI m_itemDraggableArea; - int m_fontSize; - String m_font; - Color m_fontColor; + TextStyle m_textStyle; WidgetCallbackFunc m_callback; WidgetCallbackFunc m_rightClickCallback; diff --git a/source/windowing/StarLabelWidget.cpp b/source/windowing/StarLabelWidget.cpp index 35f8618..3c1be8e 100644 --- a/source/windowing/StarLabelWidget.cpp +++ b/source/windowing/StarLabelWidget.cpp @@ -10,17 +10,14 @@ LabelWidget::LabelWidget(String text, VerticalAnchor const& vAnchor, Maybe wrapWidth, Maybe lineSpacing) - : m_fontMode(FontMode::Normal), - m_color(color), - m_hAnchor(hAnchor), + : m_hAnchor(hAnchor), m_vAnchor(vAnchor), - m_wrapWidth(std::move(wrapWidth)), - m_lineSpacing(std::move(lineSpacing)) { + m_wrapWidth(std::move(wrapWidth)) { auto assets = Root::singleton().assets(); - auto fontConfig = assets->json("/interface.config:font"); - m_fontSize = fontConfig.getInt("baseSize"); - m_processingDirectives = fontConfig.getString("defaultDirectives"); - m_font = fontConfig.queryString("defaultFont", ""); + m_style = assets->json("/interface.config:labelTextStyle"); + m_style.color = color.toRgba(); + if (lineSpacing) + m_style.lineSpacing = *lineSpacing; setText(std::move(text)); } @@ -38,16 +35,16 @@ void LabelWidget::setText(String newText) { } void LabelWidget::setFontSize(int fontSize) { - m_fontSize = fontSize; + m_style.fontSize = fontSize; updateTextRegion(); } void LabelWidget::setFontMode(FontMode fontMode) { - m_fontMode = fontMode; + m_style.shadow = fontModeToColor(fontMode).toRgba(); } void LabelWidget::setColor(Color newColor) { - m_color = std::move(newColor); + m_style.color = newColor.toRgba(); } void LabelWidget::setAnchor(HorizontalAnchor hAnchor, VerticalAnchor vAnchor) { @@ -62,12 +59,12 @@ void LabelWidget::setWrapWidth(Maybe wrapWidth) { } void LabelWidget::setLineSpacing(Maybe lineSpacing) { - m_lineSpacing = std::move(lineSpacing); + m_style.lineSpacing = lineSpacing.value(DefaultLineSpacing); updateTextRegion(); } void LabelWidget::setDirectives(String const& directives) { - m_processingDirectives = directives; + m_style.directives = directives; updateTextRegion(); } @@ -76,6 +73,12 @@ void LabelWidget::setTextCharLimit(Maybe charLimit) { updateTextRegion(); } +void LabelWidget::setTextStyle(TextStyle const& textStyle) { + m_style = textStyle; + updateTextRegion(); +} + + RectI LabelWidget::relativeBoundRect() const { return RectI(m_textRegion).translated(relativePosition()); } @@ -85,41 +88,14 @@ RectI LabelWidget::getScissorRect() const { } void LabelWidget::renderImpl() { - context()->setFont(m_font); - context()->setFontSize(m_fontSize); - context()->setFontMode(m_fontMode); - context()->setFontColor(m_color.toRgba()); - context()->setFontProcessingDirectives(m_processingDirectives); - - if (m_lineSpacing) - context()->setLineSpacing(*m_lineSpacing); - else - context()->setDefaultLineSpacing(); - + context()->setTextStyle(m_style); context()->renderInterfaceText(m_text, {Vec2F(screenPosition()), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit}); - - context()->setDefaultFont(); - context()->setFontMode(FontMode::Normal); - context()->setFontProcessingDirectives(""); - context()->setDefaultLineSpacing(); } void LabelWidget::updateTextRegion() { - context()->setFontSize(m_fontSize); - context()->setFontColor(m_color.toRgba()); - context()->setFontProcessingDirectives(m_processingDirectives); - context()->setFont(m_font); - if (m_lineSpacing) - context()->setLineSpacing(*m_lineSpacing); - else - context()->setDefaultLineSpacing(); - + context()->setTextStyle(m_style); m_textRegion = RectI(context()->determineInterfaceTextSize(m_text, {Vec2F(), m_hAnchor, m_vAnchor, m_wrapWidth, m_textCharLimit})); setSize(m_textRegion.size()); - - context()->setDefaultFont(); - context()->setFontProcessingDirectives(""); - context()->setDefaultLineSpacing(); } } diff --git a/source/windowing/StarLabelWidget.hpp b/source/windowing/StarLabelWidget.hpp index a25e531..da65fb3 100644 --- a/source/windowing/StarLabelWidget.hpp +++ b/source/windowing/StarLabelWidget.hpp @@ -25,6 +25,7 @@ public: void setLineSpacing(Maybe lineSpacing); void setDirectives(String const& directives); void setTextCharLimit(Maybe charLimit); + void setTextStyle(TextStyle const& style); RectI relativeBoundRect() const override; @@ -36,13 +37,9 @@ private: void updateTextRegion(); String m_text; - int m_fontSize; - FontMode m_fontMode; - Color m_color; + TextStyle m_style; HorizontalAnchor m_hAnchor; VerticalAnchor m_vAnchor; - String m_processingDirectives; - String m_font; Maybe m_wrapWidth; Maybe m_lineSpacing; Maybe m_textCharLimit; diff --git a/source/windowing/StarPane.cpp b/source/windowing/StarPane.cpp index 36e8435..9e1cd4f 100644 --- a/source/windowing/StarPane.cpp +++ b/source/windowing/StarPane.cpp @@ -32,8 +32,7 @@ Pane::Pane() { m_hasDisplayed = false; auto assets = Root::singleton().assets(); - m_fontSize = assets->json("/interface.config:font.baseSize").toInt(); - m_font = assets->json("/interface.config:font.defaultFont").toString(); + m_textStyle = assets->json("/interface.config:paneTextStyle"); m_iconOffset = jsonToVec2I(assets->json("/interface.config:paneIconOffset")); m_titleOffset = jsonToVec2I(assets->json("/interface.config:paneTitleOffset")); m_subTitleOffset = jsonToVec2I(assets->json("/interface.config:paneSubTitleOffset")); @@ -436,15 +435,13 @@ void Pane::renderImpl() { m_context->resetInterfaceScissorRect(); } - m_context->setFont(m_font); - m_context->setFontSize(m_fontSize); + m_context->setTextStyle(m_textStyle); m_context->setFontColor(m_titleColor.toRgba()); m_context->setFontMode(FontMode::Shadow); m_context->renderInterfaceText(m_title, {headerPos + Vec2F(m_titleOffset)}); m_context->setFontColor(m_subTitleColor.toRgba()); m_context->renderInterfaceText(m_subTitle, {headerPos + Vec2F(m_subTitleOffset)}); - m_context->setFontMode(FontMode::Normal); - m_context->setDefaultFont(); + m_context->clearTextStyle(); } } diff --git a/source/windowing/StarPane.hpp b/source/windowing/StarPane.hpp index 1d1fc2a..c5ed505 100644 --- a/source/windowing/StarPane.hpp +++ b/source/windowing/StarPane.hpp @@ -117,8 +117,7 @@ protected: WidgetPtr m_icon; String m_title; String m_subTitle; - String m_font; - unsigned m_fontSize; + TextStyle m_textStyle; Vec2I m_iconOffset; Vec2I m_titleOffset; Vec2I m_subTitleOffset; diff --git a/source/windowing/StarTextBoxWidget.cpp b/source/windowing/StarTextBoxWidget.cpp index 984021d..41b4696 100644 --- a/source/windowing/StarTextBoxWidget.cpp +++ b/source/windowing/StarTextBoxWidget.cpp @@ -23,14 +23,11 @@ TextBoxWidget::TextBoxWidget(String const& startingText, String const& hint, Wid m_overfillMode = true; m_maxWidth = assets->json("/interface.config:textBoxDefaultWidth").toInt(); - auto fontConfig = assets->json("/interface.config:font"); - m_fontSize = fontConfig.getInt("baseSize"); - m_processingDirectives = fontConfig.getString("defaultDirectives"); - m_font = fontConfig.queryString("defaultFont", ""); - m_color = Color::rgb(jsonToVec3B(fontConfig.getArray("defaultColor"))); + auto fontConfig = assets->json("/interface.config:textBoxTextStyle"); + m_textStyle = fontConfig; // Meh, padding is hard-coded here - setSize({m_maxWidth + 6, m_fontSize + 2}); + setSize({m_maxWidth + 6, m_textStyle.fontSize + 2}); m_cursorHoriz = jsonToVec2I(assets->json("/interface.config:textboxCursorHorizontal")); m_cursorVert = jsonToVec2I(assets->json("/interface.config:textboxCursorVertical")); } @@ -48,20 +45,17 @@ void TextBoxWidget::renderImpl() { else if (m_hAnchor == HorizontalAnchor::RightAnchor) pos += Vec2F(size()[0], 0); - context()->setFont(m_font); + context()->setTextStyle(m_textStyle); if ((m_maxWidth != -1) && m_overfillMode) { - context()->setFontSize(m_fontSize); int shift = std::max(0, getCursorOffset() - m_maxWidth); pos += Vec2F(-shift, 0); } - context()->setFontSize(m_fontSize); - context()->setFontProcessingDirectives(m_processingDirectives); if (m_text.empty()) { - context()->setFontColor(m_color.mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba()); + context()->setFontColor(Color::rgba(m_textStyle.color).mix(Color::rgbf(0.3f, 0.3f, 0.3f), 0.8f).mix(Color::rgbf(0.0f, 0.0f, 1.0f), blueRate).toRgba()); context()->renderInterfaceText(m_hint, {pos, m_hAnchor, m_vAnchor}); } else { - context()->setFontColor(m_color.mix(Color::rgbf(0, 0, 1), blueRate).toRgba()); + context()->setFontColor(Color::rgba(m_textStyle.color).mix(Color::rgbf(0, 0, 1), blueRate).toRgba()); if (m_textHidden) { String hiddenText('*', m_text.length()); context()->renderInterfaceText(hiddenText, { pos, m_hAnchor, m_vAnchor }); @@ -69,22 +63,21 @@ void TextBoxWidget::renderImpl() { else context()->renderInterfaceText(m_text, { pos, m_hAnchor, m_vAnchor }); } - context()->setDefaultFont(); - context()->setFontProcessingDirectives(""); - context()->setFontColor(Vec4B::filled(255)); + context()->clearTextStyle(); if (hasFocus()) { // render cursor float cc = 0.6f + 0.4f * std::sin((double)Time::monotonicMilliseconds() / 300.0); Color cursorColor = Color::rgbf(cc, cc, cc); + float fontSize = m_textStyle.fontSize; context()->drawInterfaceLine( - pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[0]), - pos + Vec2F(getCursorOffset(), m_fontSize * m_cursorVert[1]), + pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[0]), + pos + Vec2F(getCursorOffset(), fontSize * m_cursorVert[1]), cursorColor.toRgba()); context()->drawInterfaceLine( - pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[0], m_fontSize * m_cursorVert[0]), - pos + Vec2F(getCursorOffset() + m_fontSize * m_cursorHoriz[1], m_fontSize * m_cursorVert[0]), + pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[0], fontSize * m_cursorVert[0]), + pos + Vec2F(getCursorOffset() + fontSize * m_cursorHoriz[1], fontSize * m_cursorVert[0]), cursorColor.toRgba()); } @@ -94,8 +87,7 @@ void TextBoxWidget::renderImpl() { int TextBoxWidget::getCursorOffset() { // horizontal only float scale; - context()->setFont(m_font); - context()->setFontSize(m_fontSize); + context()->setTextStyle(m_textStyle); if (m_hAnchor == HorizontalAnchor::LeftAnchor) { scale = 1.0; } else if (m_hAnchor == HorizontalAnchor::HMidAnchor) { @@ -158,7 +150,7 @@ String const& TextBoxWidget::getText() const { return m_text; } -bool TextBoxWidget::setText(String const& text, bool callback) { +bool TextBoxWidget::setText(String const& text, bool callback, bool moveCursor) { if (m_text == text) return true; @@ -166,7 +158,10 @@ bool TextBoxWidget::setText(String const& text, bool callback) { return false; m_text = text; - m_cursorOffset = m_text.size(); + size_t size = m_text.size(); + if (moveCursor || m_cursorOffset > size) + m_cursorOffset = size; + m_repeatCode = SpecialRepeatKeyCodes::None; if (callback) m_callback(this); @@ -190,20 +185,20 @@ void TextBoxWidget::setRegex(String const& regex) { } void TextBoxWidget::setColor(Color const& color) { - m_color = color; + m_textStyle.color = color.toRgba(); } void TextBoxWidget::setDirectives(String const& directives) { - m_processingDirectives = directives; + m_textStyle.directives = directives; } void TextBoxWidget::setFontSize(int fontSize) { - m_fontSize = fontSize; + m_textStyle.fontSize = fontSize; } void TextBoxWidget::setMaxWidth(int maxWidth) { m_maxWidth = maxWidth; - setSize({m_maxWidth + 6, m_fontSize + 2}); + setSize({m_maxWidth + 6, m_textStyle.fontSize + 2}); } void TextBoxWidget::setOverfillMode(bool overtype) { @@ -426,8 +421,7 @@ bool TextBoxWidget::newTextValid(String const& text) const { if (!text.regexMatch(m_regex)) return false; if ((m_maxWidth != -1) && !m_overfillMode) { - context()->setFont(m_font); - context()->setFontSize(m_fontSize); + context()->setTextStyle(m_textStyle); return context()->stringInterfaceWidth(text) <= m_maxWidth; } return true; diff --git a/source/windowing/StarTextBoxWidget.hpp b/source/windowing/StarTextBoxWidget.hpp index 12294d5..5fed88e 100644 --- a/source/windowing/StarTextBoxWidget.hpp +++ b/source/windowing/StarTextBoxWidget.hpp @@ -14,7 +14,7 @@ public: virtual void update(float dt) override; String const& getText() const; - bool setText(String const& text, bool callback = true); + bool setText(String const& text, bool callback = true, bool moveCursor = true); bool getHidden() const; void setHidden(bool hidden); @@ -64,10 +64,7 @@ private: String m_regex; HorizontalAnchor m_hAnchor; VerticalAnchor m_vAnchor; - Color m_color; - String m_processingDirectives; - String m_font; - int m_fontSize; + TextStyle m_textStyle; int m_maxWidth; int m_cursorOffset; bool m_isHover;