diff --git a/out/boot.dol b/out/boot.dol index 25f692ed..83897f4a 100644 Binary files a/out/boot.dol and b/out/boot.dol differ diff --git a/source/gui/coverflow.cpp b/source/gui/coverflow.cpp index 10a4c9f4..a9b15d12 100644 --- a/source/gui/coverflow.cpp +++ b/source/gui/coverflow.cpp @@ -236,6 +236,7 @@ bool CCoverFlow::init(const u8 *font, const u32 font_size, bool vid_50hz) // Load font m_font.fromBuffer(font, font_size, TITLEFONT); m_fontColor = CColor(0xFFFFFFFF); + m_fanartFontColor = CColor(0xFFFFFFFF); if(vid_50hz) { @@ -497,6 +498,16 @@ void CCoverFlow::setColors(bool selected, const CColor &begColor, const CColor & lo.mouseOffColor = offColor; } +void CCoverFlow::setFanartPlaying(const bool isPlaying) +{ + m_fanartPlaying = isPlaying; +} + +void CCoverFlow::setFanartTextColor(const CColor textColor) +{ + m_fanartFontColor = textColor; +} + void CCoverFlow::setMirrorAlpha(float cover, float title) { m_mirrorAlpha = cover; @@ -1170,7 +1181,7 @@ void CCoverFlow::_drawTitle(int i, bool mirror, bool rectangle) Mtx modelMtx; Mtx modelViewMtx; Vector3D rotAxis(0.f, 1.f, 0.f); - CColor color(m_fontColor); + CColor color(m_fanartPlaying ? m_fanartFontColor : m_fontColor); if (m_hideCover) return; if (m_covers[i].txtColor == 0) return; diff --git a/source/gui/coverflow.hpp b/source/gui/coverflow.hpp index d33c3e49..48680e14 100644 --- a/source/gui/coverflow.hpp +++ b/source/gui/coverflow.hpp @@ -106,6 +106,8 @@ public: void setTitleWidth(bool selected, float side, float center); void setTitleStyle(bool selected, u16 side, u16 center); void setColors(bool selected, const CColor &begColor, const CColor &endColor, const CColor &offColor); + void setFanartPlaying(const bool isPlaying); + void setFanartTextColor(const CColor textColor); void setShadowColors(bool selected, const CColor ¢erColor, const CColor &begColor, const CColor &endColor, const CColor &offColor); void setShadowPos(float scale, float x, float y); void setMirrorAlpha(float cover, float title); @@ -272,6 +274,8 @@ private: u32 m_numBufCovers; SFont m_font; CColor m_fontColor; + CColor m_fanartFontColor; + bool m_fanartPlaying; bool m_box; bool m_smallBox; bool m_useHQcover; diff --git a/source/gui/fanart.cpp b/source/gui/fanart.cpp new file mode 100644 index 00000000..51a2a815 --- /dev/null +++ b/source/gui/fanart.cpp @@ -0,0 +1,314 @@ +#include "fanart.hpp" +#include "pngu.h" +#include "boxmesh.hpp" +#include "text.hpp" +#include "gecko/gecko.hpp" +#include "memory/mem2.hpp" +using namespace std; + +static guVector _GRRaxisx = (guVector){1, 0, 0}; // DO NOT MODIFY!!! +static guVector _GRRaxisy = (guVector){0, 1, 0}; // Even at runtime +static guVector _GRRaxisz = (guVector){0, 0, 1}; // NOT ever! + +CFanart::CFanart(void) + : m_animationComplete(false), m_loaded(false), m_cfg(), m_bg(), m_bglq() +{ +} + +CFanart::~CFanart(void) +{ +} + +void CFanart::unload() +{ + m_cfg.unload(); + m_loaded = false; + for(vector::iterator Elm = m_elms.begin(); Elm != m_elms.end(); Elm++) + Elm->Cleanup(); + m_elms.clear(); + TexHandle.Cleanup(m_bg); + TexHandle.Cleanup(m_bglq); +} + +bool CFanart::load(Config &m_globalConfig, const char *path, const char *id) +{ + bool retval = false; + + if(!m_globalConfig.getBool("FANART", "enable_fanart", true)) + return retval; + + unload(); + + char dir[64]; + dir[63] = '\0'; + strncpy(dir, fmt("%s/%s", path, id), 63); + + TexErr texErr = TexHandle.fromImageFile(m_bg, fmt("%s/background.png", dir)); + if(texErr == TE_ERROR) + { + strncpy(dir, fmt("%s/%.3s", path, id), 63); + texErr = TexHandle.fromImageFile(m_bg, fmt("%s/background.png", dir)); + } + if(texErr == TE_OK) + { + char cfg_char[64]; + cfg_char[63] = '\0'; + strncpy(cfg_char, fmt("%s/%s.ini", dir, id), 63); + m_cfg.load(cfg_char); + if(!m_cfg.loaded()) + { + strncpy(cfg_char, fmt("%s/%.3s.ini", dir, id), 63); + m_cfg.load(cfg_char); + if(!m_cfg.loaded()) + return retval; + } + TexHandle.fromImageFile(m_bglq, fmt("%s/background_lq.png", dir)); + for(int i = 1; i <= 6; i++) + { + CFanartElement elm(m_cfg, dir, i); + if (elm.IsValid()) m_elms.push_back(elm); + } + m_loaded = true; + retval = true; + m_defaultDelay = m_globalConfig.getInt("FANART", "delay_after_animation", 200); + m_delayAfterAnimation = m_cfg.getInt("GENERAL", "delay_after_animation", m_defaultDelay); + m_allowArtworkOnTop = m_globalConfig.getBool("FANART", "allow_artwork_on_top", true); + m_globalHideCover = m_globalConfig.getOptBool("FANART", "hidecover", 2); // 0 is false, 1 is true, 2 is default + m_globalShowCoverAfterAnimation = m_globalConfig.getOptBool("FANART", "show_cover_after_animation", 2); + } + return retval; +} + +void CFanart::getBackground(const TexData * &hq, const TexData * &lq) +{ + if(m_loaded) + { + hq = &m_bg; + lq = &m_bglq; + } + if(lq == NULL || lq->data == NULL) + lq = hq; +} + +CColor CFanart::getTextColor(CColor themeTxtColor) +{ + return m_loaded ? m_cfg.getColor("GENERAL", "textcolor", CColor(themeTxtColor)) : themeTxtColor; +} + +bool CFanart::hideCover() +{ + if(!m_loaded) + return false; // If no fanart is loaded, return false +/* + +fanart_hidecover defaults to True +fanart_showafter defaults to False + + hideCover | fanart_hideCover | showAfter | fanart_showAfter | animating | hide +1 True * * * * True +2 False * * * * False +3 default False * * * False +4 default default/True True * True True +5 default default/True True * False False +6 default default/True False * * True +7 default default/True default True True True +8 default default/True default True False False +9 * * * * * True +*/ + // rules 1 and 2 + if(m_globalHideCover != 2) + return m_globalHideCover == 1; + // rule 3 + if(!m_cfg.getBool("GENERAL", "hidecover", true)) + return false; + // rules 4, 5 and 6 + if(m_globalShowCoverAfterAnimation != 2) + return m_globalShowCoverAfterAnimation == 0 || !isAnimationComplete(); + // rules 7 and 8 + if(m_cfg.getBool("GENERAL", "show_cover_after_animation", false)) + return !isAnimationComplete(); + // rule 9 + return true; +} + +bool CFanart::isLoaded() +{ + return m_loaded; +} + +bool CFanart::isAnimationComplete() +{ + return m_animationComplete && m_delayAfterAnimation <= 0; +} + +void CFanart::tick() +{ + m_animationComplete = true; + for(u32 i = 0; i < m_elms.size(); ++i) + { + m_elms[i].tick(); + if(!m_elms[i].IsAnimationComplete()) + m_animationComplete = false; + } + if(m_animationComplete && m_delayAfterAnimation > 0) + m_delayAfterAnimation--; +} + +void CFanart::draw(bool front) +{ + if(!m_loaded) return; //derp + if(!m_allowArtworkOnTop && front) + return; // It's not allowed to draw fanart on top, it has already been drawn + + GX_SetNumChans(1); + GX_ClearVtxDesc(); + GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT); + GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); + GX_SetNumTexGens(1); + GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); + GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); + GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); + GX_SetAlphaUpdate(GX_TRUE); + GX_SetCullMode(GX_CULL_NONE); + GX_SetZMode(GX_DISABLE, GX_LEQUAL, GX_TRUE); + + for(u32 i = 0; i < m_elms.size(); ++i) + if(!m_allowArtworkOnTop || ((front && m_elms[i].ShowOnTop()) || !front)) + m_elms[i].draw(); +} + +CFanartElement::CFanartElement(Config &cfg, const char *dir, int artwork) + : m_artwork(artwork), m_isValid(false) +{ + m_isValid = (TexHandle.fromImageFile(m_art, fmt("%s/artwork%d.png", dir, artwork)) == TE_OK); + if(!m_isValid) + return; + + char *section = fmt_malloc("artwork%d", artwork); + if(section == NULL) + return; + + m_show_on_top = cfg.getBool(section, "show_on_top", true); + + m_x = cfg.getInt(section, "x", 0); + m_y = cfg.getInt(section, "y", 0); + m_scaleX = cfg.getFloat(section, "scale_x", 1.f); + m_scaleY = cfg.getFloat(section, "scale_y", 1.f); + m_alpha = min(cfg.getInt(section, "alpha", 255), 255); + m_delay = (int) (cfg.getFloat(section, "delay", 0.f) * 50); + m_angle = cfg.getFloat(section, "angle", 0.f); + + m_event_duration = (int) (cfg.getFloat(section, "duration", 0.f) * 50); + m_event_x = m_event_duration == 0 ? m_x : cfg.getInt(section, "event_x", m_x); + m_event_y = m_event_duration == 0 ? m_y : cfg.getInt(section, "event_y", m_y); + m_event_scaleX = m_event_duration == 0 ? m_scaleX : cfg.getInt(section, "event_scale_x", m_scaleX); + m_event_scaleY = m_event_duration == 0 ? m_scaleY : cfg.getInt(section, "event_scale_y", m_scaleY); + m_event_alpha = m_event_duration == 0 ? m_alpha : min(cfg.getInt(section, "event_alpha", m_alpha), 255); // Not from m_alpha, because the animation can start less translucent than m_alpha + m_event_angle = m_event_duration == 0 ? m_angle : cfg.getFloat(section, "event_angle", m_angle); + + m_step_x = m_event_duration == 0 ? 0 : (m_x - m_event_x) / m_event_duration; + m_step_y = m_event_duration == 0 ? 0 : (m_y - m_event_y) / m_event_duration; + m_step_scaleX = m_event_duration == 0 ? 0 : (m_scaleX - m_event_scaleX) / m_event_duration; + m_step_scaleY = m_event_duration == 0 ? 0 : (m_scaleY - m_event_scaleY) / m_event_duration; + m_step_alpha = m_event_duration == 0 ? 0 : (m_alpha - m_event_alpha) / m_event_duration; + m_step_angle = m_event_duration == 0 ? 0 : (m_angle - m_event_angle) / m_event_duration; + + MEM2_free(section); +} + +void CFanartElement::Cleanup(void) +{ + TexHandle.Cleanup(m_art); +} + +bool CFanartElement::IsValid() +{ + return m_isValid; +} + +bool CFanartElement::IsAnimationComplete() +{ + return m_event_duration == 0; +} + +bool CFanartElement::ShowOnTop() +{ + return m_show_on_top; +} + +void CFanartElement::tick() +{ + if(m_delay > 0) + { + m_delay--; + return; + } + + if((m_step_x < 0 && m_event_x > m_x) || (m_step_x > 0 && m_event_x < m_x)) + m_event_x = (int) (m_event_x + m_step_x); + if((m_step_y < 0 && m_event_y > m_y) || (m_step_y > 0 && m_event_y < m_y)) + m_event_y = (int) (m_event_y + m_step_y); + if((m_step_alpha < 0 && m_event_alpha > m_alpha) || (m_step_alpha > 0 && m_event_alpha < m_alpha)) + m_event_alpha = (int) (m_event_alpha + m_step_alpha); + if((m_step_scaleX < 0 && m_event_scaleX > m_scaleX) || (m_step_scaleX > 0 && m_event_scaleX < m_scaleX)) + m_event_scaleX += m_step_scaleX; + if((m_step_scaleY < 0 && m_event_scaleY > m_scaleY) || (m_step_scaleY > 0 && m_event_scaleY < m_scaleY)) + m_event_scaleY += m_step_scaleY; + if((m_step_angle < 0 && m_event_angle > m_angle) || (m_step_angle > 0 && m_event_angle < m_angle)) + m_event_angle = (int) (m_event_angle + m_step_angle); + + if(m_event_duration > 0) + m_event_duration--; +} + +void CFanartElement::draw() +{ + if(m_event_alpha == 0 || m_event_scaleX == 0.f || m_event_scaleY == 0.f || m_delay > 0) + return; + + GXTexObj artwork; + Mtx modelViewMtx, idViewMtx, rotViewMtxZ; + + guMtxIdentity(idViewMtx); + guMtxScaleApply(idViewMtx, idViewMtx, m_event_scaleX, m_event_scaleY, 1.f); + + guMtxRotAxisDeg(rotViewMtxZ, &_GRRaxisz, m_event_angle); + guMtxConcat(rotViewMtxZ, idViewMtx, modelViewMtx); + + guMtxTransApply(modelViewMtx, modelViewMtx, m_event_x, m_event_y, 0.f); + GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); + + GX_InitTexObj(&artwork, m_art.data, m_art.width, m_art.height, m_art.format, GX_CLAMP, GX_CLAMP, GX_FALSE); + GX_LoadTexObj(&artwork, GX_TEXMAP0); + + float w = (float)(m_art.width / 2); // * m_event_scaleX; + float h = (float)(m_art.height / 2); // * m_event_scaleY; + + GX_Begin(GX_QUADS, GX_VTXFMT0, 4); + + // Draw top left + GX_Position3f32(-w, -h, 0.f); + GX_Color4u8(0xFF, 0xFF, 0xFF, m_event_alpha); + GX_TexCoord2f32(0.f, 0.f); + + // Draw top right + GX_Position3f32(w, -h, 0.f); + GX_Color4u8(0xFF, 0xFF, 0xFF, m_event_alpha); + GX_TexCoord2f32(1.f, 0.f); + + // Draw bottom right + GX_Position3f32(w, h, 0.f); + GX_Color4u8(0xFF, 0xFF, 0xFF, m_event_alpha); + GX_TexCoord2f32(1.f, 1.f); + + // Draw bottom left + GX_Position3f32(-w, h, 0.f); + GX_Color4u8(0xFF, 0xFF, 0xFF, m_event_alpha); + GX_TexCoord2f32(0.f, 1.f); + GX_End(); +} diff --git a/source/gui/fanart.hpp b/source/gui/fanart.hpp new file mode 100644 index 00000000..0fa3ea1f --- /dev/null +++ b/source/gui/fanart.hpp @@ -0,0 +1,91 @@ +// Fan Art + +#ifndef __FANART_HPP +#define __FANART_HPP + +#include +#include +#include + +#include "gui.hpp" +#include "texture.hpp" +#include "config/config.hpp" + +class CFanartElement +{ +public: + CFanartElement(Config &cfg, const char *dir, int artwork); + void Cleanup(void); + + void draw(); + void tick(); + + bool IsValid(); + bool IsAnimationComplete(); + bool ShowOnTop(); +private: + TexData m_art; + int m_artwork; + int m_delay; + int m_event_duration; + + int m_x; + int m_y; + int m_alpha; + float m_scaleX; + float m_scaleY; + float m_angle; + + int m_event_x; + int m_event_y; + int m_event_alpha; + float m_event_scaleX; + float m_event_scaleY; + float m_event_angle; + + float m_step_x; + float m_step_y; + float m_step_alpha; + float m_step_scaleX; + float m_step_scaleY; + float m_step_angle; + + bool m_show_on_top; + + bool m_isValid; +}; + +class CFanart +{ +public: + CFanart(void); + ~CFanart(void); + + void unload(); + bool load(Config &m_globalConfig, const char *path, const char *id); + bool isAnimationComplete(); + bool isLoaded(); + + void getBackground(const TexData * &hq, const TexData * &lq); + CColor getTextColor(CColor themeTxtColor = CColor(0xFFFFFFFF)); + bool hideCover(); + void draw(bool front = true); + void tick(); + +private: + vector m_elms; + + bool m_animationComplete; + u16 m_delayAfterAnimation; + u8 m_globalHideCover; + u8 m_globalShowCoverAfterAnimation; + u16 m_defaultDelay; + bool m_allowArtworkOnTop; + bool m_loaded; + Config m_cfg; + + TexData m_bg; + TexData m_bglq; +}; + +#endif // __FANART_HPP \ No newline at end of file diff --git a/source/menu/menu.cpp b/source/menu/menu.cpp index 517571e2..b1a40a08 100644 --- a/source/menu/menu.cpp +++ b/source/menu/menu.cpp @@ -1674,6 +1674,10 @@ void CMenu::_mainLoopCommon(bool withCF, bool adjusting) if(withCF) CoverFlow.tick(); m_btnMgr.tick(); + m_fa.tick(); + m_fa.hideCover() ? CoverFlow.hideCover() : CoverFlow.showCover(); + CoverFlow.setFanartPlaying(m_fa.isLoaded()); + CoverFlow.setFanartTextColor(m_fa.getTextColor(m_theme.getColor("_COVERFLOW", "font_color", CColor(0xFFFFFFFF)))); /* video setup */ m_vid.prepare(); @@ -1693,6 +1697,7 @@ void CMenu::_mainLoopCommon(bool withCF, bool adjusting) m_vid.prepareAAPass(i); m_vid.setup2DProjection(false, true); _drawBg(); + m_fa.draw(false); CoverFlow.draw(); m_vid.setup2DProjection(false, true); CoverFlow.drawEffect(); @@ -1707,6 +1712,7 @@ void CMenu::_mainLoopCommon(bool withCF, bool adjusting) { m_vid.setup2DProjection(); _drawBg(); + m_fa.draw(false); if(withCF) { CoverFlow.draw(); @@ -1720,7 +1726,9 @@ void CMenu::_mainLoopCommon(bool withCF, bool adjusting) /* game video or banner drawing */ if(m_gameSelected) { - if(m_video_playing) + if(m_fa.isLoaded()) + m_fa.draw(); + else if(m_video_playing) { if(movie.Frame != NULL) { diff --git a/source/menu/menu.hpp b/source/menu/menu.hpp index 139a9634..81596d93 100644 --- a/source/menu/menu.hpp +++ b/source/menu/menu.hpp @@ -17,6 +17,7 @@ #include "gecko/wifi_gecko.hpp" #include "gui/coverflow.hpp" #include "gui/cursor.hpp" +#include "gui/fanart.hpp" #include "gui/gui.hpp" #include "list/ListGenerator.hpp" #include "loader/disc.h" @@ -71,6 +72,7 @@ private: bool hide; }; CCursor m_cursor[WPAD_MAX_WIIMOTES]; + CFanart m_fa; Config m_cfg; Config m_loc; Config m_cat; diff --git a/source/menu/menu_game.cpp b/source/menu/menu_game.cpp index 73e3483b..e53fc7b6 100644 --- a/source/menu/menu_game.cpp +++ b/source/menu/menu_game.cpp @@ -272,6 +272,9 @@ void CMenu::_setCurrentItem(const dir_discHdr *hdr) void CMenu::_hideGame(bool instant) { _cleanupVideo(); + m_fa.unload(); + CoverFlow.showCover(); + m_btnMgr.hide(m_gameBtnPlay, instant); m_btnMgr.hide(m_gameBtnBack, instant); m_btnMgr.hide(m_gameBtnPlayFull, instant); @@ -291,7 +294,21 @@ void CMenu::_hideGame(bool instant) void CMenu::_showGame(void) { - _setBg(m_gameBg, m_gameBgLQ); + CoverFlow.showCover(); + + //if(m_fa.load(m_cfg, m_fanartDir.c_str(), CoverFlow.getFilenameId(CoverFlow.getHdr(), false))) + if(m_fa.load(m_cfg, m_fanartDir.c_str(), CoverFlow.getId())) + { + const TexData *bg = NULL; + const TexData *bglq = NULL; + m_fa.getBackground(bg, bglq); + if(bg != NULL && bglq != NULL) + _setBg(*bg, *bglq); + if (m_fa.hideCover()) + CoverFlow.hideCover(); + } + else + _setBg(m_gameBg, m_gameBgLQ); return; if(!m_zoom_banner) { @@ -319,6 +336,8 @@ void CMenu::_cleanupBanner(bool gamechange) m_banner.DeleteBanner(gamechange); //movie _cleanupVideo(); + //fanart + //m_fa.unload(); } void CMenu::_cleanupVideo() @@ -776,6 +795,8 @@ void CMenu::_game(bool launch) } if(startGameSound == -10)// if -10 then we moved to new cover { + m_fa.unload(); + CoverFlow.showCover(); memcpy(hdr, CoverFlow.getHdr(), sizeof(dir_discHdr));// get new game header _setCurrentItem(hdr); diff --git a/source/menu/menu_main.cpp b/source/menu/menu_main.cpp index ffad5bee..3772a71d 100644 --- a/source/menu/menu_main.cpp +++ b/source/menu/menu_main.cpp @@ -480,6 +480,7 @@ int CMenu::main(void) bheld = true; bUsed = true; } + _setBg(m_mainBg, m_mainBgLQ); if(m_refreshGameList) { /* if changes were made to favorites, parental lock, or categories */