fix(server): 同一次输入内候选卡片只增不减
译文、云/AI 候选、混输英文等内容是陆续到达的,每一批都让卡片在两帧之间改变 宽高。每一帧本身画得完整,但尺寸来回变化看起来就是闪烁。 同一次组合内卡片只增大;自然尺寸低于保留尺寸的 70% 时(页面确实变小了)才跟着 收回,候选窗隐藏后重新开始。D2D 给 card 设置 MinWidth/MinHeight 后重新排版; WebView2 在更新内容的同一个 JS 任务里给 #realContainer 和 #measureContainer 设置最小宽高,绘制前就是最终尺寸,原生区域测量的也是同一个盒子。两端的收回比例 常量互相注明了对应关系。
This commit is contained in:
1 parent
444b015a02
commit
3a7beb18ab
3 files changed
+90
-6
No files matched your search
@@ -1825,6 +1825,43 @@ html:not(.msime-hover-armed) #realContainer.hover-active .cand:not(.first):hover
|
||||
})();
|
||||
)";
|
||||
|
||||
// Late additions to the page (translations, cloud/AI/English merges) resize the
|
||||
// card between two frames of one composition, which reads as flicker even
|
||||
// though each frame is complete. Within a composition the card only grows, and
|
||||
// shrinks back only when its natural size drops below kStickyShrinkRatio of the
|
||||
// kept one (the page really got smaller). Runs in the same task as the content
|
||||
// update, so the frame is laid out once with the final size; the native region
|
||||
// measures the same box and follows. An empty update (hide) ends the
|
||||
// composition. Mirrors kStickyCardShrinkRatio in the D2D candidate presenter.
|
||||
constexpr wchar_t kStickyCandidateCardScript[] = LR"(
|
||||
window.MsimeStickyCandidateCard = function (reset) {
|
||||
const kStickyShrinkRatio = 0.7;
|
||||
const box = document.getElementById('realContainer');
|
||||
if (!box) return;
|
||||
const measure = document.getElementById('measureContainer');
|
||||
const boxes = measure ? [box, measure] : [box];
|
||||
boxes.forEach(function (b) { b.style.minWidth = ''; b.style.minHeight = ''; });
|
||||
if (reset) { window.__msimeStickyCard = null; return; }
|
||||
box.style.boxSizing = 'border-box';
|
||||
const rect = box.getBoundingClientRect();
|
||||
const kept = window.__msimeStickyCard || {width: 0, height: 0};
|
||||
const follow = function (keptValue, natural) {
|
||||
return natural >= keptValue || natural < keptValue * kStickyShrinkRatio ? natural : keptValue;
|
||||
};
|
||||
let width = follow(kept.width, rect.width);
|
||||
const maxWidth = Number.parseFloat(box.style.maxWidth);
|
||||
if (maxWidth > 0) width = Math.min(width, maxWidth);
|
||||
const height = follow(kept.height, rect.height);
|
||||
window.__msimeStickyCard = {width: width, height: height};
|
||||
if (width > rect.width + 0.5 || height > rect.height + 0.5) {
|
||||
boxes.forEach(function (b) {
|
||||
b.style.minWidth = width + 'px';
|
||||
b.style.minHeight = height + 'px';
|
||||
});
|
||||
}
|
||||
};
|
||||
)";
|
||||
|
||||
VOID CALLBACK CandidateHoverArmTimerProc(HWND, UINT, UINT_PTR, DWORD)
|
||||
{
|
||||
MaybeArmCandidatePointerHover();
|
||||
@@ -2163,6 +2200,9 @@ void UpdateHtmlContentWithJavaScript(ComPtr<ICoreWebView2> webview, const std::w
|
||||
script.append(GetConfiguredCandidateWindowPreeditStyle() == "empty" ? L"false" : L"true");
|
||||
script.append(L"); }\n");
|
||||
script.append(L"if (window.SetPreeditCaret) { window.SetPreeditCaret(); }\n");
|
||||
script.append(kStickyCandidateCardScript);
|
||||
script.append(newContent.empty() ? L"window.MsimeStickyCandidateCard(true);\n"
|
||||
: L"window.MsimeStickyCandidateCard(false);\n");
|
||||
script.append(L"if (window.CheckContentTruncation) { window.CheckContentTruncation(); }\n");
|
||||
if (diagnosticsEnabled)
|
||||
{
|
||||
|
||||
@@ -156,6 +156,16 @@ constexpr float kShadowPadRight = 32.0f;
|
||||
constexpr float kShadowPadBottom = 40.0f;
|
||||
constexpr float kCandidateMinWidthDip = 160.0f;
|
||||
constexpr float kDecorationCardOverlap = 5.0f;
|
||||
// Below this fraction of the kept extent, the page really got smaller (fewer or
|
||||
// shorter candidates) and the card follows it; above it, the difference is a
|
||||
// late addition coming or going and the card keeps its size. Mirrors
|
||||
// kStickyShrinkRatio in the WebView2 candidate script.
|
||||
constexpr float kStickyCardShrinkRatio = 0.7f;
|
||||
|
||||
float StickyCardExtent(float kept, float natural)
|
||||
{
|
||||
return natural >= kept || natural < kept * kStickyCardShrinkRatio ? natural : kept;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1076,12 +1086,41 @@ void CandidatePresenter::ShowFromGlobalState(POINT caret)
|
||||
const HalfScreenDipLimits limits = ApplyScaleToHalfScreenLimits(caret, scale.scale);
|
||||
const float maxW = limits.maxWidthDip > 1.0 ? static_cast<float>(limits.maxWidthDip) : 480.0f;
|
||||
const float maxH = limits.maxHeightDip > 1.0 ? static_cast<float>(limits.maxHeightDip) : 640.0f;
|
||||
impl_->root->InvalidateMeasure();
|
||||
const msimeui::SizeF measured = impl_->root->MeasureInLayout({maxW, maxH});
|
||||
float widthDip = (std::max)(measured.width, kCandidateMinWidthDip + kShadowPadLeft + kShadowPadRight);
|
||||
float heightDip = (std::max)(measured.height, 36.0f);
|
||||
impl_->root->InvalidateArrange();
|
||||
impl_->root->ArrangeInLayout({0.0f, 0.0f, widthDip, heightDip});
|
||||
float widthDip = 0.0f;
|
||||
float heightDip = 0.0f;
|
||||
auto layoutRoot = [&]() {
|
||||
impl_->root->InvalidateMeasure();
|
||||
const msimeui::SizeF measured = impl_->root->MeasureInLayout({maxW, maxH});
|
||||
widthDip = (std::max)(measured.width, kCandidateMinWidthDip + kShadowPadLeft + kShadowPadRight);
|
||||
heightDip = (std::max)(measured.height, 36.0f);
|
||||
impl_->root->InvalidateArrange();
|
||||
impl_->root->ArrangeInLayout({0.0f, 0.0f, widthDip, heightDip});
|
||||
};
|
||||
// Late additions to the page (translations, cloud/AI/English merges) change
|
||||
// the card's size between two frames of the same composition, and the eye
|
||||
// reads that as flicker even though every frame is complete. Within one
|
||||
// composition the card therefore only grows; it shrinks back only when the
|
||||
// natural size falls well below the kept one (a page that really got
|
||||
// smaller). Hide() starts the next composition from the natural size.
|
||||
if (impl_->card)
|
||||
{
|
||||
impl_->card->SetMinWidth(kCandidateMinWidthDip);
|
||||
impl_->card->ClearMinHeight();
|
||||
}
|
||||
layoutRoot();
|
||||
if (impl_->card)
|
||||
{
|
||||
const msimeui::RectF natural = impl_->card->GetBounds();
|
||||
const float maxCardWidthDip = (std::max)(kCandidateMinWidthDip, maxW - kShadowPadLeft - kShadowPadRight);
|
||||
stickyCardWidthDip_ = (std::min)(StickyCardExtent(stickyCardWidthDip_, natural.width), maxCardWidthDip);
|
||||
stickyCardHeightDip_ = StickyCardExtent(stickyCardHeightDip_, natural.height);
|
||||
if (stickyCardWidthDip_ > natural.width + 0.5f || stickyCardHeightDip_ > natural.height + 0.5f)
|
||||
{
|
||||
impl_->card->SetMinWidth((std::max)(kCandidateMinWidthDip, stickyCardWidthDip_));
|
||||
impl_->card->SetMinHeight(stickyCardHeightDip_);
|
||||
layoutRoot();
|
||||
}
|
||||
}
|
||||
hoverArmed_ = false;
|
||||
if (!GetCursorPos(&hoverBaseline_))
|
||||
{
|
||||
@@ -1122,6 +1161,8 @@ void CandidatePresenter::Hide()
|
||||
SetCandidateHostCloaked(true);
|
||||
lastHostWidthPx_ = 0;
|
||||
lastHostHeightPx_ = 0;
|
||||
stickyCardWidthDip_ = 0.0f;
|
||||
stickyCardHeightDip_ = 0.0f;
|
||||
SetWindowPos(hwnd_, nullptr, 0, Global::INVALID_Y, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
}
|
||||
|
||||
@@ -59,5 +59,8 @@ class CandidatePresenter
|
||||
int lastHostHeightPx_ = 0;
|
||||
float lastLayoutWidthDip_ = 0.0f;
|
||||
float lastLayoutHeightDip_ = 0.0f;
|
||||
// Largest card size kept for the current composition; reset by Hide().
|
||||
float stickyCardWidthDip_ = 0.0f;
|
||||
float stickyCardHeightDip_ = 0.0f;
|
||||
int wheelDeltaAccumulator_ = 0;
|
||||
};
|
||||
Reference in new issue
Block a user