Partial editbox support for high scores

This commit is contained in:
elasota
2020-02-16 20:55:47 -05:00
parent bd2e27978e
commit 8f4ac13919
18 changed files with 523 additions and 80 deletions

View File

@@ -345,13 +345,19 @@ namespace PortabilityLayer
for (;;)
{
TimeTaggedVOSEvent evt;
if (WaitForEvent(&evt, 1))
const bool haveEvent = WaitForEvent(&evt, 1);
if (window->IsHandlingTickEvents())
window->OnTick();
const int16_t selection = filterFunc(this, haveEvent ? &evt : nullptr);
if (selection >= 0)
return selection;
if (haveEvent)
{
const int16_t selection = filterFunc(this, evt);
if (selection >= 0)
return selection;
if (capturingWidget != nullptr)
{
const WidgetHandleState_t state = capturingWidget->ProcessEvent(evt);
@@ -571,7 +577,7 @@ namespace PortabilityLayer
void PositionWindow(Window *window, const Rect &rect);
Dialog *LoadDialogFromTemplate(int16_t templateResID, const Rect &rect, bool visible, bool hasCloseBox, uint32_t referenceConstant, uint16_t positionSpec, Window *behindWindow, const PLPasStr &title, const DialogTextSubstitutions *substitutions);
static int16_t AlertFilter(Dialog *dialog, const TimeTaggedVOSEvent &evt);
static int16_t AlertFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt);
static DialogManagerImpl ms_instance;
};
@@ -650,7 +656,7 @@ namespace PortabilityLayer
return dialog;
}
int16_t DialogManagerImpl::AlertFilter(Dialog *dialog, const TimeTaggedVOSEvent &evt)
int16_t DialogManagerImpl::AlertFilter(Dialog *dialog, const TimeTaggedVOSEvent *evt)
{
return -1;
}

View File

@@ -132,6 +132,8 @@ namespace PortabilityLayer
bool SetItemText(const THandle<Menu> &menu, unsigned int index, const PLPasStr &str) override;
bool IsPointInMenuBar(const Vec2i &point) const override;
bool FindMenuShortcut(uint16_t &menuID, uint16_t &itemID, uint8_t shortcutChar) override;
void MenuSelect(const Vec2i &initialPoint, int16_t *outMenu, uint16_t *outItem) override;
void DrawMenuBar() override;
@@ -520,6 +522,33 @@ namespace PortabilityLayer
return point.m_y >= 0 && static_cast<uint32_t>(point.m_y) < kMenuBarHeight;
}
bool MenuManagerImpl::FindMenuShortcut(uint16_t &menuID, uint16_t &itemID, uint8_t shortcutChar)
{
MenuHandle menuH = m_firstMenu;
while (menuH)
{
Menu *menu = *menuH;
const MenuItem *items = menu->menuItems;
const size_t numItems = menu->numMenuItems;
for (size_t i = 0; i < numItems; i++)
{
if (items[i].key == shortcutChar)
{
menuID = menu->menuID;
itemID = static_cast<uint16_t>(i);
return true;
}
}
menuH = menu->nextMenu;
}
return false;
}
void MenuManagerImpl::MenuSelect(const Vec2i &initialPoint, int16_t *outMenu, uint16_t *outItem)
{
RefreshMenuBarLayout();

View File

@@ -36,6 +36,7 @@ namespace PortabilityLayer
virtual bool IsPointInMenuBar(const Vec2i &point) const = 0;
virtual bool FindMenuShortcut(uint16_t &menuID, uint16_t &itemID, uint8_t shortcutChar) = 0;
virtual void MenuSelect(const Vec2i &initialPoint, int16_t *outMenu, uint16_t *outItem) = 0;
virtual void DrawMenuBar() = 0;

View File

@@ -247,10 +247,19 @@ long MenuSelect(Point point)
return (static_cast<int32_t>(menuID) << 16) | (static_cast<int32_t>(menuItem));
}
long MenuKey(int charCode)
long MenuKey(intptr_t charCode)
{
PL_NotYetImplemented();
return PLErrors::kNone;
if (PL_KEY_GET_EVENT_TYPE(charCode) != KeyEventType::KeyEventType_ASCII)
return 0;
const uint8_t asciiChar = PL_KEY_GET_VALUE(charCode);
uint16_t menuID;
uint16_t itemID;
if (PortabilityLayer::MenuManager::GetInstance()->FindMenuShortcut(menuID, itemID, asciiChar))
return (menuID << 16) | (itemID + 1);
return 0;
}
long TickCount()
@@ -713,8 +722,8 @@ Window::Window()
, m_wmY(0)
, m_widgets(nullptr)
, m_numWidgets(0)
, m_widgetWithFocus(0)
, m_haveFocus(false)
, m_widgetWithFocus(nullptr)
, m_numTickReceivingWidgets(0)
{
}
@@ -762,9 +771,32 @@ bool Window::AddWidget(PortabilityLayer::Widget *widget)
m_widgets[m_numWidgets++] = widget;
if (widget->HandlesTickEvents())
m_numTickReceivingWidgets++;
return true;
}
void Window::FocusWidget(PortabilityLayer::Widget *widget)
{
if (m_widgetWithFocus != widget)
{
assert(widget->GetWindow() == this);
if (m_widgetWithFocus)
m_widgetWithFocus->LoseFocus();
m_widgetWithFocus = widget;
widget->GainFocus();
}
}
PortabilityLayer::Widget *Window::GetWidgetWithFocus() const
{
return m_widgetWithFocus;
}
void Window::DrawControls()
{
DrawSurface *surface = GetDrawSurface();
@@ -776,3 +808,17 @@ void Window::DrawControls()
widget->DrawControl(surface);
}
}
bool Window::IsHandlingTickEvents()
{
return m_numTickReceivingWidgets > 0;
}
void Window::OnTick()
{
if (m_numTickReceivingWidgets == 0)
return;
for (size_t i = 0; i < m_numWidgets; i++)
m_widgets[i]->OnTick();
}

View File

@@ -82,7 +82,13 @@ struct Window
bool AddWidget(PortabilityLayer::Widget *widget);
void FocusWidget(PortabilityLayer::Widget *widget);
PortabilityLayer::Widget *GetWidgetWithFocus() const;
void DrawControls();
bool IsHandlingTickEvents();
void OnTick();
DrawSurface m_surface; // Must be the first item until the immediate mode draw API is completely removed
@@ -96,9 +102,9 @@ protected:
PortabilityLayer::Widget **m_widgets;
size_t m_numWidgets;
size_t m_numTickReceivingWidgets;
size_t m_widgetWithFocus;
bool m_haveFocus;
PortabilityLayer::Widget *m_widgetWithFocus;
};
struct DateTimeRec
@@ -241,7 +247,7 @@ void SetWTitle(WindowPtr window, const PLPasStr &title);
long MenuSelect(Point point); // Breaks into menu select routine (in practice we'll just forward one from the queue?)
long MenuKey(int charCode);
long MenuKey(intptr_t charCode);
long TickCount();
short LoWord(Int32 v);

View File

@@ -14,7 +14,7 @@ class PLPasStr;
struct Control;
struct Dialog;
typedef int16_t(*DialogFilterFunc_t)(Dialog *dialog, const TimeTaggedVOSEvent &evt);
typedef int16_t(*DialogFilterFunc_t)(Dialog *dialog, const TimeTaggedVOSEvent *evt);
struct DialogTextSubstitutions
{

View File

@@ -1,7 +1,12 @@
#include "PLEditboxWidget.h"
#include "PLStandardColors.h"
#include "MemoryManager.h"
#include "FontFamily.h"
#include "InputManager.h"
#include "MacRomanConversion.h"
#include "MemoryManager.h"
#include "PLKeyEncoding.h"
#include "PLStandardColors.h"
#include "PLTimeTaggedVOSEvent.h"
#include <algorithm>
@@ -14,6 +19,8 @@ namespace PortabilityLayer
, m_chars(nullptr)
, m_selStartChar(0)
, m_selEndChar(0)
, m_hasFocus(false)
, m_caratTimer(0)
{
}
@@ -48,12 +55,63 @@ namespace PortabilityLayer
surface->SetForeColor(StdColors::White());
surface->FillRect(innerRect);
surface->SetForeColor(StdColors::Black());
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)
@@ -69,6 +127,11 @@ namespace PortabilityLayer
DrawControl(surface);
}
if (m_selStartChar > len)
m_selStartChar = len;
if (m_selEndChar > len)
m_selEndChar = len;
}
PLPasStr EditboxWidget::GetString() const
@@ -76,4 +139,181 @@ namespace PortabilityLayer
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)
{
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);
else if (ch == '\b')
HandleBackspace(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)
{
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::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();
}
}

View File

@@ -17,11 +17,30 @@ namespace PortabilityLayer
void SetString(const PLPasStr &str) override;
PLPasStr GetString() const override;
void GainFocus() override;
void LoseFocus() override;
WidgetHandleState_t ProcessEvent(const TimeTaggedVOSEvent &evt) override;
bool HandlesTickEvents() const;
private:
static const unsigned int kCaratBlinkRate = 20;
void OnTick() override;
void Redraw();
void HandleCharacter(uint8_t keyChar, const uint32_t numRepeatsRequested);
void HandleBackspace(const uint32_t numRepeatsRequested);
void HandleForwardDelete(const uint32_t numRepeatsRequested);
uint8_t *m_chars;
size_t m_capacity;
size_t m_length;
size_t m_selStartChar;
size_t m_selEndChar;
bool m_hasFocus;
uint16_t m_caratTimer;
};
}

View File

@@ -84,6 +84,19 @@ namespace PortabilityLayer
(void)style;
}
bool Widget::HandlesTickEvents() const
{
return false;
}
void Widget::GainFocus()
{
}
void Widget::LoseFocus()
{
}
void Widget::SetString(const PLPasStr &str)
{
(void)str;
@@ -99,6 +112,11 @@ namespace PortabilityLayer
return m_rect;
}
Window *Widget::GetWindow() const
{
return m_window;
}
Widget::Widget(const WidgetBasicState &state)
: m_rect(state.m_rect)
, m_window(state.m_window)
@@ -120,6 +138,10 @@ namespace PortabilityLayer
{
}
void Widget::OnTick()
{
}
void Widget::BaseRelease(void *storage)
{
PortabilityLayer::MemoryManager::GetInstance()->Release(storage);

View File

@@ -64,14 +64,23 @@ namespace PortabilityLayer
virtual void SetHighlightStyle(int16_t style);
virtual bool HandlesTickEvents() const;
const Rect &GetRect() const;
Window *GetWindow() const;
protected:
friend struct Window;
virtual void GainFocus();
virtual void LoseFocus();
explicit Widget(const WidgetBasicState &state);
virtual ~Widget();
virtual void OnEnabledChanged();
virtual void OnStateChanged();
virtual void OnTick();
static void BaseRelease(void *storage);
static void *BaseAlloc(size_t sz);