feat(server): show translations in horizontal candidate windows

This commit is contained in:
Ouchihao committed 2026-09-06 18:09:01 +08:00
1 parent dcd5879e64
commit 3fc7ba295f
6 files changed
+81 -18

No files matched your search

+1 -1
View File
@@ -52,7 +52,7 @@ cloud_candidates = true
clean_mode = false
#
# 腾讯云机器翻译(竖排候选项本地词库未命中时的补全)
# 腾讯云机器翻译(候选项本地词库未命中时的补全)
#
[tencent_tmt]
enabled = true
+2 -4
View File
@@ -961,8 +961,7 @@ std::string BuildCurrentCandidatePage()
void PrepareCandidateTranslationRequest()
{
const bool japanese = g_inputSession && g_inputSession->current_scheme_type() == SchemeType::JapaneseRomaji;
const bool enabled = GetConfiguredCandidateTranslationsEnabled() &&
GetConfiguredCandidateWindowLayout() == "vertical" && !IsUiLessMode() && !japanese;
const bool enabled = GetConfiguredCandidateTranslationsEnabled() && !IsUiLessMode() && !japanese;
auto &ui = Global::candidate_ui;
if (!enabled || ui.items.empty())
{
@@ -3420,8 +3419,7 @@ void ApplyEnglishCandidates(std::vector<WordItem> candidates, const std::string
void ApplyCandidateTranslations(std::vector<EnglishIme::TranslationResult> results, uint64_t generation, bool merge)
{
if (!EnglishIme::IsTranslationCurrent(generation) || !GetConfiguredCandidateTranslationsEnabled() ||
GetConfiguredCandidateWindowLayout() != "vertical" || IsUiLessMode() ||
g_candidate_translation_signature.empty() ||
IsUiLessMode() || g_candidate_translation_signature.empty() ||
(g_inputSession && g_inputSession->current_scheme_type() == SchemeType::JapaneseRomaji))
return;
+1 -1
View File
@@ -487,7 +487,7 @@ void CandidatePresenter::FillItemsFromUi()
item.label = std::to_wstring(i + 1);
item.text = string_to_wstring(view.text + view.badge);
item.annotation = string_to_wstring(view.annotation);
if (GetConfiguredCandidateTranslationsEnabled() && GetConfiguredCandidateWindowLayout() == "vertical")
if (GetConfiguredCandidateTranslationsEnabled())
item.translation = string_to_wstring(view.translation);
items.push_back(std::move(item));
}
@@ -11,6 +11,11 @@
<title>(IME)horizontalCandidateWindow</title>
<!-- Candidate layout and behavior are shared. The native host injects the selected built-in skin CSS here. -->
<style>
.cand .text { display: flex; align-items: baseline; }
.cand-content { min-width: 0; }
.cand-translation { display: block; font-size: 0.78em; line-height: 1.25; opacity: 0.62; }
</style>
<script>
let lastPointerScreenPosition = null;
let pointerProbeCount = 0;
@@ -63,8 +68,10 @@
if (!cand) return;
currentRightClickedCand = cand;
const menu = document.getElementById('contextMenu');
const candidateOnly = cand.cloneNode(true);
candidateOnly.querySelectorAll('.cand-translation').forEach((node) => node.remove());
document.getElementById('menuDelete').style.display =
Array.from((cand.textContent || '').replace(/^\s*\d+\s*/, '').trim()).length === 1 ? 'none' : 'block';
Array.from((candidateOnly.textContent || '').replace(/^\s*\d+\s*/, '').trim()).length === 1 ? 'none' : 'block';
const menuPosition=positionContextMenu(menu,e.clientX,e.clientY);
const submenuSize=positionContextSubmenu(menu,menuPosition.y), content=document.getElementById('realContainer');
window.chrome.webview.postMessage(serializeHostMessage({type:'contextMenuResize',data:{
+18 -11
View File
@@ -2642,6 +2642,7 @@ void CandidateList::SetAppearance(Appearance appearance)
void CandidateList::SetOrientation(Orientation orientation)
{
orientation_ = orientation;
InvalidateLayoutCache();
InvalidateMeasure();
}
@@ -2710,21 +2711,22 @@ RectF CandidateList::ItemRect(size_t index) const
SizeF CandidateList::Measure(const SizeF &availableSize)
{
itemWidths_.resize(items_.size());
itemHeight_ = appearance_.itemHeight;
const float gap = appearance_.itemGap;
if (orientation_ == Orientation::Horizontal)
{
float width = 0.0f;
for (size_t i = 0; i < items_.size(); ++i)
{
const float textWidth = EstimateTextWidth(items_[i].text, appearance_.fontSize) + 6.0f +
EstimateTextWidth(items_[i].annotation, appearance_.annotationFontSize);
const float translationWidth = EstimateTextWidth(items_[i].translation, appearance_.fontSize * 0.78f);
const float itemWidth = appearance_.contentPadLeft + appearance_.textPadLeft +
EstimateTextWidth(items_[i].label, appearance_.labelFontSize) +
appearance_.labelGap + EstimateTextWidth(items_[i].text, appearance_.fontSize) +
6.0f + EstimateTextWidth(items_[i].annotation, appearance_.annotationFontSize) +
(items_[i].translation.empty()
? 0.0f
: appearance_.fontSize * 0.65f +
EstimateTextWidth(items_[i].translation, appearance_.fontSize * 0.78f)) +
appearance_.labelGap + std::max(textWidth, translationWidth) +
appearance_.contentPadRight;
if (!items_[i].translation.empty())
itemHeight_ = appearance_.itemHeight + appearance_.fontSize * 0.78f * 1.25f;
itemWidths_[i] = itemWidth;
width += itemWidth;
if (i + 1 < items_.size())
@@ -2813,15 +2815,20 @@ void CandidateList::Render(DeviceResources &deviceResources)
const float annotationW = items_[index].annotation.empty()
? 0.0f
: EstimateTextWidth(items_[index].annotation, appearance_.annotationFontSize);
const bool horizontal = orientation_ == Orientation::Horizontal;
const float textHeight = horizontal ? appearance_.itemHeight : itemRect.height;
const float translationX =
annotationX + annotationW + (items_[index].translation.empty() ? 0.0f : translationGap);
horizontal ? textX
: annotationX + annotationW + (items_[index].translation.empty() ? 0.0f : translationGap);
const float translationW = items_[index].translation.empty()
? 0.0f
: EstimateTextWidth(items_[index].translation, translationFontSize);
const RectF labelRect = {labelX, itemRect.y, labelW, itemRect.height};
const RectF textRect = {textX, itemRect.y, textW, itemRect.height};
const RectF annotationRect = {annotationX, itemRect.y, std::max(annotationW, 1.0f), itemRect.height};
const RectF translationRect = {translationX, itemRect.y, std::max(translationW, 1.0f), itemRect.height};
const RectF labelRect = {labelX, itemRect.y, labelW, textHeight};
const RectF textRect = {textX, itemRect.y, textW, textHeight};
const RectF annotationRect = {annotationX, itemRect.y, std::max(annotationW, 1.0f), textHeight};
const RectF translationRect = {translationX, itemRect.y + (horizontal ? textHeight : 0.0f),
std::max(translationW, 1.0f),
horizontal ? itemRect.height - textHeight : itemRect.height};
auto &cache = layoutCache_[index];
if (cache.fontFamily != theme.textInputFontFamily || cache.labelWidth != labelRect.width)
+51
View File
@@ -1,6 +1,8 @@
#include "tests/includes/test_framework.h"
#include "msimeui/Controls.h"
#include "msimeui/Layout.h"
#include "msimeui/Window.h"
#include <cmath>
#include <memory>
@@ -222,3 +224,52 @@ TEST_CASE(horizontal_stack_panel_advances_along_x)
REQUIRE_NEAR(first->GetBounds().x, 0.0f);
REQUIRE_NEAR(second->GetBounds().x, 66.0f);
}
TEST_CASE(horizontal_candidate_translation_uses_a_second_line)
{
CandidateList list(28.0f);
list.SetOrientation(CandidateList::Orientation::Horizontal);
list.SetItems({{L"1", L"candidate", L"", L""}});
const SizeF plain = list.MeasureInLayout({1000.0f, 1000.0f});
list.SetItems({{L"1", L"candidate", L"", L"word"}});
const SizeF translated = list.MeasureInLayout({1000.0f, 1000.0f});
REQUIRE_NEAR(translated.width, plain.width);
REQUIRE_NEAR(translated.height, plain.height + 16.0f * 0.78f * 1.25f);
list.SetItems({{L"1", L"candidate", L"", L"a translation wider than the candidate"}});
REQUIRE(list.MeasureInLayout({1000.0f, 1000.0f}).width > translated.width);
list.SetItems({{L"1", L"candidate", L"", L""}});
REQUIRE_NEAR(list.MeasureInLayout({1000.0f, 1000.0f}).height, plain.height);
}
TEST_CASE(horizontal_candidate_translation_area_activates_the_candidate)
{
Window window(L"candidate-test", L"", 1000, 1000);
CandidateList list(28.0f);
list.Attach(&window);
list.SetOrientation(CandidateList::Orientation::Horizontal);
list.SetItems({{L"1", L"candidate", L"", L"word"}, {L"2", L"candidate", L"", L"word"}});
const SizeF size = list.MeasureInLayout({1000.0f, 1000.0f});
list.ArrangeInLayout({0.0f, 0.0f, size.width, size.height});
size_t activated = 0;
list.SetOnItemActivated([&](size_t index) { activated = index; });
const POINT point{static_cast<LONG>(size.width - 2.0f), static_cast<LONG>(size.height - 2.0f)};
REQUIRE(list.OnMouseDown(point, MK_LBUTTON));
REQUIRE(list.OnMouseUp(point, 0));
REQUIRE(activated == 1);
}
TEST_CASE(vertical_candidate_translation_stays_on_the_same_line)
{
CandidateList list(28.0f);
list.SetOrientation(CandidateList::Orientation::Horizontal);
list.SetItems({{L"1", L"candidate", L"", L"word"}});
const SizeF horizontal = list.MeasureInLayout({1000.0f, 1000.0f});
list.SetOrientation(CandidateList::Orientation::Vertical);
const SizeF vertical = list.MeasureInLayout({1000.0f, 1000.0f});
REQUIRE_NEAR(vertical.height, 28.0f);
REQUIRE(vertical.width > horizontal.width);
}