mirror of
https://github.com/risingPhil/PokeMe64.git
synced 2026-03-21 18:04:15 -05:00
Feature/scroll indication (#3)
Add scroll arrows when there are more items in the list than the list can show at a time. These arrows can be shown in the main menu (although there aren't enough items in there to have them shown) or in the various DistributionEventPokemonListScene menus. I also did provisions to support the same for the ScrollWidget in the about screen. Although I haven't implemented that (yet?)
This commit is contained in:
parent
bc7733cc36
commit
90bb9ccceb
BIN
assets/ui-arrow-down.png
Normal file
BIN
assets/ui-arrow-down.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
BIN
assets/ui-arrow-up.png
Normal file
BIN
assets/ui-arrow-up.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
#include <libdragon.h>
|
||||
|
||||
enum class UINavigationKey
|
||||
enum class UINavigationDirection
|
||||
{
|
||||
NONE,
|
||||
UP,
|
||||
RIGHT,
|
||||
DOWN,
|
||||
LEFT
|
||||
LEFT,
|
||||
MAX
|
||||
};
|
||||
|
||||
enum class NavigationInputSourceType
|
||||
|
|
@ -24,6 +24,6 @@ enum class NavigationInputSourceType
|
|||
* This function determines whether the joypad_inputs_t has analog or dpad positions/presses that could be considered for UI navigation.
|
||||
* If so, it will return the most prominent direction.
|
||||
*/
|
||||
const UINavigationKey determineUINavigationKey(joypad_inputs_t inputs, NavigationInputSourceType sourceType);
|
||||
const UINavigationDirection determineUINavigationDirection(joypad_inputs_t inputs, NavigationInputSourceType sourceType);
|
||||
|
||||
#endif
|
||||
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
#include "scenes/SceneWithDialogWidget.h"
|
||||
#include "widget/VerticalList.h"
|
||||
#include "widget/ImageWidget.h"
|
||||
#include "widget/DialogWidget.h"
|
||||
#include "widget/CursorWidget.h"
|
||||
#include "widget/MenuItemWidget.h"
|
||||
#include "widget/ListItemFiller.h"
|
||||
#include "widget/IFocusListener.h"
|
||||
#include "widget/IScrollWindowListener.h"
|
||||
|
||||
typedef struct MenuSceneContext
|
||||
{
|
||||
|
|
@ -20,7 +22,7 @@ typedef struct MenuSceneContext
|
|||
* @brief A scene showing a menu
|
||||
*
|
||||
*/
|
||||
class MenuScene : public SceneWithDialogWidget, public IFocusListener
|
||||
class MenuScene : public SceneWithDialogWidget, public IFocusListener, public IScrollWindowListener
|
||||
{
|
||||
public:
|
||||
MenuScene(SceneDependencies& deps, void* context);
|
||||
|
|
@ -36,6 +38,7 @@ public:
|
|||
virtual void onDialogDone();
|
||||
|
||||
void focusChanged(const FocusChangeStatus& status) override;
|
||||
void onScrollWindowChanged(const ScrollWindowUpdate& update) override;
|
||||
|
||||
SceneDependencies& getDependencies();
|
||||
|
||||
|
|
@ -47,7 +50,11 @@ protected:
|
|||
MenuSceneContext* context_;
|
||||
sprite_t* menu9SliceSprite_;
|
||||
sprite_t* cursorSprite_;
|
||||
sprite_t* uiArrowUpSprite_;
|
||||
sprite_t* uiArrowDownSprite_;
|
||||
VerticalList menuList_;
|
||||
ImageWidget scrollArrowUp_;
|
||||
ImageWidget scrollArrowDown_;
|
||||
CursorWidget cursorWidget_;
|
||||
ListItemFiller<VerticalList, MenuItemData, MenuItemWidget, MenuItemStyle> menuListFiller_;
|
||||
WidgetFocusChainSegment listFocusChainSegment_;
|
||||
|
|
|
|||
54
include/widget/IScrollWindowListener.h
Normal file
54
include/widget/IScrollWindowListener.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef ISCROLLWINDOWLISTENER_H
|
||||
#define ISCROLLWINDOWLISTENER_H
|
||||
|
||||
#include "core/common.h"
|
||||
#include "core/DragonUtils.h"
|
||||
|
||||
/**
|
||||
* This struct contains the various things to know about the scroll window
|
||||
* when it has been changed.
|
||||
*/
|
||||
typedef struct ScrollWindowUpdate
|
||||
{
|
||||
/**
|
||||
* The coords and size of the scroll window relative to the widget
|
||||
* that implements the scrolling
|
||||
*/
|
||||
Rectangle scrollWindowRectangle;
|
||||
/**
|
||||
* The total size of the panel containing all of the widgets in the scrolling widget.
|
||||
* this includes the parts that aren't visible (because they're not in the scroll window)
|
||||
*/
|
||||
Dimensions totalSize;
|
||||
} ScrollWindowUpdate;
|
||||
|
||||
bool canScrollTo(const ScrollWindowUpdate& info, UINavigationDirection direction);
|
||||
|
||||
/**
|
||||
* This class allows you to react to changes to the scroll window of a widget.
|
||||
* This is useful for showing UI indications like scrollbars or "more" arrows
|
||||
*/
|
||||
class IScrollWindowListener
|
||||
{
|
||||
public:
|
||||
virtual ~IScrollWindowListener();
|
||||
|
||||
virtual void onScrollWindowChanged(const ScrollWindowUpdate& update) = 0;
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
/**
|
||||
* Test implementation that will just print the scroll window update
|
||||
*/
|
||||
class TestScrollWindowListener : public IScrollWindowListener
|
||||
{
|
||||
public:
|
||||
virtual ~TestScrollWindowListener();
|
||||
|
||||
void onScrollWindowChanged(const ScrollWindowUpdate& update) override;
|
||||
protected:
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -5,11 +5,13 @@
|
|||
#include "animations/IAnimation.h"
|
||||
#include <vector>
|
||||
|
||||
typedef std::vector<IWidget*> WidgetList;
|
||||
|
||||
class IScrollWindowListener;
|
||||
class ScrollWidget;
|
||||
class AnimationManager;
|
||||
|
||||
typedef std::vector<IWidget*> WidgetList;
|
||||
typedef std::vector<IScrollWindowListener*> ScrollWindowListenerList;
|
||||
|
||||
typedef struct ScrollWidgetStyle
|
||||
{
|
||||
// it defines the amount of pixels we should scroll when the analog stick or dpad is used.
|
||||
|
|
@ -84,16 +86,8 @@ public:
|
|||
*/
|
||||
void setWindowStart(const Point& windowStart);
|
||||
|
||||
/**
|
||||
* @brief Indicates the percentage at which the current window position is at horizontally
|
||||
* in relation to the entire window
|
||||
*/
|
||||
float getWindowProgressX() const;
|
||||
/**
|
||||
* @brief Indicates the percentage at which the current window position is at vertically
|
||||
* in relation to the entire window
|
||||
*/
|
||||
float getWindowProgressY() const;
|
||||
void registerScrollWindowListener(IScrollWindowListener* listener);
|
||||
void unregisterScrollWindowListener(IScrollWindowListener* listener);
|
||||
protected:
|
||||
private:
|
||||
/**
|
||||
|
|
@ -107,8 +101,11 @@ private:
|
|||
*/
|
||||
void recalculateWindowSize();
|
||||
|
||||
void notifyScrollWindowListeners();
|
||||
|
||||
MoveScrollWidgetWindowAnimation windowAnimation_;
|
||||
WidgetList widgets_;
|
||||
ScrollWindowListenerList scrollWindowListeners_;
|
||||
ScrollWidgetStyle style_;
|
||||
AnimationManager& animManager_;
|
||||
Rectangle bounds_;
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ class RDPQGraphics;
|
|||
class IWidget;
|
||||
class VerticalList;
|
||||
class AnimationManager;
|
||||
struct FocusChangeStatus;
|
||||
class IFocusListener;
|
||||
class IScrollWindowListener;
|
||||
|
||||
typedef std::vector<IWidget*> IWidgetList;
|
||||
typedef std::vector<Rectangle> WidgetBoundsList;
|
||||
|
||||
struct FocusChangeStatus;
|
||||
class IFocusListener;
|
||||
typedef std::vector<IFocusListener*> FocusListenerList;
|
||||
typedef std::vector<IScrollWindowListener*> ScrollWindowListenerList;
|
||||
|
||||
/**
|
||||
* @brief This Animation implementation is used internal in VerticalList
|
||||
|
|
@ -138,6 +139,7 @@ public:
|
|||
bool focusPrevious();
|
||||
|
||||
void addWidget(IWidget* widget);
|
||||
void removeWidget(IWidget* widget);
|
||||
void clearWidgets();
|
||||
|
||||
VerticalListStyle& getStyle();
|
||||
|
|
@ -160,6 +162,10 @@ public:
|
|||
|
||||
void registerFocusListener(IFocusListener* listener);
|
||||
void unregisterFocusListener(IFocusListener* listener);
|
||||
|
||||
void registerScrollWindowListener(IScrollWindowListener* listener);
|
||||
void unregisterScrollWindowListener(IScrollWindowListener* listener);
|
||||
void notifyScrollWindowListeners();
|
||||
protected:
|
||||
private:
|
||||
void rebuildLayout();
|
||||
|
|
@ -176,6 +182,7 @@ private:
|
|||
IWidgetList widgetList_;
|
||||
WidgetBoundsList widgetBoundsList_;
|
||||
FocusListenerList focusListeners_;
|
||||
ScrollWindowListenerList scrollWindowListeners_;
|
||||
VerticalListStyle listStyle_;
|
||||
Rectangle bounds_;
|
||||
uint32_t windowMinY_;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
static uint8_t ANALOG_STICK_THRESHOLD = 30;
|
||||
|
||||
const UINavigationKey determineUINavigationKey(joypad_inputs_t inputs, NavigationInputSourceType sourceType)
|
||||
const UINavigationDirection determineUINavigationDirection(joypad_inputs_t inputs, NavigationInputSourceType sourceType)
|
||||
{
|
||||
if(sourceType == NavigationInputSourceType::ANALOG_STICK || sourceType == NavigationInputSourceType::BOTH)
|
||||
{
|
||||
|
|
@ -13,14 +13,14 @@ const UINavigationKey determineUINavigationKey(joypad_inputs_t inputs, Navigatio
|
|||
{
|
||||
if(absXVal >= ANALOG_STICK_THRESHOLD)
|
||||
{
|
||||
return (inputs.stick_x < 0) ? UINavigationKey::LEFT : UINavigationKey::RIGHT;
|
||||
return (inputs.stick_x < 0) ? UINavigationDirection::LEFT : UINavigationDirection::RIGHT;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(absYVal >= ANALOG_STICK_THRESHOLD)
|
||||
{
|
||||
return (inputs.stick_y < 0) ? UINavigationKey::DOWN : UINavigationKey::UP;
|
||||
return (inputs.stick_y < 0) ? UINavigationDirection::DOWN : UINavigationDirection::UP;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,20 +29,20 @@ const UINavigationKey determineUINavigationKey(joypad_inputs_t inputs, Navigatio
|
|||
{
|
||||
if(inputs.btn.d_down)
|
||||
{
|
||||
return UINavigationKey::DOWN;
|
||||
return UINavigationDirection::DOWN;
|
||||
}
|
||||
if(inputs.btn.d_up)
|
||||
{
|
||||
return UINavigationKey::UP;
|
||||
return UINavigationDirection::UP;
|
||||
}
|
||||
if(inputs.btn.d_left)
|
||||
{
|
||||
return UINavigationKey::LEFT;
|
||||
return UINavigationDirection::LEFT;
|
||||
}
|
||||
if(inputs.btn.d_right)
|
||||
{
|
||||
return UINavigationKey::RIGHT;
|
||||
return UINavigationDirection::RIGHT;
|
||||
}
|
||||
}
|
||||
return UINavigationKey::NONE;
|
||||
return UINavigationDirection::MAX;
|
||||
}
|
||||
|
|
@ -44,23 +44,23 @@ bool AbstractUIScene::handleUserInput(joypad_port_t port, const joypad_inputs_t&
|
|||
{
|
||||
// the widget did not handle the userInput. If we're dealing with a navigation key, we may want to switch focus
|
||||
WidgetFocusChainSegment* nextChainEntry;
|
||||
const UINavigationKey navKey = determineUINavigationKey(inputs, NavigationInputSourceType::BOTH);
|
||||
const UINavigationDirection navDirection = determineUINavigationDirection(inputs, NavigationInputSourceType::BOTH);
|
||||
|
||||
switch(navKey)
|
||||
switch(navDirection)
|
||||
{
|
||||
case UINavigationKey::UP:
|
||||
case UINavigationDirection::UP:
|
||||
nextChainEntry = focusChain_->onUp;
|
||||
break;
|
||||
case UINavigationKey::DOWN:
|
||||
case UINavigationDirection::DOWN:
|
||||
nextChainEntry = focusChain_->onDown;
|
||||
break;
|
||||
case UINavigationKey::LEFT:
|
||||
case UINavigationDirection::LEFT:
|
||||
nextChainEntry = focusChain_->onLeft;
|
||||
break;
|
||||
case UINavigationKey::RIGHT:
|
||||
case UINavigationDirection::RIGHT:
|
||||
nextChainEntry = focusChain_->onRight;
|
||||
break;
|
||||
case UINavigationKey::NONE:
|
||||
case UINavigationDirection::MAX:
|
||||
default:
|
||||
nextChainEntry = nullptr;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@
|
|||
#include "scenes/StatsScene.h"
|
||||
#include "transferpak/TransferPakManager.h"
|
||||
|
||||
static const Rectangle menuListBounds = {20, 20, 280, 0};
|
||||
static const Rectangle imgScrollArrowUpBounds = {.x = 154, .y = 14, .width = 11, .height = 6};
|
||||
static const Rectangle imgScrollArrowDownBounds = {.x = 154, .y = 220, .width = 11, .height = 6};
|
||||
|
||||
static DistributionPokemonListSceneContext* convert(void* context)
|
||||
{
|
||||
return static_cast<DistributionPokemonListSceneContext*>(context);
|
||||
|
|
@ -154,12 +158,17 @@ void DistributionPokemonListScene::setupMenu()
|
|||
},
|
||||
.margin = {
|
||||
.top = 5
|
||||
},
|
||||
.autogrow = {
|
||||
.enabled = true,
|
||||
.maxHeight = 200
|
||||
}
|
||||
};
|
||||
|
||||
menuList_.setStyle(listStyle);
|
||||
menuList_.setBounds(Rectangle{20, 20, 280, 150});
|
||||
menuList_.setBounds(menuListBounds);
|
||||
menuList_.setVisible(true);
|
||||
menuList_.registerScrollWindowListener(this);
|
||||
|
||||
cursorWidget_.setVisible(false);
|
||||
|
||||
|
|
@ -178,6 +187,28 @@ void DistributionPokemonListScene::setupMenu()
|
|||
};
|
||||
|
||||
menuListFiller_.addItems(context_->menuEntries, context_->numMenuEntries, itemStyle);
|
||||
|
||||
const ImageWidgetStyle scrollArrowUpStyle = {
|
||||
.image = {
|
||||
.sprite = uiArrowUpSprite_,
|
||||
.spriteBounds = {0, 0, imgScrollArrowUpBounds.width, imgScrollArrowUpBounds.height}
|
||||
}
|
||||
};
|
||||
|
||||
scrollArrowUp_.setStyle(scrollArrowUpStyle);
|
||||
scrollArrowUp_.setBounds(imgScrollArrowUpBounds);
|
||||
|
||||
const ImageWidgetStyle scrollArrowDownStyle = {
|
||||
.image = {
|
||||
.sprite = uiArrowDownSprite_,
|
||||
.spriteBounds = { 0, 0, imgScrollArrowDownBounds.width, imgScrollArrowDownBounds.height}
|
||||
}
|
||||
};
|
||||
|
||||
// note: even though autogrow is turned on for the vertical list, it doesn't matter for the down arrow.
|
||||
// because when the list is still growing, no scrolling is needed anyway, so the arrow would be invisible anyway.
|
||||
scrollArrowDown_.setStyle(scrollArrowDownStyle);
|
||||
scrollArrowDown_.setBounds(imgScrollArrowDownBounds);
|
||||
}
|
||||
|
||||
void DistributionPokemonListScene::loadDistributionPokemonList()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
#include "menu/MenuFunctions.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
|
||||
static const Rectangle menuListBounds = {100, 30, 150, 0};
|
||||
static const Rectangle imgScrollArrowUpBounds = {.x = 170, .y = 24, .width = 11, .height = 6};
|
||||
static const Rectangle imgScrollArrowDownBounds = {.x = 170, .y = 180, .width = 11, .height = 6};
|
||||
|
||||
static void dialogFinishedCallback(void* context)
|
||||
{
|
||||
|
|
@ -17,7 +22,11 @@ MenuScene::MenuScene(SceneDependencies& deps, void* context)
|
|||
, context_(static_cast<MenuSceneContext*>(context))
|
||||
, menu9SliceSprite_(nullptr)
|
||||
, cursorSprite_(nullptr)
|
||||
, uiArrowUpSprite_(nullptr)
|
||||
, uiArrowDownSprite_(nullptr)
|
||||
, menuList_(deps.animationManager)
|
||||
, scrollArrowUp_()
|
||||
, scrollArrowDown_()
|
||||
, cursorWidget_(deps.animationManager)
|
||||
, menuListFiller_(menuList_)
|
||||
, listFocusChainSegment_(WidgetFocusChainSegment{
|
||||
|
|
@ -47,6 +56,8 @@ void MenuScene::init()
|
|||
// load these sprites before the parent init because setupDialog(style) will need them
|
||||
menu9SliceSprite_ = sprite_load("rom://menu-bg-9slice.sprite");
|
||||
cursorSprite_ = sprite_load("rom://hand-cursor.sprite");
|
||||
uiArrowUpSprite_ = sprite_load("rom://ui-arrow-up.sprite");
|
||||
uiArrowDownSprite_ = sprite_load("rom://ui-arrow-down.sprite");
|
||||
|
||||
SceneWithDialogWidget::init();
|
||||
|
||||
|
|
@ -58,16 +69,25 @@ void MenuScene::init()
|
|||
void MenuScene::destroy()
|
||||
{
|
||||
menuList_.unregisterFocusListener(this);
|
||||
menuList_.unregisterScrollWindowListener(this);
|
||||
menuList_.clearWidgets();
|
||||
menuList_.setStyle({0});
|
||||
cursorWidget_.setStyle({0});
|
||||
scrollArrowUp_.setStyle({0});
|
||||
scrollArrowDown_.setStyle({0});
|
||||
|
||||
// destroy the parent before releasing the sprites because the dialog widget
|
||||
// may still have a reference to them
|
||||
SceneWithDialogWidget::destroy();
|
||||
|
||||
sprite_free(uiArrowDownSprite_);
|
||||
uiArrowDownSprite_ = nullptr;
|
||||
sprite_free(uiArrowUpSprite_);
|
||||
uiArrowUpSprite_ = nullptr;
|
||||
sprite_free(cursorSprite_);
|
||||
cursorSprite_ = nullptr;
|
||||
sprite_free(menu9SliceSprite_);
|
||||
menu9SliceSprite_ = nullptr;
|
||||
}
|
||||
|
||||
void MenuScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds)
|
||||
|
|
@ -75,12 +95,8 @@ void MenuScene::render(RDPQGraphics& gfx, const Rectangle& sceneBounds)
|
|||
menuList_.render(gfx, sceneBounds);
|
||||
cursorWidget_.render(gfx, sceneBounds);
|
||||
SceneWithDialogWidget::render(gfx, sceneBounds);
|
||||
|
||||
TextRenderSettings renderSettings = {
|
||||
.fontId = arialId_,
|
||||
.fontStyleId = fontStyleWhiteId_
|
||||
};
|
||||
gfx.drawText(Rectangle{40, 10, 280, 16}, "PokeMe64 by risingPhil. Version 0.1", renderSettings);
|
||||
scrollArrowUp_.render(gfx, sceneBounds);
|
||||
scrollArrowDown_.render(gfx, sceneBounds);
|
||||
}
|
||||
|
||||
bool MenuScene::handleUserInput(joypad_port_t port, const joypad_inputs_t& inputs)
|
||||
|
|
@ -141,6 +157,12 @@ void MenuScene::focusChanged(const FocusChangeStatus& status)
|
|||
cursorWidget_.moveToBounds(newCursorBounds);
|
||||
}
|
||||
|
||||
void MenuScene::onScrollWindowChanged(const ScrollWindowUpdate& update)
|
||||
{
|
||||
scrollArrowUp_.setVisible(canScrollTo(update, UINavigationDirection::UP));
|
||||
scrollArrowDown_.setVisible(canScrollTo(update, UINavigationDirection::DOWN));
|
||||
}
|
||||
|
||||
SceneDependencies& MenuScene::getDependencies()
|
||||
{
|
||||
return deps_;
|
||||
|
|
@ -173,11 +195,12 @@ void MenuScene::setupMenu()
|
|||
};
|
||||
|
||||
menuList_.setStyle(listStyle);
|
||||
menuList_.setBounds(Rectangle{100, 30, 150, 0});
|
||||
menuList_.setBounds(menuListBounds);
|
||||
menuList_.setVisible(true);
|
||||
cursorWidget_.setStyle(cursorStyle);
|
||||
cursorWidget_.setVisible(true);
|
||||
menuList_.registerFocusListener(this);
|
||||
menuList_.registerScrollWindowListener(this);
|
||||
|
||||
const MenuItemStyle itemStyle = {
|
||||
.size = {150, 16},
|
||||
|
|
@ -202,6 +225,28 @@ void MenuScene::setupMenu()
|
|||
}
|
||||
|
||||
menuListFiller_.addItems(context_->menuEntries, context_->numMenuEntries, itemStyle);
|
||||
|
||||
const ImageWidgetStyle scrollArrowUpStyle = {
|
||||
.image = {
|
||||
.sprite = uiArrowUpSprite_,
|
||||
.spriteBounds = {0, 0, imgScrollArrowUpBounds.width, imgScrollArrowUpBounds.height}
|
||||
}
|
||||
};
|
||||
|
||||
scrollArrowUp_.setStyle(scrollArrowUpStyle);
|
||||
scrollArrowUp_.setBounds(imgScrollArrowUpBounds);
|
||||
|
||||
const ImageWidgetStyle scrollArrowDownStyle = {
|
||||
.image = {
|
||||
.sprite = uiArrowDownSprite_,
|
||||
.spriteBounds = { 0, 0, imgScrollArrowDownBounds.width, imgScrollArrowDownBounds.height}
|
||||
}
|
||||
};
|
||||
|
||||
// note: even though autogrow is turned on for the vertical list, it doesn't matter for the down arrow.
|
||||
// because when the list is still growing, no scrolling is needed anyway, so the arrow would be invisible anyway.
|
||||
scrollArrowDown_.setStyle(scrollArrowDownStyle);
|
||||
scrollArrowDown_.setBounds(imgScrollArrowDownBounds);
|
||||
}
|
||||
|
||||
void MenuScene::setupDialog(DialogWidgetStyle& style)
|
||||
|
|
|
|||
37
src/widget/IScrollWindowListener.cpp
Normal file
37
src/widget/IScrollWindowListener.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "widget/IScrollWindowListener.h"
|
||||
#include <libdragon.h>
|
||||
|
||||
bool canScrollTo(const ScrollWindowUpdate& info, UINavigationDirection direction)
|
||||
{
|
||||
switch(direction)
|
||||
{
|
||||
case UINavigationDirection::UP:
|
||||
return (info.scrollWindowRectangle.y > 0);
|
||||
case UINavigationDirection::RIGHT:
|
||||
return (info.scrollWindowRectangle.x + info.scrollWindowRectangle.width < info.totalSize.width);
|
||||
case UINavigationDirection::DOWN:
|
||||
return (info.scrollWindowRectangle.y + info.scrollWindowRectangle.height < info.totalSize.height);
|
||||
case UINavigationDirection::LEFT:
|
||||
return (info.scrollWindowRectangle.x > 0);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IScrollWindowListener::~IScrollWindowListener()
|
||||
{
|
||||
}
|
||||
|
||||
TestScrollWindowListener::~TestScrollWindowListener()
|
||||
{
|
||||
}
|
||||
|
||||
void TestScrollWindowListener::onScrollWindowChanged(const ScrollWindowUpdate &update)
|
||||
{
|
||||
bool canScroll[4];
|
||||
for(int i=0; i < 4; ++i)
|
||||
{
|
||||
canScroll[i] = canScrollTo(update, static_cast<UINavigationDirection>(i));
|
||||
}
|
||||
debugf("[TestScrollWindowListener]:%s:%p: scrollWindowRectangle: [%d, %d, %d, %d], totalSize: [%d, %d], scrollUp: %d, scrollRight %d, scrollDown %d, scrollLeft %d\r\n", __FUNCTION__, this, update.scrollWindowRectangle.x, update.scrollWindowRectangle.y, update.scrollWindowRectangle.width, update.scrollWindowRectangle.height, update.totalSize.width, update.totalSize.height, canScroll[0], canScroll[1], canScroll[2], canScroll[3]);
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "widget/ScrollWidget.h"
|
||||
#include "widget/IScrollWindowListener.h"
|
||||
#include "core/common.h"
|
||||
#include "scenes/AbstractUIScene.h"
|
||||
#include "animations/AnimationManager.h"
|
||||
|
|
@ -135,6 +136,7 @@ void MoveScrollWidgetWindowAnimation::apply(float pos)
|
|||
ScrollWidget::ScrollWidget(AnimationManager& animManager)
|
||||
: windowAnimation_(this)
|
||||
, widgets_()
|
||||
, scrollWindowListeners_()
|
||||
, style_({0})
|
||||
, animManager_(animManager)
|
||||
, bounds_({0})
|
||||
|
|
@ -194,6 +196,7 @@ void ScrollWidget::addWidget(IWidget* widget)
|
|||
{
|
||||
widgets_.push_back(widget);
|
||||
growWindow(widget);
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
void ScrollWidget::removeWidget(IWidget* widget)
|
||||
|
|
@ -210,6 +213,7 @@ void ScrollWidget::clearWidgets()
|
|||
{
|
||||
widgets_.clear();
|
||||
windowBounds_ = {0};
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
bool ScrollWidget::handleUserInput(const joypad_inputs_t& userInput)
|
||||
|
|
@ -297,18 +301,22 @@ void ScrollWidget::setWindowStart(const Point& windowStart)
|
|||
{
|
||||
windowBounds_.x = windowStart.x;
|
||||
windowBounds_.y = windowStart.y;
|
||||
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
float ScrollWidget::getWindowProgressX() const
|
||||
void ScrollWidget::registerScrollWindowListener(IScrollWindowListener* listener)
|
||||
{
|
||||
const float maxX = windowBounds_.width - bounds_.width;
|
||||
return static_cast<float>(windowBounds_.x) / maxX;
|
||||
scrollWindowListeners_.push_back(listener);
|
||||
}
|
||||
|
||||
float ScrollWidget::getWindowProgressY() const
|
||||
void ScrollWidget::unregisterScrollWindowListener(IScrollWindowListener* listener)
|
||||
{
|
||||
const double maxY = windowBounds_.height - bounds_.height;
|
||||
return static_cast<float>(windowBounds_.y) / maxY;
|
||||
auto it = std::find(scrollWindowListeners_.begin(), scrollWindowListeners_.end(), listener);
|
||||
if(it != scrollWindowListeners_.end())
|
||||
{
|
||||
scrollWindowListeners_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void ScrollWidget::growWindow(IWidget* widget)
|
||||
|
|
@ -336,4 +344,18 @@ void ScrollWidget::recalculateWindowSize()
|
|||
{
|
||||
growWindow(widget);
|
||||
}
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
void ScrollWidget::notifyScrollWindowListeners()
|
||||
{
|
||||
const ScrollWindowUpdate update = {
|
||||
.scrollWindowRectangle = {.x = windowBounds_.x, .y = windowBounds_.y, .width = bounds_.width, .height = bounds_.height},
|
||||
.totalSize = {.width = windowBounds_.width, .height = windowBounds_.height}
|
||||
};
|
||||
|
||||
for(IScrollWindowListener* listener : scrollWindowListeners_)
|
||||
{
|
||||
listener->onScrollWindowChanged(update);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#include "widget/VerticalList.h"
|
||||
#include "widget/IWidget.h"
|
||||
#include "widget/IFocusListener.h"
|
||||
#include "widget/IScrollWindowListener.h"
|
||||
#include "core/RDPQGraphics.h"
|
||||
#include "animations/AnimationManager.h"
|
||||
#include "core/DragonUtils.h"
|
||||
|
|
@ -105,6 +106,7 @@ VerticalList::VerticalList(AnimationManager& animationManager)
|
|||
, widgetList_()
|
||||
, widgetBoundsList_()
|
||||
, focusListeners_()
|
||||
, scrollWindowListeners_()
|
||||
, listStyle_({0})
|
||||
, bounds_({0})
|
||||
, windowMinY_(0)
|
||||
|
|
@ -195,10 +197,48 @@ void VerticalList::addWidget(IWidget *widget)
|
|||
}
|
||||
else
|
||||
{
|
||||
const Rectangle lastWidgetBounds = widgetBoundsList_.back();
|
||||
const Rectangle& lastWidgetBounds = widgetBoundsList_.back();
|
||||
widgetBoundsList_.push_back(Rectangle{.x = 0, .y = lastWidgetBounds.y + lastWidgetBounds.height + listStyle_.verticalSpacingBetweenWidgets, .width = widgetSize.width, .height = widgetSize.height});
|
||||
}
|
||||
autoGrowBounds();
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
void VerticalList::removeWidget(IWidget* widget)
|
||||
{
|
||||
const auto listSize = widgetList_.size();
|
||||
for(uint16_t i = 0; i < listSize; ++i)
|
||||
{
|
||||
if(widgetList_[i] != widget)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//found the widget
|
||||
// if the widget -that we're about to remove- is focused, we need to move the focus to a different widget
|
||||
if(widgetList_[i]->isFocused() && listSize > 1)
|
||||
{
|
||||
// if the to-be-removed widget is the last one in the list, we need to focus the one before it.
|
||||
// if it's not, we'll focus the next one
|
||||
uint16_t newFocusIndex;
|
||||
if(i < (listSize - 1))
|
||||
{
|
||||
newFocusIndex = i + 1;
|
||||
// the next list item will move to the current position after the erase() call
|
||||
focusedWidgetIndex_ = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
newFocusIndex = i - 1;
|
||||
focusedWidgetIndex_ = newFocusIndex;
|
||||
}
|
||||
widgetList_[newFocusIndex]->setFocused(true);
|
||||
}
|
||||
|
||||
widgetList_.erase(widgetList_.begin() + i);
|
||||
widgetBoundsList_.erase(widgetBoundsList_.begin() + i);
|
||||
rebuildLayout();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VerticalList::clearWidgets()
|
||||
|
|
@ -206,6 +246,7 @@ void VerticalList::clearWidgets()
|
|||
widgetList_.clear();
|
||||
widgetBoundsList_.clear();
|
||||
focusedWidgetIndex_ = 0;
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
VerticalListStyle& VerticalList::getStyle()
|
||||
|
|
@ -222,6 +263,7 @@ void VerticalList::setStyle(const VerticalListStyle& style)
|
|||
void VerticalList::setViewWindowStartY(uint32_t windowStartY)
|
||||
{
|
||||
windowMinY_ = windowStartY;
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
bool VerticalList::isFocused() const
|
||||
|
|
@ -296,9 +338,9 @@ bool VerticalList::handleUserInput(const joypad_inputs_t& userInput)
|
|||
return hasFocusedWidgetHandledInput;
|
||||
}
|
||||
|
||||
const UINavigationKey navKey = determineUINavigationKey(userInput, NavigationInputSourceType::BOTH);
|
||||
const UINavigationDirection navDirection = determineUINavigationDirection(userInput, NavigationInputSourceType::BOTH);
|
||||
|
||||
if(navKey == UINavigationKey::UP)
|
||||
if(navDirection == UINavigationDirection::UP)
|
||||
{
|
||||
if(focusedWidgetIndex_ < 1)
|
||||
{
|
||||
|
|
@ -306,7 +348,7 @@ bool VerticalList::handleUserInput(const joypad_inputs_t& userInput)
|
|||
}
|
||||
return focusPrevious();
|
||||
}
|
||||
else if(navKey == UINavigationKey::DOWN)
|
||||
else if(navDirection == UINavigationDirection::DOWN)
|
||||
{
|
||||
if(focusedWidgetIndex_ == widgetList_.size() - 1)
|
||||
{
|
||||
|
|
@ -399,8 +441,52 @@ void VerticalList::unregisterFocusListener(IFocusListener* focusListener)
|
|||
}
|
||||
}
|
||||
|
||||
void VerticalList::registerScrollWindowListener(IScrollWindowListener* listener)
|
||||
{
|
||||
scrollWindowListeners_.push_back(listener);
|
||||
}
|
||||
|
||||
void VerticalList::unregisterScrollWindowListener(IScrollWindowListener* listener)
|
||||
{
|
||||
auto it = std::find(scrollWindowListeners_.begin(), scrollWindowListeners_.end(), listener);
|
||||
if(it != scrollWindowListeners_.end())
|
||||
{
|
||||
scrollWindowListeners_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void VerticalList::notifyScrollWindowListeners()
|
||||
{
|
||||
int totalHeight;
|
||||
|
||||
if(widgetList_.empty())
|
||||
{
|
||||
totalHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const Rectangle& lastWidgetBounds = widgetBoundsList_.back();
|
||||
totalHeight = lastWidgetBounds.y + lastWidgetBounds.height;
|
||||
}
|
||||
|
||||
// calculate the size of the scroll panel with the margins subtracted
|
||||
const uint32_t innerListHeight = getInnerListHeight(bounds_, listStyle_.margin.top, listStyle_.margin.bottom);
|
||||
|
||||
const ScrollWindowUpdate update = {
|
||||
.scrollWindowRectangle = {.x = 0, .y = static_cast<int>(windowMinY_), .width = bounds_.width, .height = static_cast<int>(innerListHeight)},
|
||||
.totalSize = {.width = bounds_.width, .height = totalHeight}
|
||||
};
|
||||
|
||||
for(IScrollWindowListener* listener : scrollWindowListeners_)
|
||||
{
|
||||
listener->onScrollWindowChanged(update);
|
||||
}
|
||||
}
|
||||
|
||||
void VerticalList::rebuildLayout()
|
||||
{
|
||||
Rectangle focusedWidgetBounds;
|
||||
IWidget* focusedWidget;
|
||||
int lastWidgetEndY = 0;
|
||||
if(widgetList_.empty())
|
||||
{
|
||||
|
|
@ -415,6 +501,27 @@ void VerticalList::rebuildLayout()
|
|||
widgetBoundsList_[i] = {.x = 0, .y = lastWidgetEndY, .width = widgetSize.width, .height = widgetSize.height};
|
||||
lastWidgetEndY += widgetSize.height + listStyle_.verticalSpacingBetweenWidgets;
|
||||
}
|
||||
|
||||
if(!widgetList_.empty())
|
||||
{
|
||||
focusedWidgetBounds = widgetBoundsList_[focusedWidgetIndex_];
|
||||
focusedWidget = widgetList_[focusedWidgetIndex_];
|
||||
}
|
||||
else
|
||||
{
|
||||
// HACK: safety net in case the list is empty (to avoid an out-of-bounds list access crash)
|
||||
// the use of windowMinY_ here is to compensate for what calculateListWidgetBounds is calculating
|
||||
focusedWidgetBounds = Rectangle{.x = 0, .y = static_cast<int>(windowMinY_), .width = 0, .height = 0};
|
||||
focusedWidget = nullptr;
|
||||
}
|
||||
|
||||
const FocusChangeStatus changeStatus = {
|
||||
.focusBounds = calculateListWidgetBounds(focusedWidgetBounds, windowMinY_, bounds_.x + listStyle_.margin.left, bounds_.y + listStyle_.margin.top),
|
||||
.curFocus = focusedWidget
|
||||
};
|
||||
|
||||
notifyFocusListeners(changeStatus);
|
||||
notifyScrollWindowListeners();
|
||||
}
|
||||
|
||||
int32_t VerticalList::scrollWindowToFocusedWidget()
|
||||
|
|
@ -447,7 +554,7 @@ void VerticalList::notifyFocusListeners(const FocusChangeStatus& status)
|
|||
|
||||
void VerticalList::autoGrowBounds()
|
||||
{
|
||||
if(!listStyle_.autogrow.enabled)
|
||||
if(!listStyle_.autogrow.enabled || widgetList_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user