Merge pull request #543 from fanlusky/fix/willow-green-horizontal-layout
fix(server): 杨柳青横排翻译贴底、行尾留白且末项高亮不铺满
This commit is contained in:
4 files changed
+89
-6
No files matched your search
@@ -109,6 +109,8 @@ struct CandSkinTokens
|
||||
float itemExtraHeight = 2.0f;
|
||||
float itemPadLeft = 0.0f;
|
||||
float itemPadRight = 0.0f;
|
||||
// 横排时把卡片多出的宽度均分给同一行各项,铺满高亮的皮肤(杨柳青)末项才能贴到卡片右边。
|
||||
bool justifyRows = false;
|
||||
msimeui::Thickness preeditMargin{};
|
||||
D2D1_COLOR_F rowTextSelected = D2D1::ColorF(0, 0.0f);
|
||||
D2D1_COLOR_F rowLabelSelected = D2D1::ColorF(0, 0.0f);
|
||||
@@ -424,6 +426,7 @@ void CandidatePresenter::ApplySkin()
|
||||
tokens.itemRadius = 0.0f;
|
||||
tokens.outerItemRadius = 9.0f;
|
||||
tokens.itemGap = 0.0f;
|
||||
tokens.justifyRows = true;
|
||||
// 行内边距对齐 CSS 的 .row.cand padding(加上 .text 的左内边距),预编辑行对齐 .row.pinyin。
|
||||
if (GetConfiguredCandidateWindowLayout() == "horizontal")
|
||||
{
|
||||
@@ -620,6 +623,9 @@ void CandidatePresenter::ApplySkin()
|
||||
appearance.annotationFontSize = fontSize;
|
||||
appearance.contentPadLeft = tokens.itemPadLeft;
|
||||
appearance.contentPadRight = tokens.itemPadRight;
|
||||
// itemExtraHeight 上下各半:横排翻译换到第二行时从下半段内边距上方开始,内边距留在翻译下面,
|
||||
// 对应 CSS 里 .row.cand 的 padding 包住整个 .text(杨柳青横排上下各 6px)。
|
||||
appearance.contentPadBottom = tokens.itemExtraHeight * 0.5f;
|
||||
appearance.textPadLeft = 5.0f;
|
||||
appearance.labelGap = 1.5f;
|
||||
appearance.selectedBarWidth = 3.0f;
|
||||
@@ -631,6 +637,7 @@ void CandidatePresenter::ApplySkin()
|
||||
const bool preeditHidden = GetConfiguredCandidateWindowPreeditStyle() == "empty";
|
||||
appearance.outerCornerRadius = tokens.outerItemRadius;
|
||||
appearance.outerTopCornersEnabled = preeditHidden;
|
||||
appearance.justifyHorizontalRows = tokens.justifyRows;
|
||||
// 隐藏的预编辑行高度为 0,但 body 仍在它和列表之间留 2px 行距;贴角皮肤把它抵掉,
|
||||
// 让首项贴着卡片的内容顶边(秋桂四边内边距一致,杨柳青高亮直抵卡片顶边)。
|
||||
if (preeditHidden)
|
||||
|
||||
@@ -530,8 +530,16 @@ class CandidateList : public Visual
|
||||
// row) sits between the list and the frame's top edge.
|
||||
float outerCornerRadius = 0.0f;
|
||||
bool outerTopCornersEnabled = true;
|
||||
// Horizontal only: when the list is arranged wider than a line of
|
||||
// items, spread the spare width evenly over that line's items so the
|
||||
// last one reaches the list's right edge (full-bleed highlights).
|
||||
bool justifyHorizontalRows = false;
|
||||
float contentPadLeft = 5.0f;
|
||||
float contentPadRight = 5.0f;
|
||||
// Part of itemHeight that is the row's bottom padding. Lines stacked
|
||||
// under the first one (wrapped annotation, horizontal translation)
|
||||
// start above it, and the padding is restored below the last line.
|
||||
float contentPadBottom = 0.0f;
|
||||
float textPadLeft = 0.0f;
|
||||
float labelGap = 1.5f;
|
||||
float selectedBarWidth = 3.0f;
|
||||
|
||||
@@ -325,6 +325,10 @@ CandidateList::ItemGeometry CandidateList::MeasureItem(size_t index, float width
|
||||
geometry.text = {textX, 0.0f, textW, textH};
|
||||
float height = textH;
|
||||
float lineEnd = textW;
|
||||
// height 末尾属于行底内边距的部分:只有单行高度(itemHeight)里才含这段内边距,
|
||||
// 换到下方的辅助码和翻译从它上面开始排,排完再把它补回最后一行下面。
|
||||
const float padBottom = std::clamp(appearance_.contentPadBottom, 0.0f, appearance_.itemHeight * 0.5f);
|
||||
float padBelow = textH > appearance_.itemHeight ? 0.0f : padBottom;
|
||||
|
||||
// 短字段保持原有并排方式;空间不足时让辅助码和翻译完整换行。
|
||||
if (!item.annotation.empty())
|
||||
@@ -334,9 +338,13 @@ CandidateList::ItemGeometry CandidateList::MeasureItem(size_t index, float width
|
||||
const bool inlineAnnotation = lineEnd + 4.0f + annotationW <= contentWidth;
|
||||
const float annotationH = std::max(
|
||||
appearance_.itemHeight, MeasureTextHeight(item.annotation, appearance_.annotationFontSize, annotationW));
|
||||
geometry.annotation = {textX + (inlineAnnotation ? lineEnd + 4.0f : 0.0f), inlineAnnotation ? 0.0f : height,
|
||||
annotationW, annotationH};
|
||||
height = std::max(height, geometry.annotation.y + annotationH);
|
||||
geometry.annotation = {textX + (inlineAnnotation ? lineEnd + 4.0f : 0.0f),
|
||||
inlineAnnotation ? 0.0f : height - padBelow, annotationW, annotationH};
|
||||
if (geometry.annotation.y + annotationH > height)
|
||||
{
|
||||
height = geometry.annotation.y + annotationH;
|
||||
padBelow = annotationH > appearance_.itemHeight ? 0.0f : padBottom;
|
||||
}
|
||||
lineEnd = geometry.annotation.x - textX + annotationW;
|
||||
}
|
||||
if (!item.translation.empty())
|
||||
@@ -353,9 +361,9 @@ CandidateList::ItemGeometry CandidateList::MeasureItem(size_t index, float width
|
||||
: (naturalTranslationWidth <= contentWidth
|
||||
? fontSize * 1.25f
|
||||
: std::max(fontSize * 1.25f, MeasureTextHeight(item.translation, fontSize, translationW)));
|
||||
geometry.translation = {textX + (inlineTranslation ? lineEnd + gap : 0.0f), inlineTranslation ? 0.0f : height,
|
||||
translationW, translationH};
|
||||
height = std::max(height, geometry.translation.y + translationH);
|
||||
geometry.translation = {textX + (inlineTranslation ? lineEnd + gap : 0.0f),
|
||||
inlineTranslation ? 0.0f : height - padBelow, translationW, translationH};
|
||||
height = std::max(height, geometry.translation.y + translationH + (inlineTranslation ? 0.0f : padBelow));
|
||||
}
|
||||
geometry.bounds = {0.0f, 0.0f, width, height};
|
||||
return geometry;
|
||||
@@ -377,6 +385,26 @@ RectF CandidateList::ItemRect(size_t index) const
|
||||
rect.x = bounds_.x;
|
||||
rect.width = bounds_.width;
|
||||
}
|
||||
else if (appearance_.justifyHorizontalRows)
|
||||
{
|
||||
// 横排铺满:列表比这一行的自然宽度宽时(卡片保持了同一输入下更宽的尺寸),把多出的宽度
|
||||
// 均分给这一行的每一项,末项右边贴着列表右边,高亮不会在行尾留下一截空白。
|
||||
const float lineY = itemGeometry_[index].bounds.y;
|
||||
size_t first = index;
|
||||
while (first > 0 && itemGeometry_[first - 1].bounds.y == lineY)
|
||||
--first;
|
||||
size_t last = index;
|
||||
while (last + 1 < itemGeometry_.size() && itemGeometry_[last + 1].bounds.y == lineY)
|
||||
++last;
|
||||
const RectF &tail = itemGeometry_[last].bounds;
|
||||
const float extra = bounds_.width - (tail.x + tail.width);
|
||||
if (extra > 0.0f)
|
||||
{
|
||||
const float share = extra / static_cast<float>(last - first + 1);
|
||||
rect.x += share * static_cast<float>(index - first);
|
||||
rect.width += share;
|
||||
}
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
|
||||
@@ -244,6 +244,46 @@ TEST_CASE(horizontal_candidate_translation_uses_a_second_line)
|
||||
REQUIRE_NEAR(list.MeasureInLayout({1000.0f, 1000.0f}).height, plain.height);
|
||||
}
|
||||
|
||||
TEST_CASE(horizontal_candidate_translation_keeps_the_row_bottom_padding_below_it)
|
||||
{
|
||||
// 行高里的底部内边距挪到翻译下面,而不是夹在候选与翻译之间;挪动不应让整行变高。
|
||||
CandidateList list(38.0f);
|
||||
CandidateList::Appearance appearance;
|
||||
appearance.itemHeight = 38.0f;
|
||||
appearance.contentPadBottom = 5.0f;
|
||||
list.SetAppearance(appearance);
|
||||
list.SetOrientation(CandidateList::Orientation::Horizontal);
|
||||
list.SetItems({{L"1", L"candidate", L"", L"word"}});
|
||||
const float translationH = appearance.fontSize * 0.78f * 1.25f;
|
||||
REQUIRE_NEAR(list.MeasureInLayout({1000.0f, 1000.0f}).height, 38.0f + translationH);
|
||||
list.ArrangeInLayout({0.0f, 0.0f, 1000.0f, 38.0f + translationH});
|
||||
const RectF item = list.GetItemBounds(0);
|
||||
REQUIRE_NEAR(item.height, 38.0f + translationH);
|
||||
}
|
||||
|
||||
TEST_CASE(justified_horizontal_candidates_spread_spare_width_to_the_right_edge)
|
||||
{
|
||||
CandidateList list(28.0f);
|
||||
CandidateList::Appearance appearance;
|
||||
appearance.itemHeight = 28.0f;
|
||||
appearance.itemGap = 0.0f;
|
||||
appearance.justifyHorizontalRows = true;
|
||||
list.SetAppearance(appearance);
|
||||
list.SetOrientation(CandidateList::Orientation::Horizontal);
|
||||
list.SetItems({{L"1", L"候选", L"", L""}, {L"2", L"候选", L"", L""}});
|
||||
const SizeF natural = list.MeasureInLayout({1000.0f, 1000.0f});
|
||||
const float naturalFirstWidth = list.GetItemBounds(0).width;
|
||||
|
||||
// 卡片保持了更宽的尺寸:多出的 40 均分给两项,末项右边贴着列表右边。
|
||||
list.ArrangeInLayout({10.0f, 0.0f, natural.width + 40.0f, natural.height});
|
||||
const RectF first = list.GetItemBounds(0);
|
||||
const RectF second = list.GetItemBounds(1);
|
||||
REQUIRE_NEAR(first.x, 10.0f);
|
||||
REQUIRE_NEAR(first.width, naturalFirstWidth + 20.0f);
|
||||
REQUIRE_NEAR(second.x, first.x + first.width);
|
||||
REQUIRE_NEAR(second.x + second.width, 10.0f + natural.width + 40.0f);
|
||||
}
|
||||
|
||||
TEST_CASE(vertical_candidate_rows_fill_a_wider_arranged_width)
|
||||
{
|
||||
// 竖排候选项的自然宽度较窄,但被分配到更宽的行宽时(如卡片最小宽度撑大),
|
||||
|
||||
Reference in new issue
Block a user