rodecomp/Source/UI/Edit/UIEditCtrl.cpp
ragnarok-clanker e168b4e4a6 Restore translation-unit ownership and publish verified source corrections
Owner-authorized source migration: 31 helper-only translation units were merged into their callers or existing inline headers; both build manifests updated. Includes the preserved source payload, Lua configuration matching wowdecomp, and exact malloca expansion without diagnostic pragmas.

449 native units compiled locally and 449 staged units compiled by the pinned server toolchain. No previously byte-matched function regressed. Compiler mapping diagnostics remain recorded as needs_cleanup, not accepted or peer-confirmed. Existing independent review records are preserved. README.md is intentionally unchanged.
2026-10-25 15:42:21 +00:00

495 lines
14 KiB
C++

#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 <algorithm>
#include <cstdint>
#include <cstring>
#include <list>
#include <string>
#include <windows.h>
#include <timeapi.h>
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<UIWindow*>& 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<int>(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<int>(displayed.size()));
if (this->secondColor != 0xFFFFFFFFu) {
context.SetColor(this->secondColor);
context.Draw((x - 1), this->y, value.c_str(), static_cast<int>(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<unsigned>(std::min(this->anchor, this->cursor));
unsigned end = static_cast<unsigned>(std::max(this->anchor, this->cursor));
if (this->mask != 0) {
display.assign(this->text.size(), static_cast<char>(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<unsigned>(std::strlen(prefix));
begin += prefixBytes;
end += prefixBytes + static_cast<unsigned>(this->candidateCursor);
}
const int left = begin == 0 ? 0 : measure(display.c_str(), static_cast<int>(begin));
const int right = end == 0 ? 0 : measure(display.c_str(), static_cast<int>(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<char>(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:
explicit LinkLayout(UIEditCtrl& control) : m_control(control) {
}
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<unsigned char>(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<unsigned char>(this->borderRed) | (static_cast<unsigned char>(this->borderGreen) << 8) |
(static_cast<unsigned char>(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<const UIWindow*>(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<const char*>(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<UIWindow*>(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<UIWindow*>(this->parent);
int left = 0, top = 0;
for (const UIWindow* window = this; window; window = static_cast<const UIWindow*>(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<int>(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<UIWindow*>(this->parent)->Slot35(value);
}
}