mirror of
https://github.com/LNH-team/pico-launcher.git
synced 2026-06-02 09:06:54 +02:00
Initial commit
This commit is contained in:
8
arm9/source/gui/views/DialogType.h
Normal file
8
arm9/source/gui/views/DialogType.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Enum representing a type of dialog.
|
||||
enum class DialogType
|
||||
{
|
||||
// Dialog,
|
||||
BottomSheet
|
||||
};
|
||||
21
arm9/source/gui/views/DialogView.h
Normal file
21
arm9/source/gui/views/DialogView.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "ViewContainer.h"
|
||||
#include "DialogType.h"
|
||||
|
||||
/// @brief View meant to be displayed as a dialog on top of other content.
|
||||
class DialogView : public ViewContainer
|
||||
{
|
||||
public:
|
||||
/// @brief Gets the type of dialog.
|
||||
/// @return The type of dialog.
|
||||
virtual DialogType GetDialogType() const = 0;
|
||||
|
||||
/// @brief Moves the focus to this dialog.
|
||||
/// @param focusManager The focus manager to use.
|
||||
virtual void Focus(FocusManager& focusManager) = 0;
|
||||
|
||||
/// @brief Gets the area of the screen that will be fully covered by
|
||||
/// this dialog for the purpose of culling views behind it.
|
||||
/// @return A rectangle that is fully covered by the dialog.
|
||||
virtual Rectangle GetFullyCoveredArea() const = 0;
|
||||
};
|
||||
68
arm9/source/gui/views/Label2DView.cpp
Normal file
68
arm9/source/gui/views/Label2DView.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "common.h"
|
||||
#include "gui/OamManager.h"
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "gui/IVramManager.h"
|
||||
#include "gui/VramContext.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "gui/palette/GradientPalette.h"
|
||||
#include "Label2DView.h"
|
||||
|
||||
void Label2DView::InitVram(const VramContext& vramContext)
|
||||
{
|
||||
const auto objVramManager = vramContext.GetObjVramManager();
|
||||
if (objVramManager)
|
||||
{
|
||||
_vramOffset = objVramManager->Alloc((_actualWidth * _actualHeight) >> 1);
|
||||
_vramAddress = objVramManager->GetVramAddress(_vramOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void Label2DView::UpdateTileBuffer()
|
||||
{
|
||||
LabelView::UpdateTileBuffer();
|
||||
_tileBufferUpdated = true;
|
||||
}
|
||||
|
||||
void Label2DView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
if (!graphicsContext.IsVisible(GetBounds()))
|
||||
return;
|
||||
|
||||
u32 hCellCount = _actualWidth >> 5;
|
||||
u32 vCellCount = _actualHeight >> 4;
|
||||
u32 cellCount = hCellCount * vCellCount;
|
||||
auto oams = graphicsContext.GetOamManager().AllocOams(cellCount);
|
||||
int xOffset = _position.x;
|
||||
if (_hAlign == Alignment::Center)
|
||||
xOffset += ((int)_width - (int)_stringWidth) / 2;
|
||||
else if (_hAlign == Alignment::End)
|
||||
xOffset += (int)_width - (int)_stringWidth;
|
||||
u32 paletteRow = graphicsContext.GetPaletteManager().AllocRow(
|
||||
GradientPalette(_backgroundColor, _foregroundColor), _position.y, _position.y + _height);
|
||||
u32 i = 0;
|
||||
for (u32 y = 0; y < vCellCount; y++)
|
||||
{
|
||||
for (u32 x = 0; x < hCellCount; x++)
|
||||
{
|
||||
OamBuilder::OamWithSize<32, 16>(
|
||||
xOffset + x * 32,
|
||||
_position.y + y * 16,
|
||||
(_vramOffset + i * 256) >> 7)
|
||||
.WithPalette16(paletteRow)
|
||||
.WithPriority(graphicsContext.GetPriority())
|
||||
.Build(oams[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Label2DView::VBlank()
|
||||
{
|
||||
if (_tileBufferUpdated)
|
||||
{
|
||||
memcpy((void*)_vramAddress, _tileBuffer.get(), _tileBufferSize);
|
||||
_tileBufferUpdated = false;
|
||||
_stringWidth = _newStringWidth;
|
||||
}
|
||||
}
|
||||
20
arm9/source/gui/views/Label2DView.h
Normal file
20
arm9/source/gui/views/Label2DView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "LabelView.h"
|
||||
|
||||
class Label2DView : public LabelView
|
||||
{
|
||||
public:
|
||||
Label2DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font)
|
||||
: LabelView(width, height, maxStringLength, font, false) { }
|
||||
|
||||
void InitVram(const VramContext& vramContext) override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
void VBlank() override;
|
||||
|
||||
private:
|
||||
void UpdateTileBuffer() override;
|
||||
|
||||
bool _tileBufferUpdated = false;
|
||||
u32 _vramOffset = 0;
|
||||
vu16* _vramAddress = nullptr;
|
||||
};
|
||||
58
arm9/source/gui/views/Label3DView.cpp
Normal file
58
arm9/source/gui/views/Label3DView.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "common.h"
|
||||
#include <bit>
|
||||
#include "gui/IVramManager.h"
|
||||
#include "gui/Gx.h"
|
||||
#include "gui/VramContext.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "Label3DView.h"
|
||||
|
||||
Label3DView::Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
|
||||
VBlankTextureLoader* vblankTextureLoader)
|
||||
: LabelView(width, height, maxStringLength, font, true)
|
||||
, _vblankTextureLoader(vblankTextureLoader) { }
|
||||
|
||||
void Label3DView::InitVram(const VramContext& vramContext)
|
||||
{
|
||||
const auto texVramManager = vramContext.GetTexVramManager();
|
||||
if (texVramManager)
|
||||
{
|
||||
_texVramOffset = texVramManager->Alloc(_tileBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Label3DView::UpdateTileBuffer()
|
||||
{
|
||||
_vblankTextureLoader->CancelLoad(_textureLoadRequest);
|
||||
LabelView::UpdateTileBuffer();
|
||||
_textureLoadRequest = VBlankTextureLoadRequest(_tileBuffer.get(), _tileBufferSize,
|
||||
_texVramOffset, nullptr, 0, 0, nullptr, nullptr);
|
||||
_vblankTextureLoader->RequestLoad(_textureLoadRequest);
|
||||
}
|
||||
|
||||
void Label3DView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
if (!graphicsContext.IsVisible(GetBounds()))
|
||||
return;
|
||||
|
||||
Gx::MtxIdentity();
|
||||
Gx::PolygonAttr(GX_LIGHTMASK_NONE, GX_POLYGON_MODE_MODULATE, GX_DISPLAY_MODE_FRONT,
|
||||
false, false, false, GX_DEPTH_FUNC_LESS, false, 31, graphicsContext.GetPolygonId());
|
||||
Gx::TexImageParam(_texVramOffset >> 3, false, false, false, false, (GxTexSize)(std::bit_width(_actualWidth) - 4),
|
||||
GX_TEXSIZE_1024, GX_TEXFMT_A5I3, false, GX_TEXGEN_NONE);
|
||||
graphicsContext.GetRgb6Palette()->ApplyColor(Rgb<6, 6, 6>(_foregroundColor));
|
||||
Gx::Begin(GX_PRIMITIVE_QUAD);
|
||||
Gx::TexCoord(0, 0);
|
||||
REG_GX_VTX_16 = GX_VTX_PACK(_position.x << 6, _position.y << 3);
|
||||
REG_GX_VTX_16 = (200) << 6;
|
||||
Gx::TexCoord(0, (int)_height);
|
||||
REG_GX_VTX_16 = GX_VTX_PACK(_position.x << 6, (_position.y + _height) << 3);
|
||||
REG_GX_VTX_16 = (200) << 6;
|
||||
Gx::TexCoord((int)_width, (int)_height);
|
||||
REG_GX_VTX_16 = GX_VTX_PACK((_position.x + _width) << 6, (_position.y + _height) << 3);
|
||||
REG_GX_VTX_16 = (200) << 6;
|
||||
Gx::TexCoord((int)_width, 0);
|
||||
REG_GX_VTX_16 = GX_VTX_PACK((_position.x + _width) << 6, _position.y << 3);
|
||||
REG_GX_VTX_16 = (200) << 6;
|
||||
Gx::End();
|
||||
}
|
||||
20
arm9/source/gui/views/Label3DView.h
Normal file
20
arm9/source/gui/views/Label3DView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "LabelView.h"
|
||||
#include "gui/VBlankTextureLoader.h"
|
||||
|
||||
class alignas(32) Label3DView : public LabelView
|
||||
{
|
||||
public:
|
||||
Label3DView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font,
|
||||
VBlankTextureLoader* vblankTextureLoader);
|
||||
|
||||
void InitVram(const VramContext& vramContext) override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
|
||||
private:
|
||||
void UpdateTileBuffer() override;
|
||||
|
||||
u32 _texVramOffset = 0;
|
||||
VBlankTextureLoader* _vblankTextureLoader;
|
||||
VBlankTextureLoadRequest _textureLoadRequest;
|
||||
};
|
||||
121
arm9/source/gui/views/LabelView.cpp
Normal file
121
arm9/source/gui/views/LabelView.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "common.h"
|
||||
#include <bit>
|
||||
#include "gui/OamManager.h"
|
||||
#include "gui/OamBuilder.h"
|
||||
#include "gui/IVramManager.h"
|
||||
#include "gui/VramContext.h"
|
||||
#include "gui/GraphicsContext.h"
|
||||
#include "core/StringUtil.h"
|
||||
#include "gui/palette/GradientPalette.h"
|
||||
#include "LabelView.h"
|
||||
|
||||
LabelView::LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font, bool a5i3)
|
||||
: _width(width), _height(height)
|
||||
, _maxStringLength(maxStringLength), _font(font)
|
||||
, _hAlign(Alignment::Start)
|
||||
, _stringWidth(0), _newStringWidth(0), _a5i3(a5i3)
|
||||
{
|
||||
_textBuffer = std::make_unique_for_overwrite<char16_t[]>(_maxStringLength + 1);
|
||||
_textBuffer[0] = 0;
|
||||
if (_a5i3)
|
||||
{
|
||||
_actualWidth = std::bit_ceil(width);
|
||||
_actualHeight = height;
|
||||
_tileBufferSize = _actualWidth * _actualHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_actualWidth = (width + 31) & ~31;
|
||||
_actualHeight = (height + 15) & ~15;
|
||||
_tileBufferSize = (_actualWidth * _actualHeight) >> 1;
|
||||
}
|
||||
|
||||
_tileBuffer = std::make_unique_for_overwrite<u8[]>(_tileBufferSize);
|
||||
SetText(u"");
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char* text)
|
||||
{
|
||||
StringUtil::Copy(_textBuffer.get(), text, _maxStringLength + 1);
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char16_t* text)
|
||||
{
|
||||
StringUtil::Copy(_textBuffer.get(), text, _maxStringLength + 1);
|
||||
}
|
||||
|
||||
void LabelView::SetTextBuffer(const char16_t* text, u32 length)
|
||||
{
|
||||
u32 copyLength = std::min(length, _maxStringLength);
|
||||
if (copyLength > 0)
|
||||
memcpy(_textBuffer.get(), text, copyLength * 2);
|
||||
_textBuffer[copyLength] = 0;
|
||||
}
|
||||
|
||||
void LabelView::UpdateTileBuffer()
|
||||
{
|
||||
memset(_tileBuffer.get(), 0, _tileBufferSize);
|
||||
if (_textBuffer[0] != 0)
|
||||
{
|
||||
nft2_string_render_params_t renderParams;
|
||||
renderParams.x = 0;
|
||||
renderParams.y = 0;
|
||||
renderParams.width = _width;
|
||||
renderParams.height = _height;
|
||||
renderParams.a5i3 = _a5i3;
|
||||
if (_ellipsis)
|
||||
nft2_renderStringEllipsis(_font, _textBuffer.get(), _tileBuffer.get(), _actualWidth, &renderParams, u" ... ");
|
||||
else
|
||||
nft2_renderString(_font, _textBuffer.get(), _tileBuffer.get(), _actualWidth, &renderParams);
|
||||
_newStringWidth = renderParams.textWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
_newStringWidth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
QueueTask<void> LabelView::UpdateTileBufferAsync(TaskQueueBase* taskQueue)
|
||||
{
|
||||
return taskQueue->Enqueue([this] (const vu8& canceled)
|
||||
{
|
||||
UpdateTileBuffer();
|
||||
return TaskResult<void>::Completed();
|
||||
});
|
||||
}
|
||||
|
||||
void LabelView::SetText(const char* text)
|
||||
{
|
||||
SetTextBuffer(text);
|
||||
UpdateTileBuffer();
|
||||
}
|
||||
|
||||
void LabelView::SetText(const char16_t* text)
|
||||
{
|
||||
SetTextBuffer(text);
|
||||
UpdateTileBuffer();
|
||||
}
|
||||
|
||||
void LabelView::SetText(const char16_t* text, u32 length)
|
||||
{
|
||||
SetTextBuffer(text, length);
|
||||
UpdateTileBuffer();
|
||||
}
|
||||
|
||||
QueueTask<void> LabelView::SetTextAsync(TaskQueueBase* taskQueue, const char* text)
|
||||
{
|
||||
SetTextBuffer(text);
|
||||
return UpdateTileBufferAsync(taskQueue);
|
||||
}
|
||||
|
||||
QueueTask<void> LabelView::SetTextAsync(TaskQueueBase* taskQueue, const char16_t* text)
|
||||
{
|
||||
SetTextBuffer(text);
|
||||
return UpdateTileBufferAsync(taskQueue);
|
||||
}
|
||||
|
||||
QueueTask<void> LabelView::SetTextAsync(TaskQueueBase* taskQueue, const char16_t* text, u32 length)
|
||||
{
|
||||
SetTextBuffer(text, length);
|
||||
return UpdateTileBufferAsync(taskQueue);
|
||||
}
|
||||
74
arm9/source/gui/views/LabelView.h
Normal file
74
arm9/source/gui/views/LabelView.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include <memory>
|
||||
#include "gui/font/nitroFont2.h"
|
||||
#include "core/task/TaskQueue.h"
|
||||
#include "gui/Alignment.h"
|
||||
#include "gui/materialDesign.h"
|
||||
#include "gui/views/View.h"
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class StackVramManager;
|
||||
class MaterialGraphicsContext;
|
||||
|
||||
class LabelView : public View
|
||||
{
|
||||
public:
|
||||
void SetText(const char* text);
|
||||
void SetText(const char16_t* text);
|
||||
void SetText(const char16_t* text, u32 length);
|
||||
QueueTask<void> SetTextAsync(TaskQueueBase* taskQueue, const char* text);
|
||||
QueueTask<void> SetTextAsync(TaskQueueBase* taskQueue, const char16_t* text);
|
||||
QueueTask<void> SetTextAsync(TaskQueueBase* taskQueue, const char16_t* text, u32 length);
|
||||
|
||||
void SetHorizontalAlignment(Alignment alignment)
|
||||
{
|
||||
_hAlign = alignment;
|
||||
}
|
||||
|
||||
u32 GetStringWidth() const { return _stringWidth; }
|
||||
|
||||
void SetBackgroundColor(const Rgb<8, 8, 8>& backgroundColor)
|
||||
{
|
||||
_backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
void SetForegroundColor(const Rgb<8, 8, 8>& foregroundColor)
|
||||
{
|
||||
_foregroundColor = foregroundColor;
|
||||
}
|
||||
|
||||
Rectangle GetBounds() const override
|
||||
{
|
||||
return Rectangle(_position, _width, _height);
|
||||
}
|
||||
|
||||
void SetEllipsis(bool ellipsis) { _ellipsis = ellipsis; }
|
||||
|
||||
protected:
|
||||
u32 _width;
|
||||
u32 _height;
|
||||
u32 _actualWidth;
|
||||
u32 _actualHeight;
|
||||
u32 _maxStringLength;
|
||||
std::unique_ptr<char16_t[]> _textBuffer;
|
||||
u32 _tileBufferSize;
|
||||
std::unique_ptr<u8[]> _tileBuffer;
|
||||
const nft2_header_t* _font;
|
||||
Alignment _hAlign;
|
||||
u32 _stringWidth;
|
||||
u32 _newStringWidth;
|
||||
Rgb<8, 8, 8> _backgroundColor;
|
||||
Rgb<8, 8, 8> _foregroundColor;
|
||||
int _paletteRow = -1;
|
||||
bool _ellipsis = false;
|
||||
bool _a5i3;
|
||||
|
||||
void SetTextBuffer(const char* text);
|
||||
void SetTextBuffer(const char16_t* text);
|
||||
void SetTextBuffer(const char16_t* text, u32 length);
|
||||
virtual void UpdateTileBuffer();
|
||||
QueueTask<void> UpdateTileBufferAsync(TaskQueueBase* taskQueue);
|
||||
|
||||
LabelView(u32 width, u32 height, u32 maxStringLength, const nft2_header_t* font, bool a5i3);
|
||||
};
|
||||
38
arm9/source/gui/views/RecyclerAdapter.h
Normal file
38
arm9/source/gui/views/RecyclerAdapter.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
class View;
|
||||
|
||||
/// @brief Adapter class providing the content to show in a \see RecyclerView.
|
||||
/// All items in the adapter use an identical view that can be reused to
|
||||
/// show another item of this adapter when it goes off screen.
|
||||
class RecyclerAdapter
|
||||
{
|
||||
public:
|
||||
virtual ~RecyclerAdapter() { }
|
||||
|
||||
/// @brief Returns the total number of items in this adapter.
|
||||
/// @return The total number of items in this adapter.
|
||||
virtual u32 GetItemCount() const = 0;
|
||||
|
||||
/// @brief Returns the size of each view of this adapter.
|
||||
/// @param width Returns the with of the view.
|
||||
/// @param height Returns the height of the view.
|
||||
virtual void GetViewSize(int& width, int& height) const = 0;
|
||||
|
||||
/// @brief Creates and returns a view for this adapter.
|
||||
/// @return The created view.
|
||||
virtual View* CreateView() const = 0;
|
||||
|
||||
/// @brief Destroys a \p view for this adapter that was previously created with CreateView.
|
||||
/// @param view The view to destroy.
|
||||
virtual void DestroyView(View* view) const = 0;
|
||||
|
||||
/// @brief Binds the given \p view to the item at the given \p index.
|
||||
/// @param view The view to bind.
|
||||
/// @param index The item index to bind to.
|
||||
virtual void BindView(View* view, int index) const = 0;
|
||||
|
||||
/// @brief Releases a \p view that was previously bound with BindView, such that it can be reused.
|
||||
/// @param view The view to release.
|
||||
/// @param index The item index that was bound to the view.
|
||||
virtual void ReleaseView(View* view, int index) const = 0;
|
||||
};
|
||||
549
arm9/source/gui/views/RecyclerView.cpp
Normal file
549
arm9/source/gui/views/RecyclerView.cpp
Normal file
@@ -0,0 +1,549 @@
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include "gui/materialDesign.h"
|
||||
#include "gui/input/InputProvider.h"
|
||||
#include "RecyclerView.h"
|
||||
|
||||
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)
|
||||
, _xSpacing(0), _ySpacing(0), _itemWidth(0), _itemHeight(0)
|
||||
, _itemCount(0), _selectedItem(nullptr)
|
||||
, _curRangeStart(0), _curRangeLength(0)
|
||||
{
|
||||
_position.x = x;
|
||||
_position.y = y;
|
||||
}
|
||||
|
||||
RecyclerView::~RecyclerView()
|
||||
{
|
||||
if (_adapter)
|
||||
{
|
||||
for (u32 i = 0; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_adapter->DestroyView(_viewPool[i].view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex)
|
||||
{
|
||||
if (_adapter)
|
||||
{
|
||||
_selectedItem = nullptr;
|
||||
for (u32 i = 0; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_adapter->DestroyView(_viewPool[i].view);
|
||||
}
|
||||
_viewPool.reset();
|
||||
_viewPoolFreeCount = 0;
|
||||
_viewPoolTotalCount = 0;
|
||||
}
|
||||
_adapter = adapter;
|
||||
_adapter->GetViewSize(_itemWidth, _itemHeight);
|
||||
_itemCount = _adapter->GetItemCount();
|
||||
if (_mode == Mode::HorizontalList || _mode == Mode::HorizontalGrid)
|
||||
{
|
||||
if (_mode == Mode::HorizontalList)
|
||||
{
|
||||
_rows = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_rows = std::max(1, _height / _itemHeight);
|
||||
}
|
||||
_columns = (_width + _xSpacing + _itemWidth - 1) / (_xSpacing + _itemWidth) + 1;
|
||||
_viewPoolTotalCount = _rows * (_columns + 1) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_mode == Mode::VerticalList)
|
||||
{
|
||||
_columns = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_columns = std::max(1, _width / _itemWidth);
|
||||
}
|
||||
_rows = (_height + _ySpacing + _itemHeight - 1) / (_ySpacing + _itemHeight) + 1;
|
||||
_viewPoolTotalCount = (_rows + 1) * _columns + 1;
|
||||
}
|
||||
LOG_DEBUG("_rows: %d\n", _rows);
|
||||
LOG_DEBUG("_columns: %d\n", _columns);
|
||||
LOG_DEBUG("_viewPoolTotalCount: %d\n", _viewPoolTotalCount);
|
||||
_viewPool = std::unique_ptr<ViewPoolEntry[]>(new ViewPoolEntry[_viewPoolTotalCount]);
|
||||
for (u32 i = 0; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_viewPool[i].view = _adapter->CreateView();
|
||||
_viewPool[i].view->SetParent(this);
|
||||
_viewPool[i].itemIdx = -1;
|
||||
}
|
||||
_viewPoolFreeCount = _viewPoolTotalCount;
|
||||
|
||||
if (initialSelectedIndex < 0 || initialSelectedIndex >= (int)_itemCount)
|
||||
{
|
||||
initialSelectedIndex = 0;
|
||||
}
|
||||
EnsureVisible(initialSelectedIndex, false);
|
||||
|
||||
if (_itemCount > 0)
|
||||
{
|
||||
SetSelectedItem(initialSelectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::InitVram(const VramContext& vramContext)
|
||||
{
|
||||
for (u32 i = 0; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_viewPool[i].view->InitVram(vramContext);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::Update()
|
||||
{
|
||||
if (_itemCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_scrollOffsetAnimator.IsFinished())
|
||||
{
|
||||
_scrollOffsetAnimator.Update();
|
||||
}
|
||||
|
||||
int rangeStartIndex = 0;
|
||||
int rangeEndIndex = 0;
|
||||
if (_mode == Mode::HorizontalList || _mode == Mode::HorizontalGrid)
|
||||
{
|
||||
_xOffset = _scrollOffsetAnimator.GetValue();
|
||||
rangeStartIndex = ((-_xOffset - _xPadding) / (_xSpacing + _itemWidth) - 1) * _rows;
|
||||
rangeEndIndex = rangeStartIndex + (_columns + 1) * _rows;
|
||||
}
|
||||
else
|
||||
{
|
||||
_yOffset = _scrollOffsetAnimator.GetValue();
|
||||
rangeStartIndex = ((-_yOffset - _yPadding) / (_ySpacing + _itemHeight) - 1) * _columns;
|
||||
rangeEndIndex = rangeStartIndex + (_rows + 1) * _columns;
|
||||
}
|
||||
|
||||
rangeStartIndex = std::clamp(rangeStartIndex, 0, (int)_itemCount - 1);
|
||||
rangeEndIndex = std::clamp(rangeEndIndex, 0, (int)_itemCount);
|
||||
|
||||
if (_curRangeStart != rangeStartIndex || _curRangeLength != rangeEndIndex - rangeStartIndex)
|
||||
{
|
||||
LOG_DEBUG("range: %d - %d\n", rangeStartIndex, rangeEndIndex - 1);
|
||||
if (_curRangeLength != 0)
|
||||
{
|
||||
if (_curRangeStart < rangeStartIndex)
|
||||
{
|
||||
ReleaseRange(_curRangeStart, rangeStartIndex);
|
||||
}
|
||||
if (rangeEndIndex < _curRangeStart + _curRangeLength)
|
||||
{
|
||||
ReleaseRange(rangeEndIndex, _curRangeStart + _curRangeLength);
|
||||
}
|
||||
}
|
||||
|
||||
BindRange(rangeStartIndex, rangeEndIndex);
|
||||
|
||||
_curRangeStart = rangeStartIndex;
|
||||
_curRangeLength = rangeEndIndex - rangeStartIndex;
|
||||
}
|
||||
|
||||
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
UpdatePosition(_viewPool[i]);
|
||||
_viewPool[i].view->Update();
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::Draw(GraphicsContext& graphicsContext)
|
||||
{
|
||||
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_viewPool[i].view->Draw(graphicsContext);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::VBlank()
|
||||
{
|
||||
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
_viewPool[i].view->VBlank();
|
||||
}
|
||||
}
|
||||
|
||||
View* RecyclerView::MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source)
|
||||
{
|
||||
if (_itemCount == 0)
|
||||
{
|
||||
return View::MoveFocus(currentFocus, direction, this);
|
||||
}
|
||||
|
||||
if (_mode == Mode::HorizontalList || _mode == Mode::HorizontalGrid)
|
||||
{
|
||||
return MoveFocusHorizontal(currentFocus, direction, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MoveFocusVertical(currentFocus, direction, source);
|
||||
}
|
||||
}
|
||||
|
||||
View* RecyclerView::MoveFocusHorizontal(View* currentFocus, FocusMoveDirection direction, View* source)
|
||||
{
|
||||
if (!_selectedItem || currentFocus != _selectedItem->view)
|
||||
{
|
||||
// incoming focus
|
||||
if (direction != FocusMoveDirection::Down)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int idx = (-_xOffset + currentFocus->GetPosition().x - _xPadding + ((_xSpacing + _itemWidth) >> 1)) / (_xSpacing + _itemWidth) * _rows;
|
||||
SetSelectedItem(std::clamp(idx, 0, ((int)_itemCount - 1) / _rows * _rows));
|
||||
return _selectedItem != nullptr ? _selectedItem->view : this;
|
||||
}
|
||||
|
||||
int row = _selectedItem->itemIdx % _rows;
|
||||
|
||||
if ((row == 0 && direction == FocusMoveDirection::Up) ||
|
||||
(row == _rows - 1 && direction == FocusMoveDirection::Down) ||
|
||||
(_selectedItem->itemIdx < _rows && direction == FocusMoveDirection::Left) ||
|
||||
(_selectedItem->itemIdx / _rows >= (int)(_itemCount - 1) / _rows && direction == FocusMoveDirection::Right))
|
||||
{
|
||||
return View::MoveFocus(currentFocus, direction, this);
|
||||
}
|
||||
|
||||
if (direction == FocusMoveDirection::Left)
|
||||
{
|
||||
int idx = _selectedItem->itemIdx;
|
||||
if (idx - _rows >= 0)
|
||||
{
|
||||
idx -= _rows;
|
||||
}
|
||||
|
||||
SetSelectedItem(idx);
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Right)
|
||||
{
|
||||
int idx = _selectedItem->itemIdx + _rows;
|
||||
idx = std::min(idx, (int)_itemCount - 1);
|
||||
|
||||
SetSelectedItem(idx);
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Up)
|
||||
{
|
||||
int idx = (_selectedItem->itemIdx / _rows * _rows) + std::clamp((_selectedItem->itemIdx % _rows) - 1, 0, _rows - 1);
|
||||
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Down)
|
||||
{
|
||||
int idx = (_selectedItem->itemIdx / _rows * _rows) + std::clamp((_selectedItem->itemIdx % _rows) + 1, 0, _rows - 1);
|
||||
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
|
||||
}
|
||||
|
||||
return _selectedItem != nullptr ? _selectedItem->view : this;
|
||||
}
|
||||
|
||||
View* RecyclerView::MoveFocusVertical(View* currentFocus, FocusMoveDirection direction, View* source)
|
||||
{
|
||||
if (!_selectedItem || currentFocus != _selectedItem->view)
|
||||
{
|
||||
// incoming focus
|
||||
if (direction != FocusMoveDirection::Right)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int idx = (-_yOffset + currentFocus->GetPosition().y - _yPadding + ((_ySpacing + _itemHeight) >> 1)) / (_ySpacing + _itemHeight) * _columns;
|
||||
SetSelectedItem(std::clamp(idx, 0, ((int)_itemCount - 1) / _columns * _columns));
|
||||
return _selectedItem != nullptr ? _selectedItem->view : this;
|
||||
}
|
||||
|
||||
int column = _selectedItem->itemIdx % _columns;
|
||||
|
||||
if ((column == 0 && direction == FocusMoveDirection::Left) ||
|
||||
(column == _columns - 1 && direction == FocusMoveDirection::Right) ||
|
||||
(_selectedItem->itemIdx < _columns && direction == FocusMoveDirection::Up) ||
|
||||
(_selectedItem->itemIdx / _columns >= (int)(_itemCount - 1) / _columns && direction == FocusMoveDirection::Down))
|
||||
{
|
||||
return View::MoveFocus(currentFocus, direction, this);
|
||||
}
|
||||
|
||||
if (direction == FocusMoveDirection::Up)
|
||||
{
|
||||
int idx = _selectedItem->itemIdx;
|
||||
if (idx - _columns >= 0)
|
||||
{
|
||||
idx -= _columns;
|
||||
}
|
||||
|
||||
SetSelectedItem(idx);
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Down)
|
||||
{
|
||||
int idx = _selectedItem->itemIdx + _columns;
|
||||
idx = std::min(idx, (int)_itemCount - 1);
|
||||
|
||||
SetSelectedItem(idx);
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Left)
|
||||
{
|
||||
int idx = (_selectedItem->itemIdx / _columns * _columns) + std::clamp((_selectedItem->itemIdx % _columns) - 1, 0, _columns - 1);
|
||||
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
|
||||
}
|
||||
else if (direction == FocusMoveDirection::Right)
|
||||
{
|
||||
int idx = (_selectedItem->itemIdx / _columns * _columns) + std::clamp((_selectedItem->itemIdx % _columns) + 1, 0, _columns - 1);
|
||||
SetSelectedItem(std::clamp(idx, 0, (int)_itemCount - 1));
|
||||
}
|
||||
|
||||
return _selectedItem != nullptr ? _selectedItem->view : this;
|
||||
}
|
||||
|
||||
bool RecyclerView::HandleInput(const InputProvider& inputProvider, FocusManager& focusManager)
|
||||
{
|
||||
if (inputProvider.Triggered(InputKey::L | InputKey::R))
|
||||
{
|
||||
int direction = inputProvider.Triggered(InputKey::L) ? 1 : -1;
|
||||
int selected = _selectedItem->itemIdx;
|
||||
if (_mode == Mode::HorizontalList || _mode == Mode::HorizontalGrid)
|
||||
{
|
||||
int visibleColumns = _width / (_itemWidth + _xSpacing);
|
||||
SetScrollOffset(_scrollOffsetAnimator.GetTargetValue() + direction * visibleColumns * (_itemWidth + _xSpacing), true);
|
||||
int row = selected % _rows;
|
||||
selected = std::clamp(selected - direction * visibleColumns * _rows, 0, (int)_itemCount - 1);
|
||||
selected = selected / _rows * _rows + row; // try to stay in the same row
|
||||
selected = std::clamp(selected, 0, (int)_itemCount - 1); // but clamp to the last item
|
||||
}
|
||||
else
|
||||
{
|
||||
int visibleRows = _height / (_itemHeight + _ySpacing);
|
||||
SetScrollOffset(_scrollOffsetAnimator.GetTargetValue() + direction * visibleRows * (_itemHeight + _ySpacing), true);
|
||||
int column = selected % _columns;
|
||||
selected = std::clamp(selected - direction * visibleRows * _columns, 0, (int)_itemCount - 1);
|
||||
selected = selected / _columns * _columns + column; // try to stay in the same column
|
||||
selected = std::clamp(selected, 0, (int)_itemCount - 1); // but clamp to the last item
|
||||
}
|
||||
|
||||
focusManager.Unfocus();
|
||||
SetSelectedItem(selected);
|
||||
focusManager.Focus(_selectedItem->view);
|
||||
return true;
|
||||
}
|
||||
|
||||
return View::HandleInput(inputProvider, focusManager);
|
||||
}
|
||||
|
||||
Point RecyclerView::GetItemPosition(int itemIdx)
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
switch (_mode)
|
||||
{
|
||||
case Mode::HorizontalList:
|
||||
{
|
||||
x = _xPadding + itemIdx * (_xSpacing + _itemWidth);
|
||||
y = _yPadding;
|
||||
break;
|
||||
}
|
||||
case Mode::HorizontalGrid:
|
||||
{
|
||||
x = _xPadding + (itemIdx / _rows) * (_xSpacing + _itemWidth);
|
||||
y = _yPadding + (itemIdx % _rows) * (_ySpacing + _itemHeight);
|
||||
break;
|
||||
}
|
||||
case Mode::VerticalList:
|
||||
{
|
||||
x = _xPadding;
|
||||
y = _yPadding + itemIdx * (_ySpacing + _itemHeight);
|
||||
break;
|
||||
}
|
||||
case Mode::VerticalGrid:
|
||||
{
|
||||
x = _xPadding + (itemIdx % _columns) * (_xSpacing + _itemWidth);
|
||||
y = _yPadding + (itemIdx / _columns) * (_ySpacing + _itemHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Point(x, y);
|
||||
}
|
||||
|
||||
void RecyclerView::UpdatePosition(ViewPoolEntry& viewPoolEntry)
|
||||
{
|
||||
auto itemPosition = GetItemPosition(viewPoolEntry.itemIdx);
|
||||
viewPoolEntry.view->SetPosition(
|
||||
_position.x + _xOffset + itemPosition.x,
|
||||
_position.y + _yOffset + itemPosition.y);
|
||||
}
|
||||
|
||||
RecyclerView::ViewPoolEntry* RecyclerView::GetViewPoolEntryByItemIndex(int itemIdx)
|
||||
{
|
||||
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
if (_viewPool[i].itemIdx == (int)itemIdx)
|
||||
{
|
||||
return &_viewPool[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RecyclerView::ViewPoolEntry* RecyclerView::BindViewPoolEntry(int itemIdx)
|
||||
{
|
||||
if (_viewPoolFreeCount == 0)
|
||||
{
|
||||
LOG_FATAL("No free view pool entries left\n");
|
||||
while (true);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& entry = _viewPool[_viewPoolFreeCount - 1];
|
||||
_viewPoolFreeCount--;
|
||||
entry.itemIdx = itemIdx;
|
||||
_adapter->BindView(entry.view, itemIdx);
|
||||
return &entry;
|
||||
}
|
||||
|
||||
void RecyclerView::BindRange(int start, int end)
|
||||
{
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
if ((_selectedItem && _selectedItem->itemIdx == i) ||
|
||||
(_curRangeLength != 0 && _curRangeStart <= i && i < _curRangeStart + _curRangeLength))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
BindViewPoolEntry(i);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::ReleaseViewPoolEntry(int itemIdx)
|
||||
{
|
||||
for (u32 i = _viewPoolFreeCount; i < _viewPoolTotalCount; i++)
|
||||
{
|
||||
if (_viewPool[i].itemIdx == (int)itemIdx)
|
||||
{
|
||||
_adapter->ReleaseView(_viewPool[i].view, _viewPool[i].itemIdx);
|
||||
_viewPool[i].itemIdx = -1;
|
||||
std::swap(_viewPool[i], _viewPool[_viewPoolFreeCount]);
|
||||
if (_selectedItem == &_viewPool[_viewPoolFreeCount])
|
||||
{
|
||||
_selectedItem = &_viewPool[i];
|
||||
}
|
||||
_viewPoolFreeCount++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::ReleaseRange(int start, int end)
|
||||
{
|
||||
for (int i = start; i < end; i++)
|
||||
{
|
||||
if (_selectedItem && _selectedItem->itemIdx == i)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ReleaseViewPoolEntry(i);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::SetSelectedItem(int itemIdx)
|
||||
{
|
||||
if (_selectedItem)
|
||||
{
|
||||
if (_selectedItem->itemIdx == itemIdx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectedItem->itemIdx < _curRangeStart ||
|
||||
_selectedItem->itemIdx >= _curRangeStart + _curRangeLength)
|
||||
{
|
||||
ReleaseViewPoolEntry(_selectedItem->itemIdx);
|
||||
}
|
||||
_selectedItem = nullptr;
|
||||
}
|
||||
|
||||
if (itemIdx < 0 || itemIdx >= (int)_itemCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemIdx >= _curRangeStart &&
|
||||
itemIdx < _curRangeStart + _curRangeLength)
|
||||
{
|
||||
_selectedItem = GetViewPoolEntryByItemIndex(itemIdx);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedItem = BindViewPoolEntry(itemIdx);
|
||||
}
|
||||
|
||||
EnsureVisible(itemIdx, true);
|
||||
}
|
||||
|
||||
int RecyclerView::GetMaxScrollOffset()
|
||||
{
|
||||
if (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
|
||||
{
|
||||
int totalColumns = (_itemCount + _rows - 1) / _rows;
|
||||
int contentWidth = totalColumns * _itemWidth + (totalColumns - 1) * _xSpacing + _xPadding * 2;
|
||||
return std::min(0, _width - contentWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalRows = ((_itemCount + _columns - 1) / _columns);
|
||||
int contentHeight = totalRows * _itemHeight + (totalRows - 1) * _ySpacing + _yPadding * 2;
|
||||
return std::min(0, _height - contentHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::SetScrollOffset(int offset, bool animate)
|
||||
{
|
||||
offset = std::clamp(offset, GetMaxScrollOffset(), 0);
|
||||
|
||||
if (!animate)
|
||||
{
|
||||
_scrollOffsetAnimator = Animator<int>(offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (std::abs(offset - _scrollOffsetAnimator.GetTargetValue()) <= 128)
|
||||
{
|
||||
_scrollOffsetAnimator.Goto(offset,
|
||||
md::sys::motion::duration::medium1, &md::sys::motion::easing::emphasized);
|
||||
}
|
||||
else
|
||||
{
|
||||
_scrollOffsetAnimator.Goto(offset,
|
||||
md::sys::motion::duration::long2, &md::sys::motion::easing::standard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecyclerView::EnsureVisible(int itemIdx, bool animate)
|
||||
{
|
||||
const auto itemPosition = GetItemPosition(itemIdx);
|
||||
int minItemScrollOffset;
|
||||
int maxItemScollOffset;
|
||||
if (_mode == Mode::HorizontalGrid || _mode == Mode::HorizontalList)
|
||||
{
|
||||
minItemScrollOffset = -itemPosition.x + _xPadding;
|
||||
maxItemScollOffset = -itemPosition.x + _width - _itemWidth - _xPadding;
|
||||
}
|
||||
else
|
||||
{
|
||||
minItemScrollOffset = -itemPosition.y + _yPadding;
|
||||
maxItemScollOffset = -itemPosition.y + _height - _itemHeight - _yPadding;
|
||||
}
|
||||
int targetScrollOffset = std::clamp(_scrollOffsetAnimator.GetTargetValue(), minItemScrollOffset, maxItemScollOffset);
|
||||
if (targetScrollOffset != _scrollOffsetAnimator.GetTargetValue())
|
||||
{
|
||||
SetScrollOffset(targetScrollOffset, animate);
|
||||
}
|
||||
}
|
||||
112
arm9/source/gui/views/RecyclerView.h
Normal file
112
arm9/source/gui/views/RecyclerView.h
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "View.h"
|
||||
#include "RecyclerAdapter.h"
|
||||
#include "gui/FocusManager.h"
|
||||
#include "animation/Animator.h"
|
||||
#include "RecyclerViewBase.h"
|
||||
|
||||
class RecyclerView : public RecyclerViewBase
|
||||
{
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
/// @brief A single row that grows horizontally.
|
||||
HorizontalList,
|
||||
/// @brief A multi-row grid that grows horizontally.
|
||||
HorizontalGrid,
|
||||
/// @brief A single column that grows vertically.
|
||||
VerticalList,
|
||||
/// @brief A multi-column grid that grows vertically.
|
||||
VerticalGrid
|
||||
};
|
||||
|
||||
RecyclerView(int x, int y, int width, int height, Mode mode);
|
||||
~RecyclerView();
|
||||
|
||||
void SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex = 0) override;
|
||||
void InitVram(const VramContext& vramContext) override;
|
||||
void Update() override;
|
||||
void Draw(GraphicsContext& graphicsContext) override;
|
||||
void VBlank() override;
|
||||
|
||||
Rectangle GetBounds() const override
|
||||
{
|
||||
return Rectangle(_position, _width, _height);
|
||||
}
|
||||
|
||||
View* MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source) override;
|
||||
|
||||
bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager) override;
|
||||
|
||||
void Focus(FocusManager& focusManager) override
|
||||
{
|
||||
if (_selectedItem)
|
||||
focusManager.Focus(_selectedItem->view);
|
||||
else
|
||||
focusManager.Focus(this);
|
||||
}
|
||||
|
||||
int GetSelectedItem() const override
|
||||
{
|
||||
return _selectedItem ? _selectedItem->itemIdx : -1;
|
||||
}
|
||||
|
||||
constexpr Mode GetMode() const { return _mode; }
|
||||
|
||||
void SetPadding(int x, int y)
|
||||
{
|
||||
_xPadding = x;
|
||||
_yPadding = y;
|
||||
}
|
||||
|
||||
void SetItemSpacing(int x, int y)
|
||||
{
|
||||
_xSpacing = x;
|
||||
_ySpacing = y;
|
||||
}
|
||||
|
||||
private:
|
||||
struct ViewPoolEntry
|
||||
{
|
||||
View* view;
|
||||
int itemIdx;
|
||||
};
|
||||
|
||||
int _width;
|
||||
int _height;
|
||||
Mode _mode;
|
||||
int _rows;
|
||||
int _columns;
|
||||
std::unique_ptr<ViewPoolEntry[]> _viewPool;
|
||||
u32 _viewPoolFreeCount;
|
||||
u32 _viewPoolTotalCount;
|
||||
int _xOffset;
|
||||
int _yOffset;
|
||||
int _xPadding;
|
||||
int _yPadding;
|
||||
int _xSpacing;
|
||||
int _ySpacing;
|
||||
int _itemWidth;
|
||||
int _itemHeight;
|
||||
u32 _itemCount;
|
||||
ViewPoolEntry* _selectedItem;
|
||||
int _curRangeStart;
|
||||
int _curRangeLength;
|
||||
Animator<int> _scrollOffsetAnimator;
|
||||
|
||||
void UpdatePosition(ViewPoolEntry& viewPoolEntry);
|
||||
ViewPoolEntry* GetViewPoolEntryByItemIndex(int itemIdx);
|
||||
void BindRange(int start, int end);
|
||||
ViewPoolEntry* BindViewPoolEntry(int itemIdx);
|
||||
void ReleaseRange(int start, int end);
|
||||
void ReleaseViewPoolEntry(int itemIdx);
|
||||
void SetSelectedItem(int itemIdx);
|
||||
int GetMaxScrollOffset();
|
||||
void SetScrollOffset(int offset, bool animate);
|
||||
void EnsureVisible(int itemIdx, bool animate);
|
||||
Point GetItemPosition(int itemIdx);
|
||||
|
||||
View* MoveFocusHorizontal(View* currentFocus, FocusMoveDirection direction, View* source);
|
||||
View* MoveFocusVertical(View* currentFocus, FocusMoveDirection direction, View* source);
|
||||
};
|
||||
17
arm9/source/gui/views/RecyclerViewBase.h
Normal file
17
arm9/source/gui/views/RecyclerViewBase.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "View.h"
|
||||
#include "RecyclerAdapter.h"
|
||||
#include "gui/FocusManager.h"
|
||||
|
||||
/// @brief Abstract base class for a recycler view that displays a possibly large collection of items
|
||||
/// provided by an adapter in an efficient way.
|
||||
class RecyclerViewBase : public View
|
||||
{
|
||||
public:
|
||||
virtual void SetAdapter(const RecyclerAdapter* adapter, int initialSelectedIndex = 0) = 0;
|
||||
virtual void Focus(FocusManager& focusManager) = 0;
|
||||
virtual int GetSelectedItem() const = 0;
|
||||
|
||||
protected:
|
||||
const RecyclerAdapter* _adapter = nullptr;
|
||||
};
|
||||
106
arm9/source/gui/views/View.h
Normal file
106
arm9/source/gui/views/View.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
#include "core/LinkedListLink.h"
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rectangle.h"
|
||||
#include "../FocusManager.h"
|
||||
#include "../FocusMoveDirection.h"
|
||||
|
||||
class GraphicsContext;
|
||||
class VramContext;
|
||||
class InputProvider;
|
||||
|
||||
/// @brief Base class for views.
|
||||
class View
|
||||
{
|
||||
public:
|
||||
/// @brief Link used for views that contain other views.
|
||||
LinkedListLink listLink;
|
||||
|
||||
virtual ~View() { }
|
||||
|
||||
/// @brief Initializes the vram for the view.
|
||||
/// @param vramContext The vram context to use.
|
||||
virtual void InitVram(const VramContext& vramContext) { }
|
||||
|
||||
/// @brief Updates the view.
|
||||
virtual void Update() { }
|
||||
|
||||
/// @brief Draws the view.
|
||||
/// @param graphicsContext The graphics context to use.
|
||||
virtual void Draw(GraphicsContext& graphicsContext) = 0;
|
||||
|
||||
/// @brief Performs vblank processes for the view.
|
||||
virtual void VBlank() { }
|
||||
|
||||
/// @brief Moves the focus from the currentFocus view into the given direction.
|
||||
/// @param currentFocus The currently focused view.
|
||||
/// @param direction The direction to move the focus in.
|
||||
/// @param source The view that requested this view to move focus.
|
||||
/// @return The newly focused view, or null if the focus didn't change.
|
||||
virtual View* MoveFocus(View* currentFocus, FocusMoveDirection direction, View* source)
|
||||
{
|
||||
if (_parent && _parent != source)
|
||||
return _parent->MoveFocus(currentFocus, direction, this);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// @brief Handles input for the view.
|
||||
/// @param inputProvider The input provider.
|
||||
/// @param focusManager The focus manager.
|
||||
/// @return True if input was handled, or false otherwise.
|
||||
virtual bool HandleInput(const InputProvider& inputProvider, FocusManager& focusManager)
|
||||
{
|
||||
if (_parent)
|
||||
return _parent->HandleInput(inputProvider, focusManager);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @brief Gets the bounds of the view.
|
||||
/// @return The bounds of the view.
|
||||
virtual Rectangle GetBounds() const = 0;
|
||||
|
||||
/// @brief Sets the position of the view.
|
||||
/// @param position The position to set.
|
||||
void SetPosition(const Point& position) { _position = position; }
|
||||
|
||||
/// @brief Sets the position of the view.
|
||||
/// @param x The x position to set.
|
||||
/// @param y The y position to set.
|
||||
void SetPosition(int x, int y)
|
||||
{
|
||||
_position.x = x;
|
||||
_position.y = y;
|
||||
}
|
||||
|
||||
/// @brief Gets the position of the view.
|
||||
/// @return The position of the view.
|
||||
const Point& GetPosition() const { return _position; }
|
||||
|
||||
/// @brief Sets the parent of the view.
|
||||
/// @param parent The parent to set.
|
||||
void SetParent(View* parent) { _parent = parent; }
|
||||
|
||||
/// @brief Gets the parent of the view.
|
||||
/// @return The parent of the view, or null if none.
|
||||
View* GetParent() const { return _parent; }
|
||||
|
||||
/// @brief Sets whether the view is currently focused.
|
||||
/// @param focused True if the view is currently focused, or false otherwise.
|
||||
void SetFocused(bool focused) { _isFocused = focused; }
|
||||
|
||||
/// @brief Gets whether the view is currently focused.
|
||||
/// @return True if the view is currently focused, or false otherwise.
|
||||
bool IsFocused() const { return _isFocused; }
|
||||
|
||||
protected:
|
||||
Point _position;
|
||||
bool _isFocused;
|
||||
|
||||
View()
|
||||
: _position(0, 0), _isFocused(false), _parent(nullptr) { }
|
||||
|
||||
private:
|
||||
View* _parent;
|
||||
};
|
||||
60
arm9/source/gui/views/ViewContainer.h
Normal file
60
arm9/source/gui/views/ViewContainer.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#include "View.h"
|
||||
#include "core/LinkedList.h"
|
||||
|
||||
/// @brief Base class for views that contain other views.
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @brief Adds a child to the head of the list.
|
||||
/// @param view The child to add.
|
||||
void AddChildHead(View* view)
|
||||
{
|
||||
_children.InsertHead(view);
|
||||
view->SetParent(this);
|
||||
}
|
||||
|
||||
/// @brief Adds a child to the tail of the list.
|
||||
/// @param view The child to add.
|
||||
void AddChildTail(View* view)
|
||||
{
|
||||
_children.InsertTail(view);
|
||||
view->SetParent(this);
|
||||
}
|
||||
|
||||
private:
|
||||
LinkedList<View, &View::listLink> _children;
|
||||
};
|
||||
Reference in New Issue
Block a user