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:
258
arm9/source/gui/AdvancedPaletteManager.cpp
Normal file
258
arm9/source/gui/AdvancedPaletteManager.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
#include "common.h"
|
||||
#include <libtwl/gfx/gfxStatus.h>
|
||||
#include <algorithm>
|
||||
#include "AdvancedPaletteManager.h"
|
||||
|
||||
bool AdvancedPaletteManagerBase::PaletteRow::ColorsEqualTo(const PaletteRow& other) const
|
||||
{
|
||||
if (hash != other.hash)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (colors[i] != other.colors[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int AdvancedPaletteManagerBase::HwPaletteRow::FindPlace(const PaletteRow* rows, u32 yStart, u32 yEnd) const
|
||||
{
|
||||
if (count == 0)
|
||||
{
|
||||
return 0xFF;
|
||||
}
|
||||
u32 idx = first;
|
||||
u32 prevIdx = 0xFF;
|
||||
while (idx != 0xFF && rows[idx].endY < yStart)
|
||||
{
|
||||
prevIdx = idx;
|
||||
idx = rows[idx].next;
|
||||
}
|
||||
|
||||
if (idx == 0xFF || rows[idx].startY >= yEnd)
|
||||
{
|
||||
return prevIdx;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
u32 AdvancedPaletteManagerBase::TryMerge(PaletteRow* rows, const PaletteRow& newRow, int yStart, int yEnd)
|
||||
{
|
||||
for (u32 i = 0; i < _usedRows; i++)
|
||||
{
|
||||
if (rows[i].ColorsEqualTo(newRow))
|
||||
{
|
||||
if (yStart < rows[i].startY)
|
||||
{
|
||||
u32 prev = rows[i].prev;
|
||||
if (prev != 0xFF && yStart - rows[prev].endY <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (yEnd > rows[i].endY)
|
||||
{
|
||||
u32 next = rows[i].next;
|
||||
if (next != 0xFF && rows[next].startY - yEnd <= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Could merge [%d,%d) into [%d,%d)\n", yStart, yEnd, rows[i].startY, rows[i].endY);
|
||||
|
||||
if ((yEnd < rows[i].startY && rows[i].startY - yEnd > 1) ||
|
||||
(yStart > rows[i].endY && yStart - rows[i].endY > 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Merging [%d,%d) into [%d,%d)\n", yStart, yEnd, rows[i].startY, rows[i].endY);
|
||||
|
||||
// merging
|
||||
rows[i].startY = std::min((int)rows[i].startY, yStart);
|
||||
rows[i].endY = std::max((int)rows[i].endY, yEnd);
|
||||
|
||||
return rows[i].hwRow;
|
||||
}
|
||||
}
|
||||
return PALETTE_MANAGER_ROW_IDX_INVALID;
|
||||
}
|
||||
|
||||
u32 AdvancedPaletteManagerBase::AllocRowInternal(PaletteRow* rows, const IPalette& palette, int yStart, int yEnd)
|
||||
{
|
||||
if (yStart < 0)
|
||||
{
|
||||
yStart = 0;
|
||||
}
|
||||
if (yEnd > 192)
|
||||
{
|
||||
yEnd = 192;
|
||||
}
|
||||
|
||||
u32 newIdx = _usedRows;
|
||||
PaletteRow& newRow = rows[newIdx];
|
||||
newRow.hash = palette.GetHashCode();
|
||||
palette.GetColors(newRow.colors);
|
||||
|
||||
u32 rowIdx = TryMerge(rows, newRow, yStart, yEnd);
|
||||
if (rowIdx != PALETTE_MANAGER_ROW_IDX_INVALID)
|
||||
{
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
_usedRows++;
|
||||
|
||||
int bestSlack = 0x100;//-1;
|
||||
u32 bestPrevIdx = 0xFF;
|
||||
u32 bestHwRow = 0xFF;
|
||||
for (int i = 0; i < HW_PALETTE_ROWS; i++)
|
||||
{
|
||||
int result = _hwRows[i].FindPlace(rows, yStart, yEnd);
|
||||
if (result < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int slack = result >= 0xFF ? result : yStart - rows[result].endY;
|
||||
if (slack /*>*/ < bestSlack && slack > 1)
|
||||
{
|
||||
bestSlack = slack;
|
||||
bestPrevIdx = result;
|
||||
bestHwRow = i;
|
||||
}
|
||||
}
|
||||
|
||||
rowIdx = bestHwRow;
|
||||
|
||||
if (bestHwRow == 0xFF)
|
||||
{
|
||||
LOG_FATAL("Failed to allocate palette row\n");
|
||||
while (true);
|
||||
return PALETTE_MANAGER_ROW_IDX_INVALID;
|
||||
}
|
||||
|
||||
// LOG_DEBUG("Allocated hw palette row %d for y range %d - %d\n", bestHwRow, yStart, yEnd);
|
||||
|
||||
newRow.startY = yStart;
|
||||
newRow.endY = yEnd;
|
||||
|
||||
if (bestPrevIdx >= 0xFF)
|
||||
{
|
||||
newRow.next = _hwRows[bestHwRow].first;
|
||||
if (newRow.next != 0xFF)
|
||||
{
|
||||
rows[newRow.next].prev = newIdx;
|
||||
}
|
||||
newRow.prev = 0xFF;
|
||||
_hwRows[bestHwRow].first = newIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
newRow.prev = bestPrevIdx;
|
||||
newRow.next = rows[bestPrevIdx].next;
|
||||
rows[bestPrevIdx].next = newIdx;
|
||||
if (newRow.next != 0xFF)
|
||||
{
|
||||
rows[newRow.next].prev = newIdx;
|
||||
}
|
||||
}
|
||||
_hwRows[bestHwRow].count++;
|
||||
|
||||
newRow.hwRow = rowIdx;
|
||||
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
void AdvancedPaletteManagerBase::Reset()
|
||||
{
|
||||
_usedRows = 0;
|
||||
for (int i = 0; i < HW_PALETTE_ROWS; i++)
|
||||
{
|
||||
_hwRows[i].count = 0;
|
||||
_hwRows[i].first = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void AdvancedPaletteManagerBase::CreateScheme(const PaletteRow* rows, SchemeEntry* scheme)
|
||||
{
|
||||
_nextSchemeEntry = scheme;
|
||||
u32 y = 0;
|
||||
u8 curRow[HW_PALETTE_ROWS];
|
||||
for (int i = 0; i < HW_PALETTE_ROWS; i++)
|
||||
{
|
||||
curRow[i] = _hwRows[i].first;
|
||||
}
|
||||
while (y != 0xFF)
|
||||
{
|
||||
u32 minY = 0xFF;
|
||||
for (int i = 0; i < HW_PALETTE_ROWS; i++)
|
||||
{
|
||||
if (curRow[i] == 0xFF)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const PaletteRow& row = rows[curRow[i]];
|
||||
u32 uploadLine = row.prev == 0xFF ? 0 : rows[row.prev].endY;
|
||||
if (uploadLine == y)
|
||||
{
|
||||
// LOG_DEBUG("At %d upload row %d (%d-%d) to hw row %d\n", uploadLine, curRow[i], row.startY, row.endY, i);
|
||||
scheme->src = row.colors;
|
||||
scheme->dst = _targetPalette + i * 16;
|
||||
scheme++;
|
||||
curRow[i] = row.next;
|
||||
uploadLine = row.endY;
|
||||
}
|
||||
|
||||
if (curRow[i] == 0xFF)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
minY = std::min(minY, uploadLine);
|
||||
}
|
||||
if (y != minY)
|
||||
{
|
||||
scheme->endMarker = 0;
|
||||
scheme->nextVCount = minY == 255 ? 0 : minY;
|
||||
scheme++;
|
||||
}
|
||||
y = minY;
|
||||
}
|
||||
|
||||
scheme->endMarker = 0;
|
||||
scheme->nextVCount = 0;
|
||||
}
|
||||
|
||||
void AdvancedPaletteManagerBase::VCount()
|
||||
{
|
||||
while (_curSchemeEntry->endMarker != 0)
|
||||
{
|
||||
struct palette_row_t
|
||||
{
|
||||
u32 data[8];
|
||||
};
|
||||
*(palette_row_t*)_curSchemeEntry->dst = *(palette_row_t*)_curSchemeEntry->src;
|
||||
|
||||
_curSchemeEntry++;
|
||||
}
|
||||
|
||||
if (_curSchemeEntry->nextVCount == 0)
|
||||
{
|
||||
gfx_setVCountMatchIrqEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LOG_DEBUG("vcount %d\n", _curSchemeEntry->nextVCount);
|
||||
gfx_setVCountMatchLine(_curSchemeEntry->nextVCount);
|
||||
gfx_setVCountMatchIrqEnabled(true);
|
||||
_curSchemeEntry++;
|
||||
}
|
||||
}
|
||||
101
arm9/source/gui/AdvancedPaletteManager.h
Normal file
101
arm9/source/gui/AdvancedPaletteManager.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "PaletteManager.h"
|
||||
|
||||
#define HW_PALETTE_ROWS 16
|
||||
|
||||
class AdvancedPaletteManagerBase : public PaletteManager
|
||||
{
|
||||
public:
|
||||
void Reset();
|
||||
void VCount();
|
||||
|
||||
void VBlank()
|
||||
{
|
||||
_curSchemeEntry = _nextSchemeEntry;
|
||||
VCount();
|
||||
}
|
||||
|
||||
protected:
|
||||
struct PaletteRow
|
||||
{
|
||||
u16 colors[16];
|
||||
u8 startY;
|
||||
u8 endY;
|
||||
u8 hwRow;
|
||||
u8 prev;
|
||||
u32 next;
|
||||
u32 hash;
|
||||
|
||||
bool ColorsEqualTo(const PaletteRow& other) const;
|
||||
};
|
||||
|
||||
union SchemeEntry
|
||||
{
|
||||
struct
|
||||
{
|
||||
const u16* src;
|
||||
vu16* dst;
|
||||
};
|
||||
struct
|
||||
{
|
||||
u32 endMarker;
|
||||
u32 nextVCount;
|
||||
};
|
||||
};
|
||||
|
||||
explicit AdvancedPaletteManagerBase(vu16* targetPalette)
|
||||
: _targetPalette(targetPalette), _curSchemeEntry(nullptr), _nextSchemeEntry(nullptr)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
u32 AllocRowInternal(PaletteRow* rows, const IPalette& palette, int yStart, int yEnd);
|
||||
void CreateScheme(const PaletteRow* rows, SchemeEntry* scheme);
|
||||
|
||||
private:
|
||||
struct HwPaletteRow
|
||||
{
|
||||
u8 count;
|
||||
u8 first;
|
||||
|
||||
int FindPlace(const PaletteRow* rows, u32 yStart, u32 yEnd) const;
|
||||
};
|
||||
|
||||
u32 _usedRows = 0;
|
||||
HwPaletteRow _hwRows[HW_PALETTE_ROWS];
|
||||
vu16* _targetPalette;
|
||||
const SchemeEntry* _curSchemeEntry;
|
||||
const SchemeEntry* _nextSchemeEntry;
|
||||
|
||||
u32 TryMerge(PaletteRow* rows, const PaletteRow& newRow, int yStart, int yEnd);
|
||||
};
|
||||
|
||||
/// @brief Advanced palette manager that manages dynamic palette rows that are valid on a range of scanlines.
|
||||
/// By reloading the palette during display this allows to use more than 16 palette rows in total
|
||||
/// as long as only up to 16 are in use on a scanline.
|
||||
/// @tparam MaxPaletteRows The maximum number of palette rows to manage.
|
||||
template <u32 MaxPaletteRows>
|
||||
class AdvancedPaletteManager : public AdvancedPaletteManagerBase
|
||||
{
|
||||
public:
|
||||
explicit AdvancedPaletteManager(vu16* targetPalette)
|
||||
: AdvancedPaletteManagerBase(targetPalette) { }
|
||||
|
||||
u32 AllocRow(const IPalette& palette, int yStart, int yEnd) override
|
||||
{
|
||||
return AllocRowInternal(_rows[_curSet], palette, yStart, yEnd);
|
||||
}
|
||||
|
||||
void EndOfFrame()
|
||||
{
|
||||
CreateScheme(_rows[_curSet], _scheme[_curSet]);
|
||||
_curSet = 1 - _curSet;
|
||||
Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
u32 _curSet = 0;
|
||||
PaletteRow _rows[2][MaxPaletteRows];
|
||||
SchemeEntry _scheme[2][MaxPaletteRows * 2];
|
||||
};
|
||||
8
arm9/source/gui/Alignment.h
Normal file
8
arm9/source/gui/Alignment.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
enum class Alignment
|
||||
{
|
||||
Start,
|
||||
Center,
|
||||
End
|
||||
};
|
||||
22
arm9/source/gui/AscendingStackVramManager.h
Normal file
22
arm9/source/gui/AscendingStackVramManager.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "StackVramManager.h"
|
||||
|
||||
/// @brief Stack vram manager that increases the stack pointer after an allocation.
|
||||
class AscendingStackVramManager : public StackVramManager
|
||||
{
|
||||
public:
|
||||
/// @brief Creates a new AscendingStackVramManager.
|
||||
AscendingStackVramManager() { }
|
||||
|
||||
/// @brief Creates a new AscendingStackVramManager.
|
||||
/// @param vramAddress Pointer to the start of the vram block to manage.
|
||||
explicit AscendingStackVramManager(vu16* vramAddress)
|
||||
: StackVramManager(vramAddress) { }
|
||||
|
||||
u32 Alloc(u32 length) override
|
||||
{
|
||||
u32 result = _offset;
|
||||
_offset += length;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
35
arm9/source/gui/DescendingStackVramManager.h
Normal file
35
arm9/source/gui/DescendingStackVramManager.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "StackVramManager.h"
|
||||
|
||||
/// @brief Stack vram manager that decreases the stack pointer before an allocation.
|
||||
class DescendingStackVramManager : public StackVramManager
|
||||
{
|
||||
public:
|
||||
/// @brief Creates a new DescendingStackVramManager.
|
||||
/// @param vramSize The size of the vram block to manage.
|
||||
/// This will be used as the initial offset.
|
||||
explicit DescendingStackVramManager(u32 vramSize)
|
||||
: _size(vramSize)
|
||||
{
|
||||
_offset = vramSize;
|
||||
}
|
||||
|
||||
/// @brief Creates a new DescendingStackVramManager.
|
||||
/// @param vramAddress Pointer to the start of the vram block to manage.
|
||||
/// @param vramSize The size of the vram block to manage.
|
||||
/// This will be used as the initial offset.
|
||||
DescendingStackVramManager(vu16* vramAddress, u32 vramSize)
|
||||
: StackVramManager(vramAddress), _size(vramSize)
|
||||
{
|
||||
_offset = vramSize;
|
||||
}
|
||||
|
||||
u32 Alloc(u32 length) override
|
||||
{
|
||||
_offset -= length;
|
||||
return _offset;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 _size;
|
||||
};
|
||||
58
arm9/source/gui/FocusManager.cpp
Normal file
58
arm9/source/gui/FocusManager.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "common.h"
|
||||
#include "gui/views/View.h"
|
||||
#include "input/InputProvider.h"
|
||||
#include "FocusManager.h"
|
||||
|
||||
void FocusManager::Focus(View* newFocus)
|
||||
{
|
||||
if (!newFocus)
|
||||
{
|
||||
Unfocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentFocus)
|
||||
_currentFocus->SetFocused(false);
|
||||
newFocus->SetFocused(true);
|
||||
_currentFocus = newFocus;
|
||||
}
|
||||
|
||||
void FocusManager::Unfocus()
|
||||
{
|
||||
if (_currentFocus)
|
||||
_currentFocus->SetFocused(false);
|
||||
_currentFocus = nullptr;
|
||||
}
|
||||
|
||||
void FocusManager::Update(const InputProvider& inputProvider)
|
||||
{
|
||||
if (!_currentFocus || !_currentFocus->GetParent())
|
||||
return; // todo
|
||||
|
||||
View* newFocus = nullptr;
|
||||
if (inputProvider.Triggered(InputKey::DpadUp))
|
||||
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Up, _currentFocus);
|
||||
else if (inputProvider.Triggered(InputKey::DpadDown))
|
||||
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Down, _currentFocus);
|
||||
else if (inputProvider.Triggered(InputKey::DpadLeft))
|
||||
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Left, _currentFocus);
|
||||
else if (inputProvider.Triggered(InputKey::DpadRight))
|
||||
newFocus = _currentFocus->GetParent()->MoveFocus(_currentFocus, FocusMoveDirection::Right, _currentFocus);
|
||||
else
|
||||
_currentFocus->HandleInput(inputProvider, *this);
|
||||
|
||||
if (newFocus)
|
||||
Focus(newFocus);
|
||||
}
|
||||
|
||||
bool FocusManager::IsFocusInside(const View* view) const
|
||||
{
|
||||
auto focusView = _currentFocus;
|
||||
while (focusView)
|
||||
{
|
||||
if (view == focusView)
|
||||
return true;
|
||||
focusView = focusView->GetParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
32
arm9/source/gui/FocusManager.h
Normal file
32
arm9/source/gui/FocusManager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
class View;
|
||||
class InputProvider;
|
||||
|
||||
/// @brief Class for managing focus.
|
||||
class FocusManager
|
||||
{
|
||||
public:
|
||||
/// @brief Focuses the given view.
|
||||
/// @param newFocus The view to focus.
|
||||
void Focus(View* newFocus);
|
||||
|
||||
/// @brief Clears the current focus.
|
||||
void Unfocus();
|
||||
|
||||
/// @brief Updates the focus using the given input provider.
|
||||
/// @param inputProvider The input provider to use.
|
||||
void Update(const InputProvider& inputProvider);
|
||||
|
||||
/// @brief Gets the currently focused view.
|
||||
/// @return A pointer to the view that is currently focused, or null if none.
|
||||
constexpr View* GetCurrentFocus() const { return _currentFocus; }
|
||||
|
||||
/// @brief Checks whether the current focus lies inside the given view.
|
||||
/// @param view The view to check for.
|
||||
/// @return True if the current focus lies inside the given view, or false otherwise.
|
||||
bool IsFocusInside(const View* view) const;
|
||||
|
||||
private:
|
||||
View* _currentFocus = nullptr;
|
||||
};
|
||||
9
arm9/source/gui/FocusMoveDirection.h
Normal file
9
arm9/source/gui/FocusMoveDirection.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
enum class FocusMoveDirection
|
||||
{
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
};
|
||||
67
arm9/source/gui/GraphicsContext.h
Normal file
67
arm9/source/gui/GraphicsContext.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include "core/math/Point.h"
|
||||
#include "core/math/Rectangle.h"
|
||||
#include "OamManager.h"
|
||||
#include "PaletteManager.h"
|
||||
#include "Rgb6Palette.h"
|
||||
|
||||
class GraphicsContext
|
||||
{
|
||||
public:
|
||||
GraphicsContext(OamManager* oamManager, PaletteManager* paletteManager, const Rgb6Palette* rgb6Palette)
|
||||
: _priority(3), _polygonId(0), _oamManager(oamManager)
|
||||
, _paletteManager(paletteManager), _rgb6Palette(rgb6Palette)
|
||||
, _clipArea(0, 0, 256, 192) { }
|
||||
|
||||
u32 SetPriority(u32 priority)
|
||||
{
|
||||
u32 oldPriority = _priority;
|
||||
_priority = priority;
|
||||
return oldPriority;
|
||||
}
|
||||
|
||||
constexpr u32 GetPriority() const { return _priority; }
|
||||
|
||||
u32 SetPolygonId(u32 polygonId)
|
||||
{
|
||||
u32 oldPolygonId = _polygonId;
|
||||
_polygonId = polygonId;
|
||||
return oldPolygonId;
|
||||
}
|
||||
|
||||
constexpr u32 GetPolygonId() const { return _polygonId; }
|
||||
|
||||
OamManager& GetOamManager() const { return *_oamManager; }
|
||||
PaletteManager& GetPaletteManager() const { return *_paletteManager; }
|
||||
const Rgb6Palette* GetRgb6Palette() const { return _rgb6Palette; }
|
||||
|
||||
virtual PaletteManager& GetPaletteManager(int scanline) const { return *_paletteManager; }
|
||||
|
||||
void SetClipArea(const Rectangle& clipArea, bool inverse = false)
|
||||
{
|
||||
_clipArea = clipArea;
|
||||
_inverseClipArea = inverse;
|
||||
}
|
||||
|
||||
constexpr bool IsVisible(const Rectangle& bounds) const
|
||||
{
|
||||
if (!Rectangle(0, 0, 256, 192).OverlapsWith(bounds))
|
||||
return false;
|
||||
return _inverseClipArea ? !_clipArea.Contains(bounds) : _clipArea.OverlapsWith(bounds);
|
||||
}
|
||||
|
||||
void ResetClipArea()
|
||||
{
|
||||
SetClipArea(Rectangle(0, 0, 256, 192));
|
||||
}
|
||||
|
||||
private:
|
||||
u32 _priority;
|
||||
u8 _polygonId;
|
||||
|
||||
OamManager* _oamManager;
|
||||
PaletteManager* _paletteManager;
|
||||
const Rgb6Palette* _rgb6Palette;
|
||||
Rectangle _clipArea;
|
||||
bool _inverseClipArea = false;
|
||||
};
|
||||
198
arm9/source/gui/Gx.h
Normal file
198
arm9/source/gui/Gx.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#pragma once
|
||||
#include <libtwl/gfx/gfx3dCmd.h>
|
||||
#include "core/math/ColorConverter.h"
|
||||
#include "core/math/fixed.h"
|
||||
|
||||
namespace Gx
|
||||
{
|
||||
static inline void MtxMode(GxMtxMode mode)
|
||||
{
|
||||
gx_mtxMode(mode);
|
||||
}
|
||||
|
||||
static inline void MtxIdentity()
|
||||
{
|
||||
gx_mtxIdentity();
|
||||
}
|
||||
|
||||
static inline void MtxLoad44(const mtx44_t* matrix)
|
||||
{
|
||||
gx_mtxLoad44(matrix);
|
||||
}
|
||||
|
||||
static inline void MtxLoad43(const mtx43_t* matrix)
|
||||
{
|
||||
gx_mtxLoad43(matrix);
|
||||
}
|
||||
|
||||
static inline void MtxMult44(const mtx44_t* matrix)
|
||||
{
|
||||
gx_mtxMult44(matrix);
|
||||
}
|
||||
|
||||
static inline void MtxMult43(const mtx43_t* matrix)
|
||||
{
|
||||
gx_mtxMult43(matrix);
|
||||
}
|
||||
|
||||
static inline void MtxMult33(const mtx33_t* matrix)
|
||||
{
|
||||
gx_mtxMult33(matrix);
|
||||
}
|
||||
|
||||
static inline void MtxTranslate(fix32<12> x, fix32<12> y, fix32<12> z)
|
||||
{
|
||||
gx_mtxTranslate(x.GetRawValue(), y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void MtxScale(fix32<12> x, fix32<12> y, fix32<12> z)
|
||||
{
|
||||
gx_mtxScale(x.GetRawValue(), y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void MtxPush()
|
||||
{
|
||||
gx_mtxPush();
|
||||
}
|
||||
|
||||
static inline void MtxPop(int count)
|
||||
{
|
||||
gx_mtxPop(count);
|
||||
}
|
||||
|
||||
static inline void PolygonAttr(GxLightMask lightMask, GxPolygonMode polygonMode,
|
||||
GxDisplayMode displayMode, bool xluDepthUpdate, bool clipFarPlane, bool render1Dot,
|
||||
GxDepthFunc depthFunc, bool fogEnable, u32 alpha, u32 polygonId)
|
||||
{
|
||||
gx_polygonAttr(lightMask, polygonMode, displayMode, xluDepthUpdate, clipFarPlane,
|
||||
render1Dot, depthFunc, fogEnable, alpha, polygonId);
|
||||
}
|
||||
|
||||
static inline void PolygonAttr(u32 polygonAttr)
|
||||
{
|
||||
REG_GX_POLYGON_ATTR = polygonAttr;
|
||||
}
|
||||
|
||||
static inline void TexImageParam(u16 address, bool repeatS, bool repeatT,
|
||||
bool flipS, bool flipT, GxTexSize sizeS, GxTexSize sizeT, GxTexFormat format,
|
||||
bool color0Transparent, GxTexGen texGen)
|
||||
{
|
||||
gx_texImageParam(address, repeatS, repeatT, flipS, flipT,
|
||||
sizeS, sizeT, format, color0Transparent, texGen);
|
||||
}
|
||||
|
||||
static inline void TexPlttBase(u32 address)
|
||||
{
|
||||
gx_texPlttBase(address);
|
||||
}
|
||||
|
||||
static inline void TexImageParam(u32 texImageParam)
|
||||
{
|
||||
REG_GX_TEXIMAGE_PARAM = texImageParam;
|
||||
}
|
||||
|
||||
static inline void TexCoord(fix16<4> s, fix16<4> t)
|
||||
{
|
||||
gx_texCoord(s.GetRawValue(), t.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void Normal(int x, int y, int z)
|
||||
{
|
||||
gx_normal(x, y, z);
|
||||
}
|
||||
|
||||
static inline void Color(const Rgb<5, 5, 5>& color)
|
||||
{
|
||||
gx_color(ColorConverter::ToXBGR555(color));
|
||||
}
|
||||
|
||||
static inline void Color(u32 color)
|
||||
{
|
||||
gx_color(color);
|
||||
}
|
||||
|
||||
static inline void Color(u32 r, u32 g, u32 b)
|
||||
{
|
||||
Color(Rgb<5, 5, 5>(r, g, b));
|
||||
}
|
||||
|
||||
static inline void Vtx16(fix16<12> x, fix16<12> y, fix16<12> z)
|
||||
{
|
||||
gx_vtx16(x.GetRawValue(), y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void Vtx10(fix16<6> x, fix16<6> y, fix16<6> z)
|
||||
{
|
||||
gx_vtx10(x.GetRawValue(), y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void VtxXY(fix16<12> x, fix16<12> y)
|
||||
{
|
||||
gx_vtxXY(x.GetRawValue(), y.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void VtxXZ(fix16<12> x, fix16<12> z)
|
||||
{
|
||||
gx_vtxXZ(x.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void VtxYZ(fix16<12> y, fix16<12> z)
|
||||
{
|
||||
gx_vtxYZ(y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void VtxDiff(fix16<12> x, fix16<12> y, fix16<12> z)
|
||||
{
|
||||
gx_vtxDiff(x.GetRawValue(), y.GetRawValue(), z.GetRawValue());
|
||||
}
|
||||
|
||||
static inline void Begin(GxPrimitiveType type)
|
||||
{
|
||||
gx_beginVtxs(type);
|
||||
}
|
||||
|
||||
static inline void End()
|
||||
{
|
||||
gx_endVtxs();
|
||||
}
|
||||
|
||||
static inline void LightVector(int light, int x, int y, int z)
|
||||
{
|
||||
gx_lightVector(light, x, y, z);
|
||||
}
|
||||
|
||||
static inline void LightColor(int light, u32 color)
|
||||
{
|
||||
gx_lightColor(light, color);
|
||||
}
|
||||
|
||||
static inline void DiffuseAmbient(u32 diffuse, bool setDiffuseVtxColor, u32 ambient)
|
||||
{
|
||||
gx_diffuseAmbient(diffuse, setDiffuseVtxColor, ambient);
|
||||
}
|
||||
|
||||
static inline void DiffuseAmbient(const Rgb<5, 5, 5>& diffuse, bool setDiffuseVtxColor, const Rgb<5, 5, 5>& ambient)
|
||||
{
|
||||
gx_diffuseAmbient(ColorConverter::ToXBGR555(diffuse), setDiffuseVtxColor, ColorConverter::ToXBGR555(ambient));
|
||||
}
|
||||
|
||||
static inline void SpecularEmission(u32 specular, bool useShininessTable, u32 emission)
|
||||
{
|
||||
gx_specularEmission(specular, useShininessTable, emission);
|
||||
}
|
||||
|
||||
static inline void SpecularEmission(const Rgb<5, 5, 5>& specular, bool useShininessTable, const Rgb<5, 5, 5>& emission)
|
||||
{
|
||||
gx_specularEmission(ColorConverter::ToXBGR555(specular), useShininessTable, ColorConverter::ToXBGR555(emission));
|
||||
}
|
||||
|
||||
static inline void SwapBuffers(GxXluSortMode xluSortMode, GxDepthMode depthMode)
|
||||
{
|
||||
gx_swapBuffers(xluSortMode, depthMode);
|
||||
}
|
||||
|
||||
static inline void Viewport(int x1, int y1, int x2, int y2)
|
||||
{
|
||||
gx_viewport(x1, y1, x2, y2);
|
||||
}
|
||||
};
|
||||
20
arm9/source/gui/IVramManager.h
Normal file
20
arm9/source/gui/IVramManager.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Interface for managing and allocating vram.
|
||||
class IVramManager
|
||||
{
|
||||
public:
|
||||
virtual ~IVramManager() = 0;
|
||||
|
||||
/// @brief Allocates a block of vram of the given length.
|
||||
/// @param length The size to allocate.
|
||||
/// @return The vram offset of the allocated block.
|
||||
virtual u32 Alloc(u32 length) = 0;
|
||||
|
||||
/// @brief Converts the vram offset to a vram pointer.
|
||||
/// @param offset The offset to convert.
|
||||
/// @return The pointer corresponding to the vram offset.
|
||||
virtual vu16* GetVramAddress(u32 offset) const = 0;
|
||||
};
|
||||
|
||||
inline IVramManager::~IVramManager() { }
|
||||
220
arm9/source/gui/OamBuilder.h
Normal file
220
arm9/source/gui/OamBuilder.h
Normal file
@@ -0,0 +1,220 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "core/math/Point.h"
|
||||
#include <libtwl/gfx/gfxOam.h>
|
||||
|
||||
/// @brief Helper class for building oams.
|
||||
class OamBuilder
|
||||
{
|
||||
u16 _attr0;
|
||||
u16 _attr1;
|
||||
u16 _attr2;
|
||||
|
||||
OamBuilder()
|
||||
: _attr0(0), _attr1(0), _attr2(0) { }
|
||||
|
||||
public:
|
||||
template <int width, int height>
|
||||
static constexpr OamBuilder OamWithSize(const Point& position, u32 vramOffset)
|
||||
{
|
||||
return OamWithSize<width, height>(position.x, position.y, vramOffset);
|
||||
}
|
||||
|
||||
template <int width, int height>
|
||||
static constexpr OamBuilder OamWithSize(int x, int y, u32 vramOffset)
|
||||
{
|
||||
OamBuilder builder;
|
||||
switch (width)
|
||||
{
|
||||
case 8:
|
||||
{
|
||||
switch (height)
|
||||
{
|
||||
case 8:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_8_8;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_8_8;
|
||||
break;
|
||||
case 16:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_8_16;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_8_16;
|
||||
break;
|
||||
case 32:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_8_32;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_8_32;
|
||||
break;
|
||||
default:
|
||||
static_assert(width != 8 || (height == 8 || height == 16 || height == 32), "Invalid oam height for width 8");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
switch (height)
|
||||
{
|
||||
case 8:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_16_8;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_16_8;
|
||||
break;
|
||||
case 16:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_16_16;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_16_16;
|
||||
break;
|
||||
case 32:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_16_32;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_16_32;
|
||||
break;
|
||||
default:
|
||||
static_assert(width != 16 || (height == 8 || height == 16 || height == 32), "Invalid oam height for width 16");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
switch (height)
|
||||
{
|
||||
case 8:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_32_8;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_32_8;
|
||||
break;
|
||||
case 16:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_32_16;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_32_16;
|
||||
break;
|
||||
case 32:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_32_32;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_32_32;
|
||||
break;
|
||||
case 64:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_32_64;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_32_64;
|
||||
break;
|
||||
default:
|
||||
static_assert(width != 32 || (height == 8 || height == 16 || height == 32 || height == 64), "Invalid oam height for width 32");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 64:
|
||||
{
|
||||
switch (height)
|
||||
{
|
||||
case 32:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_64_32;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_64_32;
|
||||
break;
|
||||
case 64:
|
||||
builder._attr0 = GFX_OAM_ATTR0_SHAPE_64_64;
|
||||
builder._attr1 = GFX_OAM_ATTR1_SIZE_64_64;
|
||||
break;
|
||||
default:
|
||||
static_assert(width != 64 || (height == 32 || height == 64), "Invalid oam height for width 64");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
static_assert(width == 8 || width == 16 || width == 32 || width == 64, "Invalid oam width");
|
||||
break;
|
||||
}
|
||||
builder._attr0 |= GFX_OAM_ATTR0_Y(y);
|
||||
builder._attr1 |= GFX_OAM_ATTR1_X(x);
|
||||
builder._attr2 = GFX_OAM_ATTR2_VRAM_OFFS(vramOffset);
|
||||
return builder;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& Hidden()
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_DOUBLE_AFFINE;
|
||||
_attr0 |= GFX_OAM_ATTR0_HIDDEN;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& AsWindow()
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_MODE_BITMAP;
|
||||
_attr0 |= GFX_OAM_ATTR0_MODE_WINDOW;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& AsTranslucent()
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_MODE_BITMAP;
|
||||
_attr0 |= GFX_OAM_ATTR0_MODE_TRANSLUCENT;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& AsBitmap(int alpha = 16)
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_PLTT256;
|
||||
_attr0 |= GFX_OAM_ATTR0_MODE_BITMAP;
|
||||
|
||||
if (alpha == 0)
|
||||
return Hidden();
|
||||
|
||||
_attr2 &= ~GFX_OAM_ATTR2_BMP_ALPHA(0xF);
|
||||
_attr2 |= GFX_OAM_ATTR2_BMP_ALPHA(alpha - 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithAffineMtx(int mtx, bool doubleSize = false)
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_DOUBLE_AFFINE;
|
||||
if (doubleSize)
|
||||
_attr0 |= GFX_OAM_ATTR0_DOUBLE_AFFINE;
|
||||
else
|
||||
_attr0 |= GFX_OAM_ATTR0_AFFINE;
|
||||
_attr1 &= ~GFX_OAM_ATTR1_MTX(0x1F);
|
||||
_attr1 |= GFX_OAM_ATTR1_MTX(mtx);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithHFlip(bool hFlip = true)
|
||||
{
|
||||
if (hFlip)
|
||||
_attr1 |= GFX_OAM_ATTR1_HFLIP;
|
||||
else
|
||||
_attr1 &= ~GFX_OAM_ATTR1_HFLIP;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithVFlip(bool vFlip = true)
|
||||
{
|
||||
if (vFlip)
|
||||
_attr1 |= GFX_OAM_ATTR1_VFLIP;
|
||||
else
|
||||
_attr1 &= ~GFX_OAM_ATTR1_VFLIP;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithPriority(int priority)
|
||||
{
|
||||
_attr2 &= ~GFX_OAM_ATTR2_PRIORITY(3);
|
||||
_attr2 |= GFX_OAM_ATTR2_PRIORITY(priority);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithPalette16(int palette)
|
||||
{
|
||||
_attr0 &= ~GFX_OAM_ATTR0_PLTT256;
|
||||
_attr2 &= ~GFX_OAM_ATTR2_PLTT(0xF);
|
||||
_attr2 |= GFX_OAM_ATTR2_PLTT(palette);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr OamBuilder& WithPalette256(int palette = 0)
|
||||
{
|
||||
_attr0 |= GFX_OAM_ATTR0_PLTT256;
|
||||
_attr2 &= ~GFX_OAM_ATTR2_PLTT(0xF);
|
||||
_attr2 |= GFX_OAM_ATTR2_PLTT(palette);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr void Build(gfx_oam_entry_t& oamEntry) const
|
||||
{
|
||||
oamEntry.attr0 = _attr0;
|
||||
oamEntry.attr1 = _attr1;
|
||||
oamEntry.attr2 = _attr2;
|
||||
}
|
||||
};
|
||||
16
arm9/source/gui/OamManager.cpp
Normal file
16
arm9/source/gui/OamManager.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "common.h"
|
||||
#include <nds/arm9/cache.h>
|
||||
#include "OamManager.h"
|
||||
|
||||
void OamManager::Apply(vu16* dst)
|
||||
{
|
||||
DC_FlushRange(&_oamTable, sizeof(gfx_oam_table_t));
|
||||
dma_ntrCopy32(3, &_oamTable, dst, sizeof(gfx_oam_table_t));
|
||||
}
|
||||
|
||||
void OamManager::Clear()
|
||||
{
|
||||
memset(&_oamTable, 0x2, sizeof(gfx_oam_table_t));
|
||||
_oamPtr = &_oamTable.objs[128];
|
||||
_mtxIdx = 0;
|
||||
}
|
||||
37
arm9/source/gui/OamManager.h
Normal file
37
arm9/source/gui/OamManager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <libtwl/gfx/gfxOam.h>
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include <string.h>
|
||||
|
||||
/// @brief Class for managing oams and affine matrices.
|
||||
class alignas(32) OamManager
|
||||
{
|
||||
public:
|
||||
OamManager()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
gfx_oam_entry_t* AllocOams(u32 count)
|
||||
{
|
||||
_oamPtr -= count;
|
||||
return _oamPtr;
|
||||
}
|
||||
|
||||
gfx_oam_mtx_t* AllocMatrices(u32 count, u32& mtxId)
|
||||
{
|
||||
mtxId = _mtxIdx;
|
||||
gfx_oam_mtx_t* result = &_oamTable.mtxs[_mtxIdx];
|
||||
_mtxIdx += count;
|
||||
return result;
|
||||
}
|
||||
|
||||
void Apply(vu16* dst);
|
||||
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
gfx_oam_table_t _oamTable alignas(32);
|
||||
gfx_oam_entry_t* _oamPtr;
|
||||
u32 _mtxIdx;
|
||||
};
|
||||
30
arm9/source/gui/PaletteManager.h
Normal file
30
arm9/source/gui/PaletteManager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "palette/IPalette.h"
|
||||
|
||||
#define PALETTE_MANAGER_ROW_IDX_INVALID 0xFF
|
||||
|
||||
/// @brief Base class for palette management.
|
||||
class PaletteManager
|
||||
{
|
||||
public:
|
||||
virtual ~PaletteManager() { }
|
||||
|
||||
/// @brief Allocates a palette row for the given palette, which is used
|
||||
/// in the display line range from yStart to yEnd (exclusive).
|
||||
/// @param palette The palette to allocate a row for.
|
||||
/// @param yStart The first display line the palette will be used on.
|
||||
/// @param yEnd The display line from which the palette will not be used anymore.
|
||||
/// @return The allocated palette row number.
|
||||
virtual u32 AllocRow(const IPalette& palette, int yStart, int yEnd) = 0;
|
||||
|
||||
/// @brief Allocates a palette row for the given palette, assuming it
|
||||
/// will be used on all display lines.
|
||||
/// @param palette The palette to allocate a row for.
|
||||
/// @return The allocated palette row number.
|
||||
/// @note It is recommended to specify the start and end display line
|
||||
/// for more efficient palette management.
|
||||
u32 AllocRow(const IPalette& palette)
|
||||
{
|
||||
return AllocRow(palette, 0, 192);
|
||||
}
|
||||
};
|
||||
118
arm9/source/gui/Rgb6Palette.cpp
Normal file
118
arm9/source/gui/Rgb6Palette.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "common.h"
|
||||
#include "gui/VramContext.h"
|
||||
#include "gui/Gx.h"
|
||||
#include "core/math/ColorConverter.h"
|
||||
#include "Rgb6Palette.h"
|
||||
|
||||
#define PLTT_IDX_15 0
|
||||
#define PLTT_IDX_30 1
|
||||
#define PLTT_IDX_31 2
|
||||
|
||||
struct color_mapping_t
|
||||
{
|
||||
u8 vtxColorComponent;
|
||||
u8 plttIdx;
|
||||
};
|
||||
|
||||
static const color_mapping_t sMappingTable[64] =
|
||||
{
|
||||
{ 0, PLTT_IDX_31 }, // 0
|
||||
{ 1, PLTT_IDX_15 }, // 1
|
||||
{ 2, PLTT_IDX_15 }, // 2
|
||||
{ 1, PLTT_IDX_31 }, // 3
|
||||
{ 4, PLTT_IDX_15 }, // 4
|
||||
{ 2, PLTT_IDX_31 }, // 5
|
||||
{ 6, PLTT_IDX_15 }, // 6
|
||||
{ 3, PLTT_IDX_31 }, // 7
|
||||
{ 8, PLTT_IDX_15 }, // 8
|
||||
{ 4, PLTT_IDX_31 }, // 9
|
||||
{ 10, PLTT_IDX_15 }, // 10
|
||||
{ 5, PLTT_IDX_31 }, // 11
|
||||
{ 12, PLTT_IDX_15 }, // 12
|
||||
{ 6, PLTT_IDX_31 }, // 13
|
||||
{ 14, PLTT_IDX_15 }, // 14
|
||||
{ 7, PLTT_IDX_31 }, // 15
|
||||
{ 16, PLTT_IDX_15 }, // 16
|
||||
{ 8, PLTT_IDX_31 }, // 17
|
||||
{ 18, PLTT_IDX_15 }, // 18
|
||||
{ 9, PLTT_IDX_31 }, // 19
|
||||
{ 20, PLTT_IDX_15 }, // 20
|
||||
{ 10, PLTT_IDX_31 }, // 21
|
||||
{ 22, PLTT_IDX_15 }, // 22
|
||||
{ 11, PLTT_IDX_31 }, // 23
|
||||
{ 24, PLTT_IDX_15 }, // 24
|
||||
{ 12, PLTT_IDX_31 }, // 25
|
||||
{ 26, PLTT_IDX_15 }, // 26
|
||||
{ 13, PLTT_IDX_31 }, // 27
|
||||
{ 28, PLTT_IDX_15 }, // 28
|
||||
{ 14, PLTT_IDX_31 }, // 29
|
||||
{ 30, PLTT_IDX_15 }, // 30
|
||||
{ 15, PLTT_IDX_31 }, // 31
|
||||
{ 16, PLTT_IDX_30 }, // 32
|
||||
{ 16, PLTT_IDX_31 }, // 33
|
||||
{ 17, PLTT_IDX_30 }, // 34
|
||||
{ 17, PLTT_IDX_31 }, // 35
|
||||
{ 18, PLTT_IDX_30 }, // 36
|
||||
{ 18, PLTT_IDX_31 }, // 37
|
||||
{ 19, PLTT_IDX_30 }, // 38
|
||||
{ 19, PLTT_IDX_31 }, // 39
|
||||
{ 20, PLTT_IDX_30 }, // 40
|
||||
{ 20, PLTT_IDX_31 }, // 41
|
||||
{ 21, PLTT_IDX_30 }, // 42
|
||||
{ 21, PLTT_IDX_31 }, // 43
|
||||
{ 22, PLTT_IDX_30 }, // 44
|
||||
{ 22, PLTT_IDX_31 }, // 45
|
||||
{ 23, PLTT_IDX_30 }, // 46
|
||||
{ 23, PLTT_IDX_31 }, // 47
|
||||
{ 24, PLTT_IDX_30 }, // 48
|
||||
{ 24, PLTT_IDX_31 }, // 49
|
||||
{ 25, PLTT_IDX_30 }, // 50
|
||||
{ 25, PLTT_IDX_31 }, // 51
|
||||
{ 26, PLTT_IDX_30 }, // 52
|
||||
{ 26, PLTT_IDX_31 }, // 53
|
||||
{ 27, PLTT_IDX_30 }, // 54
|
||||
{ 27, PLTT_IDX_31 }, // 55
|
||||
{ 28, PLTT_IDX_30 }, // 56
|
||||
{ 28, PLTT_IDX_31 }, // 57
|
||||
{ 29, PLTT_IDX_30 }, // 58
|
||||
{ 29, PLTT_IDX_31 }, // 59
|
||||
{ 30, PLTT_IDX_30 }, // 60
|
||||
{ 31, PLTT_IDX_30 }, // 61
|
||||
{ 31, PLTT_IDX_30 }, // 62 -> 61
|
||||
{ 31, PLTT_IDX_31 } // 63
|
||||
};
|
||||
|
||||
void Rgb6Palette::UploadGraphics(const VramContext& vramContext)
|
||||
{
|
||||
const auto paletteVramManager = vramContext.GetTexPlttVramManager();
|
||||
|
||||
if (paletteVramManager)
|
||||
{
|
||||
_vramOffset = paletteVramManager->Alloc(432);
|
||||
static const u8 colorValues[3] = { 15, 30, 31 };
|
||||
for (int r = 0; r < 3; r++)
|
||||
{
|
||||
for (int g = 0; g < 3; g++)
|
||||
{
|
||||
for (int b = 0; b < 3; b++)
|
||||
{
|
||||
int index = b * 3 * 3 + g * 3 + r;
|
||||
paletteVramManager->GetVramAddress(_vramOffset)[index * 8]
|
||||
= ColorConverter::ToXBGR555(Rgb<5, 5, 5>(colorValues[r], colorValues[g], colorValues[b]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rgb6Palette::ApplyColor(const Rgb<6,6,6>& color) const
|
||||
{
|
||||
const auto& rConversion = sMappingTable[color.r];
|
||||
const auto& gConversion = sMappingTable[color.g];
|
||||
const auto& bConversion = sMappingTable[color.b];
|
||||
|
||||
Gx::Color(ColorConverter::ToXBGR555(Rgb<5, 5, 5>(
|
||||
rConversion.vtxColorComponent, gConversion.vtxColorComponent, bConversion.vtxColorComponent)));
|
||||
u32 colorOffset = bConversion.plttIdx * 3 * 3 + gConversion.plttIdx * 3 + rConversion.plttIdx;
|
||||
Gx::TexPlttBase((_vramOffset >> 4) + colorOffset);
|
||||
}
|
||||
14
arm9/source/gui/Rgb6Palette.h
Normal file
14
arm9/source/gui/Rgb6Palette.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "core/math/Rgb.h"
|
||||
|
||||
class VramContext;
|
||||
|
||||
class Rgb6Palette
|
||||
{
|
||||
public:
|
||||
void UploadGraphics(const VramContext& vramContext);
|
||||
void ApplyColor(const Rgb<6, 6, 6>& color) const;
|
||||
|
||||
private:
|
||||
u32 _vramOffset = 0;
|
||||
};
|
||||
15
arm9/source/gui/SimplePaletteManager.cpp
Normal file
15
arm9/source/gui/SimplePaletteManager.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "common.h"
|
||||
#include <nds/arm9/cache.h>
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include "SimplePaletteManager.h"
|
||||
|
||||
void SimplePaletteManager::Apply(vu16* dst)
|
||||
{
|
||||
if (_idxOffset > 0 && _curRow == _idxOffset)
|
||||
return;
|
||||
|
||||
u32 rows = _idxOffset == 0 ? 16 : _curRow - _idxOffset;
|
||||
dst += _idxOffset * 16;
|
||||
DC_FlushRange(_palette[_idxOffset], rows * 16 * 2);
|
||||
dma_ntrCopy32(3, _palette[_idxOffset], dst, rows * 16 * 2);
|
||||
}
|
||||
42
arm9/source/gui/SimplePaletteManager.h
Normal file
42
arm9/source/gui/SimplePaletteManager.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
#include "PaletteManager.h"
|
||||
|
||||
/// @brief Simple palette manager that manages 16 static palette rows.
|
||||
class alignas(32) SimplePaletteManager : public PaletteManager
|
||||
{
|
||||
public:
|
||||
using PaletteManager::AllocRow;
|
||||
|
||||
u32 GetState() const { return _curRow; }
|
||||
void SetState(u32 state) { _curRow = state; }
|
||||
|
||||
void Reset(u32 idxOffset = 0)
|
||||
{
|
||||
_curRow = idxOffset;
|
||||
_idxOffset = idxOffset;
|
||||
}
|
||||
|
||||
u32 AllocRow(const IPalette& palette, int yStart, int yEnd) override
|
||||
{
|
||||
u32 rowIdx = _curRow++;
|
||||
palette.GetColors(_palette[rowIdx]);
|
||||
return rowIdx;
|
||||
}
|
||||
|
||||
u16* GetRow(u32 rowIdx)
|
||||
{
|
||||
return _palette[rowIdx];
|
||||
}
|
||||
|
||||
void SetColor0(u16 color)
|
||||
{
|
||||
_palette[0][0] = color;
|
||||
}
|
||||
|
||||
void Apply(vu16* dst);
|
||||
|
||||
private:
|
||||
u16 _palette[16][16] alignas(32);
|
||||
u8 _curRow = 0;
|
||||
u8 _idxOffset = 0;
|
||||
};
|
||||
30
arm9/source/gui/StackVramManager.h
Normal file
30
arm9/source/gui/StackVramManager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "IVramManager.h"
|
||||
|
||||
/// @brief Base class for vram managers that work like a stack.
|
||||
class StackVramManager : public IVramManager
|
||||
{
|
||||
public:
|
||||
/// @brief Gets the current state of the stack.
|
||||
/// @return The current state of the stack.
|
||||
u32 GetState() const { return _offset; }
|
||||
|
||||
/// @brief Restores the stack to a previously stored state.
|
||||
/// @param state The state to restore.
|
||||
void SetState(u32 state) { _offset = state; }
|
||||
|
||||
vu16* GetVramAddress(u32 offset) const override
|
||||
{
|
||||
return (vu16*)((vu8*)_vramAddress + offset);
|
||||
}
|
||||
|
||||
protected:
|
||||
u32 _offset;
|
||||
vu16* _vramAddress;
|
||||
|
||||
StackVramManager()
|
||||
: _offset(0), _vramAddress(nullptr) { }
|
||||
|
||||
explicit StackVramManager(vu16* vramAddress)
|
||||
: _offset(0), _vramAddress(vramAddress) { }
|
||||
};
|
||||
37
arm9/source/gui/VBlankTextureLoadRequest.h
Normal file
37
arm9/source/gui/VBlankTextureLoadRequest.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "core/LinkedListLink.h"
|
||||
#include "VBlankTextureLoadRequestState.h"
|
||||
|
||||
class VBlankTextureLoadRequest
|
||||
{
|
||||
friend class VBlankTextureLoader;
|
||||
|
||||
public:
|
||||
typedef void (*CallbackFunc)(const VBlankTextureLoadRequest* request, void* arg);
|
||||
|
||||
VBlankTextureLoadRequest() { }
|
||||
|
||||
VBlankTextureLoadRequest(const void* textureData, u32 textureDataLength, u32 textureVramOffset,
|
||||
const void* paletteData, u32 paletteDataLength, u32 paletteVramOffset,
|
||||
CallbackFunc callbackFunc, void* callbackArg)
|
||||
: _textureData(textureData), _textureDataLength(textureDataLength), _textureVramOffset(textureVramOffset)
|
||||
, _paletteData(paletteData), _paletteDataLength(paletteDataLength), _paletteVramOffset(paletteVramOffset)
|
||||
, _callbackFunc(callbackFunc), _callbackArg(callbackArg) { }
|
||||
|
||||
VBlankTextureLoadRequestState GetState() const
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
private:
|
||||
VBlankTextureLoadRequestState _state = VBlankTextureLoadRequestState::NotLoaded;
|
||||
LinkedListLink _listLink;
|
||||
const void* _textureData = nullptr;
|
||||
u32 _textureDataLength = 0;
|
||||
u32 _textureVramOffset = 0;
|
||||
const void* _paletteData = nullptr;
|
||||
u32 _paletteDataLength = 0;
|
||||
u32 _paletteVramOffset = 0;
|
||||
CallbackFunc _callbackFunc = nullptr;
|
||||
void* _callbackArg = nullptr;
|
||||
};
|
||||
10
arm9/source/gui/VBlankTextureLoadRequestState.h
Normal file
10
arm9/source/gui/VBlankTextureLoadRequestState.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
enum class VBlankTextureLoadRequestState
|
||||
{
|
||||
NotLoaded,
|
||||
LoadRequested,
|
||||
Loading,
|
||||
LoadComplete,
|
||||
Canceled
|
||||
};
|
||||
177
arm9/source/gui/VBlankTextureLoader.cpp
Normal file
177
arm9/source/gui/VBlankTextureLoader.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "common.h"
|
||||
#include <string.h>
|
||||
#include <libtwl/mem/memVram.h>
|
||||
#include <libtwl/gfx/gfxStatus.h>
|
||||
#include <libtwl/dma/dmaNitro.h>
|
||||
#include "VBlankTextureLoader.h"
|
||||
|
||||
#define TEXLOADER_BYTES_PER_LINE 2048
|
||||
#define TEXLOADER_G3_VBLANK_START_LINE 191
|
||||
#define TEXLOADER_G3_VBLANK_END_LINE 213
|
||||
|
||||
void VBlankTextureLoader::VBlank()
|
||||
{
|
||||
rtos_lockMutex(&_mutex);
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
auto request = _requests.GetHead();
|
||||
if (!request)
|
||||
{
|
||||
_currentRequest = nullptr;
|
||||
break;
|
||||
}
|
||||
if (request->GetState() == VBlankTextureLoadRequestState::Canceled)
|
||||
{
|
||||
_currentRequest = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_currentRequest != request)
|
||||
{
|
||||
_currentRequest = request;
|
||||
_currentLoadingOffset = 0;
|
||||
_currentLoadingStage = LoadingStage::Texture;
|
||||
}
|
||||
|
||||
if (_currentLoadingStage == LoadingStage::Texture)
|
||||
{
|
||||
if (!LoadTexture())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentRequest->GetState() == VBlankTextureLoadRequestState::Canceled)
|
||||
{
|
||||
_currentRequest = nullptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_currentLoadingStage == LoadingStage::Palette)
|
||||
{
|
||||
if (!LoadPalette())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_currentRequest->_state = VBlankTextureLoadRequestState::LoadComplete;
|
||||
if (_currentRequest->_callbackFunc)
|
||||
{
|
||||
_currentRequest->_callbackFunc(_currentRequest, _currentRequest->_callbackArg);
|
||||
}
|
||||
_requests.Remove(_currentRequest);
|
||||
_currentRequest = nullptr;
|
||||
}
|
||||
}
|
||||
rtos_unlockMutex(&_mutex);
|
||||
}
|
||||
|
||||
bool VBlankTextureLoader::LoadTexture()
|
||||
{
|
||||
int size = _currentRequest->_textureDataLength - _currentLoadingOffset;
|
||||
if (size > 0)
|
||||
{
|
||||
int line = gfx_getVCount();
|
||||
if (line >= TEXLOADER_G3_VBLANK_END_LINE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxLength = (TEXLOADER_G3_VBLANK_END_LINE - line) * TEXLOADER_BYTES_PER_LINE;
|
||||
auto vramDMapping = mem_getVramDMapping();
|
||||
mem_setVramDMapping(MEM_VRAM_D_LCDC);
|
||||
if (size <= maxLength)
|
||||
{
|
||||
dma_ntrCopy32(3,
|
||||
(u8*)_currentRequest->_textureData + _currentLoadingOffset,
|
||||
(u8*)0x6860000 + _currentRequest->_textureVramOffset + _currentLoadingOffset,
|
||||
size);
|
||||
mem_setVramDMapping(vramDMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
dma_ntrCopy32(3,
|
||||
(u8*)_currentRequest->_textureData + _currentLoadingOffset,
|
||||
(u8*)0x6860000 + _currentRequest->_textureVramOffset + _currentLoadingOffset,
|
||||
maxLength);
|
||||
mem_setVramDMapping(vramDMapping);
|
||||
_currentLoadingOffset += maxLength;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_currentLoadingOffset = 0;
|
||||
_currentLoadingStage = LoadingStage::Palette;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VBlankTextureLoader::LoadPalette()
|
||||
{
|
||||
int size = _currentRequest->_paletteDataLength - _currentLoadingOffset;
|
||||
if (size > 0)
|
||||
{
|
||||
int line = gfx_getVCount();
|
||||
if (line >= TEXLOADER_G3_VBLANK_END_LINE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxLength = (TEXLOADER_G3_VBLANK_END_LINE - line) * TEXLOADER_BYTES_PER_LINE;
|
||||
auto vramEMapping = mem_getVramEMapping();
|
||||
mem_setVramEMapping(MEM_VRAM_E_LCDC);
|
||||
if (size <= maxLength)
|
||||
{
|
||||
dma_ntrCopy32(3,
|
||||
(u8*)_currentRequest->_paletteData + _currentLoadingOffset,
|
||||
(u8*)0x6880000 + _currentRequest->_paletteVramOffset + _currentLoadingOffset,
|
||||
size);
|
||||
mem_setVramEMapping(vramEMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
dma_ntrCopy32(3,
|
||||
(u8*)_currentRequest->_paletteData + _currentLoadingOffset,
|
||||
(u8*)0x6880000 + _currentRequest->_paletteVramOffset + _currentLoadingOffset,
|
||||
maxLength);
|
||||
mem_setVramEMapping(vramEMapping);
|
||||
_currentLoadingOffset += maxLength;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VBlankTextureLoader::RequestLoad(VBlankTextureLoadRequest& request)
|
||||
{
|
||||
rtos_lockMutex(&_mutex);
|
||||
{
|
||||
if (request._state == VBlankTextureLoadRequestState::NotLoaded)
|
||||
{
|
||||
_requests.InsertTail(&request);
|
||||
request._state = VBlankTextureLoadRequestState::LoadRequested;
|
||||
}
|
||||
}
|
||||
rtos_unlockMutex(&_mutex);
|
||||
}
|
||||
|
||||
void VBlankTextureLoader::CancelLoad(VBlankTextureLoadRequest& request)
|
||||
{
|
||||
rtos_lockMutex(&_mutex);
|
||||
{
|
||||
if (request._state == VBlankTextureLoadRequestState::LoadRequested || request._state == VBlankTextureLoadRequestState::Loading)
|
||||
{
|
||||
_requests.Remove(&request);
|
||||
request._state = VBlankTextureLoadRequestState::Canceled;
|
||||
if (_currentRequest == &request)
|
||||
{
|
||||
_currentRequest = nullptr;
|
||||
_currentLoadingOffset = 0;
|
||||
_currentLoadingStage = LoadingStage::Texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
rtos_unlockMutex(&_mutex);
|
||||
}
|
||||
35
arm9/source/gui/VBlankTextureLoader.h
Normal file
35
arm9/source/gui/VBlankTextureLoader.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <libtwl/rtos/rtosMutex.h>
|
||||
#include "VBlankTextureLoadRequest.h"
|
||||
#include "core/LinkedList.h"
|
||||
|
||||
/// @brief Class for loading texture data during vblank.
|
||||
class VBlankTextureLoader
|
||||
{
|
||||
public:
|
||||
VBlankTextureLoader()
|
||||
{
|
||||
rtos_createMutex(&_mutex);
|
||||
}
|
||||
|
||||
void VBlank();
|
||||
|
||||
void RequestLoad(VBlankTextureLoadRequest& request);
|
||||
void CancelLoad(VBlankTextureLoadRequest& request);
|
||||
|
||||
private:
|
||||
enum class LoadingStage
|
||||
{
|
||||
Texture,
|
||||
Palette
|
||||
};
|
||||
|
||||
rtos_mutex_t _mutex;
|
||||
LinkedList<VBlankTextureLoadRequest, &VBlankTextureLoadRequest::_listLink> _requests;
|
||||
VBlankTextureLoadRequest* _currentRequest = nullptr;
|
||||
u32 _currentLoadingOffset = 0;
|
||||
LoadingStage _currentLoadingStage;
|
||||
|
||||
bool LoadTexture();
|
||||
bool LoadPalette();
|
||||
};
|
||||
23
arm9/source/gui/VramContext.h
Normal file
23
arm9/source/gui/VramContext.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "IVramManager.h"
|
||||
|
||||
class VramContext
|
||||
{
|
||||
public:
|
||||
VramContext(IVramManager* bgVramManager, IVramManager* objVramManager,
|
||||
IVramManager* texVramManager, IVramManager* texPlttVramManager)
|
||||
: _bgVramManager(bgVramManager), _objVramManager(objVramManager)
|
||||
, _texVramManager(texVramManager), _texPlttVramManager(texPlttVramManager)
|
||||
{ }
|
||||
|
||||
IVramManager* GetBgVramManager() const { return _bgVramManager; }
|
||||
IVramManager* GetObjVramManager() const { return _objVramManager; }
|
||||
IVramManager* GetTexVramManager() const { return _texVramManager; }
|
||||
IVramManager* GetTexPlttVramManager() const { return _texPlttVramManager; }
|
||||
|
||||
private:
|
||||
IVramManager* _bgVramManager;
|
||||
IVramManager* _objVramManager;
|
||||
IVramManager* _texVramManager;
|
||||
IVramManager* _texPlttVramManager;
|
||||
};
|
||||
324
arm9/source/gui/font/nitroFont2.cpp
Normal file
324
arm9/source/gui/font/nitroFont2.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
#include "common.h"
|
||||
#include "nitroFont2.h"
|
||||
|
||||
bool nft2_unpack(nft2_header_t* font)
|
||||
{
|
||||
if (font->signature != NFT2_SIGNATURE)
|
||||
return false;
|
||||
|
||||
font->glyphInfoPtr = (const nft2_glyph_t*)((u32)font + (u32)font->glyphInfoPtr);
|
||||
font->charMapPtr = (const nft2_char_map_entry_t*)((u32)font + (u32)font->charMapPtr);
|
||||
font->glyphDataPtr = (const u8*)((u32)font + (u32)font->glyphDataPtr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character)
|
||||
{
|
||||
const nft2_char_map_entry_t* charMapEntry = font->charMapPtr;
|
||||
while (charMapEntry->count > 0)
|
||||
{
|
||||
if (charMapEntry->startChar <= character && character < charMapEntry->startChar + charMapEntry->count)
|
||||
return charMapEntry->glyphs[character - charMapEntry->startChar];
|
||||
|
||||
charMapEntry = (const nft2_char_map_entry_t*)((u32)charMapEntry + 4 + 2 * charMapEntry->count);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void renderGlyph(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride, bool a5i3)
|
||||
{
|
||||
int yOffset = glyph->spacingTop;
|
||||
u32 xStart = xPos < 0 ? -xPos : 0;
|
||||
u32 yStart = yPos + yOffset < 0 ? -(yPos + yOffset) : 0;
|
||||
|
||||
int xEnd = glyph->glyphWidth;
|
||||
if (xPos + xEnd > width)
|
||||
{
|
||||
// by returning we only render complete glyphs
|
||||
return;
|
||||
// old code for rendering partial glyphs
|
||||
// xEnd = width - xPos;
|
||||
}
|
||||
|
||||
int yEnd = glyph->glyphHeight;
|
||||
if (yPos + yOffset + yEnd > height)
|
||||
yEnd = height - (yPos + yOffset); // allow partial glyphs in the vertical direction
|
||||
|
||||
const u8* glyphData = &font->glyphDataPtr[glyph->dataOffset];
|
||||
glyphData += yStart * ((glyph->glyphWidth + 1) >> 1);
|
||||
for (int y = yStart; y < yEnd; y++)
|
||||
{
|
||||
for (int x = xStart; x < xEnd; x++)
|
||||
{
|
||||
u32 data = glyphData[x >> 1];
|
||||
if ((x & 1) == 0)
|
||||
data &= 0xF;
|
||||
else
|
||||
data >>= 4;
|
||||
|
||||
if (data == 0)
|
||||
continue;
|
||||
|
||||
u32 finalX = x + xPos;
|
||||
u32 finalY = y + yPos + yOffset;
|
||||
|
||||
if (a5i3)
|
||||
{
|
||||
dst[finalY * stride + finalX] = (data << 4) | (data ? (1 << 3) : 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 tileX = finalX >> 3;
|
||||
u32 tileY = finalY >> 3;
|
||||
|
||||
u32 tileIdx = (tileY >> 1) * stride + (tileX >> 2) * 8 + (tileY & 1) * 4 + (tileX & 3);
|
||||
|
||||
u32 offset = tileIdx * 64 + ((finalY & 7) << 3) + (finalX & 7);
|
||||
u32 value = dst[offset >> 1];
|
||||
if (offset & 1)
|
||||
{
|
||||
u32 prevData = value >> 4;
|
||||
if (prevData < data)
|
||||
dst[offset >> 1] = (value & 0xF) | (data << 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 prevData = value & 0xF;
|
||||
if (prevData < data)
|
||||
dst[offset >> 1] = (value & 0xF0) | data;
|
||||
}
|
||||
}
|
||||
}
|
||||
glyphData += (glyph->glyphWidth + 1) >> 1;
|
||||
}
|
||||
}
|
||||
|
||||
static ITCM_CODE void renderGlyphTiled(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
|
||||
{
|
||||
renderGlyph(font, glyph, xPos, yPos, width, height, dst, stride, false);
|
||||
}
|
||||
|
||||
static ITCM_CODE void renderGlyphA5I3(const nft2_header_t* font, const nft2_glyph_t* glyph,
|
||||
int xPos, int yPos, int width, int height, u8* dst, u32 stride)
|
||||
{
|
||||
renderGlyph(font, glyph, xPos, yPos, width, height, dst, stride, true);
|
||||
}
|
||||
|
||||
ITCM_CODE void nft2_renderString(const nft2_header_t* font, const char16_t* string, u8* dst, u32 stride,
|
||||
nft2_string_render_params_t* renderParams)
|
||||
{
|
||||
int xPos = renderParams->x;
|
||||
int yPos = renderParams->y;
|
||||
bool a5i3 = renderParams->a5i3;
|
||||
u32 textWidth = 0;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *string++;
|
||||
if (c == 0)
|
||||
break;
|
||||
if (c == '\n')
|
||||
{
|
||||
xPos = renderParams->x;
|
||||
yPos += font->ascend + font->descend + 1;
|
||||
if (yPos >= (int)renderParams->height)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
renderParams->textWidth = textWidth;
|
||||
}
|
||||
|
||||
ITCM_CODE void nft2_measureString(const nft2_header_t* font, const char16_t* string, u32& width, u32& height)
|
||||
{
|
||||
int xPos = 0;
|
||||
int yPos = 0;
|
||||
u32 textWidth = 0;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *string++;
|
||||
if (c == 0)
|
||||
break;
|
||||
if (c == '\n')
|
||||
{
|
||||
xPos = 0;
|
||||
yPos += font->ascend + font->descend + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft;
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
width = textWidth;
|
||||
height = yPos + font->ascend + font->descend;
|
||||
}
|
||||
|
||||
static ITCM_CODE const char16_t* findFirstCharacterThatDoesNotFit(const nft2_header_t* font, const char16_t* string, u32 width)
|
||||
{
|
||||
int xPos = 0;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *string++;
|
||||
if (c == 0 || c == '\n')
|
||||
return string - 1;
|
||||
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft + glyph->glyphWidth + glyph->spacingRight;
|
||||
if (xPos > (int)width)
|
||||
return string - 1;
|
||||
}
|
||||
}
|
||||
|
||||
static ITCM_CODE const char16_t* findLastCharacterThatFitsBackwards(const nft2_header_t* font, const char16_t* stringStart, const char16_t* stringEnd, u32 width)
|
||||
{
|
||||
const char16_t* string = stringEnd;
|
||||
int xPos = (int)width;
|
||||
while (string >= stringStart)
|
||||
{
|
||||
u16 c = *--string;
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
if (string != stringEnd - 1)
|
||||
xPos -= glyph->spacingRight;
|
||||
xPos -= glyph->glyphWidth;
|
||||
xPos -= glyph->spacingLeft;
|
||||
if (xPos < 0)
|
||||
return string + 1;
|
||||
}
|
||||
return stringStart;
|
||||
}
|
||||
|
||||
static ITCM_CODE int measureEllipsisWidth(const nft2_header_t* font, const char16_t* ellipsisString)
|
||||
{
|
||||
int ellipsisWidth = 0;
|
||||
const char16_t* stringPtr = ellipsisString;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *stringPtr++;
|
||||
if (c == 0)
|
||||
break;
|
||||
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
ellipsisWidth += glyph->spacingLeft + glyph->glyphWidth + glyph->spacingRight;
|
||||
}
|
||||
return ellipsisWidth;
|
||||
}
|
||||
|
||||
ITCM_CODE void nft2_renderStringEllipsis(const nft2_header_t* font, const char16_t* string, u8* dst,
|
||||
u32 stride, nft2_string_render_params_t* renderParams, const char16_t* ellipsisString)
|
||||
{
|
||||
u32 stringWidth, stringHeight;
|
||||
nft2_measureString(font, string, stringWidth, stringHeight);
|
||||
if (stringWidth <= renderParams->width)
|
||||
{
|
||||
// no ellipsis needed
|
||||
nft2_renderString(font, string, dst, stride, renderParams);
|
||||
return;
|
||||
}
|
||||
u32 splitPoint = (renderParams->width - renderParams->x) * 3 / 4;
|
||||
int ellipsisWidth = measureEllipsisWidth(font, ellipsisString);
|
||||
u32 splitLeftEnd = splitPoint - (ellipsisWidth >> 1);
|
||||
u32 splitRightStart = splitPoint + ((ellipsisWidth + 1) >> 1);
|
||||
const char16_t* endOfFirstPart = findFirstCharacterThatDoesNotFit(font, string, splitLeftEnd);
|
||||
u32 stringLength = 0;
|
||||
while (string[stringLength] != 0)
|
||||
stringLength++;
|
||||
const char16_t* startOfSecondPart = findLastCharacterThatFitsBackwards(
|
||||
font, endOfFirstPart, string + stringLength, renderParams->width - renderParams->x - splitRightStart);
|
||||
|
||||
int xPos = renderParams->x;
|
||||
int yPos = renderParams->y;
|
||||
bool a5i3 = renderParams->a5i3;
|
||||
u32 textWidth = 0;
|
||||
const char16_t* stringPtr = string;
|
||||
while (stringPtr < endOfFirstPart)
|
||||
{
|
||||
u16 c = *stringPtr++;
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
stringPtr = ellipsisString;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *stringPtr++;
|
||||
if (c == 0)
|
||||
break;
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
stringPtr = startOfSecondPart;
|
||||
while (true)
|
||||
{
|
||||
u16 c = *stringPtr++;
|
||||
if (c == 0)
|
||||
break;
|
||||
int glyphIdx = nft2_findGlyphIdxForCharacter(font, c);
|
||||
const nft2_glyph_t* glyph = &font->glyphInfoPtr[glyphIdx];
|
||||
xPos += glyph->spacingLeft;
|
||||
if (a5i3)
|
||||
{
|
||||
renderGlyphA5I3(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderGlyphTiled(font, glyph, xPos, yPos, renderParams->width, renderParams->height, dst, stride);
|
||||
}
|
||||
xPos += glyph->glyphWidth;
|
||||
if (xPos > (int)textWidth)
|
||||
textWidth = xPos;
|
||||
xPos += glyph->spacingRight;
|
||||
}
|
||||
renderParams->textWidth = textWidth;
|
||||
}
|
||||
84
arm9/source/gui/font/nitroFont2.h
Normal file
84
arm9/source/gui/font/nitroFont2.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#define NFT2_SIGNATURE 0x3254464E
|
||||
|
||||
struct nft2_glyph_t
|
||||
{
|
||||
u32 dataOffset : 24;
|
||||
u32 glyphWidth : 8;
|
||||
s8 spacingLeft;
|
||||
s8 spacingRight;
|
||||
u8 glyphHeight;
|
||||
s8 spacingTop;
|
||||
};
|
||||
|
||||
struct nft2_char_map_entry_t
|
||||
{
|
||||
u16 count;
|
||||
u16 startChar;
|
||||
u16 glyphs[1];
|
||||
};
|
||||
|
||||
struct nft2_header_t
|
||||
{
|
||||
u32 signature;
|
||||
const nft2_glyph_t* glyphInfoPtr;
|
||||
const nft2_char_map_entry_t* charMapPtr;
|
||||
const u8* glyphDataPtr;
|
||||
u8 ascend;
|
||||
u8 descend;
|
||||
u16 glyphCount;
|
||||
};
|
||||
|
||||
struct nft2_string_render_params_t
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 textWidth;
|
||||
// u32 textHeight;
|
||||
bool a5i3;
|
||||
};
|
||||
|
||||
/// @brief Prepares the ntf2 data of the given \p font for runtime use.
|
||||
/// Call this method once after loading a font file.
|
||||
/// @param font The font to prepare.
|
||||
/// @return True if preparing was successful, or false otherwise.
|
||||
bool nft2_unpack(nft2_header_t* font);
|
||||
|
||||
/// @brief Finds the glyph index in the given \p font that corresponds to the given \p character.
|
||||
/// @param font The font the find the glyph index in.
|
||||
/// @param character The character to find the glyph index for.
|
||||
/// @return The glyph index if found, or 0 otherwise.
|
||||
int nft2_findGlyphIdxForCharacter(const nft2_header_t* font, u16 character);
|
||||
|
||||
/// @brief Renders the given \p string with the given \p font to the \p dst buffer
|
||||
/// with the given \p stride and \p renderParams.
|
||||
/// @param font The font to use.
|
||||
/// @param string The string to render.
|
||||
/// @param dst The destination buffer.
|
||||
/// @param stride The stride of the destination buffer.
|
||||
/// @param renderParams The render params.
|
||||
void nft2_renderString(const nft2_header_t* font, const char16_t* string, u8* dst,
|
||||
u32 stride, nft2_string_render_params_t* renderParams);
|
||||
|
||||
/// @brief Measures the given \p string with the given \p font. Returns the \p width and \p height.
|
||||
/// @param font The font to use.
|
||||
/// @param string The string to measure.
|
||||
/// @param width The measured width.
|
||||
/// @param height The measured height.
|
||||
void nft2_measureString(const nft2_header_t* font, const char16_t* string, u32& width, u32& height);
|
||||
|
||||
/// @brief Renders the given \p string with the given \p font to the \p dst buffer
|
||||
/// with the given \p stride and \p renderParams. If the string is too long to
|
||||
/// fit the buffer, \p ellipsisString is rendered at 3/4rd of the string, cutting
|
||||
/// it in two parts.
|
||||
/// @param font The font to use.
|
||||
/// @param string The string to render.
|
||||
/// @param dst The destination buffer.
|
||||
/// @param stride The stride of the destination buffer.
|
||||
/// @param renderParams The render params.
|
||||
/// @param ellipsisString The ellipsis string.
|
||||
void nft2_renderStringEllipsis(const nft2_header_t* font, const char16_t* string, u8* dst,
|
||||
u32 stride, nft2_string_render_params_t* renderParams, const char16_t* ellipsisString);
|
||||
11
arm9/source/gui/input/IInputSource.h
Normal file
11
arm9/source/gui/input/IInputSource.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "InputKey.h"
|
||||
|
||||
/// @brief Interface for a source of key input.
|
||||
class IInputSource
|
||||
{
|
||||
public:
|
||||
virtual ~IInputSource() { }
|
||||
|
||||
virtual InputKey Sample() const = 0;
|
||||
};
|
||||
45
arm9/source/gui/input/InputKey.h
Normal file
45
arm9/source/gui/input/InputKey.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
enum class InputKey : u16
|
||||
{
|
||||
None = 0,
|
||||
A = 1 << 0,
|
||||
B = 1 << 1,
|
||||
Select = 1 << 2,
|
||||
Start = 1 << 3,
|
||||
DpadRight = 1 << 4,
|
||||
DpadLeft = 1 << 5,
|
||||
DpadUp = 1 << 6,
|
||||
DpadDown = 1 << 7,
|
||||
R = 1 << 8,
|
||||
L = 1 << 9,
|
||||
X = 1 << 10,
|
||||
Y = 1 << 11,
|
||||
Debug = 1 << 13
|
||||
};
|
||||
|
||||
inline InputKey operator&(InputKey lhs, InputKey rhs)
|
||||
{
|
||||
return static_cast<InputKey>(static_cast<u16>(lhs) & static_cast<u16>(rhs));
|
||||
}
|
||||
|
||||
inline InputKey operator|(InputKey lhs, InputKey rhs)
|
||||
{
|
||||
return static_cast<InputKey>(static_cast<u16>(lhs) | static_cast<u16>(rhs));
|
||||
}
|
||||
|
||||
inline InputKey operator~(InputKey lhs)
|
||||
{
|
||||
return static_cast<InputKey>(~static_cast<u16>(lhs));
|
||||
}
|
||||
|
||||
inline InputKey operator^(InputKey lhs, InputKey rhs)
|
||||
{
|
||||
return static_cast<InputKey>(static_cast<u16>(lhs) ^ static_cast<u16>(rhs));
|
||||
}
|
||||
|
||||
inline InputKey operator|=(InputKey& lhs, InputKey rhs)
|
||||
{
|
||||
lhs = lhs | rhs; // don't use |= to prevent recursion
|
||||
return lhs;
|
||||
}
|
||||
58
arm9/source/gui/input/InputProvider.h
Normal file
58
arm9/source/gui/input/InputProvider.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "InputKey.h"
|
||||
|
||||
class InputProvider
|
||||
{
|
||||
public:
|
||||
/// @brief Returns a bitmask of the keys currently being held.
|
||||
/// @return A bitmask of the keys currently being held.
|
||||
InputKey GetCurrentKeys() const { return _currentKeys; }
|
||||
|
||||
/// @brief Returns whether any of the keys in the given \p mask is currently being held.
|
||||
/// @param mask The mask to check for.
|
||||
/// @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 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
|
||||
{
|
||||
return _triggeredKeys;
|
||||
}
|
||||
|
||||
/// @brief Returns whether any of the keys in the given \p mask went from unpressed to pressed in the latest update.
|
||||
/// @param mask The mask to check for.
|
||||
/// @return \c true if any of the keys in the \p mask went from unpressed to pressed
|
||||
/// in the latest update, or \c false otherwise.
|
||||
bool Triggered(InputKey mask) const { return static_cast<bool>(_triggeredKeys & mask); }
|
||||
|
||||
/// @brief Returns a bitmask of the keys that went from pressed to unpressed in the latest update.
|
||||
/// @return A bitmask of the keys that went from pressed to unpressed in the latest update.
|
||||
InputKey GetReleasedKeys() const { return _releasedKeys; }
|
||||
|
||||
/// @brief Returns whether any of the keys in the given \p mask went from pressed to unpressed in the latest update.
|
||||
/// @param mask The mask to check for.
|
||||
/// @return \c true if any of the keys in the \p mask went from pressed to unpressed
|
||||
/// in the latest update, or \c false otherwise.
|
||||
bool Released(InputKey mask) const { return static_cast<bool>(_releasedKeys & mask); }
|
||||
|
||||
/// @brief Updates the input provider.
|
||||
virtual void Update() = 0;
|
||||
|
||||
/// @brief Resets the input provider.
|
||||
virtual void Reset()
|
||||
{
|
||||
_currentKeys = InputKey::None;
|
||||
_triggeredKeys = InputKey::None;
|
||||
_releasedKeys = InputKey::None;
|
||||
}
|
||||
|
||||
protected:
|
||||
InputKey _currentKeys;
|
||||
InputKey _triggeredKeys;
|
||||
InputKey _releasedKeys;
|
||||
|
||||
InputProvider()
|
||||
: _currentKeys(InputKey::None), _triggeredKeys(InputKey::None), _releasedKeys(InputKey::None) { }
|
||||
};
|
||||
58
arm9/source/gui/input/InputRepeater.cpp
Normal file
58
arm9/source/gui/input/InputRepeater.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "common.h"
|
||||
#include "InputRepeater.h"
|
||||
|
||||
void InputRepeater::Update()
|
||||
{
|
||||
_inputProvider->Update();
|
||||
InputKey curKeys = _inputProvider->GetCurrentKeys();
|
||||
InputKey repKeys = InputKey::None;
|
||||
if (_state == State::Idle)
|
||||
{
|
||||
if (static_cast<bool>(curKeys & _repeatMask))
|
||||
{
|
||||
_state = State::FirstRepeat;
|
||||
_frameCounter = 0;
|
||||
repKeys = curKeys & _repeatMask;
|
||||
}
|
||||
}
|
||||
else if (_state == State::FirstRepeat)
|
||||
{
|
||||
if (static_cast<bool>(curKeys & _repeatMask))
|
||||
{
|
||||
if (++_frameCounter >= _firstRepeatDelayFrames)
|
||||
{
|
||||
_state = State::NextRepeat;
|
||||
_frameCounter = 0;
|
||||
repKeys = curKeys & _repeatMask;
|
||||
}
|
||||
}
|
||||
else
|
||||
_state = State::Idle;
|
||||
}
|
||||
else if (_state == State::NextRepeat)
|
||||
{
|
||||
if (static_cast<bool>(curKeys & _repeatMask))
|
||||
{
|
||||
if (++_frameCounter >= _nextRepeatDelayFrames)
|
||||
{
|
||||
_frameCounter = 0;
|
||||
repKeys = curKeys & _repeatMask;
|
||||
}
|
||||
}
|
||||
else
|
||||
_state = State::Idle;
|
||||
}
|
||||
|
||||
InputKey lastRepKeys = _currentKeys & _repeatMask;
|
||||
_currentKeys = curKeys | repKeys;
|
||||
_triggeredKeys = _inputProvider->GetTriggeredKeys() | repKeys;
|
||||
_releasedKeys = _inputProvider->GetReleasedKeys() | (lastRepKeys & (lastRepKeys ^ repKeys));
|
||||
}
|
||||
|
||||
void InputRepeater::Reset()
|
||||
{
|
||||
InputProvider::Reset();
|
||||
_inputProvider->Reset();
|
||||
_state = State::Idle;
|
||||
_frameCounter = 0;
|
||||
}
|
||||
31
arm9/source/gui/input/InputRepeater.h
Normal file
31
arm9/source/gui/input/InputRepeater.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "InputProvider.h"
|
||||
|
||||
/// @brief Input provider that wraps another input provider and adds key repetition on top of it.
|
||||
class InputRepeater : public InputProvider
|
||||
{
|
||||
public:
|
||||
InputRepeater(InputProvider* inputProvider, InputKey repeatMask, u16 firstRepeatDelay, u16 nextRepeatDelay)
|
||||
: _inputProvider(inputProvider), _state(State::Idle)
|
||||
, _frameCounter(0), _repeatMask(repeatMask)
|
||||
, _firstRepeatDelayFrames(firstRepeatDelay)
|
||||
, _nextRepeatDelayFrames(nextRepeatDelay) { }
|
||||
|
||||
void Update() override;
|
||||
void Reset() override;
|
||||
|
||||
private:
|
||||
enum class State
|
||||
{
|
||||
Idle,
|
||||
FirstRepeat,
|
||||
NextRepeat
|
||||
};
|
||||
|
||||
InputProvider* _inputProvider;
|
||||
State _state;
|
||||
u16 _frameCounter;
|
||||
InputKey _repeatMask;
|
||||
u16 _firstRepeatDelayFrames;
|
||||
u16 _nextRepeatDelayFrames;
|
||||
};
|
||||
17
arm9/source/gui/input/PadInputSource.h
Normal file
17
arm9/source/gui/input/PadInputSource.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include <nds/input.h>
|
||||
#include "IInputSource.h"
|
||||
#include "sharedMemory.h"
|
||||
|
||||
/// @brief Input source from the physical DS buttons.
|
||||
class PadInputSource : public IInputSource
|
||||
{
|
||||
public:
|
||||
InputKey Sample() const override
|
||||
{
|
||||
u16 arm9Mask = (~REG_KEYINPUT & 0x3FF);
|
||||
u16 arm7Mask = (~SHARED_KEY_XY & 0xB);
|
||||
return static_cast<InputKey>((arm9Mask | (arm7Mask << 10)));
|
||||
}
|
||||
};
|
||||
22
arm9/source/gui/input/SampledInputProvider.cpp
Normal file
22
arm9/source/gui/input/SampledInputProvider.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "common.h"
|
||||
#include "SampledInputProvider.h"
|
||||
|
||||
void SampledInputProvider::Update()
|
||||
{
|
||||
InputKey curKeys = _currentKeys;
|
||||
InputKey trig = InputKey::None;
|
||||
InputKey rel = InputKey::None;
|
||||
|
||||
while (_inputBufferReadPtr != _inputBufferWritePtr)
|
||||
{
|
||||
InputKey nextKeys = _inputBuffer[_inputBufferReadPtr];
|
||||
trig |= (nextKeys ^ curKeys) & nextKeys;
|
||||
rel |= (nextKeys ^ curKeys) & curKeys;
|
||||
curKeys = nextKeys;
|
||||
_inputBufferReadPtr = (_inputBufferReadPtr + 1) & 3;
|
||||
}
|
||||
|
||||
_triggeredKeys = trig;
|
||||
_releasedKeys = rel;
|
||||
_currentKeys = curKeys;
|
||||
}
|
||||
34
arm9/source/gui/input/SampledInputProvider.h
Normal file
34
arm9/source/gui/input/SampledInputProvider.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "InputProvider.h"
|
||||
#include "IInputSource.h"
|
||||
|
||||
/// @brief Input provider providing input from an \see IInputSource.
|
||||
class SampledInputProvider : public InputProvider
|
||||
{
|
||||
public:
|
||||
explicit SampledInputProvider(const IInputSource* inputSource)
|
||||
: _inputSource(inputSource), _inputBufferReadPtr(0), _inputBufferWritePtr(0) { }
|
||||
|
||||
void Update() override;
|
||||
|
||||
/// @brief Samples the input source.
|
||||
void Sample()
|
||||
{
|
||||
_inputBuffer[_inputBufferWritePtr] = _inputSource->Sample();
|
||||
_inputBufferWritePtr = (_inputBufferWritePtr + 1) & 3;
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
InputProvider::Reset();
|
||||
_inputBufferReadPtr = 0;
|
||||
_inputBufferWritePtr = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const IInputSource* _inputSource;
|
||||
|
||||
InputKey _inputBuffer[4];
|
||||
u8 _inputBufferReadPtr;
|
||||
u8 _inputBufferWritePtr;
|
||||
};
|
||||
62
arm9/source/gui/materialDesign.h
Normal file
62
arm9/source/gui/materialDesign.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include "animation/LinearCurve.h"
|
||||
#include "animation/CubicBezierCurve.h"
|
||||
#include "animation/ThreePointCubicBezierCurve.h"
|
||||
|
||||
namespace md::sys
|
||||
{
|
||||
enum class color
|
||||
{
|
||||
primary,
|
||||
onPrimary,
|
||||
secondaryContainer,
|
||||
onSecondaryContainer,
|
||||
onSurfaceVariant,
|
||||
outline,
|
||||
onSurface,
|
||||
inverseOnSurface,
|
||||
surfaceContainerLow = inverseOnSurface, // basically the same color
|
||||
surfaceContainerHighest,
|
||||
surfaceBright,
|
||||
|
||||
Max = surfaceBright
|
||||
};
|
||||
}
|
||||
|
||||
namespace md::sys::motion::duration
|
||||
{
|
||||
#define MILLISECONDS_TO_FRAMES(x) ((x) * 60 / 1000)
|
||||
|
||||
constexpr int short1 = MILLISECONDS_TO_FRAMES(50);
|
||||
constexpr int short2 = MILLISECONDS_TO_FRAMES(100);
|
||||
constexpr int short3 = MILLISECONDS_TO_FRAMES(150);
|
||||
constexpr int short4 = MILLISECONDS_TO_FRAMES(200);
|
||||
constexpr int medium1 = MILLISECONDS_TO_FRAMES(250);
|
||||
constexpr int medium2 = MILLISECONDS_TO_FRAMES(300);
|
||||
constexpr int medium3 = MILLISECONDS_TO_FRAMES(350);
|
||||
constexpr int medium4 = MILLISECONDS_TO_FRAMES(400);
|
||||
constexpr int long1 = MILLISECONDS_TO_FRAMES(450);
|
||||
constexpr int long2 = MILLISECONDS_TO_FRAMES(500);
|
||||
constexpr int long3 = MILLISECONDS_TO_FRAMES(550);
|
||||
constexpr int long4 = MILLISECONDS_TO_FRAMES(600);
|
||||
constexpr int extraLong1 = MILLISECONDS_TO_FRAMES(700);
|
||||
constexpr int extraLong2 = MILLISECONDS_TO_FRAMES(800);
|
||||
constexpr int extraLong3 = MILLISECONDS_TO_FRAMES(900);
|
||||
constexpr int extraLong4 = MILLISECONDS_TO_FRAMES(1000);
|
||||
|
||||
#undef MILLISECONDS_TO_FRAMES
|
||||
}
|
||||
|
||||
namespace md::sys::motion::easing
|
||||
{
|
||||
constexpr auto linear = LinearCurve();
|
||||
constexpr auto standard = CubicBezierCurve(0.2, 0, 0, 1);
|
||||
constexpr auto standardAccelerate = CubicBezierCurve(0.3, 0, 1, 1);
|
||||
constexpr auto standardDecelerate = CubicBezierCurve(0, 0, 0, 1);
|
||||
constexpr auto emphasized = ThreePointCubicBezierCurve(
|
||||
0.05, 0, 0.133333, 0.06,
|
||||
0.166666, 0.4,
|
||||
0.208333, 0.82, 0.25, 1);
|
||||
constexpr auto emphasizedDecelerate = CubicBezierCurve(0.05, 0.7, 0.1, 1);
|
||||
constexpr auto emphasizedAccelerate = CubicBezierCurve(0.3, 0, 0.8, 0.15);
|
||||
}
|
||||
13
arm9/source/gui/palette/DirectPalette.cpp
Normal file
13
arm9/source/gui/palette/DirectPalette.cpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#include "common.h"
|
||||
#include "DirectPalette.h"
|
||||
|
||||
u32 DirectPalette::GetHashCode() const
|
||||
{
|
||||
u32 hashcode = 1430287;
|
||||
#pragma GCC unroll 8
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
hashcode = hashcode * 7302013 + ((u32*)_colors)[i];
|
||||
}
|
||||
return hashcode;
|
||||
}
|
||||
23
arm9/source/gui/palette/DirectPalette.h
Normal file
23
arm9/source/gui/palette/DirectPalette.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <string.h>
|
||||
#include "IPalette.h"
|
||||
|
||||
/// @brief Palette of 16 specified colors.
|
||||
class alignas(4) DirectPalette : public IPalette
|
||||
{
|
||||
public:
|
||||
explicit DirectPalette(const u16* colors)
|
||||
{
|
||||
memcpy(_colors, colors, sizeof(_colors));
|
||||
}
|
||||
|
||||
void GetColors(u16* dst) const override
|
||||
{
|
||||
memcpy(dst, _colors, sizeof(_colors));
|
||||
}
|
||||
|
||||
u32 GetHashCode() const override;
|
||||
|
||||
private:
|
||||
u16 _colors[16];
|
||||
};
|
||||
14
arm9/source/gui/palette/GradientPalette.cpp
Normal file
14
arm9/source/gui/palette/GradientPalette.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "common.h"
|
||||
#include "GradientPalette.h"
|
||||
|
||||
u32 GradientPalette::GetHashCode() const
|
||||
{
|
||||
u32 hashcode = 1430287;
|
||||
hashcode = hashcode * 7302013 + _fromColor.r;
|
||||
hashcode = hashcode * 7302013 + _fromColor.g;
|
||||
hashcode = hashcode * 7302013 + _fromColor.b;
|
||||
hashcode = hashcode * 7302013 + _toColor.r;
|
||||
hashcode = hashcode * 7302013 + _toColor.g;
|
||||
hashcode = hashcode * 7302013 + _toColor.b;
|
||||
return hashcode;
|
||||
}
|
||||
23
arm9/source/gui/palette/GradientPalette.h
Normal file
23
arm9/source/gui/palette/GradientPalette.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include "core/math/Rgb.h"
|
||||
#include "core/math/RgbMixer.h"
|
||||
#include "IPalette.h"
|
||||
|
||||
/// @brief Gradient palette between two colors.
|
||||
class GradientPalette : public IPalette
|
||||
{
|
||||
public:
|
||||
GradientPalette(const Rgb<8, 8, 8>& from, const Rgb<8, 8, 8>& to)
|
||||
: _fromColor(from), _toColor(to) { }
|
||||
|
||||
void GetColors(u16* dst) const override
|
||||
{
|
||||
RgbMixer::MakeGradientPalette(dst, _fromColor, _toColor);
|
||||
}
|
||||
|
||||
u32 GetHashCode() const override;
|
||||
|
||||
private:
|
||||
Rgb<8, 8, 8> _fromColor;
|
||||
Rgb<8, 8, 8> _toColor;
|
||||
};
|
||||
18
arm9/source/gui/palette/IPalette.h
Normal file
18
arm9/source/gui/palette/IPalette.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
/// @brief Interface for a color palette.
|
||||
class IPalette
|
||||
{
|
||||
public:
|
||||
virtual ~IPalette() = 0;
|
||||
|
||||
/// @brief Gets the colors of the palette.
|
||||
/// @param dst A pointer to an array the colors should be written to.
|
||||
virtual void GetColors(u16* dst) const = 0;
|
||||
|
||||
/// @brief Gets a hash that represents this color palette.
|
||||
/// @return The hash.
|
||||
virtual u32 GetHashCode() const = 0;
|
||||
};
|
||||
|
||||
inline IPalette::~IPalette() { }
|
||||
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