统一文本输入(新增 Utilities/Inc/TextEdit.hpp + TextEdit.cpp) - 一个共享的编辑模型,提供光标/选区/过滤/限长,接入全部输入框: 样式区数值与信号名/分组名、波形图例改名、视图标签页改名、 拾色器 R/G/B 与色号、光标时间与网格上下限。 - 新增能力:Ctrl+A/C/X/V(含剪贴板清洗)、Shift+方向键与 Home/End 选区、 退格与删除清除选区、代理对安全的光标步进。 - 渲染改为选区高亮 + 插入符,文本超出框宽时水平滚动并让插入符始终可见; 初始态为整段选中,首字符键入即整体替换(保持"登录框"式交互)。 - 新增 Utf8Util::PasteTextFromClipboard(换行/制表符折叠为空格)。 修复网格合并(Utilities/Src/DataModel.cpp) - 拖拽信号到其它网格时先固定目标网格的锚信号,再移除源信号并按引用找回目标, 避免空网格回收导致下标错位,从"与原网格交换位置"改为真正合并。 - 自定义划分下未分配网格的可见信号改为各自独占一个新网格, 修复"拖拽合并后再勾选新信号被并入该合并网格"的问题。 绘图区(Components/PlotView) - 网格右键菜单新增"时间轴自适应(匹配本网格)",把时间轴对齐到该网格信号范围。 - 时间轴放大到采样点在屏幕上可分辨(>=6 逻辑像素)时,拖动光标吸附到最近数据点; 缩小到无法分辨时保持自由移动,避免光标"跳格"。 验证:真实输入脚本逐项核对——拖拽合并后单网格含两条曲线、拖拽合并再勾选新信号得 G2 独立网格、 Shift 选区 + Delete 清除、Ctrl+A/C/V 复制粘贴、46 字符文本水平滚动、拾色器色号 1E90FF、 标签页改名 A1、光标时间框复制粘贴、全视图拖动得 0.522213(不吸附)而放大后得 0.28125(= 225 × 0.00125,已吸附)。
553 lines
16 KiB
C++
553 lines
16 KiB
C++
#include "PaintUtil.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace jdi
|
|
{
|
|
|
|
namespace
|
|
{
|
|
/**
|
|
* @brief 颜色样块补充描边的不透明度阈值。
|
|
*
|
|
* 不透明度低于该值时,色块自身与浅色背景的对比度已不足,需要额外描边才能辨识;
|
|
* 高于该值时按需求不绘制描边,保持与预设色块一致的纯净外观。
|
|
*/
|
|
const float kSwatchOutlineOpacity = 0.45f;
|
|
|
|
/**
|
|
* @brief 构造圆角矩形路径。
|
|
* @param [IN,OUT] path 目标路径对象。
|
|
* @param [IN] rect 目标矩形。
|
|
* @param [IN] radius 圆角半径。
|
|
*/
|
|
void BuildRoundRectPath(gd::GraphicsPath& path, const gd::RectF& rect, float radius)
|
|
{
|
|
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float diameter = radius * 2.0f;
|
|
if (diameter > rect.Width)
|
|
{
|
|
diameter = rect.Width;
|
|
}
|
|
if (diameter > rect.Height)
|
|
{
|
|
diameter = rect.Height;
|
|
}
|
|
if (diameter < 1.0f)
|
|
{
|
|
path.AddRectangle(rect);
|
|
return;
|
|
}
|
|
|
|
const float right = rect.X + rect.Width;
|
|
const float bottom = rect.Y + rect.Height;
|
|
|
|
path.AddArc(rect.X, rect.Y, diameter, diameter, 180.0f, 90.0f);
|
|
path.AddArc(right - diameter, rect.Y, diameter, diameter, 270.0f, 90.0f);
|
|
path.AddArc(right - diameter, bottom - diameter, diameter, diameter, 0.0f, 90.0f);
|
|
path.AddArc(rect.X, bottom - diameter, diameter, diameter, 90.0f, 90.0f);
|
|
path.CloseFigure();
|
|
}
|
|
|
|
/**
|
|
* @brief 创建指定字号的界面字体。
|
|
* @param [IN] face 字体名称。
|
|
* @param [IN] pixelHeight 字符高度(像素)。
|
|
* @param [IN] bold 是否加粗。
|
|
* @return 字体句柄,失败返回 nullptr。
|
|
*/
|
|
HFONT CreateUiFont(const wchar_t* face, int pixelHeight, bool bold)
|
|
{
|
|
LOGFONTW logFont = {};
|
|
logFont.lfHeight = -pixelHeight;
|
|
logFont.lfWeight = bold ? FW_SEMIBOLD : FW_NORMAL;
|
|
logFont.lfCharSet = DEFAULT_CHARSET;
|
|
logFont.lfOutPrecision = OUT_TT_PRECIS;
|
|
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
|
logFont.lfQuality = CLEARTYPE_QUALITY;
|
|
logFont.lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
|
|
wcsncpy(logFont.lfFaceName, face, LF_FACESIZE - 1);
|
|
logFont.lfFaceName[LF_FACESIZE - 1] = L'\0';
|
|
|
|
HFONT font = CreateFontIndirectW(&logFont);
|
|
if (font == nullptr)
|
|
{
|
|
font = static_cast<HFONT>(GetStockObject(DEFAULT_GUI_FONT));
|
|
}
|
|
return font;
|
|
}
|
|
}
|
|
|
|
bool UiFonts::Create(UINT targetDpi)
|
|
{
|
|
Destroy();
|
|
dpi = (targetDpi == 0) ? 96u : targetDpi;
|
|
|
|
const double scale = static_cast<double>(dpi) / 96.0;
|
|
const int uiHeight = static_cast<int>(std::lround(14.0 * scale));
|
|
const int smallHeight = static_cast<int>(std::lround(12.0 * scale));
|
|
const int titleHeight = static_cast<int>(std::lround(15.0 * scale));
|
|
const int monoHeight = static_cast<int>(std::lround(13.0 * scale));
|
|
|
|
ui = CreateUiFont(L"Segoe UI", uiHeight, false);
|
|
uiBold = CreateUiFont(L"Segoe UI Semibold", uiHeight, true);
|
|
uiSmall = CreateUiFont(L"Segoe UI", smallHeight, false);
|
|
uiSmallBold = CreateUiFont(L"Segoe UI Semibold", smallHeight, true);
|
|
title = CreateUiFont(L"Segoe UI Semibold", titleHeight, true);
|
|
mono = CreateUiFont(L"Consolas", monoHeight, false);
|
|
monoSmall = CreateUiFont(L"Consolas", smallHeight, false);
|
|
|
|
return ui != nullptr && uiBold != nullptr && uiSmall != nullptr && title != nullptr && mono != nullptr;
|
|
}
|
|
|
|
void UiFonts::Destroy()
|
|
{
|
|
const HFONT resources[] = {ui, uiBold, uiSmall, uiSmallBold, title, mono, monoSmall};
|
|
for (const HFONT font : resources)
|
|
{
|
|
if (font != nullptr)
|
|
{
|
|
DeleteObject(font);
|
|
}
|
|
}
|
|
ui = nullptr;
|
|
uiBold = nullptr;
|
|
uiSmall = nullptr;
|
|
uiSmallBold = nullptr;
|
|
title = nullptr;
|
|
mono = nullptr;
|
|
monoSmall = nullptr;
|
|
}
|
|
|
|
void UiFillRect(gd::Graphics& g, const gd::RectF& rect, std::uint32_t argb)
|
|
{
|
|
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
gd::SolidBrush brush(ToGdiColor(argb));
|
|
g.FillRectangle(&brush, rect);
|
|
}
|
|
|
|
void UiFillRoundRect(gd::Graphics& g, const gd::RectF& rect, float radius, std::uint32_t argb)
|
|
{
|
|
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
gd::GraphicsPath path;
|
|
BuildRoundRectPath(path, rect, radius);
|
|
gd::SolidBrush brush(ToGdiColor(argb));
|
|
g.FillPath(&brush, &path);
|
|
}
|
|
|
|
void UiDrawRoundRect(gd::Graphics& g, const gd::RectF& rect, float radius, std::uint32_t argb, float width)
|
|
{
|
|
if (rect.Width <= 0.0f || rect.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
gd::GraphicsPath path;
|
|
BuildRoundRectPath(path, rect, radius);
|
|
gd::Pen pen(ToGdiColor(argb), width);
|
|
g.DrawPath(&pen, &path);
|
|
}
|
|
|
|
void UiDrawLine(gd::Graphics& g, float x1, float y1, float x2, float y2, std::uint32_t argb, float width)
|
|
{
|
|
gd::Pen pen(ToGdiColor(argb), width);
|
|
pen.SetStartCap(gd::LineCapFlat);
|
|
pen.SetEndCap(gd::LineCapFlat);
|
|
g.DrawLine(&pen, x1, y1, x2, y2);
|
|
}
|
|
|
|
void UiText(gd::Graphics& g, const std::wstring& text, HFONT font, const gd::RectF& rect, std::uint32_t argb,
|
|
TextAlign align)
|
|
{
|
|
if (text.empty() || font == nullptr || rect.Width <= 0.0f || rect.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HDC hdc = g.GetHDC();
|
|
if (hdc == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HGDIOBJ oldFont = SelectObject(hdc, font);
|
|
const int oldMode = SetBkMode(hdc, TRANSPARENT);
|
|
const COLORREF oldColor = SetTextColor(hdc, ToColorRef(argb));
|
|
|
|
RECT rc;
|
|
rc.left = static_cast<LONG>(std::floor(rect.X));
|
|
rc.top = static_cast<LONG>(std::floor(rect.Y));
|
|
rc.right = static_cast<LONG>(std::ceil(rect.X + rect.Width));
|
|
rc.bottom = static_cast<LONG>(std::ceil(rect.Y + rect.Height));
|
|
|
|
UINT flags = DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS;
|
|
switch (align)
|
|
{
|
|
case TextAlign::CENTER:
|
|
flags |= DT_CENTER;
|
|
break;
|
|
case TextAlign::RIGHT:
|
|
flags |= DT_RIGHT;
|
|
break;
|
|
case TextAlign::LEFT:
|
|
default:
|
|
flags |= DT_LEFT;
|
|
break;
|
|
}
|
|
|
|
DrawTextW(hdc, text.c_str(), static_cast<int>(text.size()), &rc, flags);
|
|
|
|
SetTextColor(hdc, oldColor);
|
|
SetBkMode(hdc, oldMode);
|
|
SelectObject(hdc, oldFont);
|
|
g.ReleaseHDC(hdc);
|
|
}
|
|
|
|
float UiTextWidth(gd::Graphics& g, const std::wstring& text, HFONT font)
|
|
{
|
|
if (text.empty() || font == nullptr)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
HDC hdc = g.GetHDC();
|
|
if (hdc == nullptr)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
HGDIOBJ oldFont = SelectObject(hdc, font);
|
|
SIZE size = {};
|
|
GetTextExtentPoint32W(hdc, text.c_str(), static_cast<int>(text.size()), &size);
|
|
SelectObject(hdc, oldFont);
|
|
g.ReleaseHDC(hdc);
|
|
|
|
return static_cast<float>(size.cx);
|
|
}
|
|
|
|
float UiMeasureTextWidth(HFONT font, const std::wstring& text)
|
|
{
|
|
if (text.empty() || font == nullptr)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
HDC hdc = GetDC(nullptr);
|
|
if (hdc == nullptr)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
|
|
HGDIOBJ oldFont = SelectObject(hdc, font);
|
|
SIZE size = {};
|
|
GetTextExtentPoint32W(hdc, text.c_str(), static_cast<int>(text.size()), &size);
|
|
SelectObject(hdc, oldFont);
|
|
ReleaseDC(nullptr, hdc);
|
|
|
|
return static_cast<float>(size.cx);
|
|
}
|
|
|
|
std::wstring UiEllipsize(gd::Graphics& g, const std::wstring& text, HFONT font, float maxWidth)
|
|
{
|
|
if (text.empty() || font == nullptr || maxWidth <= 0.0f)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
if (UiTextWidth(g, text, font) <= maxWidth)
|
|
{
|
|
return text;
|
|
}
|
|
|
|
const std::wstring ellipsis = L"…";
|
|
const float ellipsisWidth = UiTextWidth(g, ellipsis, font);
|
|
if (ellipsisWidth > maxWidth)
|
|
{
|
|
return std::wstring();
|
|
}
|
|
|
|
std::wstring result = text;
|
|
while (!result.empty())
|
|
{
|
|
result.pop_back();
|
|
if (UiTextWidth(g, result, font) + ellipsisWidth <= maxWidth)
|
|
{
|
|
return result + ellipsis;
|
|
}
|
|
}
|
|
return ellipsis;
|
|
}
|
|
|
|
void UiDrawPlusIcon(gd::Graphics& g, const gd::RectF& rect, std::uint32_t argb, float strokeWidth)
|
|
{
|
|
const float cx = rect.X + rect.Width * 0.5f;
|
|
const float cy = rect.Y + rect.Height * 0.5f;
|
|
const float arm = std::min(rect.Width, rect.Height) * 0.3f;
|
|
UiDrawLine(g, cx - arm, cy, cx + arm, cy, argb, strokeWidth);
|
|
UiDrawLine(g, cx, cy - arm, cx, cy + arm, argb, strokeWidth);
|
|
}
|
|
|
|
void UiDrawCrossIcon(gd::Graphics& g, const gd::RectF& rect, std::uint32_t argb, float strokeWidth)
|
|
{
|
|
const float cx = rect.X + rect.Width * 0.5f;
|
|
const float cy = rect.Y + rect.Height * 0.5f;
|
|
const float arm = std::min(rect.Width, rect.Height) * 0.3f;
|
|
UiDrawLine(g, cx - arm, cy - arm, cx + arm, cy + arm, argb, strokeWidth);
|
|
UiDrawLine(g, cx + arm, cy - arm, cx - arm, cy + arm, argb, strokeWidth);
|
|
}
|
|
|
|
void UiDrawTriangleIcon(gd::Graphics& g, const gd::RectF& rect, std::uint32_t argb, IconDirection direction)
|
|
{
|
|
const float cx = rect.X + rect.Width * 0.5f;
|
|
const float cy = rect.Y + rect.Height * 0.5f;
|
|
const float arm = std::min(rect.Width, rect.Height) * 0.28f;
|
|
|
|
gd::PointF points[3];
|
|
switch (direction)
|
|
{
|
|
case IconDirection::LEFT:
|
|
points[0] = gd::PointF(cx + arm * 0.7f, cy - arm);
|
|
points[1] = gd::PointF(cx - arm * 0.7f, cy);
|
|
points[2] = gd::PointF(cx + arm * 0.7f, cy + arm);
|
|
break;
|
|
case IconDirection::RIGHT:
|
|
points[0] = gd::PointF(cx - arm * 0.7f, cy - arm);
|
|
points[1] = gd::PointF(cx + arm * 0.7f, cy);
|
|
points[2] = gd::PointF(cx - arm * 0.7f, cy + arm);
|
|
break;
|
|
case IconDirection::UP:
|
|
points[0] = gd::PointF(cx - arm, cy + arm * 0.7f);
|
|
points[1] = gd::PointF(cx, cy - arm * 0.7f);
|
|
points[2] = gd::PointF(cx + arm, cy + arm * 0.7f);
|
|
break;
|
|
case IconDirection::DOWN:
|
|
default:
|
|
points[0] = gd::PointF(cx - arm, cy - arm * 0.7f);
|
|
points[1] = gd::PointF(cx, cy + arm * 0.7f);
|
|
points[2] = gd::PointF(cx + arm, cy - arm * 0.7f);
|
|
break;
|
|
}
|
|
|
|
gd::SolidBrush brush(ToGdiColor(argb));
|
|
g.FillPolygon(&brush, points, 3);
|
|
}
|
|
|
|
double UiSliderRatioFromX(const gd::RectF& trackRect, float x)
|
|
{
|
|
if (trackRect.Width <= 0.0f)
|
|
{
|
|
return 0.0;
|
|
}
|
|
const double ratio = static_cast<double>(x - trackRect.X) / static_cast<double>(trackRect.Width);
|
|
if (ratio < 0.0)
|
|
{
|
|
return 0.0;
|
|
}
|
|
if (ratio > 1.0)
|
|
{
|
|
return 1.0;
|
|
}
|
|
return ratio;
|
|
}
|
|
|
|
void UiDrawCheckBox(gd::Graphics& g, const gd::RectF& rect, int state, float radius)
|
|
{
|
|
const Theme& theme = SolarizedLight();
|
|
const float centerY = rect.Y + rect.Height * 0.5f;
|
|
|
|
if (state == 1)
|
|
{
|
|
UiFillRoundRect(g, rect, radius, theme.accent);
|
|
gd::Pen pen(ToGdiColor(theme.textOnAccent), std::max(1.4f, rect.Height * 0.11f));
|
|
pen.SetStartCap(gd::LineCapRound);
|
|
pen.SetEndCap(gd::LineCapRound);
|
|
g.DrawLine(&pen, rect.X + rect.Width * 0.22f, centerY, rect.X + rect.Width * 0.42f,
|
|
rect.Y + rect.Height * 0.72f);
|
|
g.DrawLine(&pen, rect.X + rect.Width * 0.42f, rect.Y + rect.Height * 0.72f,
|
|
rect.X + rect.Width * 0.78f, rect.Y + rect.Height * 0.28f);
|
|
}
|
|
else if (state == 2)
|
|
{
|
|
UiFillRoundRect(g, rect, radius, theme.accentSoft);
|
|
UiDrawRoundRect(g, rect, radius, theme.accent, 1.0f);
|
|
UiFillRect(g, gd::RectF(rect.X + rect.Width * 0.22f, centerY - rect.Height * 0.08f,
|
|
rect.Width * 0.56f, rect.Height * 0.16f),
|
|
theme.accent);
|
|
}
|
|
else
|
|
{
|
|
UiFillRoundRect(g, rect, radius, theme.cardBg);
|
|
UiDrawRoundRect(g, rect, radius, theme.borderStrong, 1.0f);
|
|
}
|
|
}
|
|
|
|
void UiDrawSlider(gd::Graphics& g, const gd::RectF& track, double ratio, std::uint32_t fillColor,
|
|
bool active, float scale)
|
|
{
|
|
if (track.Width <= 0.0f || track.Height <= 0.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (ratio < 0.0)
|
|
{
|
|
ratio = 0.0;
|
|
}
|
|
if (ratio > 1.0)
|
|
{
|
|
ratio = 1.0;
|
|
}
|
|
|
|
const float radius = track.Height * 0.5f;
|
|
UiFillRoundRect(g, track, radius, 0x2A657B83u);
|
|
|
|
// 已填充部分严格止于圆钮中心,最小值时不会留下超出圆钮的填充残留。
|
|
const float fillWidth = static_cast<float>(ratio) * track.Width;
|
|
if (fillWidth > 0.5f)
|
|
{
|
|
UiFillRoundRect(g, gd::RectF(track.X, track.Y, fillWidth, track.Height), radius, fillColor);
|
|
}
|
|
|
|
const float knobRadius = radius + (active ? 2.5f * scale : 0.8f * scale);
|
|
const float knobX = track.X + static_cast<float>(ratio) * track.Width;
|
|
|
|
gd::SolidBrush knob(ToGdiColor(0xFFFFFFFFu));
|
|
gd::Pen knobPen(ToGdiColor(0xFF586E75u), 1.4f * scale);
|
|
g.FillEllipse(&knob, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
|
|
knobRadius * 2.0f);
|
|
g.DrawEllipse(&knobPen, knobX - knobRadius, track.Y + radius - knobRadius, knobRadius * 2.0f,
|
|
knobRadius * 2.0f);
|
|
}
|
|
|
|
void UiDrawEditField(gd::Graphics& g, const gd::RectF& rect, const TextEdit& edit, HFONT font, float scale)
|
|
{
|
|
if (rect.Width <= 0.0f || rect.Height <= 0.0f || font == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const Theme& theme = SolarizedLight();
|
|
const float corner = 3.0f * scale;
|
|
|
|
UiFillRoundRect(g, rect, corner, theme.cardBg);
|
|
UiDrawRoundRect(g, rect, corner, theme.accent, 1.6f);
|
|
|
|
const std::wstring& text = edit.Text();
|
|
if (text.empty())
|
|
{
|
|
// 空内容仍绘制插入符,保持焦点可见。
|
|
const float caretX = rect.X + 6.0f * scale;
|
|
UiDrawLine(g, caretX, rect.Y + 4.0f * scale, caretX, rect.Y + rect.Height - 4.0f * scale,
|
|
theme.accent, 1.4f * scale);
|
|
return;
|
|
}
|
|
|
|
const float innerLeft = rect.X + 6.0f * scale;
|
|
const float innerWidth = std::max(4.0f, rect.Width - 12.0f * scale);
|
|
|
|
// 光标与选区端点对应的像素位置(以文本左端为原点)。
|
|
const float caretX = UiTextWidth(g, text.substr(0, edit.Caret()), font);
|
|
const float selBeginX =
|
|
edit.HasSelection() ? UiTextWidth(g, text.substr(0, edit.SelectionBegin()), font) : 0.0f;
|
|
const float selEndX =
|
|
edit.HasSelection() ? UiTextWidth(g, text.substr(0, edit.SelectionEnd()), font) : 0.0f;
|
|
const float textWidth = UiTextWidth(g, text, font);
|
|
|
|
// 内容超出可视宽度时横向滚动,保证光标始终可见(向常规输入框靠齐)。
|
|
float scroll = 0.0f;
|
|
if (textWidth > innerWidth)
|
|
{
|
|
if (caretX > innerWidth)
|
|
{
|
|
scroll = caretX - innerWidth;
|
|
}
|
|
const float maximum = textWidth - innerWidth;
|
|
scroll = std::max(0.0f, std::min(scroll, maximum));
|
|
}
|
|
|
|
const gd::RectF clipRect(innerLeft, rect.Y + 1.0f * scale, innerWidth, rect.Height - 2.0f * scale);
|
|
const int saveState = g.Save();
|
|
g.SetClip(clipRect, gd::CombineModeIntersect);
|
|
|
|
const float textLeft = innerLeft - scroll;
|
|
|
|
// 选区高亮绘制在文本之下,整段选中时呈现“已全选”观感。
|
|
const bool fullSelection = edit.HasSelection() && edit.SelectionBegin() == 0 &&
|
|
edit.SelectionEnd() == text.size();
|
|
if (edit.HasSelection() && selEndX > selBeginX)
|
|
{
|
|
const gd::RectF highlight(textLeft + selBeginX, rect.Y + 3.0f * scale,
|
|
selEndX - selBeginX, rect.Height - 6.0f * scale);
|
|
UiFillRect(g, highlight, theme.accent);
|
|
}
|
|
|
|
UiText(g, text, font, gd::RectF(textLeft, rect.Y, std::max(textWidth, innerWidth), rect.Height),
|
|
theme.textStrong, TextAlign::LEFT);
|
|
|
|
if (edit.HasSelection() && !fullSelection && selEndX > selBeginX)
|
|
{
|
|
// 局部选区:在反白底衬上重绘被选中的片段,其余文字保持常规颜色。
|
|
const std::wstring selected =
|
|
text.substr(edit.SelectionBegin(), edit.SelectionEnd() - edit.SelectionBegin());
|
|
UiText(g, selected, font,
|
|
gd::RectF(textLeft + selBeginX, rect.Y, selEndX - selBeginX, rect.Height),
|
|
theme.textOnAccent, TextAlign::LEFT);
|
|
}
|
|
else if (fullSelection)
|
|
{
|
|
UiText(g, text, font, gd::RectF(textLeft, rect.Y, std::max(textWidth, innerWidth), rect.Height),
|
|
theme.textOnAccent, TextAlign::LEFT);
|
|
}
|
|
|
|
// 插入符。
|
|
const float caretDrawX = textLeft + caretX;
|
|
UiDrawLine(g, caretDrawX, rect.Y + 4.0f * scale, caretDrawX, rect.Y + rect.Height - 4.0f * scale,
|
|
theme.textStrong, 1.4f * scale);
|
|
|
|
g.Restore(saveState);
|
|
}
|
|
|
|
bool UiSwatchNeedsOutline(float opacity)
|
|
{
|
|
return !(opacity >= kSwatchOutlineOpacity);
|
|
}
|
|
|
|
std::uint32_t UiDrawSwatch(gd::Graphics& g, const gd::RectF& rect, std::uint32_t color, float opacity,
|
|
float radius, float scale)
|
|
{
|
|
double alpha = static_cast<double>(opacity);
|
|
if (!(alpha > 0.0))
|
|
{
|
|
alpha = 0.0;
|
|
}
|
|
if (alpha > 1.0)
|
|
{
|
|
alpha = 1.0;
|
|
}
|
|
|
|
const std::uint32_t filled = WithAlpha(color, static_cast<unsigned int>(alpha * 255.0 + 0.5));
|
|
if (rect.Width > 0.0f && rect.Height > 0.0f)
|
|
{
|
|
UiFillRoundRect(g, rect, radius, filled);
|
|
|
|
// 透明度过低时色块本身难以辨识,补充一圈描边以保持可读性;可见时不绘制描边。
|
|
if (UiSwatchNeedsOutline(opacity))
|
|
{
|
|
UiDrawRoundRect(g, rect, radius, SolarizedLight().borderStrong,
|
|
std::max(1.0f, scale));
|
|
}
|
|
}
|
|
return filled;
|
|
}
|
|
|
|
} // namespace jdi
|