Files
Aerofoil/PortabilityLayer/PLEditboxWidget.cpp
elasota 7548690c51 Fix up radio buttons, more editbox functionality.
All blocking issues with UI should now be fixed.
2020-02-16 21:57:02 -05:00

339 lines
8.8 KiB
C++

#include "PLEditboxWidget.h"
#include "FontFamily.h"
#include "InputManager.h"
#include "MacRomanConversion.h"
#include "MemoryManager.h"
#include "PLKeyEncoding.h"
#include "PLStandardColors.h"
#include "PLTimeTaggedVOSEvent.h"
#include <algorithm>
namespace PortabilityLayer
{
EditboxWidget::EditboxWidget(const WidgetBasicState &state)
: WidgetSpec<EditboxWidget>(state)
, m_capacity(255)
, m_length(0)
, m_chars(nullptr)
, m_selStartChar(0)
, m_selEndChar(0)
, m_hasFocus(false)
, m_caratTimer(0)
{
}
EditboxWidget::~EditboxWidget()
{
PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance();
if (m_chars)
mm->Release(m_chars);
}
bool EditboxWidget::Init(const WidgetBasicState &state)
{
PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance();
m_capacity = 255;
m_chars = static_cast<uint8_t*>(mm->Alloc(m_capacity * sizeof(m_chars[0])));
if (!m_chars)
return false;
return true;
}
void EditboxWidget::EditboxWidget::DrawControl(DrawSurface *surface)
{
const Rect textRect = m_rect;
const Rect innerRect = textRect.Inset(-2, -2);
const Rect outerRect = innerRect.Inset(-1, -1);
surface->SetForeColor(StdColors::Black());
surface->FillRect(outerRect);
surface->SetForeColor(StdColors::White());
surface->FillRect(innerRect);
surface->SetSystemFont(12, PortabilityLayer::FontFamilyFlag_None);
int32_t ascender = surface->MeasureFontAscender();
int32_t lineGap = surface->MeasureFontLineGap();
const PLPasStr str = this->GetString();
assert(m_selStartChar <= str.Length());
assert(m_selEndChar <= str.Length());
assert(m_selStartChar <= m_selEndChar);
const char *strChars = str.Chars();
size_t preSelWidth = 0;
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)
selWidth = surface->MeasureString(PLPasStr(static_cast<uint8_t>(m_selEndChar - m_selStartChar), strChars + m_selStartChar));
//if (m_selEndChar < str.Length())
// 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->DrawStringConstrained(basePoint, this->GetString(), true, m_rect);
if (m_hasFocus && m_selEndChar == m_selStartChar && m_caratTimer < kCaratBlinkRate)
{
int16_t caratTop = (textRect.top + textRect.bottom - lineGap + 1) / 2;
int16_t caratBottom = (textRect.top + textRect.bottom + lineGap + 1) / 2;
int16_t caratH = static_cast<int16_t>(basePoint.h + preSelWidth);
Rect caratRect = Rect::Create(caratTop, caratH, caratBottom, caratH + 1);
caratRect = caratRect.Intersect(m_rect);
if (caratRect.IsValid())
surface->FillRect(caratRect);
}
}
void EditboxWidget::SetString(const PLPasStr &str)
{
const size_t len = std::min<size_t>(m_capacity, str.Length());
m_length = len;
memcpy(m_chars, str.UChars(), len);
if (m_window)
{
DrawSurface *surface = m_window->GetDrawSurface();
DrawControl(surface);
}
if (m_selStartChar > len)
m_selStartChar = len;
if (m_selEndChar > len)
m_selEndChar = len;
}
PLPasStr EditboxWidget::GetString() const
{
const uint8_t len = static_cast<uint8_t>(std::min<size_t>(255, m_length));
return PLPasStr(len, reinterpret_cast<const char*>(m_chars));
}
void EditboxWidget::GainFocus()
{
m_hasFocus = true;
m_selStartChar = 0;
m_selEndChar = this->GetString().Length();
if (m_window)
{
DrawSurface *surface = m_window->GetDrawSurface();
DrawControl(surface);
}
}
void EditboxWidget::LoseFocus()
{
m_hasFocus = false;
m_selStartChar = 0;
m_selEndChar = 0;
Redraw();
}
WidgetHandleState_t EditboxWidget::ProcessEvent(const TimeTaggedVOSEvent &evt)
{
if (evt.m_vosEvent.m_eventType == GpVOSEventTypes::kKeyboardInput)
{
if (!m_hasFocus)
return WidgetHandleStates::kIgnored;
const GpKeyboardInputEvent &keyEvent = evt.m_vosEvent.m_event.m_keyboardInputEvent;
if (keyEvent.m_eventType == GpKeyboardInputEventTypes::kAutoChar || keyEvent.m_eventType == GpKeyboardInputEventTypes::kDownChar)
{
// Resolve character
bool resolvedChar = false;
uint8_t ch = 0;
if (keyEvent.m_keyIDSubset == GpKeyIDSubsets::kASCII)
{
ch = static_cast<uint8_t>(keyEvent.m_key.m_asciiChar);
resolvedChar = true;
}
else if (keyEvent.m_keyIDSubset == GpKeyIDSubsets::kUnicode)
{
uint32_t codePoint = keyEvent.m_key.m_unicodeChar;
if (codePoint < 0xffff)
resolvedChar = MacRoman::FromUnicode(ch, keyEvent.m_key.m_unicodeChar);
}
if (resolvedChar)
{
if (ch >= 0x20 && ch <= 0x7e)
HandleCharacter(ch, keyEvent.m_repeatCount);
return WidgetHandleStates::kDigested;
}
}
else if (keyEvent.m_eventType == GpKeyboardInputEventTypes::kAuto || keyEvent.m_eventType == GpKeyboardInputEventTypes::kDown)
{
const KeyDownStates *downStates = PortabilityLayer::InputManager::GetInstance()->GetKeys();
const bool isShiftHeld = downStates->m_special.Get(GpKeySpecials::kLeftShift) || downStates->m_special.Get(GpKeySpecials::kRightShift);
if (keyEvent.m_keyIDSubset == GpKeyIDSubsets::kSpecial)
{
if (keyEvent.m_key.m_specialKey == GpKeySpecials::kBackspace)
{
HandleBackspace(keyEvent.m_repeatCount);
return WidgetHandleStates::kDigested;
}
else if (keyEvent.m_key.m_specialKey == GpKeySpecials::kDelete)
{
return WidgetHandleStates::kDigested;
}
}
}
}
return WidgetHandleStates::kIgnored;
}
void EditboxWidget::Redraw()
{
if (m_window)
{
DrawSurface *surface = m_window->GetDrawSurface();
DrawControl(surface);
}
}
bool EditboxWidget::HandlesTickEvents() const
{
return true;
}
void EditboxWidget::SetSelection(size_t startChar, size_t endChar)
{
if (startChar > m_length)
startChar = m_length;
if (endChar < startChar)
endChar = startChar;
if (endChar > m_length)
endChar = m_length;
m_selStartChar = startChar;
m_selEndChar = endChar;
m_caratTimer = 0;
Redraw();
}
void EditboxWidget::OnTick()
{
if (m_hasFocus)
{
m_caratTimer++;
if (m_caratTimer == kCaratBlinkRate)
Redraw();
else if (m_caratTimer == kCaratBlinkRate * 2)
{
m_caratTimer = 0;
Redraw();
}
}
}
void EditboxWidget::HandleCharacter(uint8_t ch, const uint32_t numRepeatsRequested)
{
const size_t numPostSelChars = m_length - m_selEndChar;
const size_t numSelChars = m_selEndChar - m_selStartChar;
const size_t numPreSelChars = m_selStartChar;
const size_t lengthWithSelectionRemoved = m_length - numSelChars;
const size_t availableInsertions = m_capacity - lengthWithSelectionRemoved;
const size_t numInsertions = std::min<size_t>(availableInsertions, numRepeatsRequested);
if (m_selEndChar != m_length)
{
size_t moveSize = m_length - m_selEndChar;
uint8_t *moveSrc = m_chars + m_selEndChar;
uint8_t *moveDest = m_chars + m_selStartChar + numInsertions;
if (moveSrc != moveDest)
memmove(moveDest, moveSrc, numPostSelChars);
}
uint8_t *insertPos = m_chars + m_selStartChar;
for (size_t r = 0; r < numInsertions; r++)
insertPos[r] = ch;
// Reset selection
m_selStartChar += numInsertions;
m_selEndChar = m_selStartChar;
// Reset length;
m_length = numPreSelChars + numInsertions + numPostSelChars;
m_caratTimer = 0;
Redraw();
}
void EditboxWidget::HandleBackspace(uint32_t numRepeatsRequested)
{
const size_t numPostSelChars = m_length - m_selEndChar;
const size_t numSelChars = m_selEndChar - m_selStartChar;
const size_t numPreSelChars = m_selStartChar;
size_t prefixTrim = numRepeatsRequested;
if (numSelChars != 0)
prefixTrim--;
if (prefixTrim > numPreSelChars)
prefixTrim = numPreSelChars;
const size_t prefixKeep = numPreSelChars - prefixTrim;
const size_t suffixKeep = numPostSelChars;
if (suffixKeep > 0)
{
uint8_t *moveSrc = m_chars + m_selEndChar;
uint8_t *moveDest = m_chars + prefixKeep;
if (moveSrc != moveDest)
memmove(moveDest, moveSrc, suffixKeep);
}
m_length = prefixKeep + suffixKeep;
m_selStartChar = m_selEndChar = prefixKeep;
m_caratTimer = 0;
Redraw();
}
}