Skip to content

Commit adfe1b3

Browse files
try(widget): 尝试保留便签手动滚动(编译通过,待验证) / Try preserving sticky-note manual scrolling (build passed, validation pending)
中文: - 仅在光标、选区或输入内容变化后执行一次光标可见性调整。 - 多行输入框发生手动滚轮滚动后保留用户偏移,后续编辑时恢复光标跟随。 - 增加光标跟随请求状态的规则测试。 English: - Adjust caret visibility once after caret, selection, or input content changes. - Preserve the user's wheel offset in multiline inputs and resume caret following after later edits. - Add rule coverage for caret-follow request state. 验证 / Validation: - cmake --build .build --config Release --target SnowDesktopWidgetTextInputRulesTests(通过 / passed) - .build\\Release\\SnowDesktopWidgetTextInputRulesTests.exe(通过 / passed) - scripts\\build.bat(未运行:待执行完整 Release 构建 / not run: full Release build pending) - 便签末尾光标滚动场景(未运行:待宿主交互验证 / not run: host interaction validation pending) 限制 / Limitations: - 尚未完成完整宿主构建与便签实际交互验证 / Full host build and live sticky-note interaction are not yet verified
1 parent 36fcda1 commit adfe1b3

5 files changed

Lines changed: 92 additions & 1 deletion

File tree

‎src/widget_engine.cpp‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7920,7 +7920,9 @@ static int lua_UiTextArea(lua_State* L)
79207920
focusedLayout->HitTestTextPosition(
79217921
hitPosition, trailing, &caretX, &caretY,
79227922
&caretMetrics));
7923-
if (hasCaret && s && s->engine)
7923+
if (hasCaret && s && s->engine &&
7924+
s->engine->RuntimeConsumeHostInputCaretVisibilityRequest(
7925+
BoundWidgetId(L), id))
79247926
{
79257927
const float caretTop = padding + caretY;
79267928
const float caretBottom = caretTop +
@@ -26483,6 +26485,7 @@ void WidgetEngine::RuntimeRegisterHostControl(const std::wstring& widgetId,
2648326485
focusedHostInput_.cursor = *cursor;
2648426486
focusedHostInput_.compositionText.clear();
2648526487
focusedHostInput_.compositionCursor = 0;
26488+
focusedHostInput_.caretVisibility.Request();
2648626489
}
2648726490
}
2648826491
}
@@ -26613,6 +26616,7 @@ bool WidgetEngine::RuntimeFocusHostInput(const std::wstring& widgetId,
2661326616
found->selectAll ? 0 : storedText.size();
2661426617
focusedHostInput_.compositionText.clear();
2661526618
focusedHostInput_.compositionCursor = 0;
26619+
focusedHostInput_.caretVisibility.Request();
2661626620
}
2661726621
}
2661826622
focusedHostInput_.multiline = found->multiline;
@@ -26645,8 +26649,10 @@ bool WidgetEngine::RuntimeFocusHostInput(const std::wstring& widgetId,
2664526649
{
2664626650
focusedHostInput_.selectionAnchor = *anchor;
2664726651
focusedHostInput_.cursor = *cursor;
26652+
focusedHostInput_.caretVisibility.Request();
2664826653
}
2664926654
}
26655+
focusedHostInput_.caretVisibility.Request();
2665026656
if (hostInputFocusCallback_)
2665126657
hostInputFocusCallback_();
2665226658
return true;
@@ -26702,6 +26708,7 @@ bool WidgetEngine::RuntimeFocusHostInput(const std::wstring& widgetId,
2670226708
focusedHostInput_.step = found->step;
2670326709
focusedHostInput_.maximumUtf8Bytes =
2670426710
found->maximumUtf8Bytes;
26711+
focusedHostInput_.caretVisibility.Request();
2670526712
DispatchHostInputAction(widgetId, id,
2670626713
focusedHostInput_.focusAction, "focus",
2670726714
focusedHostInput_.text, false,
@@ -26913,6 +26920,17 @@ bool WidgetEngine::RuntimeFocusViewTarget(const std::wstring& widgetId,
2691326920
return true;
2691426921
}
2691526922

26923+
bool WidgetEngine::RuntimeConsumeHostInputCaretVisibilityRequest(
26924+
const std::wstring& widgetId, const std::string& id)
26925+
{
26926+
if (!focusedHostInput_.active ||
26927+
focusedHostInput_.widgetId != widgetId ||
26928+
focusedHostInput_.id != id ||
26929+
focusedHostInput_.surface != CurrentWidgetSurface(d2dState_))
26930+
return false;
26931+
return focusedHostInput_.caretVisibility.Consume();
26932+
}
26933+
2691626934
void WidgetEngine::BeginHostLogicalSlotPointer(
2691726935
LuaWidget& widget, int x, int y)
2691826936
{
@@ -27950,6 +27968,7 @@ bool WidgetEngine::HandleHostInputPointerMove(
2795027968
}
2795127969
focusedHostInput_.cursor = HitTestHostInputPosition(
2795227970
*control, widgetId, x, y);
27971+
focusedHostInput_.caretVisibility.Request();
2795327972
RuntimeInvalidateHost(widgetId);
2795427973
return true;
2795527974
}
@@ -27977,9 +27996,12 @@ bool WidgetEngine::HandleHostInputPointerUp(
2797727996
HostControlBelongsToSurface(item, surface);
2797827997
});
2797927998
if (control != controls.rend())
27999+
{
2798028000
focusedHostInput_.cursor =
2798128001
HitTestHostInputPosition(
2798228002
*control, widgetId, x, y);
28003+
focusedHostInput_.caretVisibility.Request();
28004+
}
2798328005
}
2798428006
DispatchHostInputSelectionChange(widgetId,
2798528007
focusedHostInput_.id,
@@ -28175,6 +28197,7 @@ bool WidgetEngine::RuntimePerformAccessibilityAction(
2817528197
focusedHostInput_.selectionAnchor = value.size();
2817628198
focusedHostInput_.compositionText.clear();
2817728199
focusedHostInput_.compositionCursor = 0;
28200+
focusedHostInput_.caretVisibility.Request();
2817828201
}
2817928202

2818028203
if (control->controlled)
@@ -28591,6 +28614,7 @@ bool WidgetEngine::SetHostInputComposition(
2859128614
focusedHostInput_.compositionText = text;
2859228615
focusedHostInput_.compositionCursor =
2859328616
std::min(cursor, text.size());
28617+
focusedHostInput_.caretVisibility.Request();
2859428618
RuntimeInvalidateHost(focusedHostInput_.widgetId);
2859528619
return true;
2859628620
}
@@ -28628,6 +28652,7 @@ bool WidgetEngine::CommitHostInputComposition(
2862828652
{
2862928653
focusedHostInput_.compositionText.clear();
2863028654
focusedHostInput_.compositionCursor = 0;
28655+
focusedHostInput_.caretVisibility.Request();
2863128656
RuntimeInvalidateHost(focusedHostInput_.widgetId);
2863228657
return true;
2863328658
}
@@ -28636,6 +28661,7 @@ bool WidgetEngine::CommitHostInputComposition(
2863628661
focusedHostInput_.cursor;
2863728662
focusedHostInput_.compositionText.clear();
2863828663
focusedHostInput_.compositionCursor = 0;
28664+
focusedHostInput_.caretVisibility.Request();
2863928665
if (focusedHostInput_.liveUpdate)
2864028666
{
2864128667
if (focusedHostInput_.controlled)
@@ -28660,6 +28686,7 @@ void WidgetEngine::ClearHostInputComposition()
2866028686
return;
2866128687
focusedHostInput_.compositionText.clear();
2866228688
focusedHostInput_.compositionCursor = 0;
28689+
focusedHostInput_.caretVisibility.Request();
2866328690
RuntimeInvalidateHost(focusedHostInput_.widgetId);
2866428691
}
2866528692

@@ -28714,6 +28741,7 @@ bool WidgetEngine::HandleHostInputChar(wchar_t ch)
2871428741
focusedHostInput_.cursor = nextCursor;
2871528742
focusedHostInput_.selectionAnchor =
2871628743
focusedHostInput_.cursor;
28744+
focusedHostInput_.caretVisibility.Request();
2871728745
if (focusedHostInput_.liveUpdate)
2871828746
{
2871928747
if (focusedHostInput_.controlled)
@@ -28794,6 +28822,7 @@ bool WidgetEngine::HandleHostInputKey(WPARAM key)
2879428822
if (!shift)
2879528823
focusedHostInput_.selectionAnchor =
2879628824
focusedHostInput_.cursor;
28825+
focusedHostInput_.caretVisibility.Request();
2879728826
dispatchSelectionChange();
2879828827
RuntimeInvalidateHost(focusedHostInput_.widgetId);
2879928828
return true;
@@ -28836,6 +28865,7 @@ bool WidgetEngine::HandleHostInputKey(WPARAM key)
2883628865
{
2883728866
focusedHostInput_.selectionAnchor = 0;
2883828867
focusedHostInput_.cursor = focusedHostInput_.text.size();
28868+
focusedHostInput_.caretVisibility.Request();
2883928869
dispatchSelectionChange();
2884028870
RuntimeInvalidateHost(focusedHostInput_.widgetId);
2884128871
return true;
@@ -29099,6 +29129,7 @@ bool WidgetEngine::HandleHostInputKey(WPARAM key)
2909929129

2910029130
if (changed)
2910129131
{
29132+
focusedHostInput_.caretVisibility.Request();
2910229133
if (focusedHostInput_.liveUpdate)
2910329134
{
2910429135
if (focusedHostInput_.controlled)
@@ -29373,6 +29404,13 @@ bool WidgetEngine::HandleHostUiPointer(const std::wstring& widgetId, int x, int
2937329404
if (!result.moved)
2937429405
continue;
2937529406
offset = result.offset;
29407+
if (focusedHostInput_.active &&
29408+
focusedHostInput_.widgetId == widgetId &&
29409+
focusedHostInput_.id == it->id &&
29410+
focusedHostInput_.surface == normalizedSurface)
29411+
{
29412+
focusedHostInput_.caretVisibility.PreserveManualScroll();
29413+
}
2937629414
const std::string targetKey = it->id;
2937729415
{
2937829416
snowdesktop::widget_runtime::WidgetTrustedGestureScope
@@ -29406,6 +29444,7 @@ bool WidgetEngine::HandleHostUiPointer(const std::wstring& widgetId, int x, int
2940629444
focusedHostInput_.selectionAnchor =
2940729445
focusedHostInput_.cursor;
2940829446
focusedHostInput_.pointerSelecting = true;
29447+
focusedHostInput_.caretVisibility.Request();
2940929448
RuntimeInvalidateHost(widgetId);
2941029449
}
2941129450
return true;

‎src/widget_engine.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,6 +1538,8 @@ class WidgetEngine
15381538
bool RuntimeGetFocusedHostInput(const std::wstring& widgetId, const std::string& id,
15391539
std::wstring& text, size_t& cursor, size_t& selectionAnchor,
15401540
std::wstring& compositionText, size_t& compositionCursor) const;
1541+
bool RuntimeConsumeHostInputCaretVisibilityRequest(
1542+
const std::wstring& widgetId, const std::string& id);
15411543
bool RuntimeIsWidgetSelected(const std::wstring& widgetId) const;
15421544
std::vector<LuaWidgetAccessibilitySnapshot>
15431545
RuntimeAccessibilitySnapshots() const;
@@ -1910,6 +1912,8 @@ class WidgetEngine
19101912
bool numeric = false;
19111913
bool liveUpdate = true;
19121914
bool multiline = false;
1915+
snowdesktop::widget_runtime::HostInputCaretVisibilityRequest
1916+
caretVisibility;
19131917
float minimum = 0.0f;
19141918
float maximum = 1.0f;
19151919
float step = 0.01f;

‎src/widget_text_input_rules.cpp‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ const std::string& DeferredHostInputFocus::ControlId() const noexcept
8282
return controlId_;
8383
}
8484

85+
void HostInputCaretVisibilityRequest::Request() noexcept
86+
{
87+
pending_ = true;
88+
}
89+
90+
void HostInputCaretVisibilityRequest::PreserveManualScroll() noexcept
91+
{
92+
pending_ = false;
93+
}
94+
95+
bool HostInputCaretVisibilityRequest::Consume() noexcept
96+
{
97+
const bool pending = pending_;
98+
pending_ = false;
99+
return pending;
100+
}
101+
85102
std::size_t Utf8BytesForHostText(std::wstring_view text) noexcept
86103
{
87104
std::size_t bytes = 0;

‎src/widget_text_input_rules.h‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ class DeferredHostInputFocus
2222
std::string surface_;
2323
};
2424

25+
class HostInputCaretVisibilityRequest
26+
{
27+
public:
28+
void Request() noexcept;
29+
void PreserveManualScroll() noexcept;
30+
bool Consume() noexcept;
31+
32+
private:
33+
bool pending_ = false;
34+
};
35+
2536
std::size_t Utf8BytesForHostText(std::wstring_view text) noexcept;
2637
std::size_t Utf8ByteOffsetForHostTextOffset(
2738
std::wstring_view text, std::size_t hostOffset) noexcept;

‎tests/widget_text_input_rules_tests.cpp‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,25 @@ void TestDeferredFocusRequest()
9696
Check(!request.Active() && request.ControlId().empty(),
9797
"consuming deferred focus clears the request");
9898
}
99+
100+
void TestCaretVisibilityRequest()
101+
{
102+
using snowdesktop::widget_runtime::HostInputCaretVisibilityRequest;
103+
104+
HostInputCaretVisibilityRequest request;
105+
Check(!request.Consume(),
106+
"caret visibility starts without a pending adjustment");
107+
request.Request();
108+
Check(request.Consume() && !request.Consume(),
109+
"caret visibility is consumed once after focus or editing");
110+
request.Request();
111+
request.PreserveManualScroll();
112+
Check(!request.Consume(),
113+
"manual multiline scrolling cancels the pending caret adjustment");
114+
request.Request();
115+
Check(request.Consume(),
116+
"later caret movement restores caret-follow scrolling");
117+
}
99118
}
100119

101120
int main()
@@ -104,6 +123,7 @@ int main()
104123
TestBoundedReplacement();
105124
TestReadOnlyMutationGate();
106125
TestDeferredFocusRequest();
126+
TestCaretVisibilityRequest();
107127
if (failures != 0)
108128
{
109129
std::cerr << failures << " widget text-input rule checks failed\n";

0 commit comments

Comments
 (0)