#include "Client/Input/InputCharset.h" #include "Client/Input/TextInputBuffer.h" #include "Network/TextEncoding.h" #include "Render/DeviceContext/DeviceContext.h" #include "Render/Text/TextContext.h" #include "Resource/Bitmap/BitmapImage.h" #include "UI/Edit/EditTextState.h" #include "UI/Edit/EditViewport.h" #include "UI/Window/InputFocus.h" #include "UI/Buttons/UIButton.h" #include "UI/Edit/UIEditCtrl.h" #include "UI/Window/WindowManagerWindows.h" #include "UI/Window/UIWindow.h" #include #include #include #include #include #include #include namespace { class EditLinkLayout { public: virtual void Update() = 0; protected: ~EditLinkLayout() = default; }; struct EditPainting { std::string& text; const int& anchor; const int& cursor; const int& mask; const int& firstVisible; const int& y; const int& invalidatesParent; const std::uint32_t& color; const std::uint32_t& secondColor; const bool& focused; const bool& chatInputFocused; const bool& caretEnabled; const std::uint32_t& tick; const int& candidateCursor; const int& width; const int& height; const int& screenWidth; const int& screenHeight; TextInputBuffer& input; const InputCharset& charset; EditViewport& viewport; CDC& surface; FontCache& fonts; const TextLocale& locale; const BitmapImage& colorChip; UIWindow* const& parent; UIWindow* const& candidateWindow; UIWindow* const& readingWindow; const int& absoluteX; const int& absoluteY; const std::list& linkWindows; EditLinkLayout& links; bool alternate = false; const std::string* placeholder = nullptr; std::uint32_t placeholderColor = 0; bool placeholderEnabled = false; void Draw(); }; // INLINED void EditPainting::Draw() { TextContext context(this->surface, this->fonts, this->locale); context.SetFont(0, 12, 0, 0); const auto measure = [&](const char* text, int bytes) { return context.Measure(text, bytes ? bytes : static_cast(std::strlen(text))).cx; }; int x = 3; if (this->invalidatesParent == 0 && this->firstVisible != 0) { x = (3 - measure(this->text.c_str(), this->firstVisible)); } const auto draw = [&](const std::string& value) { const bool placeholder = this->alternate && !this->focused && this->placeholderEnabled && this->secondColor == 0xFFFFFFFFu; context.SetColor(placeholder ? this->placeholderColor : this->color); const std::string& displayed = placeholder && value.empty() ? *this->placeholder : value; context.Draw(x, this->y, displayed.c_str(), static_cast(displayed.size())); if (this->secondColor != 0xFFFFFFFFu) { context.SetColor(this->secondColor); context.Draw((x - 1), this->y, value.c_str(), static_cast(value.size())); } }; const auto highlight = [&](int left, int extent) { const int start = std::max(left, 3); const int adjusted = left < 3 ? (extent + (left - 3)) : extent; if (start < this->width && adjusted > 0) { std::uint32_t pixel = 0x00FF0000u; if (this->colorChip.width > 6 && this->colorChip.height > 2) { pixel = this->colorChip.pixels[2 * this->colorChip.width + 6]; } const std::uint32_t color = ((pixel >> 16) & 255) | (pixel & 0xFF00) | ((pixel & 255) << 16); RECT rectangle{start, 0, std::min((start + adjusted), this->width), this->height}; this->surface.Fill(&rectangle, color); } }; const auto moveCandidate = [&](UIWindow* window, int offset) { if (!window) { return; } int left = (offset + this->absoluteX); int top = ((this->height + 6) + this->absoluteY); if ((window->geometry.width + left) > this->screenWidth) { left = (this->screenWidth - window->geometry.width); } if ((window->geometry.height + top) > this->screenHeight) { top = (((top - 12) - this->height) - window->geometry.height); } window->Move(left, top); }; std::string display; if (this->focused) { this->viewport.Synchronize(); unsigned begin = static_cast(std::min(this->anchor, this->cursor)); unsigned end = static_cast(std::max(this->anchor, this->cursor)); if (this->mask != 0) { display.assign(this->text.size(), static_cast(this->mask)); } else { const char* prefix = this->alternate ? this->input.candidate.c_str() : this->charset.Prefix(false); display = prefix; display += this->text; const auto prefixBytes = static_cast(std::strlen(prefix)); begin += prefixBytes; end += prefixBytes + static_cast(this->candidateCursor); } const int left = begin == 0 ? 0 : measure(display.c_str(), static_cast(begin)); const int right = end == 0 ? 0 : measure(display.c_str(), static_cast(end)); if (this->input.cursor != this->input.anchor) { highlight((x + left), (right - left)); draw(display); } else if (this->candidateCursor != 0) { moveCandidate(this->candidateWindow, left); moveCandidate(this->readingWindow, right); if (((this->tick / 500u) & 1u) == 0) { highlight((x + left), std::max((right - left), 2)); } draw(display); } else { draw(display); if (((this->tick / 500u) & 1u) == 0 && (this->alternate ? this->caretEnabled : (!this->chatInputFocused || this->caretEnabled))) { context.SetColor(this->color); context.Draw(((x + left) - 2), this->y, "|", 1); } } } else { if (this->mask != 0) { display.assign(this->text.size(), static_cast(this->mask)); } else { display = this->text; } draw(display); if (this->invalidatesParent != 0 && this->parent) { this->parent->Invalidate(); } } if (std::any_of(this->linkWindows.begin(), this->linkWindows.end(), [](const UIWindow* window) { return window->geometry.id == 438; })) { this->links.Update(); } } class LinkLayout : public EditLinkLayout { public: // INLINED explicit LinkLayout(UIEditCtrl& control) : m_control(control) { } // INLINED void Update() override { this->m_control.UpdateLinks(); } private: UIEditCtrl& m_control; }; } // OFFSET: 0x0084FAE0 // FUNCTION: fn_10663 UIEditCtrl::UIEditCtrl(UIWindowContext& windows, UIEditContext& edit) : UIWindow(windows, 0), anchor(0), cursor(0), mask(0), maximum(255), flat(1), red(255), green(255), blue(255), borderRed(0), borderGreen(0), borderBlue(0), textColor(0), secondColor(0xFFFFFFFF), firstVisible(0), textY(0), invalidatesParent(0), disabled(0), unknown_00F0(0), topPadding(0), preserveInput(0), m_edit(&edit) { this->text.reserve(255); } // OFFSET: 0x008517E0 // OFFSET: 0x00852980 // FUNCTION: fn_10694 UIEditCtrl::~UIEditCtrl() = default; // OFFSET: 0x005C3210 // FUNCTION: fn_03552 bool UIEditCtrl::Slot05() { return this->disabled == 0; } // OFFSET: 0x0085CCA0 // FUNCTION: fn_10807 void UIEditCtrl::Paint() { const auto component = [&](int value) { return static_cast(this->disabled != 0 && value > 30 ? value - 30 : value); }; const unsigned color = component(this->red) | (component(this->green) << 8) | (component(this->blue) << 16); this->textY = 0; if (this->geometry.surface) { if (this->flat == 0) { RECT rectangle{0, 0, this->geometry.width, this->geometry.height}; const unsigned border = static_cast(this->borderRed) | (static_cast(this->borderGreen) << 8) | (static_cast(this->borderBlue) << 16); this->geometry.surface->Fill(&rectangle, border); rectangle = {1, 1, this->geometry.width - 1, this->geometry.height - 1}; this->geometry.surface->Fill(&rectangle, color); } else { this->geometry.surface->Fill(nullptr, color); } } this->textY = this->topPadding + 2; this->PaintText(); } // OFFSET: 0x0085AAB0 // FUNCTION: fn_10783 void UIEditCtrl::Slot23() { this->m_edit->input.CancelComposition(); this->m_edit->input.CancelComposition(); this->m_edit->input.Insert(this->text.c_str()); this->m_edit->password = this->mask != 0; this->SelectAll(); } // OFFSET: 0x0085E3B0 // FUNCTION: fn_10826 void UIEditCtrl::Slot24(int) { this->Synchronize(); this->Invalidate(); } // OFFSET: 0x0085EC20 // FUNCTION: fn_10842 void UIEditCtrl::Slot25(int, int) { if (this->disabled == 0) { this->m_edit->focus.Set(this); } } // OFFSET: 0x0085F390 // FUNCTION: fn_10860 void UIEditCtrl::Slot28(int x, int y) { if (this->disabled != 0) { return; } this->Slot27(x, y); if (!this->tooltip.empty()) { int left = 10, top = -20; for (const UIWindow* window = this; window; window = static_cast(window->parent)) { left += window->geometry.x; top += window->geometry.y; } this->m_edit->buttons.services.Tooltip(this->tooltip.c_str(), left, top, -1, false, true); } } // OFFSET: 0x008641C0 // FUNCTION: fn_10914 int UIEditCtrl::SendMsg(UIWindow* sender, int message, std::intptr_t a, std::intptr_t b, std::intptr_t c, std::intptr_t d) { if (message == 153) { this->InsertLink(reinterpret_cast(a)); } else if (message == 98 && c > 0) { UIWindow* window = this->m_edit->windows.EnsureWindow(12); if (window) { window->SendMsg(nullptr, 24, c, 0, 0, 0); } } UIWindow::SendMsg(sender, message, a, b, c, d); if (message == 0 && this->preserveInput == 0) { this->m_edit->input.CancelComposition(); } return 0; } // OFFSET: 0x005C44E0 // FUNCTION: fn_03561 int UIEditCtrl::Slot46() { return 2; } // OFFSET: 0x008680D0 // FUNCTION: fn_10952 void UIEditCtrl::SetText(const char* value) { this->text.assign(value, std::strlen(value)); this->Trim(); if (this->m_edit->focus.window == this) { this->m_edit->input.CancelComposition(); this->m_edit->input.CancelComposition(); this->m_edit->input.Insert(this->text.c_str()); } this->SelectAll(); if (this->parent) { static_cast(this->parent)->Invalidate(); } } // OFFSET: 0x0085A590 void UIEditCtrl::SelectAll() { const bool focused = this->m_edit->focus.window == this; EditTextState state{this->text, this->anchor, this->cursor, this->maximum, focused, this->m_edit->input, g_textCodePage}; state.SelectAll(); } // OFFSET: 0x00856980 // FUNCTION: fn_10755 void UIEditCtrl::PaintText() { this->PaintTextWithPolicy(false, nullptr, 0, false); } // INLINED void UIEditCtrl::PaintTextWithPolicy(bool alternate, const std::string* placeholder, std::uint32_t placeholderColor, bool placeholderEnabled) { const bool focused = this->m_edit->focus.window == this; EditTextState state{this->text, this->anchor, this->cursor, this->maximum, focused, this->m_edit->input, g_textCodePage}; EditViewport viewport{ this->text, this->anchor, this->cursor, this->firstVisible, this->geometry.width, g_textCodePage, this->m_edit->input, state, *this->geometry.surface, this->m_edit->buttons.fonts, this->m_edit->buttons.locale }; UIWindow* parent = static_cast(this->parent); int left = 0, top = 0; for (const UIWindow* window = this; window; window = static_cast(window->parent)) { left += window->geometry.x; top += window->geometry.y; } const bool chat = this->m_edit->input.chatInput == this || this->m_edit->otherChatInput == this; const DWORD tick = timeGetTime(); LinkLayout layout(*this); EditPainting painting{ this->text, this->anchor, this->cursor, this->mask, this->firstVisible, this->textY, this->invalidatesParent, this->textColor, this->secondColor, focused, chat, this->m_edit->input.suggestionsEnabled, tick, this->m_edit->candidateCursor, this->geometry.width, this->geometry.height, this->m_edit->screenWidth, this->m_edit->screenHeight, this->m_edit->input, this->m_edit->charset, viewport, *this->geometry.surface, this->m_edit->buttons.fonts, this->m_edit->buttons.locale, this->m_edit->colorChip, parent, this->m_edit->candidateWindow, this->m_edit->readingWindow, left, top, this->links, layout }; painting.alternate = alternate; painting.placeholder = placeholder; painting.placeholderColor = placeholderColor; painting.placeholderEnabled = placeholderEnabled; painting.Draw(); } // OFFSET: 0x00862FB0 void UIEditCtrl::Synchronize() { const bool focused = this->m_edit->focus.window == this; EditTextState state{this->text, this->anchor, this->cursor, this->maximum, focused, this->m_edit->input, g_textCodePage}; EditViewport viewport{ this->text, this->anchor, this->cursor, this->firstVisible, this->geometry.width, g_textCodePage, this->m_edit->input, state, *this->geometry.surface, this->m_edit->buttons.fonts, this->m_edit->buttons.locale }; viewport.Synchronize(); } // OFFSET: 0x00868450 void UIEditCtrl::Trim() { const bool focused = this->m_edit->focus.window == this; EditTextState state{this->text, this->anchor, this->cursor, this->maximum, focused, this->m_edit->input, g_textCodePage}; state.Trim(); } // OFFSET: 0x00858D50 // FUNCTION: fn_10768 int UIEditCtrl::Length() const { return static_cast(this->m_edit->focus.window == this ? this->m_edit->input.text.size() : this->text.size()); } // OFFSET: 0x00858D20 // FUNCTION: fn_10767 const char* UIEditCtrl::Text() { if (this->m_edit->focus.window == this) { this->Synchronize(); } return this->text.c_str(); } // OFFSET: 0x00865DD0 // FUNCTION: fn_10937 void UIEditCtrl::Background(int red, int green, int blue) { this->red = red; this->green = green; this->blue = blue; this->Invalidate(); } // OFFSET: 0x00868570 // FUNCTION: fn_10960 void UIEditCtrl::TextColor(std::uint32_t primary, std::uint32_t secondary) { this->secondColor = secondary; this->textColor = primary; this->Invalidate(); } // INLINED UIEditCtrlWheel::UIEditCtrlWheel(UIWindowContext& windows, UIEditContext& edit) : UIEditCtrl(windows, edit) { } // OFFSET: 0x009701B0 UIEditCtrlWheel::~UIEditCtrlWheel() = default; // OFFSET: 0x0098A240 // FUNCTION: fn_13408 void UIEditCtrlWheel::Slot35(int value) { if (this->parent) { static_cast(this->parent)->Slot35(value); } }