Add touch input support, add fast scrolling support for coverflow display mode, fix use after free bug in banner list mode

This commit is contained in:
Gericom
2026-04-04 19:24:39 +02:00
parent 21a8790ebc
commit 97762b14d3
119 changed files with 2251 additions and 762 deletions

View File

@@ -2,10 +2,10 @@
#include "InputKey.h"
/// @brief Interface for a source of key input.
class IInputSource
class IKeyInputSource
{
public:
virtual ~IInputSource() { }
virtual ~IKeyInputSource() { }
virtual InputKey Sample() const = 0;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "core/math/Point.h"
/// @brief Interface for a source of touch input.
class ITouchInputSource
{
public:
virtual ~ITouchInputSource() { }
/// @brief Samples the touch input.
/// @param touchPosition When the pen is down, the current touch position.
/// @return \c true when the pen is down, or \c false otherwise.
virtual bool Sample(Point& touchPosition) const = 0;
};

View File

@@ -15,7 +15,9 @@ enum class InputKey : u16
L = 1 << 9,
X = 1 << 10,
Y = 1 << 11,
Debug = 1 << 13
Debug = 1 << 13,
Touch = 1 << 14,
Lid = 1 << 15
};
inline InputKey operator&(InputKey lhs, InputKey rhs)

View File

@@ -1,5 +1,5 @@
#pragma once
#include "core/math/Point.h"
#include "InputKey.h"
class InputProvider
@@ -14,6 +14,23 @@ public:
/// @return \c true if any of the keys in the \p mask is being held, or \c false otherwise.
bool Current(InputKey mask) const { return static_cast<bool>(_currentKeys & mask); }
/// @brief Returns the current touch point if the screen is being touched.
/// @param touchPoint The current touch point if the screen is being touched.
/// @return \c true if the screen is being touched, or \c false otherwise.
bool GetCurrentTouchPoint(Point& touchPoint)
{
if (Current(InputKey::Touch))
{
touchPoint = _currentTouchPoint;
return true;
}
else
{
touchPoint = Point(0, 0);
return false;
}
}
/// @brief Returns a bitmask of the keys that went from unpressed to pressed in the latest update.
/// @return A bitmask of the keys that went from unpressed to pressed in the latest update.
InputKey GetTriggeredKeys() const
@@ -46,13 +63,16 @@ public:
_currentKeys = InputKey::None;
_triggeredKeys = InputKey::None;
_releasedKeys = InputKey::None;
_currentTouchPoint = Point(0, 0);
}
protected:
InputKey _currentKeys;
InputKey _triggeredKeys;
InputKey _releasedKeys;
Point _currentTouchPoint;
InputProvider()
: _currentKeys(InputKey::None), _triggeredKeys(InputKey::None), _releasedKeys(InputKey::None) { }
: _currentKeys(InputKey::None), _triggeredKeys(InputKey::None), _releasedKeys(InputKey::None)
, _currentTouchPoint(0, 0) { }
};

View File

@@ -27,7 +27,9 @@ void InputRepeater::Update()
}
}
else
{
_state = State::Idle;
}
}
else if (_state == State::NextRepeat)
{
@@ -40,13 +42,24 @@ void InputRepeater::Update()
}
}
else
{
_state = State::Idle;
}
}
InputKey lastRepKeys = _currentKeys & _repeatMask;
_currentKeys = curKeys | repKeys;
_triggeredKeys = _inputProvider->GetTriggeredKeys() | repKeys;
_releasedKeys = _inputProvider->GetReleasedKeys() | (lastRepKeys & (lastRepKeys ^ repKeys));
Point touchPoint;
if (_inputProvider->GetCurrentTouchPoint(touchPoint))
{
_currentTouchPoint = touchPoint;
}
else
{
_currentTouchPoint = Point(0, 0);
}
}
void InputRepeater::Reset()

View File

@@ -7,7 +7,7 @@ class InputRepeater : public InputProvider
public:
InputRepeater(InputProvider* inputProvider, InputKey repeatMask, u16 firstRepeatDelay, u16 nextRepeatDelay)
: _inputProvider(inputProvider), _state(State::Idle)
, _frameCounter(0), _repeatMask(repeatMask)
, _frameCounter(0), _repeatMask(repeatMask & ~InputKey::Touch)
, _firstRepeatDelayFrames(firstRepeatDelay)
, _nextRepeatDelayFrames(nextRepeatDelay) { }

View File

@@ -1,17 +1,18 @@
#pragma once
#include "common.h"
#include <nds/input.h>
#include "IInputSource.h"
#include "IKeyInputSource.h"
#include "sharedMemory.h"
/// @brief Input source from the physical DS buttons.
class PadInputSource : public IInputSource
class PadInputSource : public IKeyInputSource
{
public:
InputKey Sample() const override
{
u16 arm9Mask = (~REG_KEYINPUT & 0x3FF);
u16 arm7Mask = (~SHARED_KEY_XY & 0xB);
u16 arm7Mask = (~SHARED_KEY_XY & 0xCB);
arm7Mask = (arm7Mask & 0xB) | ((arm7Mask & 0xC0) >> 2);
return static_cast<InputKey>((arm9Mask | (arm7Mask << 10)));
}
};

View File

@@ -7,16 +7,44 @@ void SampledInputProvider::Update()
InputKey trig = InputKey::None;
InputKey rel = InputKey::None;
Point touchPoint(0, 0);
int touchPointCount = 0;
while (_inputBufferReadPtr != _inputBufferWritePtr)
{
InputKey nextKeys = _inputBuffer[_inputBufferReadPtr];
InputKey nextKeys = _keyInputBuffer[_inputBufferReadPtr];
trig |= (nextKeys ^ curKeys) & nextKeys;
rel |= (nextKeys ^ curKeys) & curKeys;
curKeys = nextKeys;
if ((nextKeys & InputKey::Touch) != InputKey::None)
{
touchPoint.x += _touchInputBuffer[_inputBufferReadPtr].x;
touchPoint.y += _touchInputBuffer[_inputBufferReadPtr].y;
touchPointCount++;
}
else
{
touchPoint = Point(0, 0);
touchPointCount = 0;
}
_inputBufferReadPtr = (_inputBufferReadPtr + 1) & 3;
}
_triggeredKeys = trig;
_releasedKeys = rel;
_currentKeys = curKeys;
if (touchPointCount == 0)
{
if ((_triggeredKeys & InputKey::Touch) != InputKey::None ||
(_currentKeys & InputKey::Touch) != InputKey::None)
{
_releasedKeys = _releasedKeys | InputKey::Touch;
}
_triggeredKeys = _triggeredKeys & ~InputKey::Touch;
_currentKeys = _currentKeys & ~InputKey::Touch;
}
else
{
_currentTouchPoint.x = touchPoint.x / touchPointCount;
_currentTouchPoint.y = touchPoint.y / touchPointCount;
}
}

View File

@@ -1,20 +1,23 @@
#pragma once
#include "InputProvider.h"
#include "IInputSource.h"
#include "IKeyInputSource.h"
#include "ITouchInputSource.h"
/// @brief Input provider providing input from an \see IInputSource.
/// @brief Input provider providing input from an \see IKeyInputSource.
class SampledInputProvider : public InputProvider
{
public:
explicit SampledInputProvider(const IInputSource* inputSource)
: _inputSource(inputSource), _inputBufferReadPtr(0), _inputBufferWritePtr(0) { }
explicit SampledInputProvider(const IKeyInputSource* keyInputSource, const ITouchInputSource* touchInputSource)
: _keyInputSource(keyInputSource), _touchInputSource(touchInputSource)
, _inputBufferReadPtr(0), _inputBufferWritePtr(0) { }
void Update() override;
/// @brief Samples the input source.
void Sample()
{
_inputBuffer[_inputBufferWritePtr] = _inputSource->Sample();
_keyInputBuffer[_inputBufferWritePtr] = _keyInputSource->Sample();
_touchInputSource->Sample(_touchInputBuffer[_inputBufferWritePtr]);
_inputBufferWritePtr = (_inputBufferWritePtr + 1) & 3;
}
@@ -26,9 +29,11 @@ public:
}
private:
const IInputSource* _inputSource;
const IKeyInputSource* _keyInputSource;
const ITouchInputSource* _touchInputSource;
InputKey _inputBuffer[4];
InputKey _keyInputBuffer[4];
Point _touchInputBuffer[4];
u8 _inputBufferReadPtr;
u8 _inputBufferWritePtr;
};

View File

@@ -0,0 +1,24 @@
#pragma once
#include "common.h"
#include <nds/input.h>
#include "ITouchInputSource.h"
#include "sharedMemory.h"
/// @brief Input source from the physical DS touch screen.
class TouchInputSource : public ITouchInputSource
{
public:
bool Sample(Point& touchPosition) const override
{
if (SHARED_KEY_XY & (1 << 6))
{
touchPosition = Point(0, 0);
return false;
}
else
{
touchPosition = Point(SHARED_TOUCH_X, SHARED_TOUCH_Y);
return true;
}
}
};

View File

@@ -3,15 +3,17 @@
class Label2DView : public LabelView
{
public:
Label2DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font)
: LabelView(width, height, maxStringLength, font, false) { }
SHARED_ONLY(Label2DView)
public:
void InitVram(const VramContext& vramContext) override;
void Draw(GraphicsContext& graphicsContext) override;
void VBlank() override;
private:
Label2DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font)
: LabelView(width, height, maxStringLength, font, false) { }
void UpdateTileBuffer() override;
bool _tileBufferUpdated = false;

View File

@@ -12,6 +12,11 @@ Label3DView::Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_
: LabelView(width, height, maxStringLength, font, true)
, _vblankTextureLoader(vblankTextureLoader) { }
Label3DView::~Label3DView()
{
_vblankTextureLoader->CancelLoad(_textureLoadRequest);
}
void Label3DView::InitVram(const VramContext& vramContext)
{
const auto texVramManager = vramContext.GetTexVramManager();

View File

@@ -4,17 +4,21 @@
class alignas(32) Label3DView : public LabelView
{
SHARED_ONLY(Label3DView)
public:
Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
VBlankTextureLoader* vblankTextureLoader);
~Label3DView() override;
void InitVram(const VramContext& vramContext) override;
void Draw(GraphicsContext& graphicsContext) override;
private:
Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
VBlankTextureLoader* vblankTextureLoader);
void UpdateTileBuffer() override;
u32 _texVramOffset = 0;
VBlankTextureLoader* _vblankTextureLoader;
VBlankTextureLoadRequest _textureLoadRequest;
};
};

View File

@@ -4,7 +4,7 @@
#include "gui/input/InputProvider.h"
#include "RecyclerView.h"
RecyclerView::RecyclerView(Private, int x, int y, int width, int height, Mode mode)
RecyclerView::RecyclerView(int x, int y, int width, int height, Mode mode)
: _width(width), _height(height), _mode(mode), _rows(0), _columns(0)
, _viewPoolFreeCount(0), _viewPoolTotalCount(0)
, _xOffset(0), _yOffset(0), _xPadding(0), _yPadding(0)
@@ -341,6 +341,117 @@ bool RecyclerView::HandleInput(const InputProvider& inputProvider, FocusManager&
return View::HandleInput(inputProvider, focusManager);
}
void RecyclerView::HandlePenDown(const Point& touchPoint, FocusManager& focusManager)
{
if (GetBounds().Contains(touchPoint))
{
_penDown = true;
_penDownPosition = touchPoint;
_hasScrollStarted = false;
_penDownScrollOffset = _scrollOffsetAnimator.GetValue();
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenDown(touchPoint, focusManager);
}
}
}
void RecyclerView::HandlePenMove(const Point& touchPoint, FocusManager& focusManager)
{
if (!_penDown)
{
return;
}
if (!_hasScrollStarted)
{
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenMove(touchPoint, focusManager);
if (focusManager.GetCurrentFocus().GetPointer() == _viewPool[i].view.GetPointer())
{
SetSelectedItem(_viewPool[i].itemIdx);
}
}
int dx = touchPoint.x - _penDownPosition.x;
int dy = touchPoint.y - _penDownPosition.y;
if (dx * dx + dy * dy > 7 * 7)
{
bool shouldScrollStart = (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
? (std::abs(touchPoint.x - _penDownPosition.x) > std::abs(touchPoint.y - _penDownPosition.y))
: (std::abs(touchPoint.x - _penDownPosition.x) < std::abs(touchPoint.y - _penDownPosition.y));
if (shouldScrollStart)
{
_hasScrollStarted = true;
}
else
{
_penDown = false; //wrong direction drag, so cancel it
}
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenUp(Point(-1, -1), focusManager);
}
}
}
else
{
int newScrollOffset;
if (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
{
newScrollOffset = _penDownScrollOffset + touchPoint.x - _penDownPosition.x;
if (-newScrollOffset < 0)
{
newScrollOffset = 0;
_penDownScrollOffset = 0;
_penDownPosition.x = touchPoint.x;
}
else if (newScrollOffset < GetMaxScrollOffset())
{
newScrollOffset = GetMaxScrollOffset();
_penDownScrollOffset = newScrollOffset;
_penDownPosition.x = touchPoint.x;
}
}
else
{
newScrollOffset = _penDownScrollOffset + touchPoint.y - _penDownPosition.y;
if (-newScrollOffset < 0)
{
newScrollOffset = 0;
_penDownScrollOffset = 0;
_penDownPosition.y = touchPoint.y;
}
else if (newScrollOffset < GetMaxScrollOffset())
{
newScrollOffset = GetMaxScrollOffset();
_penDownScrollOffset = newScrollOffset;
_penDownPosition.y = touchPoint.y;
}
}
SetScrollOffset(newScrollOffset, false);
}
}
void RecyclerView::HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager)
{
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
{
_viewPool[i].view->HandlePenUp(lastTouchPoint, focusManager);
if (focusManager.GetCurrentFocus().GetPointer() == _viewPool[i].view.GetPointer())
{
SetSelectedItem(_viewPool[i].itemIdx);
}
}
_penDown = false;
}
Point RecyclerView::GetItemPosition(int itemIdx)
{
int x = 0;

View File

@@ -1,15 +1,14 @@
#pragma once
#include <memory>
#include "core/EnableSharedFromThis.h"
#include "View.h"
#include "RecyclerAdapter.h"
#include "gui/FocusManager.h"
#include "animation/Animator.h"
#include "RecyclerViewBase.h"
class RecyclerView : public RecyclerViewBase, public EnableSharedFromThis<RecyclerView>
class RecyclerView : public RecyclerViewBase
{
struct Private { explicit Private() = default; };
SHARED_ONLY(RecyclerView)
public:
enum class Mode
@@ -24,13 +23,6 @@ public:
VerticalGrid
};
RecyclerView(Private, int x, int y, int width, int height, Mode mode);
static SharedPtr<RecyclerView> CreateShared(int x, int y, int width, int height, Mode mode)
{
return SharedPtr<RecyclerView>::MakeShared(Private(), x, y, width, height, mode);
}
~RecyclerView();
void SetAdapter(SharedPtr<const RecyclerAdapter> adapter, int initialSelectedIndex = 0) override;
@@ -47,6 +39,9 @@ public:
SharedPtr<View> MoveFocus(const SharedPtr<View>& currentFocus, FocusMoveDirection direction, View* source) override;
bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager) override;
void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) override;
void Focus(FocusManager& focusManager) override
{
@@ -107,6 +102,12 @@ private:
int _curRangeStart;
int _curRangeLength;
Animator<int> _scrollOffsetAnimator;
bool _penDown = false;
Point _penDownPosition = Point(0, 0);
bool _hasScrollStarted = false;
int _penDownScrollOffset = 0;
RecyclerView(int x, int y, int width, int height, Mode mode);
void UpdatePosition(ViewPoolEntry& viewPoolEntry);
ViewPoolEntry* GetViewPoolEntryByItemIndex(int itemIdx);

View File

@@ -3,6 +3,7 @@
#include "core/math/Point.h"
#include "core/math/Rectangle.h"
#include "core/SharedPtr.h"
#include "core/EnableSharedFromThis.h"
#include "../FocusManager.h"
#include "../FocusMoveDirection.h"
@@ -11,7 +12,7 @@ class VramContext;
class InputProvider;
/// @brief Base class for views.
class View
class View : public EnableSharedFromThis<View>
{
public:
/// @brief Link used for views that contain other views.
@@ -58,6 +59,21 @@ public:
return false;
}
/// @brief Handles a pen down event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
virtual void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) { }
/// @brief Handles a pen move event.
/// @param touchPoint The touch point.
/// @param focusManager The focus manager.
virtual void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) { }
/// @brief Handles a pen up event.
/// @param lastTouchPoint The last touch point.
/// @param focusManager The focus manager.
virtual void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) { }
/// @brief Gets the bounds of the view.
/// @return The bounds of the view.
virtual Rectangle GetBounds() const = 0;

View File

@@ -0,0 +1,58 @@
#include "common.h"
#include "ViewContainer.h"
void ViewContainer::InitVram(const VramContext& vramContext)
{
for (auto& view : _children)
{
view.InitVram(vramContext);
}
}
void ViewContainer::Update()
{
for (auto& view : _children)
{
view.Update();
}
}
void ViewContainer::Draw(GraphicsContext& graphicsContext)
{
for (auto& view : _children)
{
view.Draw(graphicsContext);
}
}
void ViewContainer::VBlank()
{
for (auto& view : _children)
{
view.VBlank();
}
}
void ViewContainer::HandlePenDown(const Point& touchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenDown(touchPoint, focusManager);
}
}
void ViewContainer::HandlePenMove(const Point& touchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenMove(touchPoint, focusManager);
}
}
void ViewContainer::HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager)
{
for (auto& view : _children)
{
view.HandlePenUp(lastTouchPoint, focusManager);
}
}

View File

@@ -6,37 +6,13 @@
class ViewContainer : public View
{
public:
void InitVram(const VramContext& vramContext) override
{
for (auto& view : _children)
{
view.InitVram(vramContext);
}
}
void Update() override
{
for (auto& view : _children)
{
view.Update();
}
}
void Draw(GraphicsContext& graphicsContext) override
{
for (auto& view : _children)
{
view.Draw(graphicsContext);
}
}
void VBlank() override
{
for (auto& view : _children)
{
view.VBlank();
}
}
void InitVram(const VramContext& vramContext) override;
void Update() override;
void Draw(GraphicsContext& graphicsContext) override;
void VBlank() override;
void HandlePenDown(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenMove(const Point& touchPoint, FocusManager& focusManager) override;
void HandlePenUp(const Point& lastTouchPoint, FocusManager& focusManager) override;
protected:
/// @brief Adds a child to the head of the list.
@@ -55,6 +31,14 @@ protected:
view->SetParent(this);
}
/// @brief Removes a child from the list.
/// @param view The child to remove.
void RemoveChild(View* view)
{
_children.Remove(view);
view->SetParent(nullptr);
}
private:
LinkedList<View, &View::listLink> _children;
};