mirror of
https://github.com/elasota/Aerofoil.git
synced 2025-09-23 23:00:42 +00:00
More editbox functionality
This commit is contained in:
@@ -14,6 +14,8 @@
|
|||||||
#include "DialogUtils.h"
|
#include "DialogUtils.h"
|
||||||
#include "HostDisplayDriver.h"
|
#include "HostDisplayDriver.h"
|
||||||
#include "IGpDisplayDriver.h"
|
#include "IGpDisplayDriver.h"
|
||||||
|
#include "PLArrayView.h"
|
||||||
|
#include "PLEditboxWidget.h"
|
||||||
#include "PLTimeTaggedVOSEvent.h"
|
#include "PLTimeTaggedVOSEvent.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -232,6 +234,9 @@ void DoHouseInfo (void)
|
|||||||
SetPort(&houseInfoDialog->GetWindow()->GetDrawSurface()->m_port);
|
SetPort(&houseInfoDialog->GetWindow()->GetDrawSurface()->m_port);
|
||||||
ShowWindow(houseInfoDialog->GetWindow());
|
ShowWindow(houseInfoDialog->GetWindow());
|
||||||
|
|
||||||
|
static_cast<PortabilityLayer::EditboxWidget*>(houseInfoDialog->GetItems()[kBannerTextItem - 1].GetWidget())->SetMultiLine(true);
|
||||||
|
static_cast<PortabilityLayer::EditboxWidget*>(houseInfoDialog->GetItems()[kTrailerTextItem - 1].GetWidget())->SetMultiLine(true);
|
||||||
|
|
||||||
SetDialogString(houseInfoDialog, kBannerTextItem, banner);
|
SetDialogString(houseInfoDialog, kBannerTextItem, banner);
|
||||||
SetDialogString(houseInfoDialog, kTrailerTextItem, trailer);
|
SetDialogString(houseInfoDialog, kTrailerTextItem, trailer);
|
||||||
SelectDialogItemText(houseInfoDialog, kBannerTextItem, 0, 1024);
|
SelectDialogItemText(houseInfoDialog, kBannerTextItem, 0, 1024);
|
||||||
|
@@ -1,12 +1,16 @@
|
|||||||
#include "PLEditboxWidget.h"
|
#include "PLEditboxWidget.h"
|
||||||
|
|
||||||
#include "FontFamily.h"
|
#include "FontFamily.h"
|
||||||
|
#include "FontManager.h"
|
||||||
#include "InputManager.h"
|
#include "InputManager.h"
|
||||||
#include "MacRomanConversion.h"
|
#include "MacRomanConversion.h"
|
||||||
#include "MemoryManager.h"
|
#include "MemoryManager.h"
|
||||||
|
#include "RenderedFont.h"
|
||||||
|
#include "RenderedFontMetrics.h"
|
||||||
#include "PLKeyEncoding.h"
|
#include "PLKeyEncoding.h"
|
||||||
#include "PLStandardColors.h"
|
#include "PLStandardColors.h"
|
||||||
#include "PLTimeTaggedVOSEvent.h"
|
#include "PLTimeTaggedVOSEvent.h"
|
||||||
|
#include "TextPlacer.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
@@ -19,8 +23,12 @@ namespace PortabilityLayer
|
|||||||
, m_chars(nullptr)
|
, m_chars(nullptr)
|
||||||
, m_selStartChar(0)
|
, m_selStartChar(0)
|
||||||
, m_selEndChar(0)
|
, m_selEndChar(0)
|
||||||
|
, m_caratSelectionAnchor(CaratSelectionAnchor_End)
|
||||||
|
, m_caratScrollPosition(0, 0)
|
||||||
|
, m_caratScrollLocked(false)
|
||||||
, m_hasFocus(false)
|
, m_hasFocus(false)
|
||||||
, m_caratTimer(0)
|
, m_caratTimer(0)
|
||||||
|
, m_isMultiLine(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,45 +78,29 @@ namespace PortabilityLayer
|
|||||||
|
|
||||||
const char *strChars = str.Chars();
|
const char *strChars = str.Chars();
|
||||||
|
|
||||||
size_t preSelWidth = 0;
|
Vec2i basePoint = ResolveBasePoint();
|
||||||
size_t selWidth = 0;
|
|
||||||
//size_t postSelWidth = 0;
|
|
||||||
if (m_selStartChar > 0)
|
|
||||||
preSelWidth = surface->MeasureString(PLPasStr(static_cast<uint8_t>(m_selStartChar), strChars));
|
|
||||||
|
|
||||||
if (m_selEndChar > m_selStartChar)
|
if (m_hasFocus && m_selStartChar != m_selEndChar)
|
||||||
selWidth = surface->MeasureString(PLPasStr(static_cast<uint8_t>(m_selEndChar - m_selStartChar), strChars + m_selStartChar));
|
DrawSelection(surface, basePoint);
|
||||||
|
|
||||||
//if (m_selEndChar < str.Length())
|
int32_t verticalOffset = (ascender + lineGap + 1) / 2;
|
||||||
// postSelWidth = surface->MeasureString(PLPasStr(static_cast<uint8_t>(m_selEndChar - str.Length()), strChars + m_selEndChar));
|
|
||||||
|
|
||||||
Point basePoint = Point::Create(textRect.left, (textRect.top + textRect.bottom + ascender + 1) / 2);
|
|
||||||
|
|
||||||
if (m_hasFocus && selWidth > 0)
|
|
||||||
{
|
|
||||||
Rect selRect = Rect::Create(m_rect.top, static_cast<int16_t>(basePoint.h + preSelWidth), m_rect.bottom, m_rect.right);
|
|
||||||
if (m_selEndChar != str.Length())
|
|
||||||
selRect.right = static_cast<int16_t>(basePoint.h + preSelWidth + selWidth);
|
|
||||||
|
|
||||||
selRect = selRect.Intersect(m_rect);
|
|
||||||
|
|
||||||
if (selRect.IsValid())
|
|
||||||
{
|
|
||||||
PortabilityLayer::RGBAColor focusColor = PortabilityLayer::RGBAColor::Create(153, 153, 255, 255);
|
|
||||||
surface->SetForeColor(focusColor);
|
|
||||||
surface->FillRect(selRect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
surface->SetForeColor(StdColors::Black());
|
surface->SetForeColor(StdColors::Black());
|
||||||
surface->DrawStringConstrained(basePoint, this->GetString(), true, m_rect);
|
|
||||||
|
const Point stringBasePoint = Point::Create(basePoint.m_x, basePoint.m_y + verticalOffset);
|
||||||
|
|
||||||
|
if (m_isMultiLine)
|
||||||
|
surface->DrawStringWrap(stringBasePoint, m_rect, this->GetString(), true);
|
||||||
|
else
|
||||||
|
surface->DrawStringConstrained(stringBasePoint, this->GetString(), true, m_rect);
|
||||||
|
|
||||||
if (m_hasFocus && m_selEndChar == m_selStartChar && m_caratTimer < kCaratBlinkRate)
|
if (m_hasFocus && m_selEndChar == m_selStartChar && m_caratTimer < kCaratBlinkRate)
|
||||||
{
|
{
|
||||||
int16_t caratTop = (textRect.top + textRect.bottom - lineGap + 1) / 2;
|
PortabilityLayer::Vec2i caratPos = ResolveCaratPos(basePoint, surface->ResolveFont(true));
|
||||||
int16_t caratBottom = (textRect.top + textRect.bottom + lineGap + 1) / 2;
|
|
||||||
int16_t caratH = static_cast<int16_t>(basePoint.h + preSelWidth);
|
int32_t caratTop = caratPos.m_y;
|
||||||
Rect caratRect = Rect::Create(caratTop, caratH, caratBottom, caratH + 1);
|
int32_t caratBottom = caratTop + lineGap;
|
||||||
|
Rect caratRect = Rect::Create(caratTop, caratPos.m_x, caratBottom, caratPos.m_x + 1);
|
||||||
|
|
||||||
caratRect = caratRect.Intersect(m_rect);
|
caratRect = caratRect.Intersect(m_rect);
|
||||||
|
|
||||||
@@ -148,6 +140,8 @@ namespace PortabilityLayer
|
|||||||
m_hasFocus = true;
|
m_hasFocus = true;
|
||||||
m_selStartChar = 0;
|
m_selStartChar = 0;
|
||||||
m_selEndChar = this->GetString().Length();
|
m_selEndChar = this->GetString().Length();
|
||||||
|
m_caratSelectionAnchor = CaratSelectionAnchor_End;
|
||||||
|
m_caratScrollLocked = false;
|
||||||
|
|
||||||
if (m_window)
|
if (m_window)
|
||||||
{
|
{
|
||||||
@@ -161,6 +155,8 @@ namespace PortabilityLayer
|
|||||||
m_hasFocus = false;
|
m_hasFocus = false;
|
||||||
m_selStartChar = 0;
|
m_selStartChar = 0;
|
||||||
m_selEndChar = 0;
|
m_selEndChar = 0;
|
||||||
|
m_caratSelectionAnchor = CaratSelectionAnchor_End;
|
||||||
|
m_caratScrollLocked = false;
|
||||||
|
|
||||||
Redraw();
|
Redraw();
|
||||||
}
|
}
|
||||||
@@ -200,6 +196,8 @@ namespace PortabilityLayer
|
|||||||
{
|
{
|
||||||
if (ch >= 0x20 && ch <= 0x7e)
|
if (ch >= 0x20 && ch <= 0x7e)
|
||||||
HandleCharacter(ch, keyEvent.m_repeatCount);
|
HandleCharacter(ch, keyEvent.m_repeatCount);
|
||||||
|
else if ((ch == '\r' || ch == '\n') && m_isMultiLine)
|
||||||
|
HandleCharacter('\r', keyEvent.m_repeatCount);
|
||||||
|
|
||||||
return WidgetHandleStates::kDigested;
|
return WidgetHandleStates::kDigested;
|
||||||
}
|
}
|
||||||
@@ -216,13 +214,38 @@ namespace PortabilityLayer
|
|||||||
HandleBackspace(keyEvent.m_repeatCount);
|
HandleBackspace(keyEvent.m_repeatCount);
|
||||||
return WidgetHandleStates::kDigested;
|
return WidgetHandleStates::kDigested;
|
||||||
}
|
}
|
||||||
|
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kUpArrow)
|
||||||
|
{
|
||||||
|
HandleUpArrow(keyEvent.m_repeatCount, isShiftHeld);
|
||||||
|
return WidgetHandleStates::kDigested;
|
||||||
|
}
|
||||||
|
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kLeftArrow)
|
||||||
|
{
|
||||||
|
HandleLeftArrow(keyEvent.m_repeatCount, isShiftHeld);
|
||||||
|
return WidgetHandleStates::kDigested;
|
||||||
|
}
|
||||||
|
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kRightArrow)
|
||||||
|
{
|
||||||
|
HandleRightArrow(keyEvent.m_repeatCount, isShiftHeld);
|
||||||
|
return WidgetHandleStates::kDigested;
|
||||||
|
}
|
||||||
|
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDownArrow)
|
||||||
|
{
|
||||||
|
HandleDownArrow(keyEvent.m_repeatCount, isShiftHeld);
|
||||||
|
return WidgetHandleStates::kDigested;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDelete)
|
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDelete)
|
||||||
{
|
{
|
||||||
return WidgetHandleStates::kDigested;
|
return WidgetHandleStates::kDigested;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (evt.m_vosEvent.m_eventType == GpVOSEventTypes::kMouseInput)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
return WidgetHandleStates::kIgnored;
|
return WidgetHandleStates::kIgnored;
|
||||||
}
|
}
|
||||||
@@ -259,6 +282,7 @@ namespace PortabilityLayer
|
|||||||
|
|
||||||
m_selStartChar = startChar;
|
m_selStartChar = startChar;
|
||||||
m_selEndChar = endChar;
|
m_selEndChar = endChar;
|
||||||
|
m_caratSelectionAnchor = CaratSelectionAnchor_End;
|
||||||
|
|
||||||
m_caratTimer = 0;
|
m_caratTimer = 0;
|
||||||
Redraw();
|
Redraw();
|
||||||
@@ -313,6 +337,8 @@ namespace PortabilityLayer
|
|||||||
|
|
||||||
m_caratTimer = 0;
|
m_caratTimer = 0;
|
||||||
Redraw();
|
Redraw();
|
||||||
|
|
||||||
|
m_caratScrollLocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditboxWidget::HandleBackspace(uint32_t numRepeatsRequested)
|
void EditboxWidget::HandleBackspace(uint32_t numRepeatsRequested)
|
||||||
@@ -345,5 +371,369 @@ namespace PortabilityLayer
|
|||||||
|
|
||||||
m_caratTimer = 0;
|
m_caratTimer = 0;
|
||||||
Redraw();
|
Redraw();
|
||||||
|
|
||||||
|
m_caratScrollLocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::HandleUpArrow(const uint32_t numRepeatsRequested, bool shiftHeld)
|
||||||
|
{
|
||||||
|
if (!m_isMultiLine)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t caratChar = ResolveCaratChar();
|
||||||
|
|
||||||
|
PortabilityLayer::RenderedFont *rfont = GetRenderedFont();
|
||||||
|
int32_t lineGap = rfont->GetMetrics().m_linegap;
|
||||||
|
|
||||||
|
if (!rfont)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_caratScrollLocked)
|
||||||
|
{
|
||||||
|
m_caratScrollPosition = ResolveCaratPos(Vec2i(0, 0), rfont);
|
||||||
|
m_caratScrollLocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i caratPos = m_caratScrollPosition;
|
||||||
|
|
||||||
|
for (uint32_t r = 0; r < numRepeatsRequested; r++)
|
||||||
|
{
|
||||||
|
bool isOutOfRange = false;
|
||||||
|
m_caratScrollPosition.m_y -= lineGap;
|
||||||
|
caratChar = FindVerticalMovementCaratPos(m_caratScrollPosition, isOutOfRange);
|
||||||
|
HandleKeyMoveCarat(caratChar, shiftHeld);
|
||||||
|
|
||||||
|
if (isOutOfRange)
|
||||||
|
{
|
||||||
|
m_caratScrollPosition.m_y += lineGap;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_caratTimer = 0;
|
||||||
|
Redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::HandleDownArrow(const uint32_t numRepeatsRequested, bool shiftHeld)
|
||||||
|
{
|
||||||
|
if (!m_isMultiLine)
|
||||||
|
return;
|
||||||
|
|
||||||
|
size_t caratChar = ResolveCaratChar();
|
||||||
|
|
||||||
|
PortabilityLayer::RenderedFont *rfont = GetRenderedFont();
|
||||||
|
int32_t lineGap = rfont->GetMetrics().m_linegap;
|
||||||
|
|
||||||
|
if (!rfont)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!m_caratScrollLocked)
|
||||||
|
{
|
||||||
|
m_caratScrollPosition = ResolveCaratPos(Vec2i(0, 0), rfont);
|
||||||
|
m_caratScrollLocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i caratPos = m_caratScrollPosition;
|
||||||
|
|
||||||
|
for (uint32_t r = 0; r < numRepeatsRequested; r++)
|
||||||
|
{
|
||||||
|
bool isOutOfRange = false;
|
||||||
|
m_caratScrollPosition.m_y += lineGap;
|
||||||
|
caratChar = FindVerticalMovementCaratPos(m_caratScrollPosition, isOutOfRange);
|
||||||
|
HandleKeyMoveCarat(caratChar, shiftHeld);
|
||||||
|
|
||||||
|
if (isOutOfRange)
|
||||||
|
{
|
||||||
|
m_caratScrollPosition.m_y -= lineGap;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_caratTimer = 0;
|
||||||
|
Redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::HandleLeftArrow(const uint32_t numRepeatsRequested, bool shiftHeld)
|
||||||
|
{
|
||||||
|
size_t caratChar = ResolveCaratChar();
|
||||||
|
|
||||||
|
for (uint32_t r = 0; r < numRepeatsRequested; r++)
|
||||||
|
{
|
||||||
|
if (!shiftHeld && m_selStartChar != m_selEndChar)
|
||||||
|
m_selEndChar = m_selStartChar;
|
||||||
|
else if (caratChar > 0)
|
||||||
|
HandleKeyMoveCarat(caratChar - 1, shiftHeld);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_caratScrollLocked = false;
|
||||||
|
|
||||||
|
m_caratTimer = 0;
|
||||||
|
Redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::HandleRightArrow(const uint32_t numRepeatsRequested, bool shiftHeld)
|
||||||
|
{
|
||||||
|
size_t caratChar = ResolveCaratChar();
|
||||||
|
|
||||||
|
for (uint32_t r = 0; r < numRepeatsRequested; r++)
|
||||||
|
{
|
||||||
|
if (!shiftHeld && m_selStartChar != m_selEndChar)
|
||||||
|
m_selStartChar = m_selEndChar;
|
||||||
|
else if (caratChar < m_length)
|
||||||
|
HandleKeyMoveCarat(caratChar + 1, shiftHeld);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_caratScrollLocked = false;
|
||||||
|
|
||||||
|
m_caratTimer = 0;
|
||||||
|
Redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t EditboxWidget::FindVerticalMovementCaratPos(const Vec2i &desiredPos, bool &outShouldUnlock) const
|
||||||
|
{
|
||||||
|
assert(m_isMultiLine);
|
||||||
|
|
||||||
|
Vec2i basePoint = Vec2i(0, 0);
|
||||||
|
|
||||||
|
if (desiredPos.m_y < basePoint.m_y)
|
||||||
|
{
|
||||||
|
outShouldUnlock = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PortabilityLayer::TextPlacer placer(basePoint, m_rect.Width(), GetRenderedFont(), GetString());
|
||||||
|
|
||||||
|
bool foundLine = false;
|
||||||
|
size_t caratChar = 0;
|
||||||
|
|
||||||
|
PortabilityLayer::GlyphPlacementCharacteristics characteristics;
|
||||||
|
while (placer.PlaceGlyph(characteristics))
|
||||||
|
{
|
||||||
|
if (characteristics.m_glyphStartPos.m_y > desiredPos.m_y)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (characteristics.m_glyphStartPos.m_y == desiredPos.m_y)
|
||||||
|
{
|
||||||
|
caratChar = characteristics.m_characterIndex;
|
||||||
|
if (characteristics.m_character == '\r')
|
||||||
|
caratChar--;
|
||||||
|
foundLine = true;
|
||||||
|
|
||||||
|
if (characteristics.m_glyphStartPos.m_x <= desiredPos.m_x && characteristics.m_glyphEndPos.m_x > desiredPos.m_x)
|
||||||
|
{
|
||||||
|
int32_t distanceToEnd = characteristics.m_glyphEndPos.m_x - desiredPos.m_x;
|
||||||
|
int32_t distanceToStart = desiredPos.m_x - characteristics.m_glyphStartPos.m_x;
|
||||||
|
|
||||||
|
if (distanceToStart <= distanceToEnd)
|
||||||
|
caratChar = characteristics.m_characterIndex;
|
||||||
|
else
|
||||||
|
caratChar = characteristics.m_characterIndex + 1;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundLine)
|
||||||
|
{
|
||||||
|
outShouldUnlock = false;
|
||||||
|
return caratChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
outShouldUnlock = true;
|
||||||
|
return m_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::HandleKeyMoveCarat(size_t newPos, bool shiftHeld)
|
||||||
|
{
|
||||||
|
if (shiftHeld)
|
||||||
|
{
|
||||||
|
size_t otherSelection = m_selStartChar;
|
||||||
|
if (m_caratSelectionAnchor == CaratSelectionAnchor_Start)
|
||||||
|
otherSelection = m_selEndChar;
|
||||||
|
|
||||||
|
m_selStartChar = std::min<size_t>(newPos, otherSelection);
|
||||||
|
m_selEndChar = std::max<size_t>(newPos, otherSelection);
|
||||||
|
|
||||||
|
if (m_selStartChar == newPos)
|
||||||
|
m_caratSelectionAnchor = CaratSelectionAnchor_Start;
|
||||||
|
else if (m_selEndChar == newPos)
|
||||||
|
m_caratSelectionAnchor = CaratSelectionAnchor_End;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_selStartChar = newPos;
|
||||||
|
m_selEndChar = newPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::DrawSelection(DrawSurface *surface, const Vec2i &basePoint) const
|
||||||
|
{
|
||||||
|
PortabilityLayer::RenderedFont *rfont = surface->ResolveFont(true);
|
||||||
|
PortabilityLayer::TextPlacer placer(basePoint, m_isMultiLine ? m_rect.Width() : -1, rfont, GetString());
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (m_selStartChar == m_selEndChar)
|
||||||
|
{
|
||||||
|
PortabilityLayer::GlyphPlacementCharacteristics characteristics;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const bool placedGlyph = placer.PlaceGlyph(characteristics);
|
||||||
|
|
||||||
|
if (!placedGlyph)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (characteristics.m_characterIndex == m_selStartChar)
|
||||||
|
{
|
||||||
|
caratPos = characteristics.m_glyphStartPos;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (characteristics.m_characterIndex < m_selStartChar)
|
||||||
|
caratPos = characteristics.m_glyphEndPos;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
outCaratPos = Point::Create(caratPos.m_x, caratPos.m_y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PortabilityLayer::Vec2i globalSelStart;
|
||||||
|
PortabilityLayer::Vec2i globalSelEnd;
|
||||||
|
bool endIsLineBreak = false;
|
||||||
|
bool startSet = false;
|
||||||
|
bool endSet = false;
|
||||||
|
|
||||||
|
PortabilityLayer::GlyphPlacementCharacteristics characteristics;
|
||||||
|
size_t placedIndex = 0;
|
||||||
|
|
||||||
|
while (placer.PlaceGlyph(characteristics))
|
||||||
|
{
|
||||||
|
bool isTerminalForThisPara = false;
|
||||||
|
bool isTerminalForEverything = false;
|
||||||
|
bool isLineBreakSelected = false;
|
||||||
|
|
||||||
|
if (characteristics.m_characterIndex == m_selStartChar)
|
||||||
|
{
|
||||||
|
globalSelStart = characteristics.m_glyphStartPos;
|
||||||
|
startSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (characteristics.m_characterIndex + 1 == m_selEndChar)
|
||||||
|
{
|
||||||
|
globalSelEnd = characteristics.m_glyphEndPos;
|
||||||
|
if (characteristics.m_character == '\r')
|
||||||
|
endIsLineBreak = true;
|
||||||
|
|
||||||
|
endSet = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!endSet || !startSet)
|
||||||
|
{
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PortabilityLayer::RGBAColor focusColor = PortabilityLayer::RGBAColor::Create(153, 153, 255, 255);
|
||||||
|
surface->SetForeColor(focusColor);
|
||||||
|
|
||||||
|
int32_t lineGap = rfont->GetMetrics().m_linegap;
|
||||||
|
int32_t ascender = rfont->GetMetrics().m_ascent;
|
||||||
|
int32_t startY = basePoint.m_y;
|
||||||
|
|
||||||
|
if (globalSelStart.m_y == globalSelEnd.m_y)
|
||||||
|
{
|
||||||
|
Rect selRect = Rect::Create(globalSelStart.m_y, globalSelStart.m_x, globalSelStart.m_y + lineGap, globalSelEnd.m_x).Intersect(m_rect);
|
||||||
|
if (endIsLineBreak || (m_isMultiLine == false && m_selEndChar == m_length))
|
||||||
|
selRect.right = m_rect.right;
|
||||||
|
|
||||||
|
surface->FillRect(selRect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const Rect firstLineRect = Rect::Create(globalSelStart.m_y, globalSelStart.m_x, globalSelStart.m_y + lineGap, m_rect.right).Intersect(m_rect);
|
||||||
|
surface->FillRect(firstLineRect);
|
||||||
|
|
||||||
|
const Rect midLinesRect = Rect::Create(globalSelStart.m_y + lineGap, m_rect.left, globalSelEnd.m_y, m_rect.right).Intersect(m_rect);
|
||||||
|
surface->FillRect(midLinesRect);
|
||||||
|
|
||||||
|
Rect lastLineRect = Rect::Create(globalSelEnd.m_y, m_rect.left, globalSelEnd.m_y + lineGap, globalSelEnd.m_x);
|
||||||
|
if (endIsLineBreak || (m_isMultiLine == false && m_selEndChar == m_length))
|
||||||
|
lastLineRect.right = m_rect.right;
|
||||||
|
|
||||||
|
surface->FillRect(lastLineRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i EditboxWidget::ResolveCaratPos(const Vec2i &basePoint, PortabilityLayer::RenderedFont *rfont) const
|
||||||
|
{
|
||||||
|
int32_t lineGap = rfont->GetMetrics().m_linegap;
|
||||||
|
bool failed = false;
|
||||||
|
|
||||||
|
PortabilityLayer::Vec2i caratPos = basePoint;
|
||||||
|
|
||||||
|
const size_t caratChar = ResolveCaratChar();
|
||||||
|
|
||||||
|
if (caratChar > 0)
|
||||||
|
{
|
||||||
|
PortabilityLayer::RenderedFont *rfont = GetRenderedFont();
|
||||||
|
PortabilityLayer::TextPlacer placer(basePoint, m_isMultiLine ? m_rect.Width() : -1, rfont, GetString());
|
||||||
|
|
||||||
|
PortabilityLayer::GlyphPlacementCharacteristics characteristics;
|
||||||
|
for (size_t i = 0; i < caratChar; i++)
|
||||||
|
{
|
||||||
|
if (!placer.PlaceGlyph(characteristics))
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!failed)
|
||||||
|
{
|
||||||
|
if (characteristics.m_character == '\r')
|
||||||
|
caratPos = PortabilityLayer::Vec2i(basePoint.m_x, characteristics.m_glyphStartPos.m_y + lineGap);
|
||||||
|
else
|
||||||
|
caratPos = characteristics.m_glyphEndPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return caratPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2i EditboxWidget::ResolveBasePoint() const
|
||||||
|
{
|
||||||
|
return Vec2i(m_rect.left, m_rect.top);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t EditboxWidget::ResolveCaratChar() const
|
||||||
|
{
|
||||||
|
if (m_caratSelectionAnchor == CaratSelectionAnchor_End)
|
||||||
|
return m_selEndChar;
|
||||||
|
else
|
||||||
|
return m_selStartChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
FontFamily *EditboxWidget::GetFontFamily() const
|
||||||
|
{
|
||||||
|
return PortabilityLayer::FontManager::GetInstance()->GetSystemFont(12, FontFamilyFlag_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderedFont *EditboxWidget::GetRenderedFont() const
|
||||||
|
{
|
||||||
|
return PortabilityLayer::FontManager::GetInstance()->GetRenderedFontFromFamily(GetFontFamily(), 12, true, FontFamilyFlag_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EditboxWidget::SetMultiLine(bool isMultiLine)
|
||||||
|
{
|
||||||
|
if (m_isMultiLine != isMultiLine)
|
||||||
|
{
|
||||||
|
m_isMultiLine = isMultiLine;
|
||||||
|
Redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include "PascalStr.h"
|
#include "PascalStr.h"
|
||||||
#include "PLWidgets.h"
|
#include "PLWidgets.h"
|
||||||
|
#include "Vec2i.h"
|
||||||
|
|
||||||
|
struct DrawSurface;
|
||||||
|
|
||||||
namespace PortabilityLayer
|
namespace PortabilityLayer
|
||||||
{
|
{
|
||||||
@@ -28,9 +31,17 @@ namespace PortabilityLayer
|
|||||||
|
|
||||||
void SetSelection(size_t startChar, size_t endChar);
|
void SetSelection(size_t startChar, size_t endChar);
|
||||||
|
|
||||||
|
void SetMultiLine(bool isMultiLine);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const unsigned int kCaratBlinkRate = 20;
|
static const unsigned int kCaratBlinkRate = 20;
|
||||||
|
|
||||||
|
enum CaratSelectionAnchor
|
||||||
|
{
|
||||||
|
CaratSelectionAnchor_Start,
|
||||||
|
CaratSelectionAnchor_End
|
||||||
|
};
|
||||||
|
|
||||||
void OnTick() override;
|
void OnTick() override;
|
||||||
void Redraw();
|
void Redraw();
|
||||||
|
|
||||||
@@ -38,13 +49,35 @@ namespace PortabilityLayer
|
|||||||
void HandleBackspace(const uint32_t numRepeatsRequested);
|
void HandleBackspace(const uint32_t numRepeatsRequested);
|
||||||
void HandleForwardDelete(const uint32_t numRepeatsRequested);
|
void HandleForwardDelete(const uint32_t numRepeatsRequested);
|
||||||
|
|
||||||
|
void HandleUpArrow(const uint32_t numRepeatsRequested, bool shiftHeld);
|
||||||
|
void HandleDownArrow(const uint32_t numRepeatsRequested, bool shiftHeld);
|
||||||
|
void HandleLeftArrow(const uint32_t numRepeatsRequested, bool shiftHeld);
|
||||||
|
void HandleRightArrow(const uint32_t numRepeatsRequested, bool shiftHeld);
|
||||||
|
|
||||||
|
size_t FindVerticalMovementCaratPos(const Vec2i &desiredPos, bool &isOutOfRange) const;
|
||||||
|
void HandleKeyMoveCarat(size_t newPos, bool shiftHeld);
|
||||||
|
|
||||||
|
void DrawSelection(DrawSurface *surface, const Vec2i &basePoint) const;
|
||||||
|
|
||||||
|
Vec2i ResolveCaratPos(const Vec2i &basePoint, PortabilityLayer::RenderedFont *rfont) const;
|
||||||
|
Vec2i ResolveBasePoint() const;
|
||||||
|
size_t ResolveCaratChar() const;
|
||||||
|
|
||||||
|
PortabilityLayer::FontFamily *GetFontFamily() const;
|
||||||
|
PortabilityLayer::RenderedFont *GetRenderedFont() const;
|
||||||
|
|
||||||
uint8_t *m_chars;
|
uint8_t *m_chars;
|
||||||
size_t m_capacity;
|
size_t m_capacity;
|
||||||
size_t m_length;
|
size_t m_length;
|
||||||
size_t m_selStartChar;
|
size_t m_selStartChar;
|
||||||
size_t m_selEndChar;
|
size_t m_selEndChar;
|
||||||
|
CaratSelectionAnchor m_caratSelectionAnchor;
|
||||||
|
|
||||||
|
Vec2i m_caratScrollPosition;
|
||||||
|
bool m_caratScrollLocked;
|
||||||
|
|
||||||
bool m_hasFocus;
|
bool m_hasFocus;
|
||||||
|
bool m_isMultiLine;
|
||||||
uint16_t m_caratTimer;
|
uint16_t m_caratTimer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "ScanlineMaskIterator.h"
|
#include "ScanlineMaskIterator.h"
|
||||||
#include "QDGraf.h"
|
#include "QDGraf.h"
|
||||||
#include "QDStandardPalette.h"
|
#include "QDStandardPalette.h"
|
||||||
|
#include "TextPlacer.h"
|
||||||
#include "WindowManager.h"
|
#include "WindowManager.h"
|
||||||
#include "QDGraf.h"
|
#include "QDGraf.h"
|
||||||
#include "QDPixMap.h"
|
#include "QDPixMap.h"
|
||||||
@@ -288,7 +289,7 @@ void GetForeColor(RGBColor *color)
|
|||||||
*color = RGBColor(foreColor.r, foreColor.g, foreColor.b);
|
*color = RGBColor(foreColor.r, foreColor.g, foreColor.b);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, Point &penPos, const PortabilityLayer::RenderedFont *rfont, unsigned int character,
|
static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, const Point &penPos, const PortabilityLayer::RenderedFont *rfont, unsigned int character,
|
||||||
PortabilityLayer::AntiAliasTable *&cachedAATable, PortabilityLayer::RGBAColor &cachedAATableColor)
|
PortabilityLayer::AntiAliasTable *&cachedAATable, PortabilityLayer::RGBAColor &cachedAATableColor)
|
||||||
{
|
{
|
||||||
assert(rect.IsValid());
|
assert(rect.IsValid());
|
||||||
@@ -298,12 +299,8 @@ static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const
|
|||||||
if (!rfont->GetGlyph(character, metrics, data))
|
if (!rfont->GetGlyph(character, metrics, data))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const Point originalPoint = penPos;
|
const int32_t leftCoord = penPos.h + metrics->m_bearingX;
|
||||||
|
const int32_t topCoord = penPos.v - metrics->m_bearingY;
|
||||||
penPos.h += metrics->m_advanceX;
|
|
||||||
|
|
||||||
const int32_t leftCoord = originalPoint.h + metrics->m_bearingX;
|
|
||||||
const int32_t topCoord = originalPoint.v - metrics->m_bearingY;
|
|
||||||
const int32_t rightCoord = leftCoord + metrics->m_glyphWidth;
|
const int32_t rightCoord = leftCoord + metrics->m_glyphWidth;
|
||||||
const int32_t bottomCoord = topCoord + metrics->m_glyphHeight;
|
const int32_t bottomCoord = topCoord + metrics->m_glyphHeight;
|
||||||
|
|
||||||
@@ -399,6 +396,17 @@ static void DrawGlyph(PortabilityLayer::QDState *qdState, PixMap *pixMap, const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void DrawText(PortabilityLayer::TextPlacer &placer, PortabilityLayer::QDState *qdState, PixMap *pixMap, const Rect &rect, const PortabilityLayer::RenderedFont *rfont,
|
||||||
|
PortabilityLayer::AntiAliasTable *&cachedAATable, PortabilityLayer::RGBAColor &cachedAATableColor)
|
||||||
|
{
|
||||||
|
PortabilityLayer::GlyphPlacementCharacteristics characteristics;
|
||||||
|
while (placer.PlaceGlyph(characteristics))
|
||||||
|
{
|
||||||
|
if (characteristics.m_haveGlyph)
|
||||||
|
DrawGlyph(qdState, pixMap, rect, Point::Create(characteristics.m_glyphStartPos.m_x, characteristics.m_glyphStartPos.m_y), rfont, characteristics.m_character, cachedAATable, cachedAATableColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DrawSurface::DrawString(const Point &point, const PLPasStr &str, bool aa)
|
void DrawSurface::DrawString(const Point &point, const PLPasStr &str, bool aa)
|
||||||
{
|
{
|
||||||
DrawStringConstrained(point, str, aa, Rect::CreateLargest());
|
DrawStringConstrained(point, str, aa, Rect::CreateLargest());
|
||||||
@@ -417,11 +425,6 @@ void DrawSurface::DrawStringConstrained(const Point &point, const PLPasStr &str,
|
|||||||
PortabilityLayer::FontFamily *fontFamily = qdState->m_fontFamily;
|
PortabilityLayer::FontFamily *fontFamily = qdState->m_fontFamily;
|
||||||
|
|
||||||
PortabilityLayer::RenderedFont *rfont = fontManager->GetRenderedFontFromFamily(fontFamily, fontSize, aa, fontVariationFlags);
|
PortabilityLayer::RenderedFont *rfont = fontManager->GetRenderedFontFromFamily(fontFamily, fontSize, aa, fontVariationFlags);
|
||||||
|
|
||||||
Point penPos = point;
|
|
||||||
const size_t len = str.Length();
|
|
||||||
const uint8_t *chars = str.UChars();
|
|
||||||
|
|
||||||
PixMap *pixMap = *port->GetPixMap();
|
PixMap *pixMap = *port->GetPixMap();
|
||||||
|
|
||||||
const Rect rect = pixMap->m_rect.Intersect(constraintRect);
|
const Rect rect = pixMap->m_rect.Intersect(constraintRect);
|
||||||
@@ -429,18 +432,9 @@ void DrawSurface::DrawStringConstrained(const Point &point, const PLPasStr &str,
|
|||||||
if (!rect.IsValid())
|
if (!rect.IsValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Point paraStartPos = penPos;
|
PortabilityLayer::TextPlacer placer(PortabilityLayer::Vec2i(point.h, point.v), -1, rfont, str);
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++)
|
DrawText(placer, qdState, pixMap, rect, rfont, m_cachedAATable, m_cachedAAColor);
|
||||||
{
|
|
||||||
if (chars[i] == static_cast<uint8_t>('\r'))
|
|
||||||
{
|
|
||||||
paraStartPos.v += rfont->GetMetrics().m_linegap;
|
|
||||||
penPos = paraStartPos;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
DrawGlyph(qdState, pixMap, rect, penPos, rfont, chars[i], m_cachedAATable, m_cachedAAColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents);
|
m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents);
|
||||||
}
|
}
|
||||||
@@ -471,91 +465,9 @@ void DrawSurface::DrawStringWrap(const Point &point, const Rect &constrainRect,
|
|||||||
if (!limitRect.IsValid() || !areaRect.IsValid())
|
if (!limitRect.IsValid() || !areaRect.IsValid())
|
||||||
return; // ???
|
return; // ???
|
||||||
|
|
||||||
Point paraStartPos = penPos;
|
PortabilityLayer::TextPlacer placer(PortabilityLayer::Vec2i(point.h, point.v), areaRect.Width(), rfont, str);
|
||||||
|
|
||||||
size_t currentStartChar = 0;
|
DrawText(placer, qdState, pixMap, limitRect, rfont, m_cachedAATable, m_cachedAAColor);
|
||||||
size_t currentSpanLength = 0;
|
|
||||||
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (currentStartChar == len)
|
|
||||||
break;
|
|
||||||
|
|
||||||
size_t lastWhitespace = currentStartChar;
|
|
||||||
bool shouldSkipSpaces = false;
|
|
||||||
|
|
||||||
size_t committedLength = 0;
|
|
||||||
|
|
||||||
size_t i = currentStartChar;
|
|
||||||
|
|
||||||
// Find a span to print
|
|
||||||
int32_t spanWidth = 0;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
if (i == len)
|
|
||||||
{
|
|
||||||
committedLength = i - currentStartChar;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t character = chars[i];
|
|
||||||
|
|
||||||
if (character <= ' ')
|
|
||||||
{
|
|
||||||
committedLength = i - currentStartChar + 1;
|
|
||||||
if (character == '\r')
|
|
||||||
break;
|
|
||||||
|
|
||||||
const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr;
|
|
||||||
const void *glyphData = nullptr;
|
|
||||||
if (rfont->GetGlyph(chars[i], metrics, glyphData))
|
|
||||||
spanWidth += metrics->m_advanceX;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr;
|
|
||||||
const void *glyphData = nullptr;
|
|
||||||
if (!rfont->GetGlyph(chars[i], metrics, glyphData))
|
|
||||||
{
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
spanWidth += metrics->m_advanceX;
|
|
||||||
|
|
||||||
int32_t glyphEnd = penPos.h + spanWidth;
|
|
||||||
|
|
||||||
if (glyphEnd >= constrainRect.right)
|
|
||||||
{
|
|
||||||
if (committedLength == 0)
|
|
||||||
{
|
|
||||||
// Word didn't fit
|
|
||||||
committedLength = i - currentStartChar;
|
|
||||||
if (committedLength == 0)
|
|
||||||
committedLength = 1; // Nothing fit, consume one char
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t ci = 0; ci < committedLength; ci++)
|
|
||||||
{
|
|
||||||
const uint8_t character = chars[currentStartChar + ci];
|
|
||||||
|
|
||||||
DrawGlyph(qdState, pixMap, limitRect, penPos, rfont, character, m_cachedAATable, m_cachedAAColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
currentStartChar += committedLength;
|
|
||||||
|
|
||||||
paraStartPos.v += rfont->GetMetrics().m_linegap;
|
|
||||||
penPos = paraStartPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents);
|
m_port.SetDirty(PortabilityLayer::QDPortDirtyFlag_Contents);
|
||||||
}
|
}
|
||||||
@@ -938,6 +850,19 @@ void DrawSurface::SetClipRect(const Rect &rect)
|
|||||||
m_port.GetState()->m_clipRect = rect;;
|
m_port.GetState()->m_clipRect = rect;;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PortabilityLayer::RenderedFont *DrawSurface::ResolveFont(bool aa) const
|
||||||
|
{
|
||||||
|
const PortabilityLayer::QDState *qdState = m_port.GetState();
|
||||||
|
|
||||||
|
PortabilityLayer::FontManager *fontManager = PortabilityLayer::FontManager::GetInstance();
|
||||||
|
|
||||||
|
const int fontSize = qdState->m_fontSize;
|
||||||
|
const int fontVariationFlags = qdState->m_fontVariationFlags;
|
||||||
|
PortabilityLayer::FontFamily *fontFamily = qdState->m_fontFamily;
|
||||||
|
|
||||||
|
return fontManager->GetRenderedFontFromFamily(fontFamily, fontSize, aa, fontVariationFlags);
|
||||||
|
}
|
||||||
|
|
||||||
void DrawSurface::FillRect(const Rect &rect)
|
void DrawSurface::FillRect(const Rect &rect)
|
||||||
{
|
{
|
||||||
if (!rect.IsValid())
|
if (!rect.IsValid())
|
||||||
|
@@ -228,6 +228,7 @@
|
|||||||
<ClInclude Include="PLScrollBarWidget.h" />
|
<ClInclude Include="PLScrollBarWidget.h" />
|
||||||
<ClInclude Include="PLUnalignedPtr.h" />
|
<ClInclude Include="PLUnalignedPtr.h" />
|
||||||
<ClInclude Include="PLWidgets.h" />
|
<ClInclude Include="PLWidgets.h" />
|
||||||
|
<ClInclude Include="TextPlacer.h" />
|
||||||
<ClInclude Include="UTF8.h" />
|
<ClInclude Include="UTF8.h" />
|
||||||
<ClInclude Include="ZipFileProxy.h" />
|
<ClInclude Include="ZipFileProxy.h" />
|
||||||
<ClInclude Include="SimpleImage.h" />
|
<ClInclude Include="SimpleImage.h" />
|
||||||
@@ -383,6 +384,7 @@
|
|||||||
<ClCompile Include="ScanlineMaskIterator.cpp" />
|
<ClCompile Include="ScanlineMaskIterator.cpp" />
|
||||||
<ClCompile Include="SimpleGraphic.cpp" />
|
<ClCompile Include="SimpleGraphic.cpp" />
|
||||||
<ClCompile Include="PLHandle.cpp" />
|
<ClCompile Include="PLHandle.cpp" />
|
||||||
|
<ClCompile Include="TextPlacer.cpp" />
|
||||||
<ClCompile Include="UTF8.cpp" />
|
<ClCompile Include="UTF8.cpp" />
|
||||||
<ClCompile Include="WindowDef.cpp" />
|
<ClCompile Include="WindowDef.cpp" />
|
||||||
<ClCompile Include="WindowManager.cpp" />
|
<ClCompile Include="WindowManager.cpp" />
|
||||||
|
@@ -477,6 +477,9 @@
|
|||||||
<ClInclude Include="PLUnalignedPtr.h">
|
<ClInclude Include="PLUnalignedPtr.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="TextPlacer.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="CFileStream.cpp">
|
<ClCompile Include="CFileStream.cpp">
|
||||||
@@ -749,5 +752,8 @@
|
|||||||
<ClCompile Include="UTF8.cpp">
|
<ClCompile Include="UTF8.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="TextPlacer.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@@ -12,6 +12,7 @@ namespace PortabilityLayer
|
|||||||
struct AntiAliasTable;
|
struct AntiAliasTable;
|
||||||
class FontFamily;
|
class FontFamily;
|
||||||
struct RGBAColor;
|
struct RGBAColor;
|
||||||
|
class RenderedFont;
|
||||||
class ScanlineMask;
|
class ScanlineMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +96,8 @@ struct DrawSurface final
|
|||||||
Rect GetClipRect() const;
|
Rect GetClipRect() const;
|
||||||
void SetClipRect(const Rect &rect);
|
void SetClipRect(const Rect &rect);
|
||||||
|
|
||||||
|
PortabilityLayer::RenderedFont *ResolveFont(bool aa) const;
|
||||||
|
|
||||||
// Must be the first item
|
// Must be the first item
|
||||||
PortabilityLayer::QDPort m_port;
|
PortabilityLayer::QDPort m_port;
|
||||||
|
|
||||||
|
151
PortabilityLayer/TextPlacer.cpp
Normal file
151
PortabilityLayer/TextPlacer.cpp
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
#include "TextPlacer.h"
|
||||||
|
|
||||||
|
#include "PLPasStr.h"
|
||||||
|
#include "RenderedFontMetrics.h"
|
||||||
|
#include "RenderedGlyphMetrics.h"
|
||||||
|
#include "RenderedFont.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace PortabilityLayer
|
||||||
|
{
|
||||||
|
TextPlacer::TextPlacer(const Vec2i &basePoint, int32_t spanWidth, PortabilityLayer::RenderedFont *rfont, const PLPasStr &str)
|
||||||
|
: m_basePoint(basePoint)
|
||||||
|
, m_penPos(0, 0)
|
||||||
|
, m_paraStartPos(0, 0)
|
||||||
|
, m_rfont(rfont)
|
||||||
|
, m_chars(str.UChars())
|
||||||
|
, m_length(str.Length())
|
||||||
|
, m_currentStartChar(0)
|
||||||
|
, m_currentSpanLength(0)
|
||||||
|
, m_committedLength(0)
|
||||||
|
, m_emitOffset(0)
|
||||||
|
, m_haveCommitted(false)
|
||||||
|
, m_maxSpanWidth(spanWidth)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TextPlacer::~TextPlacer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextPlacer::PlaceGlyph(GlyphPlacementCharacteristics &outCharacteristics)
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (!m_haveCommitted)
|
||||||
|
{
|
||||||
|
if (m_currentStartChar == m_length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t lastWhitespace = m_currentStartChar;
|
||||||
|
bool shouldSkipSpaces = false;
|
||||||
|
|
||||||
|
size_t committedLength = 0;
|
||||||
|
|
||||||
|
size_t i = m_currentStartChar;
|
||||||
|
|
||||||
|
// Find a span to print
|
||||||
|
int32_t spanWidth = 0;
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (i == m_length)
|
||||||
|
{
|
||||||
|
committedLength = i - m_currentStartChar;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t character = m_chars[i];
|
||||||
|
|
||||||
|
if (character <= ' ')
|
||||||
|
{
|
||||||
|
committedLength = i - m_currentStartChar + 1;
|
||||||
|
if (character == '\r')
|
||||||
|
break;
|
||||||
|
|
||||||
|
const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr;
|
||||||
|
const void *glyphData = nullptr;
|
||||||
|
if (m_rfont->GetGlyph(m_chars[i], metrics, glyphData))
|
||||||
|
spanWidth += metrics->m_advanceX;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const PortabilityLayer::RenderedGlyphMetrics *metrics = nullptr;
|
||||||
|
const void *glyphData = nullptr;
|
||||||
|
if (!m_rfont->GetGlyph(m_chars[i], metrics, glyphData))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spanWidth += metrics->m_advanceX;
|
||||||
|
|
||||||
|
if (m_maxSpanWidth >= 0 && spanWidth > m_maxSpanWidth)
|
||||||
|
{
|
||||||
|
if (committedLength == 0)
|
||||||
|
{
|
||||||
|
// Word didn't fit
|
||||||
|
committedLength = i - m_currentStartChar;
|
||||||
|
if (committedLength == 0)
|
||||||
|
committedLength = 1; // Nothing fit, consume one char
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_haveCommitted = true;
|
||||||
|
m_committedLength = committedLength;
|
||||||
|
m_emitOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(m_emitOffset < m_committedLength);
|
||||||
|
|
||||||
|
const size_t charIndex = m_currentStartChar + m_emitOffset;
|
||||||
|
const uint8_t character = m_chars[charIndex];
|
||||||
|
|
||||||
|
outCharacteristics.m_glyphStartPos = m_penPos + m_basePoint;
|
||||||
|
outCharacteristics.m_glyphEndPos = outCharacteristics.m_glyphStartPos;
|
||||||
|
outCharacteristics.m_isParaStart = (m_emitOffset == 0);
|
||||||
|
outCharacteristics.m_isParaEnd = (m_emitOffset == m_committedLength - 1);
|
||||||
|
outCharacteristics.m_characterIndex = m_currentStartChar + m_emitOffset;
|
||||||
|
outCharacteristics.m_character = character;
|
||||||
|
|
||||||
|
const PortabilityLayer::RenderedGlyphMetrics *metrics;
|
||||||
|
const void *data;
|
||||||
|
if (!m_rfont->GetGlyph(character, metrics, data))
|
||||||
|
{
|
||||||
|
outCharacteristics.m_haveGlyph = false;
|
||||||
|
outCharacteristics.m_glyphMetrics = nullptr;
|
||||||
|
outCharacteristics.m_glyphData = nullptr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_penPos.m_x += metrics->m_advanceX;
|
||||||
|
|
||||||
|
outCharacteristics.m_haveGlyph = true;
|
||||||
|
outCharacteristics.m_glyphMetrics = metrics;
|
||||||
|
outCharacteristics.m_glyphData = data;
|
||||||
|
outCharacteristics.m_glyphEndPos.m_x += metrics->m_advanceX;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_emitOffset++;
|
||||||
|
|
||||||
|
if (m_emitOffset == m_committedLength)
|
||||||
|
{
|
||||||
|
m_currentStartChar += m_committedLength;
|
||||||
|
m_haveCommitted = false;
|
||||||
|
|
||||||
|
m_paraStartPos.m_y += m_rfont->GetMetrics().m_linegap;
|
||||||
|
m_penPos = m_paraStartPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
PortabilityLayer/TextPlacer.h
Normal file
49
PortabilityLayer/TextPlacer.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Vec2i.h"
|
||||||
|
|
||||||
|
class PLPasStr;
|
||||||
|
|
||||||
|
namespace PortabilityLayer
|
||||||
|
{
|
||||||
|
class RenderedFont;
|
||||||
|
struct RenderedGlyphMetrics;
|
||||||
|
|
||||||
|
struct GlyphPlacementCharacteristics
|
||||||
|
{
|
||||||
|
bool m_haveGlyph;
|
||||||
|
bool m_isParaStart; // Character is the first character in the paragraph
|
||||||
|
bool m_isParaEnd; // Character is the last character in the paragraph
|
||||||
|
const RenderedGlyphMetrics *m_glyphMetrics; // Glyph metrics
|
||||||
|
const void *m_glyphData; // Glyph data
|
||||||
|
Vec2i m_glyphStartPos; // Glyph start position
|
||||||
|
Vec2i m_glyphEndPos; // Glyph end position
|
||||||
|
unsigned int m_character; // Character code
|
||||||
|
size_t m_characterIndex; // Index in the input string
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextPlacer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TextPlacer(const Vec2i &basePoint, int32_t spanWidth, PortabilityLayer::RenderedFont *rfont, const PLPasStr &str);
|
||||||
|
~TextPlacer();
|
||||||
|
|
||||||
|
bool PlaceGlyph(GlyphPlacementCharacteristics &outCharacteristics);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2i m_basePoint;
|
||||||
|
Vec2i m_penPos;
|
||||||
|
Vec2i m_paraStartPos;
|
||||||
|
PortabilityLayer::RenderedFont *m_rfont;
|
||||||
|
const uint8_t *m_chars;
|
||||||
|
size_t m_length;
|
||||||
|
|
||||||
|
size_t m_currentStartChar;
|
||||||
|
size_t m_currentSpanLength;
|
||||||
|
size_t m_committedLength;
|
||||||
|
size_t m_emitOffset;
|
||||||
|
bool m_haveCommitted;
|
||||||
|
|
||||||
|
int32_t m_maxSpanWidth;
|
||||||
|
};
|
||||||
|
}
|
Reference in New Issue
Block a user