Add horizontal caret scrolling for single-line TextBox
This commit is contained in:
1 parent
ea71bad9f9
commit
2cb7c21793
6 files changed
+83
-11
No files matched your search
@@ -80,6 +80,7 @@
|
||||
- 调整了 `TreeView` 各层级节点背景框的左侧起点:子节点的圆角矩形现在会随缩进一起右移,更贴合本层内容的实际包裹范围
|
||||
- 为 `TreeView` 补充了竖向层级引导线:祖先层级列和展开节点的子树主干线现在都会显示,层级结构更直观
|
||||
- 微调了 `TreeView` 每个节点左侧的内部留白和引导线位置,让竖向层级线与下一层级节点边框之间留出更自然的间距
|
||||
- 单行 `TextBox` 现在支持基于光标位置的横向自动滚动:当文本向右溢出时,左右箭头、输入插入、删除和鼠标定位都会自动把当前 caret 滚动到可见区域
|
||||
|
||||
## 构建
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ void TextBox::Arrange(const RectF &finalRect)
|
||||
{
|
||||
RECT rc = ComputeEditorHostRect();
|
||||
editor_->SetHostRect(rc);
|
||||
editor_->SetSingleLine(preferredHeight_ <= 100.0f ? TRUE : FALSE);
|
||||
editor_->SetContentPadding({});
|
||||
editor_->UpdateLayout();
|
||||
editor_->SetContentPadding(ComputeEditorContentPadding());
|
||||
|
||||
@@ -24,6 +24,7 @@ void CTextEditor::MoveSelection(UINT nSelStart, UINT nSelEnd)
|
||||
_nSelStart = nSelStart;
|
||||
_nSelEnd = nSelEnd;
|
||||
_layout.ResetCaretBlink();
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
|
||||
_pTextStore->OnSelectionChange();
|
||||
}
|
||||
@@ -149,6 +150,7 @@ BOOL CTextEditor::InsertAtSelection(LPCWSTR psz)
|
||||
_nSelEnd = _nSelStart;
|
||||
_layout.ResetCaretBlink();
|
||||
UpdateLayout();
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
|
||||
_pTextStore->OnTextChange(_nSelStart, lOldSelEnd, _nSelEnd);
|
||||
_pTextStore->OnSelectionChange();
|
||||
@@ -177,6 +179,7 @@ BOOL CTextEditor::DeleteAtSelection(BOOL fBack)
|
||||
|
||||
_layout.ResetCaretBlink();
|
||||
UpdateLayout();
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
_pTextStore->OnTextChange(_nSelEnd, _nSelEnd + 1, _nSelEnd);
|
||||
}
|
||||
|
||||
@@ -189,6 +192,7 @@ BOOL CTextEditor::DeleteAtSelection(BOOL fBack)
|
||||
_nSelEnd = _nSelStart;
|
||||
_layout.ResetCaretBlink();
|
||||
UpdateLayout();
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
|
||||
_pTextStore->OnTextChange(_nSelStart, _nSelStart + 1, _nSelStart);
|
||||
_pTextStore->OnSelectionChange();
|
||||
@@ -211,6 +215,7 @@ BOOL CTextEditor::DeleteSelection()
|
||||
_nSelEnd = _nSelStart;
|
||||
_layout.ResetCaretBlink();
|
||||
UpdateLayout();
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
|
||||
_pTextStore->OnTextChange(_nSelStart, nSelOldEnd, _nSelStart);
|
||||
_pTextStore->OnSelectionChange();
|
||||
@@ -276,7 +281,8 @@ void CTextEditor::UpdateLayout()
|
||||
const LONG width = max(_rcHost.right - _rcHost.left, 1L);
|
||||
_layout.Layout(GetTextBuffer(), GetTextLength(), &_lfCurrentFont, static_cast<FLOAT>(width), dpi, dpi,
|
||||
static_cast<FLOAT>(_rcContentPadding.left), static_cast<FLOAT>(_rcContentPadding.top),
|
||||
static_cast<FLOAT>(_rcContentPadding.right), static_cast<FLOAT>(_rcContentPadding.bottom));
|
||||
static_cast<FLOAT>(_rcContentPadding.right), static_cast<FLOAT>(_rcContentPadding.bottom), _singleLine);
|
||||
_layout.EnsureCaretVisible(_nSelEnd);
|
||||
}
|
||||
|
||||
void CTextEditor::NotifyLayoutChange()
|
||||
|
||||
@@ -62,6 +62,10 @@ class CTextEditor : public CTextContainer
|
||||
{
|
||||
_rcContentPadding = rcPadding;
|
||||
}
|
||||
void SetSingleLine(BOOL singleLine)
|
||||
{
|
||||
_singleLine = singleLine;
|
||||
}
|
||||
|
||||
UINT GetSelectionStart()
|
||||
{
|
||||
@@ -134,6 +138,7 @@ class CTextEditor : public CTextContainer
|
||||
HWND _hwnd;
|
||||
RECT _rcHost = {};
|
||||
RECT _rcContentPadding = {};
|
||||
BOOL _singleLine = FALSE;
|
||||
|
||||
CTextLayout _layout;
|
||||
LOGFONT _lfCurrentFont;
|
||||
|
||||
@@ -79,7 +79,7 @@ BOOL CTextLayout::EnsureTextFormat(const LOGFONT *plf, FLOAT dpiY)
|
||||
|
||||
BOOL CTextLayout::Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT layoutWidthPixels, FLOAT dpiX, FLOAT dpiY,
|
||||
FLOAT paddingLeftPixels, FLOAT paddingTopPixels, FLOAT paddingRightPixels,
|
||||
FLOAT paddingBottomPixels)
|
||||
FLOAT paddingBottomPixels, BOOL singleLine)
|
||||
{
|
||||
Clear();
|
||||
|
||||
@@ -94,6 +94,7 @@ BOOL CTextLayout::Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT
|
||||
_paddingTopDips = PixelsToDipsY(kEditorPaddingPixels + paddingTopPixels);
|
||||
_paddingRightDips = PixelsToDipsX(kEditorPaddingPixels + paddingRightPixels);
|
||||
_paddingBottomDips = PixelsToDipsY(kEditorPaddingPixels + paddingBottomPixels);
|
||||
_singleLine = singleLine;
|
||||
_layoutWidth = max(PixelsToDipsX(layoutWidthPixels) - _paddingLeftDips - _paddingRightDips, 1.0f);
|
||||
|
||||
const WCHAR *layoutText = psz ? psz : L"";
|
||||
@@ -112,7 +113,7 @@ BOOL CTextLayout::Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
_pTextLayout->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP);
|
||||
_pTextLayout->SetWordWrapping(_singleLine ? DWRITE_WORD_WRAPPING_NO_WRAP : DWRITE_WORD_WRAPPING_WRAP);
|
||||
|
||||
DWRITE_TEXT_METRICS textMetrics = {};
|
||||
if (FAILED(_pTextLayout->GetMetrics(&textMetrics)))
|
||||
@@ -280,7 +281,9 @@ BOOL CTextLayout::Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz,
|
||||
for (UINT j = nSelStartInLine; j < nSelEndInLine; j++)
|
||||
{
|
||||
const D2D1_RECT_F &rc = line.prgCharInfo[j].rc;
|
||||
pRenderTarget->FillRectangle(D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pSelectionBrush);
|
||||
const FLOAT scrollX = _singleLine ? _horizontalScrollDips : 0.0f;
|
||||
pRenderTarget->FillRectangle(D2D1::RectF(rc.left - scrollX, rc.top, rc.right - scrollX, rc.bottom),
|
||||
pSelectionBrush);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,7 +291,9 @@ BOOL CTextLayout::Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz,
|
||||
|
||||
if (_pTextLayout && nCnt > 0)
|
||||
{
|
||||
pRenderTarget->DrawTextLayout(D2D1::Point2F(_paddingLeftDips, _paddingTopDips), _pTextLayout, pTextBrush,
|
||||
pRenderTarget->DrawTextLayout(D2D1::Point2F(_paddingLeftDips - (_singleLine ? _horizontalScrollDips : 0.0f),
|
||||
_paddingTopDips),
|
||||
_pTextLayout, pTextBrush,
|
||||
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
|
||||
}
|
||||
|
||||
@@ -318,7 +323,9 @@ BOOL CTextLayout::Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz,
|
||||
if (composition.da.crBk.type != TF_CT_NONE)
|
||||
{
|
||||
pCompositionBrush->SetColor(GetAttributeColor(&composition.da.crBk, GetSysColor(COLOR_WINDOW)));
|
||||
pRenderTarget->FillRectangle(D2D1::RectF(rc.left, rc.top, rc.right, rc.bottom), pCompositionBrush);
|
||||
const FLOAT scrollX = _singleLine ? _horizontalScrollDips : 0.0f;
|
||||
pRenderTarget->FillRectangle(D2D1::RectF(rc.left - scrollX, rc.top, rc.right - scrollX, rc.bottom),
|
||||
pCompositionBrush);
|
||||
}
|
||||
|
||||
WCHAR ch = psz[line.nPos + k];
|
||||
@@ -329,8 +336,9 @@ BOOL CTextLayout::Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz,
|
||||
}
|
||||
|
||||
pCompositionBrush->SetColor(compositionTextColor);
|
||||
const FLOAT scrollX = _singleLine ? _horizontalScrollDips : 0.0f;
|
||||
pRenderTarget->DrawTextW(&ch, 1, _pTextFormat,
|
||||
D2D1::RectF(rc.left, rc.top, rc.right + PixelsToDipsX(2.0f), rc.bottom),
|
||||
D2D1::RectF(rc.left - scrollX, rc.top, rc.right - scrollX + PixelsToDipsX(2.0f), rc.bottom),
|
||||
pCompositionBrush, D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,
|
||||
DWRITE_MEASURING_MODE_NATURAL);
|
||||
|
||||
@@ -433,6 +441,21 @@ BOOL CTextLayout::RectFromCharPos(UINT nPos, RECT *prc)
|
||||
}
|
||||
|
||||
BOOL CTextLayout::RectFromCharPosDips(UINT nPos, D2D1_RECT_F *prc)
|
||||
{
|
||||
if (!RectFromCharPosDipsRaw(nPos, prc))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (_singleLine)
|
||||
{
|
||||
prc->left -= _horizontalScrollDips;
|
||||
prc->right -= _horizontalScrollDips;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL CTextLayout::RectFromCharPosDipsRaw(UINT nPos, D2D1_RECT_F *prc)
|
||||
{
|
||||
*prc = D2D1::RectF();
|
||||
|
||||
@@ -481,7 +504,7 @@ BOOL CTextLayout::RectFromCharPosDips(UINT nPos, D2D1_RECT_F *prc)
|
||||
|
||||
UINT CTextLayout::CharPosFromPoint(POINT pt)
|
||||
{
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x));
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x)) + (_singleLine ? _horizontalScrollDips : 0.0f);
|
||||
const FLOAT dipY = PixelsToDipsY(static_cast<FLOAT>(pt.y));
|
||||
for (UINT i = 0; i < _nLineCnt; i++)
|
||||
{
|
||||
@@ -515,7 +538,7 @@ UINT CTextLayout::InsertionIndexFromPoint(POINT pt)
|
||||
return 0;
|
||||
}
|
||||
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x));
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x)) + (_singleLine ? _horizontalScrollDips : 0.0f);
|
||||
const FLOAT dipY = PixelsToDipsY(static_cast<FLOAT>(pt.y));
|
||||
|
||||
for (UINT i = 0; i < _nLineCnt; i++)
|
||||
@@ -576,7 +599,7 @@ UINT CTextLayout::InsertionIndexFromPoint(POINT pt)
|
||||
|
||||
UINT CTextLayout::ExactCharPosFromPoint(POINT pt)
|
||||
{
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x));
|
||||
const FLOAT dipX = PixelsToDipsX(static_cast<FLOAT>(pt.x)) + (_singleLine ? _horizontalScrollDips : 0.0f);
|
||||
const FLOAT dipY = PixelsToDipsY(static_cast<FLOAT>(pt.y));
|
||||
for (UINT i = 0; i < _nLineCnt; i++)
|
||||
{
|
||||
@@ -609,6 +632,36 @@ UINT CTextLayout::FineFirstEndCharPosInLine(UINT uCurPos, BOOL bFirst)
|
||||
return static_cast<UINT>(-1);
|
||||
}
|
||||
|
||||
void CTextLayout::EnsureCaretVisible(UINT nPos)
|
||||
{
|
||||
if (!_singleLine)
|
||||
{
|
||||
_horizontalScrollDips = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
D2D1_RECT_F rawCaret = {};
|
||||
if (!RectFromCharPosDipsRaw(nPos, &rawCaret))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FLOAT caretWidth = PixelsToDipsX(kCaretWidthPixels);
|
||||
const FLOAT visibleLeft = _paddingLeftDips;
|
||||
const FLOAT visibleRight = _paddingLeftDips + _layoutWidth;
|
||||
const FLOAT displayLeft = rawCaret.left - _horizontalScrollDips;
|
||||
const FLOAT displayRight = max(rawCaret.right, rawCaret.left + caretWidth) - _horizontalScrollDips;
|
||||
|
||||
if (displayLeft < visibleLeft)
|
||||
{
|
||||
_horizontalScrollDips = max(rawCaret.left - visibleLeft, 0.0f);
|
||||
}
|
||||
else if (displayRight > visibleRight)
|
||||
{
|
||||
_horizontalScrollDips = max(max(rawCaret.right, rawCaret.left + caretWidth) - visibleRight, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextLayout::Clear()
|
||||
{
|
||||
if (_pTextLayout)
|
||||
|
||||
@@ -67,6 +67,8 @@ class CTextLayout
|
||||
_paddingTopDips = 0.0f;
|
||||
_paddingRightDips = 0.0f;
|
||||
_paddingBottomDips = 0.0f;
|
||||
_horizontalScrollDips = 0.0f;
|
||||
_singleLine = FALSE;
|
||||
_rcCaret = D2D1::RectF();
|
||||
}
|
||||
|
||||
@@ -86,7 +88,7 @@ class CTextLayout
|
||||
BOOL Initialize(IDWriteFactory *pDWriteFactory);
|
||||
BOOL Layout(const WCHAR *psz, UINT nCnt, const LOGFONT *plf, FLOAT layoutWidthPixels, FLOAT dpiX, FLOAT dpiY,
|
||||
FLOAT paddingLeftPixels = 0.0f, FLOAT paddingTopPixels = 0.0f, FLOAT paddingRightPixels = 0.0f,
|
||||
FLOAT paddingBottomPixels = 0.0f);
|
||||
FLOAT paddingBottomPixels = 0.0f, BOOL singleLine = FALSE);
|
||||
BOOL Render(ID2D1HwndRenderTarget *pRenderTarget, const WCHAR *psz, UINT nCnt, UINT nSelStart, UINT nSelEnd,
|
||||
const COMPOSITIONRENDERINFO *pCompositionRenderInfo, UINT nCompositionRenderInfo);
|
||||
BOOL RectFromCharPos(UINT nPos, RECT *prc);
|
||||
@@ -94,6 +96,7 @@ class CTextLayout
|
||||
UINT InsertionIndexFromPoint(POINT pt);
|
||||
UINT ExactCharPosFromPoint(POINT pt);
|
||||
UINT FineFirstEndCharPosInLine(UINT uCurPos, BOOL bFirst);
|
||||
void EnsureCaretVisible(UINT nPos);
|
||||
void ToggleCaretBlink();
|
||||
void ResetCaretBlink();
|
||||
void SetCaretVisible(BOOL fVisible)
|
||||
@@ -130,6 +133,7 @@ class CTextLayout
|
||||
private:
|
||||
BOOL EnsureTextFormat(const LOGFONT *plf, FLOAT dpiY);
|
||||
BOOL RectFromCharPosDips(UINT nPos, D2D1_RECT_F *prc);
|
||||
BOOL RectFromCharPosDipsRaw(UINT nPos, D2D1_RECT_F *prc);
|
||||
FLOAT PixelsToDipsX(FLOAT value) const;
|
||||
FLOAT PixelsToDipsY(FLOAT value) const;
|
||||
LONG DipsToPixelsX(FLOAT value) const;
|
||||
@@ -152,6 +156,8 @@ class CTextLayout
|
||||
FLOAT _paddingTopDips;
|
||||
FLOAT _paddingRightDips;
|
||||
FLOAT _paddingBottomDips;
|
||||
FLOAT _horizontalScrollDips;
|
||||
BOOL _singleLine;
|
||||
|
||||
LINEINFO *_prgLines;
|
||||
UINT _nLineCnt;
|
||||
|
||||
Reference in new issue
Block a user