mirror of
https://github.com/risingPhil/PokeMe64.git
synced 2026-04-24 15:06:57 -05:00
Create PokemonPartyIconWidget for displaying the party menu icon in the distribution event pokemon menu (untested)
This commit is contained in:
parent
ddafa46dfc
commit
7373337e2e
156
include/widget/PokemonPartyIconWidget.h
Normal file
156
include/widget/PokemonPartyIconWidget.h
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#ifndef _POKEMONPARTYICONWIDGET_H
|
||||
#define _POKEMONPARTYICONWIDGET_H
|
||||
|
||||
#include "widget/IWidget.h"
|
||||
#include "core/Sprite.h"
|
||||
#include "gen1/Gen1Common.h"
|
||||
#include "gen2/Gen2Common.h"
|
||||
|
||||
class IRomReader;
|
||||
class ISaveManager;
|
||||
|
||||
typedef struct PokemonPartyIconWidgetStyle
|
||||
{
|
||||
struct {
|
||||
/**
|
||||
* (optional) background sprite
|
||||
*/
|
||||
sprite_t* sprite;
|
||||
/*
|
||||
* RenderSettings that influence how the backgroundSprite is
|
||||
* being rendered
|
||||
*/
|
||||
SpriteRenderSettings spriteSettings;
|
||||
} background;
|
||||
struct {
|
||||
Rectangle bounds;
|
||||
uint8_t yOffsetWhenTheresNoFrame2;
|
||||
} icon;
|
||||
} PokemonPartyIconWidgetStyle;
|
||||
|
||||
typedef struct PokemonPartyIconWidgetData
|
||||
{
|
||||
/**
|
||||
* The generation of the pokemon game (gen 1/2)
|
||||
*/
|
||||
uint8_t generation;
|
||||
/**
|
||||
* The specific variant of the game
|
||||
*/
|
||||
uint8_t specificGenVersion;
|
||||
/**
|
||||
* The pokemon icon type we need to display
|
||||
*/
|
||||
uint8_t iconType;
|
||||
uint8_t fpsWhenFocused;
|
||||
uint8_t fpsWhenNotFocused;
|
||||
} PokemonPartyIconWidgetData;
|
||||
|
||||
/**
|
||||
* This factory class provides a function to obtain a surface_t instance for a given icon.
|
||||
* It acts as a form of cache to prevent decoding/allocating the same icon more than once if multiple
|
||||
* instances of PokemonPartyIconWidget are created.
|
||||
*/
|
||||
class PokemonPartyIconFactory
|
||||
{
|
||||
public:
|
||||
typedef struct IconMapEntry
|
||||
{
|
||||
surface_t frame1;
|
||||
surface_t frame2;
|
||||
} IconMapEntry;
|
||||
|
||||
PokemonPartyIconFactory(IRomReader& romReader);
|
||||
~PokemonPartyIconFactory();
|
||||
|
||||
surface_t getIcon(uint8_t generation, uint8_t specificGenType, uint8_t iconType, bool firstFrame);
|
||||
protected:
|
||||
private:
|
||||
// This is the map of icons. Gen2 has the most icons, that's why I'm using GEN2_ICONTYPE_MAX as the array size
|
||||
IconMapEntry iconMap_[(unsigned)Gen2PokemonIconType::GEN2_ICONTYPE_MAX];
|
||||
IRomReader& romReader_;
|
||||
};
|
||||
|
||||
class PokemonPartyIconWidget : public IWidget
|
||||
{
|
||||
public:
|
||||
PokemonPartyIconWidget(PokemonPartyIconFactory& iconFactory);
|
||||
~PokemonPartyIconWidget();
|
||||
|
||||
void setStyle(const PokemonPartyIconWidgetStyle& style);
|
||||
void setData(const PokemonPartyIconWidgetData& data);
|
||||
|
||||
/**
|
||||
* @brief Returns whether the widget is currently focused
|
||||
*/
|
||||
bool isFocused() const override;
|
||||
|
||||
/**
|
||||
* @brief Sets whether the widget is currently focused
|
||||
*
|
||||
*/
|
||||
void setFocused(bool isFocused) override;
|
||||
|
||||
/**
|
||||
* @brief Returns whether the widget is currently visible
|
||||
*/
|
||||
bool isVisible() const override;
|
||||
|
||||
/**
|
||||
* @brief Changes the visibility of the widget
|
||||
*/
|
||||
void setVisible(bool visible) override;
|
||||
|
||||
/**
|
||||
* @brief Returns the current (relative) bounds of the widget
|
||||
*/
|
||||
Rectangle getBounds() const override;
|
||||
|
||||
/**
|
||||
* @brief Changes the current (relative) bounds of the widget
|
||||
*/
|
||||
void setBounds(const Rectangle& bounds) override;
|
||||
|
||||
/**
|
||||
* @brief Returns the size (width/height) of the widget
|
||||
*/
|
||||
Dimensions getSize() const override;
|
||||
|
||||
/**
|
||||
* @brief Handles user input
|
||||
*
|
||||
* For button presses, it is advised to track button release situations instead of
|
||||
* button presses for executing an action. Otherwise the key press might be handled again immediately
|
||||
* in the next scene/widget because the user wouldn't have had the time to actually release the key.
|
||||
*/
|
||||
bool handleUserInput(const joypad_inputs_t& userInput) override;
|
||||
|
||||
/**
|
||||
* @brief Renders the widget
|
||||
*
|
||||
* @param gfx The graphics instance that must be used to render the widget
|
||||
* @param parentBounds The bounds of the parent widget or scene. You must add the x,y offset of your own bounds
|
||||
* to the parentBounds to get the absolute bounds for rendering.
|
||||
*
|
||||
* Getting the parentBounds as an argument of this function was done because a parent widget may be
|
||||
* animated or change positions independent of the child widget. But when the parent widget moves, the child must as well!
|
||||
*/
|
||||
void render(RDPQGraphics& gfx, const Rectangle& parentBounds) override;
|
||||
protected:
|
||||
private:
|
||||
void reset();
|
||||
|
||||
PokemonPartyIconWidgetStyle style_;
|
||||
PokemonPartyIconWidgetData data_;
|
||||
surface_t iconFrame1_;
|
||||
surface_t iconFrame2_;
|
||||
Rectangle bounds_;
|
||||
uint32_t frameSwitchTimeoutInTicks_;
|
||||
uint64_t nextFrameSwitchTime_;
|
||||
PokemonPartyIconFactory& iconFactory_;
|
||||
bool focused_;
|
||||
bool visible_;
|
||||
bool showFirstIconFrame_;
|
||||
};
|
||||
|
||||
#endif
|
||||
235
src/widget/PokemonPartyIconWidget.cpp
Normal file
235
src/widget/PokemonPartyIconWidget.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#include "widget/PokemonPartyIconWidget.h"
|
||||
#include "core/RDPQGraphics.h"
|
||||
#include "gen1/Gen1GameReader.h"
|
||||
#include "gen2/Gen2GameReader.h"
|
||||
#include "SaveManager.h"
|
||||
#include "SpriteRenderer.h"
|
||||
|
||||
#define DEFAULT_FPS_WHEN_FOCUSED 4
|
||||
#define DEFAULT_FPS_WHEN_NOT_FOCUSED 2
|
||||
|
||||
static uint32_t calculateFrameSwitchTimeout(uint8_t fps)
|
||||
{
|
||||
return TICKS_FROM_MS(1000u / fps);
|
||||
}
|
||||
|
||||
PokemonPartyIconFactory::PokemonPartyIconFactory(IRomReader& romReader)
|
||||
: iconMap_()
|
||||
, romReader_(romReader)
|
||||
{
|
||||
memset(iconMap_, 0, sizeof(IconMapEntry) * Gen2PokemonIconType::GEN2_ICONTYPE_MAX);
|
||||
}
|
||||
|
||||
PokemonPartyIconFactory::~PokemonPartyIconFactory()
|
||||
{
|
||||
for(uint8_t i = 0; i < (uint8_t)Gen2PokemonIconType::GEN2_ICONTYPE_MAX; ++i)
|
||||
{
|
||||
if(iconMap_[i].frame1.buffer)
|
||||
{
|
||||
surface_free(&iconMap_[i].frame1);
|
||||
}
|
||||
if(iconMap_[i].frame2.buffer)
|
||||
{
|
||||
surface_free(&iconMap_[i].frame2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
surface_t PokemonPartyIconFactory::getIcon(uint8_t generation, uint8_t specificGenType, uint8_t iconType, bool firstFrame)
|
||||
{
|
||||
BufferBasedSaveManager dummy(nullptr, 0);
|
||||
SpriteRenderer spriteRenderer;
|
||||
surface_t result = (firstFrame) ? iconMap_[iconType].frame1 : iconMap_[iconType].frame2;
|
||||
const uint8_t* outputBuffer = nullptr;
|
||||
uint8_t iconWidthInPixels;
|
||||
uint8_t iconHeightInPixels;
|
||||
// For RGBA16, we have 2 bytes per color
|
||||
const uint8_t bytesPerColor = 2;
|
||||
|
||||
if(result.buffer)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if(generation == 1)
|
||||
{
|
||||
Gen1GameReader gameReader(romReader_, dummy, (Gen1GameType)specificGenType);
|
||||
iconWidthInPixels = GEN1_ICON_WIDTH_IN_TILES * 8;
|
||||
iconHeightInPixels = GEN1_ICON_HEIGHT_IN_TILES * 8;
|
||||
|
||||
const uint8_t* spriteBuffer = gameReader.decodePokemonIcon((Gen1PokemonIconType)iconType, firstFrame);
|
||||
if(!spriteBuffer)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
spriteRenderer.draw(spriteBuffer, SpriteRenderer::OutputFormat::RGBA16, monochromeGBColorPalette, GEN1_ICON_WIDTH_IN_TILES, GEN1_ICON_HEIGHT_IN_TILES);
|
||||
outputBuffer = spriteRenderer.removeWhiteBackground(GEN1_ICON_WIDTH_IN_TILES, GEN1_ICON_HEIGHT_IN_TILES);
|
||||
}
|
||||
else if(generation == 2)
|
||||
{
|
||||
Gen2GameReader gameReader(romReader_, dummy, (Gen2GameType)specificGenType);
|
||||
iconWidthInPixels = GEN2_ICON_WIDTH_IN_TILES * 8;
|
||||
iconHeightInPixels = GEN2_ICON_HEIGHT_IN_TILES * 8;
|
||||
|
||||
const uint8_t* spriteBuffer = gameReader.decodePokemonIcon((Gen2PokemonIconType)iconType, firstFrame);
|
||||
if(!spriteBuffer)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
spriteRenderer.draw(spriteBuffer, SpriteRenderer::OutputFormat::RGBA16, gen2_iconColorPalette, GEN2_ICON_WIDTH_IN_TILES, GEN2_ICON_HEIGHT_IN_TILES);
|
||||
outputBuffer = spriteRenderer.removeWhiteBackground(GEN2_ICON_WIDTH_IN_TILES, GEN2_ICON_HEIGHT_IN_TILES);
|
||||
}
|
||||
else
|
||||
{
|
||||
debugf("Invalid generation %hu!\n", generation);
|
||||
return result;
|
||||
}
|
||||
|
||||
// due to alignment constraints, we should use the surface_alloc function to ensure that it is done correctly
|
||||
// and respect its resulting stride field
|
||||
result = surface_alloc(FMT_RGBA16, iconWidthInPixels, iconHeightInPixels);
|
||||
|
||||
//now copy the sprite to the surface, line by line
|
||||
const uint32_t actualStride = iconWidthInPixels * bytesPerColor;
|
||||
for(uint8_t i=0; i < iconHeightInPixels; ++i)
|
||||
{
|
||||
const uint8_t* src = outputBuffer + (i * actualStride);
|
||||
uint8_t* dst = ((uint8_t*)(result.buffer)) + (i * result.stride);
|
||||
// copy the source buffer to the surface line by line, but advancing with the surface stride on every new row
|
||||
memcpy(dst, src, actualStride);
|
||||
}
|
||||
|
||||
// now we have a valid surface_t. Let's store it in our map
|
||||
if(firstFrame)
|
||||
{
|
||||
iconMap_[iconType].frame1 = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
iconMap_[iconType].frame2 = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
PokemonPartyIconWidget::PokemonPartyIconWidget(PokemonPartyIconFactory& iconFactory)
|
||||
: style_({0})
|
||||
, data_({0})
|
||||
, iconFrame1_({0})
|
||||
, iconFrame2_({0})
|
||||
, bounds_({0})
|
||||
, frameSwitchTimeoutInTicks_(0)
|
||||
, nextFrameSwitchTime_(0)
|
||||
, iconFactory_(iconFactory)
|
||||
, focused_(false)
|
||||
, visible_(true)
|
||||
, showFirstIconFrame_(true)
|
||||
{
|
||||
}
|
||||
|
||||
PokemonPartyIconWidget::~PokemonPartyIconWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::setStyle(const PokemonPartyIconWidgetStyle& style)
|
||||
{
|
||||
style_ = style;
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::setData(const PokemonPartyIconWidgetData& data)
|
||||
{
|
||||
data_ = data;
|
||||
iconFrame1_ = iconFactory_.getIcon(data_.generation, data_.specificGenVersion, data_.iconType, true);
|
||||
iconFrame2_ = iconFactory_.getIcon(data_.generation, data_.specificGenVersion, data_.iconType, false);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
bool PokemonPartyIconWidget::isFocused() const
|
||||
{
|
||||
return focused_;
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::setFocused(bool focused)
|
||||
{
|
||||
focused_ = focused;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
bool PokemonPartyIconWidget::isVisible() const
|
||||
{
|
||||
return visible_;
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::setVisible(bool visible)
|
||||
{
|
||||
visible_ = visible;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
Rectangle PokemonPartyIconWidget::getBounds() const
|
||||
{
|
||||
return bounds_;
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::setBounds(const Rectangle& bounds)
|
||||
{
|
||||
bounds_ = bounds;
|
||||
}
|
||||
|
||||
Dimensions PokemonPartyIconWidget::getSize() const
|
||||
{
|
||||
return Dimensions{bounds_.width, bounds_.height};
|
||||
}
|
||||
|
||||
bool PokemonPartyIconWidget::handleUserInput(const joypad_inputs_t&)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::render(RDPQGraphics& gfx, const Rectangle& parentBounds)
|
||||
{
|
||||
const SurfaceRenderSettings renderSettings = { 0 };
|
||||
const Rectangle absoluteBounds = addOffset(bounds_, parentBounds);
|
||||
const Rectangle spriteBounds = addOffset(absoluteBounds, style_.icon.bounds);
|
||||
|
||||
const uint64_t currentTicks = get_ticks();
|
||||
if(currentTicks >= nextFrameSwitchTime_)
|
||||
{
|
||||
showFirstIconFrame_ = !showFirstIconFrame_;
|
||||
nextFrameSwitchTime_ += frameSwitchTimeoutInTicks_;
|
||||
}
|
||||
|
||||
if(style_.background.sprite)
|
||||
{
|
||||
gfx.drawSprite(absoluteBounds, style_.background.sprite, style_.background.spriteSettings);
|
||||
}
|
||||
|
||||
if(showFirstIconFrame_)
|
||||
{
|
||||
gfx.drawSurface(spriteBounds, &iconFrame1_, renderSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(iconFrame2_.buffer)
|
||||
{
|
||||
gfx.drawSurface(spriteBounds, &iconFrame2_, renderSettings);
|
||||
}
|
||||
else
|
||||
{
|
||||
const Rectangle modifiedSpriteBounds = { .x = spriteBounds.x, .y = spriteBounds.y + style_.icon.yOffsetWhenTheresNoFrame2, .width = spriteBounds.width, .height = spriteBounds.height };
|
||||
gfx.drawSurface(modifiedSpriteBounds, &iconFrame1_, renderSettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PokemonPartyIconWidget::reset()
|
||||
{
|
||||
const uint8_t fps = (focused_) ? data_.fpsWhenFocused : data_.fpsWhenNotFocused;
|
||||
frameSwitchTimeoutInTicks_ = calculateFrameSwitchTimeout(fps);
|
||||
nextFrameSwitchTime_ = get_ticks() + frameSwitchTimeoutInTicks_;
|
||||
|
||||
showFirstIconFrame_ = true;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user