feat(server): show translations in horizontal candidate windows
This commit is contained in:
1 parent
dcd5879e64
commit
3fc7ba295f
6 files changed
+81
-18
No files matched your search
@@ -52,7 +52,7 @@ cloud_candidates = true
|
||||
clean_mode = false
|
||||
|
||||
#
|
||||
# 腾讯云机器翻译(竖排候选项本地词库未命中时的补全)
|
||||
# 腾讯云机器翻译(候选项本地词库未命中时的补全)
|
||||
#
|
||||
[tencent_tmt]
|
||||
enabled = true
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user