#include #include #include #include #include #include #include #include #include "coverflow.hpp" #include "pngu.h" #include "boxmesh.hpp" #include "lockMutex.hpp" #include "fonts.h" #include "types.h" #include "gui/fmt.h" #include "gecko/gecko.hpp" #include "menu/menu.hpp" #include "plugin/plugin.hpp" #include "memory/mem2.hpp" #include "fileOps/fileOps.h" #include "wstringEx/wstringEx.hpp" static const int black_len = 16; static const char* black[black_len] = {"RZZJEL","RZNJ01","SEKJ99","SX3J01","SX3P01","R5WJA4","RUYJ99","S3HJ08","SJBJ01","CKBE88","CCPE01","SMMP01","MDUE01","SL2J01","APR","AFR"}; static const int red_len = 2; static const char* red[red_len] = {"SMN","HBW"}; static const int yellow_len = 2; static const char* yellow[yellow_len] = {"SIIE8P","SIIP8P"}; static const int greenOne_len = 1; static const char* greenOne[greenOne_len] = {"SF8J01"}; static const int greenTwo_len = 1; static const char* greenTwo[greenTwo_len] = {"PDUE01"}; static lwp_t coverLoaderThread = LWP_THREAD_NULL; static inline int loopNum(int i, int s) { return i < 0 ? (s - (-i % s)) % s : i % s; } CCoverFlow CoverFlow; u8 CCoverFlow::coverThreadStack[32768] ATTRIBUTE_ALIGN(32); const u32 CCoverFlow::coverThreadStackSize = 32768; CCoverFlow::CCover::CCover(void) { index = 0; txtAngle = 0.f; txtTargetAngle = 0.f; txtColor = 0; txtTargetColor = 0; color = 0x00FFFFFF; targetColor = 0xFFFFFFFF; shadowColor = 0x00000000; targetShadowColor = 0x00000000; scale = Vector3D(1.f, 1.f, 1.f); targetScale = Vector3D(1.f, 1.f, 1.f); } CCoverFlow::CItem::CItem(dir_discHdr *itemHdr, int playcount, unsigned int lastPlayed) : hdr(itemHdr), playcount(playcount), lastPlayed(lastPlayed), boxTexture(false), state(STATE_Loading) { } static inline wchar_t upperCaseWChar(wchar_t c) { return c >= L'a' && c <= L'z' ? c & 0x00DF : c; } float m_normal_speed; float m_selected_speed; CCoverFlow::CCoverFlow(void) { m_loNormal.camera = Vector3D(0.f, 1.5f, 5.f); m_loNormal.cameraAim = Vector3D(0.f, 0.f, -1.f); m_loNormal.leftScale = Vector3D(1.f, 1.f, 1.f); m_loNormal.rightScale = Vector3D(1.f, 1.f, 1.f); m_loNormal.centerScale = Vector3D(1.f, 1.f, 1.f); m_loNormal.rowCenterScale = Vector3D(1.f, 1.f, 1.f); m_loNormal.leftPos = Vector3D(-1.6f, 0.f, 0.f); m_loNormal.rightPos = Vector3D(1.6f, 0.f, 0.f); m_loNormal.centerPos = Vector3D(0.f, 0.f, 1.f); m_loNormal.rowCenterPos = Vector3D(0.f, 0.f, 0.f); m_loNormal.leftAngle = Vector3D(0.f, 70.f, 0.f); m_loNormal.rightAngle = Vector3D(0.f, -70.f, 0.f); m_loNormal.centerAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.rowCenterAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.leftSpacer = Vector3D(-0.35f, 0.f, 0.f); m_loNormal.rightSpacer = Vector3D(0.35f, 0.f, 0.f); m_loNormal.leftDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.rightDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.txtLeftAngle = -55.f; m_loNormal.txtRightAngle = 55.f; m_loNormal.txtCenterAngle = 0.f; m_loNormal.txtLeftPos = Vector3D(-4.f, 0.f, 1.3f); m_loNormal.txtRightPos = Vector3D(4.f, 0.f, 1.3f); m_loNormal.txtCenterPos = Vector3D(0.f, 0.f, 2.6f); m_loNormal.txtSideWidth = 500.f; m_loNormal.txtCenterWidth = 500.f; m_loNormal.txtSideStyle = FTGX_ALIGN_BOTTOM | FTGX_JUSTIFY_CENTER; m_loNormal.txtCenterStyle = FTGX_ALIGN_BOTTOM | FTGX_JUSTIFY_CENTER; m_loNormal.endColor = CColor(0x3FFFFFFF); m_loNormal.begColor = CColor(0xCFFFFFFF); m_loNormal.mouseOffColor = CColor(0xFF00FF00); m_loNormal.shadowColorCenter = CColor(0x00000000); m_loNormal.shadowColorEnd = CColor(0x00000000); m_loNormal.shadowColorBeg = CColor(0x00000000); m_loNormal.shadowColorOff = CColor(0x00000000); m_loNormal.topSpacer = Vector3D(0.f, 2.f, 0.f); m_loNormal.bottomSpacer = Vector3D(0.f, -2.f, 0.f); m_loNormal.topDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.bottomDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.topAngle = Vector3D(0.f, 0.f, 0.f); m_loNormal.bottomAngle = Vector3D(0.f, 0.f, 0.f); // m_loSelected.camera = Vector3D(0.f, 1.5f, 5.f); m_loSelected.cameraAim = Vector3D(0.f, 0.f, -1.f); m_loSelected.leftScale = Vector3D(1.f, 1.f, 1.f); m_loSelected.rightScale = Vector3D(1.f, 1.f, 1.f); m_loSelected.centerScale = Vector3D(1.f, 1.f, 1.f); m_loSelected.rowCenterScale = Vector3D(1.f, 1.f, 1.f); m_loSelected.leftPos = Vector3D(-4.6f, 2.f, 0.f); m_loSelected.rightPos = Vector3D(4.6f, 2.f, 0.f); m_loSelected.centerPos = Vector3D(-0.6f, 0.f, 2.6f); m_loSelected.rowCenterPos = Vector3D(0.f, 0.f, 0.f); m_loSelected.leftAngle = Vector3D(-45.f, 90.f, 0.f); m_loSelected.rightAngle = Vector3D(-45.f, -90.f, 0.f); m_loSelected.centerAngle = Vector3D(0.f, 380.f, 0.f); m_loSelected.rowCenterAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.leftSpacer = Vector3D(-0.35f, 0.f, 0.f); m_loSelected.rightSpacer = Vector3D(0.35f, 0.f, 0.f); m_loSelected.leftDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.rightDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.txtLeftAngle = -55.f; m_loSelected.txtRightAngle = 55.f; m_loSelected.txtCenterAngle = 0.f; m_loSelected.txtLeftPos = Vector3D(-4.35f, 0.f, 1.3f); m_loSelected.txtRightPos = Vector3D(4.35f, 0.f, 1.3f); m_loSelected.txtCenterPos = Vector3D(2.3f, 1.8f, 1.f); m_loSelected.txtSideWidth = 500.f; m_loSelected.txtCenterWidth = 310.f; m_loSelected.txtSideStyle = FTGX_ALIGN_BOTTOM | FTGX_JUSTIFY_CENTER; m_loSelected.txtCenterStyle = FTGX_ALIGN_TOP | FTGX_JUSTIFY_RIGHT; m_loSelected.endColor = CColor(0x3FFFFFFF); m_loSelected.begColor = CColor(0xCFFFFFFF); m_loSelected.mouseOffColor = CColor(0xFF00FF00); m_loSelected.shadowColorCenter = CColor(0x00000000); m_loSelected.shadowColorEnd = CColor(0x00000000); m_loSelected.shadowColorBeg = CColor(0x00000000); m_loSelected.shadowColorOff = CColor(0x00000000); m_loSelected.topSpacer = Vector3D(0.f, 2.f, 0.f); m_loSelected.bottomSpacer = Vector3D(0.f, -2.f, 0.f); m_loSelected.topDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.bottomDeltaAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.topAngle = Vector3D(0.f, 0.f, 0.f); m_loSelected.bottomAngle = Vector3D(0.f, 0.f, 0.f); // m_mirrorAlpha = 0.2f; m_txtMirrorAlpha = 0.2f; m_delay = 0; m_minDelay = 5; m_jump = 0; m_mutex = 0; m_dvdskin_loaded = false; m_defcovers_loaded = false; m_loadingCovers = false; m_coverThrdBusy = false; m_renderTex = false; m_renderingTex = NULL; m_moved = false; m_selected = false; m_hideCover = false; m_tickCount = 0; m_blurRadius = 3; m_blurFactor = 1.f; // m_mirrorBlur = false; m_effectTex.width = 96; m_effectTex.height = 72; m_effectTex.format = GX_TF_RGBA8; m_shadowScale = 1.f; m_shadowX = 0.f; m_shadowY = 0.f; m_flipCoverPos = Vector3D(0.f, 0.f, 0.f); m_flipCoverAngle = Vector3D(0.f, 180.f, 0.f); m_flipCoverScale = Vector3D(1.f, 1.f, 1.f); // Settings m_lodBias = -0.3f; m_aniso = GX_ANISO_1; m_edgeLOD = false; m_numBufCovers = 20; m_compressTextures = true; m_compressCache = false; m_deletePicsAfterCaching = false; m_pluginCacheFolders = false; m_box = true; m_smallBox = false; m_useHQcover = false; m_rows = 1; m_columns = 11; m_range = m_rows * m_columns; for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--) m_mouse[chan] = -1; sndCopyNum = 0; m_soundVolume = 0xFF; m_sorting = SORT_ALPHA; m_normal_speed = 0.1f; m_selected_speed = 0.07f; // m_flipSound = NULL; m_hoverSound = NULL; m_selectSound = NULL; m_cancelSound = NULL; // m_covers = NULL; LWP_MutexInit(&m_mutex, 0); } 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); if(vid_50hz) { gprintf("WiiFlow is in 50hz mode\n"); m_normal_speed = 0.12f; m_selected_speed = 0.084f; m_minDelay = 4; } if (CONF_GetAspectRatio() == CONF_ASPECT_16_9) guPerspective(m_projMtx, 45, 16.f / 9.f, .1f, 300.f); else guPerspective(m_projMtx, RadToDeg(2.f * atan(tan(DegToRad(45 / 2)) / (4.f / 3.f) * (16.f / 9.f))), 4.f / 3.f, .1f, 300.f); return true; } void CCoverFlow::simulateOtherScreenFormat(bool s) { if ((CONF_GetAspectRatio() == CONF_ASPECT_16_9) != s) guPerspective(m_projMtx, 45, 16.f / 9.f, .1f, 300.f); else guPerspective(m_projMtx, RadToDeg(2.f * atan(tan(DegToRad(45 / 2)) / (4.f / 3.f) * (16.f / 9.f))), 4.f / 3.f, .1f, 300.f); } CCoverFlow::~CCoverFlow(void) { shutdown(); LWP_MutexDestroy(m_mutex); } void CCoverFlow::setCachePath(const char *path, bool pluginCacheFolders) { m_cachePath = path; m_pluginCacheFolders = pluginCacheFolders; } void CCoverFlow::setTextureQuality(float lodBias, int aniso, bool edgeLOD) { m_lodBias = min(max(-3.f, lodBias), 1.f); switch (aniso) { case 1: m_aniso = GX_ANISO_2; break; case 2: m_aniso = GX_ANISO_4; break; default: m_aniso = GX_ANISO_1; } m_edgeLOD = edgeLOD; } void CCoverFlow::setBoxMode(bool box) { m_box = box; } void CCoverFlow::setSmallBoxMode(bool smallBox) { m_smallBox = smallBox; } void CCoverFlow::setHQcover(bool HQ) { m_useHQcover = HQ; } void CCoverFlow::setBufferSize(u32 numCovers) { if(m_covers != NULL) return; m_numBufCovers = min(max(4ul, numCovers / 2ul), 40ul);//8 to 80 } void CCoverFlow::setTextures(const string &loadingPic, const string &loadingPicFlat, const string &noCoverPic, const string &noCoverPicFlat) { if (m_covers != NULL) return; m_pngLoadCover = loadingPic; m_pngLoadCoverFlat = loadingPicFlat; m_pngNoCover = noCoverPic; m_pngNoCoverFlat = noCoverPicFlat; } void CCoverFlow::setFont(const SFont &font, const CColor &color) { if(m_font.font != NULL) delete m_font.font; m_font = font; m_fontColor = color; if (m_covers != NULL) { for (u32 i = 0; i < m_range; ++i) _loadCover(i, m_covers[i].index); _updateAllTargets(); } } void CCoverFlow::_transposeCover(CCover* &dst, u32 rows, u32 columns, int pos) { int i = pos - (int)(rows * columns / 2); int j = rows >= 3 ? abs(i) - ((abs(i) + (int)rows / 2) / (int)rows) * 2 : abs(i); if (m_rows >= 3) j += ((j + ((int)m_rows - 2) / 2) / ((int)m_rows - 2)) * 2; int k = m_range / 2 + (i < 0 ? -j : j); if ((u32)k < m_range) dst[pos] = m_covers[k]; } void CCoverFlow::setRange(u32 rows, u32 columns) { rows = rows < 3ul ? 1ul : min(rows | 1ul, 9ul) + 2ul; columns = min(max(3ul, columns | 1ul), 21ul) + 2ul; u32 range = rows * columns; if (rows == m_rows && columns == m_columns && range == m_range) return; if (m_covers != NULL) { stopCoverLoader(); MEM2_free(m_covers); CCover *tmpCovers = (CCover*)MEM2_alloc(sizeof(CCover) * range); for(size_t i = 0; i < range; ++i) { // does not allocate memory -- calls: operator new (sizeof(CCover), tmpCovers+i) // only constructs an object at tmpCovers+i // delete is not needed // this is the same as CCover tmpCovers[range] except the objects memory address is specified and won't go out of scope tmpCovers[i] = *(new (tmpCovers+i) CCover); } if (rows >= 3) for (u32 x = 0; x < columns; ++x) for (u32 y = 1; y < rows - 1; ++y) _transposeCover(tmpCovers, rows, columns, x * rows + y); else for (u32 x = 0; x < range; ++x) _transposeCover(tmpCovers, rows, columns, x); m_rows = rows; m_columns = columns; m_range = range; m_covers = tmpCovers; _loadAllCovers(m_covers[m_range / 2].index); _updateAllTargets(); startCoverLoader(); } else { m_rows = rows; m_columns = columns; m_range = range; } } void CCoverFlow::setCameraPos(bool selected, const Vector3D &pos, const Vector3D &aim) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.camera = pos; lo.cameraAim = aim; } void CCoverFlow::setCameraOsc(bool selected, const Vector3D &speed, const Vector3D &) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.cameraOscSpeed = speed; lo.cameraOscAmp = amp; } void CCoverFlow::setCoverScale(bool selected, const Vector3D &left, const Vector3D &right, const Vector3D ¢er, const Vector3D &rowCenter) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.leftScale = left; lo.rightScale = right; lo.centerScale = center; lo.rowCenterScale = rowCenter; } void CCoverFlow::setCoverPos(bool selected, const Vector3D &left, const Vector3D &right, const Vector3D ¢er, const Vector3D &rowCenter) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.leftPos = left; lo.rightPos = right; lo.centerPos = center; lo.rowCenterPos = rowCenter; } void CCoverFlow::setCoverAngleOsc(bool selected, const Vector3D &speed, const Vector3D &) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.coverOscASpeed = speed; lo.coverOscAAmp = amp; } void CCoverFlow::setCoverPosOsc(bool selected, const Vector3D &speed, const Vector3D &) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.coverOscPSpeed = speed; lo.coverOscPAmp = amp; } void CCoverFlow::setSpacers(bool selected, const Vector3D &left, const Vector3D &right) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.leftSpacer = left; lo.rightSpacer = right; } void CCoverFlow::setDeltaAngles(bool selected, const Vector3D &left, const Vector3D &right) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.leftDeltaAngle = left; lo.rightDeltaAngle = right; } void CCoverFlow::setAngles(bool selected, const Vector3D &left, const Vector3D &right, const Vector3D ¢er, const Vector3D &rowCenter) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.leftAngle = left; lo.rightAngle = right; lo.centerAngle = center; lo.rowCenterAngle = rowCenter; } void CCoverFlow::setTitleAngles(bool selected, float left, float right, float center) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.txtLeftAngle = left; lo.txtRightAngle = right; lo.txtCenterAngle = center; } void CCoverFlow::setTitlePos(bool selected, const Vector3D &left, const Vector3D &right, const Vector3D ¢er) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.txtLeftPos = left; lo.txtRightPos= right; lo.txtCenterPos= center; } void CCoverFlow::setTitleWidth(bool selected, float side, float center) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.txtSideWidth = side; lo.txtCenterWidth= center; } void CCoverFlow::setTitleStyle(bool selected, u16 side, u16 center) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.txtSideStyle = side; lo.txtCenterStyle = center; } void CCoverFlow::setColors(bool selected, const CColor &begColor, const CColor &endColor, const CColor &offColor) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.begColor = begColor; lo.endColor = endColor; lo.mouseOffColor = offColor; } void CCoverFlow::setMirrorAlpha(float cover, float title) { m_mirrorAlpha = cover; m_txtMirrorAlpha = title; } void CCoverFlow::setMirrorBlur(bool blur) { m_mirrorBlur = blur; } void CCoverFlow::setShadowColors(bool selected, const CColor ¢erColor, const CColor &begColor, const CColor &endColor, const CColor &offColor) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.shadowColorCenter = centerColor; lo.shadowColorBeg = begColor; lo.shadowColorEnd = endColor; lo.shadowColorOff = offColor; } void CCoverFlow::setShadowPos(float scale, float x, float y) { m_shadowScale = scale; m_shadowX = x; m_shadowY = y; } void CCoverFlow::setRowSpacers(bool selected, const Vector3D &top, const Vector3D &bottom) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.topSpacer = top; lo.bottomSpacer = bottom; } void CCoverFlow::setRowDeltaAngles(bool selected, const Vector3D &top, const Vector3D &bottom) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.topDeltaAngle = top; lo.bottomDeltaAngle = bottom; } void CCoverFlow::setRowAngles(bool selected, const Vector3D &top, const Vector3D &bottom) { SLayout &lo = selected ? m_loSelected : m_loNormal; lo.topAngle = top; lo.bottomAngle = bottom; } void CCoverFlow::setCoverFlipping(const Vector3D &pos, const Vector3D &angle, const Vector3D &scale) { m_flipCoverPos = pos; m_flipCoverAngle = angle; m_flipCoverScale = scale; } void CCoverFlow::setCoverFlipPos(const Vector3D &pos) { if(m_covers == NULL || !m_selected) return; LockMutex lock(m_mutex); m_covers[m_range / 2].targetPos = m_loSelected.centerPos + pos; } void CCoverFlow::setBlur(u32 blurResolution, u32 blurRadius, float blurFactor) { static const struct { u32 x; u32 y; } blurRes[] = { { 64, 48 }, { 96, 72 }, { 128, 96 }, { 192, 144 } }; u32 i = min(max(0ul, blurResolution), sizeof blurRes / sizeof blurRes[0] - 1ul); m_effectTex.width = blurRes[i].x; m_effectTex.height = blurRes[i].y; if(m_effectTex.data != NULL) { free(m_effectTex.data); m_effectTex.data = NULL; } m_blurRadius = min(max(1ul, blurRadius), 3ul); m_blurFactor = min(max(1.f, blurFactor), 2.f); } void CCoverFlow::setSorting(Sorting sorting) { m_sorting = sorting; } void CCoverFlow::setSounds(GuiSound *flipSound, GuiSound *hoverSound, GuiSound *selectSound, GuiSound *cancelSound) { //for(u8 i = 0; i < 4; i++) m_flipSound = flipSound; m_hoverSound = hoverSound; m_selectSound = selectSound; m_cancelSound = cancelSound; } void CCoverFlow::setSoundVolume(u8 vol) { m_soundVolume = vol; } void CCoverFlow::_stopSound(GuiSound * &snd) { if(snd == NULL) return; snd->Stop(); } void CCoverFlow::_playSound(GuiSound * &snd) { if(snd == NULL) return; snd->Play(m_soundVolume); } void CCoverFlow::stopSound(void) { //for(u8 i = 0; i < 4; i++) _stopSound(m_flipSound); _stopSound(m_hoverSound); _stopSound(m_selectSound); _stopSound(m_cancelSound); } void CCoverFlow::applySettings(void) { if (m_covers == NULL) return; LockMutex lock(m_mutex); _updateAllTargets(true); } void CCoverFlow::stopCoverLoader(bool empty) { m_loadingCovers = false; m_moved = true; if(coverLoaderThread != LWP_THREAD_NULL) { if(LWP_ThreadIsSuspended(coverLoaderThread)) LWP_ResumeThread(coverLoaderThread); while(m_coverThrdBusy) usleep(50); LWP_JoinThread(coverLoaderThread, NULL); coverLoaderThread = LWP_THREAD_NULL; if(empty) { for(u32 i = 0; i < m_items.size(); ++i) { TexHandle.Cleanup(m_items[i].texture); m_items[i].state = STATE_Loading; } } //gprintf("Coverflow stopped!\n"); } } void CCoverFlow::startCoverLoader(void) { if(m_covers == NULL || coverLoaderThread != LWP_THREAD_NULL || m_loadingCovers) return; m_loadingCovers = true; m_moved = true; LWP_CreateThread(&coverLoaderThread, _coverLoader, this, coverThreadStack, coverThreadStackSize, 40); //gprintf("Coverflow started!\n"); } void CCoverFlow::clear(void) { stopCoverLoader(true); if(m_covers != NULL) MEM2_free(m_covers); m_covers = NULL; m_items.clear(); //vector().swap(m_items); } void CCoverFlow::shutdown(void) { gprintf("Cleanup Coverflow\n"); TexHandle.Cleanup(m_dvdSkin); TexHandle.Cleanup(m_dvdSkin_Red); TexHandle.Cleanup(m_dvdSkin_Black); TexHandle.Cleanup(m_dvdSkin_Yellow); TexHandle.Cleanup(m_dvdSkin_GreenOne); TexHandle.Cleanup(m_dvdSkin_GreenTwo); clear(); if(m_flipSound != NULL) delete m_flipSound; m_flipSound = NULL; if(m_cancelSound != NULL) delete m_cancelSound; m_cancelSound = NULL; LWP_MutexDestroy(m_mutex); } void CCoverFlow::reserve(u32 capacity) { m_items.reserve(capacity); } void CCoverFlow::addItem(dir_discHdr *hdr, int playcount, unsigned int lastPlayed) { if (m_covers != NULL) return; m_items.push_back(CCoverFlow::CItem(hdr, playcount, lastPlayed)); } // Draws a plane in the Z-Buffer only. void CCoverFlow::_drawMirrorZ(void) { GX_LoadPosMtxImm(m_viewMtx, GX_PNMTX0); // GX setup GX_SetNumChans(0); GX_ClearVtxDesc(); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GX_SetNumTexGens(0); GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_DISABLE, GX_COLORNULL); GX_SetAlphaUpdate(GX_FALSE); GX_SetColorUpdate(GX_FALSE); GX_SetZMode(GX_ENABLE, GX_LEQUAL, GX_TRUE); GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position3f32(-10000.f, 0.f, -10000.f); GX_Position3f32(10000.f, 0.f, -10000.f); GX_Position3f32(10000.f, 0.f, 10000.f); GX_Position3f32(-10000.f, 0.f, 10000.f); GX_End(); GX_SetAlphaUpdate(GX_TRUE); GX_SetColorUpdate(GX_TRUE); } void CCoverFlow::_effectBg(const TexData * &tex) { Mtx modelViewMtx; GXTexObj texObj; if(tex == NULL || tex->data == NULL) return; GX_ClearVtxDesc(); GX_SetNumTevStages(1); GX_SetNumChans(0); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 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_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); GX_SetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); GX_SetAlphaUpdate(GX_TRUE); GX_SetCullMode(GX_CULL_NONE); GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); guMtxIdentity(modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); GX_InitTexObj(&texObj, tex->data, tex->width, tex->height, tex->format, GX_CLAMP, GX_CLAMP, GX_FALSE); GX_LoadTexObj(&texObj, GX_TEXMAP0); GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position3f32(0.f, 0.f, 0.f); GX_TexCoord2f32(0.f, 0.f); GX_Position3f32(640.f, 0.f, 0.f); GX_TexCoord2f32(1.f, 0.f); GX_Position3f32(640.f, 480.f, 0.f); GX_TexCoord2f32(1.f, 1.f); GX_Position3f32(0.f, 480.f, 0.f); GX_TexCoord2f32(0.f, 1.f); GX_End(); } void CCoverFlow::_effectBlur(bool vertical) { int kSize = m_blurRadius * 2 + 1; GXTexObj texObj; Mtx mh1; Mtx mh2; Mtx mh3; Mtx mh4; Mtx mh5; Mtx mh6; Mtx modelViewMtx; float pixDist = m_blurFactor; float w = (float)m_effectTex.width; float h = (float)m_effectTex.height; float x = 0.f; float y = 0.f; CColor kGauss7(0xFF * 20 / 64, 0xFF * 15 / 64, 0xFF * 6 / 64, 0xFF * 1 / 64); CColor kGauss5(0xFF * 6 / 16, 0xFF * 4 / 16, 0xFF * 1 / 16, 0); CColor kGauss3(0xFF * 2 / 4, 0xFF * 1 / 4, 0, 0); CColor kBox(0xFF / kSize, 0xFF / kSize, 0xFF / kSize, 0xFF / kSize); Mtx44 projMtx; GX_SetScissor(0, 0, m_effectTex.width, m_effectTex.height); GX_SetPixelFmt(GX_PF_RGBA6_Z24, GX_ZC_LINEAR); GX_InvVtxCache(); GX_InvalidateTexAll(); GX_InitTexObj(&texObj, m_effectTex.data, m_effectTex.width, m_effectTex.height, m_effectTex.format, GX_CLAMP, GX_CLAMP, GX_FALSE); GX_LoadTexObj(&texObj, GX_TEXMAP0); GX_SetNumTevStages(kSize); GX_SetNumTexGens(kSize); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); guMtxIdentity(mh1); guMtxIdentity(mh2); guMtxIdentity(mh3); guMtxIdentity(mh4); guMtxIdentity(mh5); guMtxIdentity(mh6); if (vertical) { guMtxRowCol(mh1, 0, 3) = pixDist * -1.f / w; guMtxRowCol(mh2, 0, 3) = pixDist * 1.f / w; guMtxRowCol(mh3, 0, 3) = pixDist * -2.f / w; guMtxRowCol(mh4, 0, 3) = pixDist * 2.f / w; guMtxRowCol(mh5, 0, 3) = pixDist * -3.f / w; guMtxRowCol(mh6, 0, 3) = pixDist * 3.f / w; } else { guMtxRowCol(mh1, 1, 3) = pixDist * -1.f / h; guMtxRowCol(mh2, 1, 3) = pixDist * 1.f / h; guMtxRowCol(mh3, 1, 3) = pixDist * -2.f / h; guMtxRowCol(mh4, 1, 3) = pixDist * 2.f / h; guMtxRowCol(mh5, 1, 3) = pixDist * -3.f / h; guMtxRowCol(mh6, 1, 3) = pixDist * 3.f / h; } GX_LoadTexMtxImm(mh1, GX_TEXMTX1, GX_MTX2x4); GX_LoadTexMtxImm(mh2, GX_TEXMTX2, GX_MTX2x4); GX_LoadTexMtxImm(mh3, GX_TEXMTX3, GX_MTX2x4); GX_LoadTexMtxImm(mh4, GX_TEXMTX4, GX_MTX2x4); GX_LoadTexMtxImm(mh3, GX_TEXMTX5, GX_MTX2x4); GX_LoadTexMtxImm(mh4, GX_TEXMTX6, GX_MTX2x4); GX_SetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX1); GX_SetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX2); GX_SetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX3); GX_SetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX4); GX_SetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX5); GX_SetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX6); switch (m_blurRadius) { case 1: GX_SetTevKColor(GX_KCOLOR0, kGauss3); break; case 2: GX_SetTevKColor(GX_KCOLOR0, kGauss5); break; case 3: GX_SetTevKColor(GX_KCOLOR0, kGauss7); break; default: GX_SetTevKColor(GX_KCOLOR0, kBox); } GX_SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0_R); GX_SetTevKColorSel(GX_TEVSTAGE1, GX_TEV_KCSEL_K0_G); GX_SetTevKColorSel(GX_TEVSTAGE2, GX_TEV_KCSEL_K0_G); GX_SetTevKColorSel(GX_TEVSTAGE3, GX_TEV_KCSEL_K0_B); GX_SetTevKColorSel(GX_TEVSTAGE4, GX_TEV_KCSEL_K0_B); GX_SetTevKColorSel(GX_TEVSTAGE5, GX_TEV_KCSEL_K0_A); GX_SetTevKColorSel(GX_TEVSTAGE6, GX_TEV_KCSEL_K0_A); GX_SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_R); GX_SetTevKAlphaSel(GX_TEVSTAGE1, GX_TEV_KASEL_K0_G); GX_SetTevKAlphaSel(GX_TEVSTAGE2, GX_TEV_KASEL_K0_G); GX_SetTevKAlphaSel(GX_TEVSTAGE3, GX_TEV_KASEL_K0_B); GX_SetTevKAlphaSel(GX_TEVSTAGE4, GX_TEV_KASEL_K0_B); GX_SetTevKAlphaSel(GX_TEVSTAGE5, GX_TEV_KASEL_K0_A); GX_SetTevKAlphaSel(GX_TEVSTAGE6, GX_TEV_KASEL_K0_A); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_ZERO); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_ZERO); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); for (int i = 1; i < kSize; ++i) { GX_SetTevOrder(GX_TEVSTAGE0 + i, GX_TEXCOORD0 + i, GX_TEXMAP0, GX_COLORNULL); GX_SetTevColorIn(GX_TEVSTAGE0 + i, GX_CC_ZERO, GX_CC_TEXC, GX_CC_KONST, GX_CC_CPREV); GX_SetTevAlphaIn(GX_TEVSTAGE0 + i, GX_CA_ZERO, GX_CA_TEXA, GX_CA_KONST, GX_CA_APREV); GX_SetTevColorOp(GX_TEVSTAGE0 + i, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0 + i, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); } GX_SetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); GX_SetZMode(GX_DISABLE, GX_ALWAYS, GX_FALSE); GX_SetViewport(0, 0, 640.f, 480.f, 0.f, 1.f); guOrtho(projMtx, 0, 480.f + 0, 0, 640.f + 0, 0.f, 1000.0f); GX_LoadProjectionMtx(projMtx, GX_ORTHOGRAPHIC); 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_TEX0, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); GX_SetAlphaUpdate(GX_TRUE); GX_SetCullMode(GX_CULL_NONE); guMtxIdentity(modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position3f32(x, y, 0.f); GX_TexCoord2f32(0.f, 0.f); GX_Position3f32(x + w, y, 0.f); GX_TexCoord2f32(1.f, 0.f); GX_Position3f32(x + w, y + h, 0.f); GX_TexCoord2f32(1.f, 1.f); GX_Position3f32(x, y + h, 0.f); GX_TexCoord2f32(0.f, 1.f); GX_End(); m_vid.renderToTexture(m_effectTex, true); } bool CCoverFlow::_effectVisible(void) { SLayout &lo = m_selected ? m_loSelected : m_loNormal; return (m_mirrorAlpha > 0.01f && m_mirrorBlur) || lo.shadowColorCenter.a > 0 || lo.shadowColorBeg.a > 0 || lo.shadowColorEnd.a > 0 || lo.shadowColorOff.a > 0; } void CCoverFlow::makeEffectTexture(const TexData * &bg) { if (!_effectVisible()) return; int aa = 8; GX_SetDither(GX_DISABLE); m_vid.setAA(aa, true, m_effectTex.width, m_effectTex.height); for (int i = 0; i < aa; ++i) { m_vid.prepareAAPass(i); m_vid.setup2DProjection(false, true); _effectBg(bg); if (m_mirrorBlur) _draw(CCoverFlow::CFDR_NORMAL, true, false); m_vid.shiftViewPort(m_shadowX, m_shadowY); _draw(CCoverFlow::CFDR_SHADOW, false, true); m_vid.renderAAPass(i); } GX_SetPixelFmt(GX_PF_RGBA6_Z24, GX_ZC_LINEAR); GX_InvVtxCache(); GX_InvalidateTexAll(); m_vid.setup2DProjection(false, true); GX_SetViewport(0.f, 0.f, (float)m_effectTex.width, (float)m_effectTex.height, 0.f, 1.f); GX_SetScissor(0, 0, m_effectTex.width, m_effectTex.height); m_vid.drawAAScene(); m_vid.renderToTexture(m_effectTex, true); _effectBlur(false); _effectBlur(true); GX_SetDither(GX_ENABLE); } void CCoverFlow::drawEffect(void) { if (m_covers == NULL) return; if (_effectVisible()) { Mtx modelViewMtx; GXTexObj texObj; float w = 640.f; float h = 480.f; float x = 0.f; float y = 0.f; GX_SetNumTevStages(1); GX_SetNumChans(0); 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_TEX0, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0); GX_SetNumTexGens(1); GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); 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_FALSE); GX_SetCullMode(GX_CULL_NONE); GX_SetZMode(GX_ENABLE, GX_LEQUAL, GX_FALSE); guMtxIdentity(modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); GX_InitTexObj(&texObj, m_effectTex.data, m_effectTex.width, m_effectTex.height, m_effectTex.format, GX_CLAMP, GX_CLAMP, GX_FALSE); GX_LoadTexObj(&texObj, GX_TEXMAP0); GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position3f32(x, y, -999.f); GX_TexCoord2f32(0.f, 0.f); GX_Position3f32(x + w, y, -999.f); GX_TexCoord2f32(1.f, 0.f); GX_Position3f32(x + w, y + h, -999.f); GX_TexCoord2f32(1.f, 1.f); GX_Position3f32(x, y + h, -999.f); GX_TexCoord2f32(0.f, 1.f); GX_End(); } if (!m_mirrorBlur) _draw(CCoverFlow::CFDR_NORMAL, true, true); } void CCoverFlow::draw(void) { _draw(); } void CCoverFlow::drawText(bool withRectangle) { LockMutex lock(m_mutex); Vector3D up(0.f, 1.f, 0.f); Vector3D dir(m_cameraAim); Vector3D pos(m_cameraPos); if (m_covers == NULL) return; if (m_fontColor.a == 0) return; pos += _cameraMoves(); // Camera GX_LoadProjectionMtx(m_projMtx, GX_PERSPECTIVE); guLookAt(m_viewMtx, &pos, &up, &dir); // Text GX_SetNumChans(1); GX_SetCullMode(GX_CULL_NONE); GX_SetZMode(GX_DISABLE, GX_LEQUAL, GX_FALSE); GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); for (u32 i = 0; i < m_range; ++i) { _drawTitle(loopNum((i & 1) != 0 ? m_range / 2 - (i + 1) / 2 : m_range / 2 + i / 2, m_range), true, withRectangle); _drawTitle(loopNum((i & 1) != 0 ? m_range / 2 - (i + 1) / 2 : m_range / 2 + i / 2, m_range), false, withRectangle); } } void CCoverFlow::hideCover(void) { m_hideCover = true; } void CCoverFlow::showCover(void) { m_hideCover = false; } inline int innerToOuter(int i, int range) { return loopNum((i & 1) != 0 ? range / 2 - (i + 1) / 2 : range / 2 + i / 2, range); } void CCoverFlow::_draw(DrawMode dm, bool mirror, bool blend) { LockMutex lock(m_mutex); Vector3D up(0.f, 1.f, 0.f); Vector3D dir(m_cameraAim); Vector3D pos(m_cameraPos); if (mirror && m_mirrorAlpha <= 0.f) return; if (m_covers == NULL) return; pos += _cameraMoves(); // GX setup GX_ClearVtxDesc(); GX_SetNumTevStages(1); if (dm == CCoverFlow::CFDR_NORMAL) { GX_SetNumChans(0); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 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_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_KONST, GX_CC_TEXC, GX_CC_ZERO); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_KONST, GX_CA_TEXA, GX_CA_ZERO); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLORNULL); } else { GX_SetNumChans(1); GX_SetNumTexGens(0); GX_SetVtxDesc(GX_VA_POS, GX_DIRECT); GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLORNULL); } GX_SetTevKColor(GX_KCOLOR0, CColor(0xFF, 0xFF, 0xFF, 0xFF)); GX_SetTevKColorSel(GX_TEVSTAGE0, GX_TEV_KCSEL_K0); GX_SetTevKAlphaSel(GX_TEVSTAGE0, GX_TEV_KASEL_K0_A); if (blend) GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); else GX_SetBlendMode(GX_BM_NONE, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); GX_SetAlphaUpdate(GX_TRUE); // Camera GX_LoadProjectionMtx(m_projMtx, GX_PERSPECTIVE); guLookAt(m_viewMtx, &pos, &up, &dir); // Covers GX_SetCullMode(m_box ? (mirror ? GX_CULL_BACK : GX_CULL_FRONT) : GX_CULL_NONE); if (dm == CCoverFlow::CFDR_SHADOW) { // Shadow GX_SetZMode(GX_DISABLE, GX_LEQUAL, GX_FALSE); for (int x = (int)m_columns - 3; x >= 0; --x) { int xx = innerToOuter(x, m_columns); if (m_rows >= 3) for (int y = (int)m_rows - 3; y >= 0; --y) _drawCover(xx * m_rows + innerToOuter(y, m_rows), mirror, dm); else _drawCover(xx, mirror, dm); } } else { // Normal GX_SetZMode(GX_ENABLE, GX_LEQUAL, GX_TRUE); for (int x = 0; x < (int)m_columns - 2; ++x) { int xx = innerToOuter(x, m_columns); if (m_rows >= 3) for (int y = 0; y < (int)m_rows - 2; ++y) _drawCover(xx * m_rows + innerToOuter(y, m_rows), mirror, dm); else _drawCover(xx, mirror, dm); } // Vanishing covers GX_SetZMode(GX_ENABLE, GX_LEQUAL, GX_FALSE); for (u32 y = 0; y < m_rows; ++y) { _drawCover(y, mirror, dm); _drawCover((m_columns - 1) * m_rows + y, mirror, dm); } if (m_rows >= 3) for (u32 x = 1; x < m_columns - 1; ++x) { _drawCover(x * m_rows, mirror, dm); _drawCover(x * m_rows + m_rows - 1, mirror, dm); } } } Vector3D CCoverFlow::_cameraMoves(void) { SLayout &lo = m_selected ? m_loSelected : m_loNormal; float tick = (float)m_tickCount * 0.005f; return Vector3D(cos(tick * lo.cameraOscSpeed.x) * lo.cameraOscAmp.x, cos(tick * lo.cameraOscSpeed.y) * lo.cameraOscAmp.y, cos(tick * lo.cameraOscSpeed.z) * lo.cameraOscAmp.z); } Vector3D CCoverFlow::_coverMovesA(void)//angle { SLayout &lo = m_selected ? m_loSelected : m_loNormal; float tick = (float)m_tickCount * 0.005f; return Vector3D(cos(tick * lo.coverOscASpeed.x) * lo.coverOscAAmp.x, sin(tick * lo.coverOscASpeed.y) * lo.coverOscAAmp.y, cos(tick * lo.coverOscASpeed.z) * lo.coverOscAAmp.z); } Vector3D CCoverFlow::_coverMovesP(void)//position { SLayout &lo = m_selected ? m_loSelected : m_loNormal; float tick = (float)m_tickCount * 0.005f; return Vector3D(cos(tick * lo.coverOscPSpeed.x) * lo.coverOscPAmp.x, sin(tick * lo.coverOscPSpeed.y) * lo.coverOscPAmp.y, cos(tick * lo.coverOscPSpeed.z) * lo.coverOscPAmp.z); } 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); if (m_hideCover) return; if (m_covers[i].txtColor == 0) return; color.a = mirror ? (u8)((float)m_covers[i].txtColor * m_txtMirrorAlpha) : m_covers[i].txtColor; // rectangle is never used. in menu.cpp _mainLoopCommon() adjusting is never set true // but if it would be. rectangle is the same as using a frame for the text as when the gui draws a label. // when using a frame the text style is used. otherwise it is not used. if (rectangle && !mirror) { // GX setup GX_SetNumTevStages(1); 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_SetNumTexGens(0); GX_SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC); GX_SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA); GX_SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORDNULL, GX_TEXMAP_NULL, GX_COLOR0A0); GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); // Mtx rotMtx; guMtxIdentity(modelMtx); guMtxScaleApply(modelMtx, modelMtx, 0.005f, 0.005f, 0.005f); guMtxRotDeg(rotMtx, 'Y', m_covers[i].txtAngle); guMtxConcat(rotMtx, modelMtx, modelMtx); guMtxTransApply(modelMtx, modelMtx, m_covers[i].txtPos.x, mirror ? -m_covers[i].txtPos.y : m_covers[i].txtPos.y, m_covers[i].txtPos.z); guMtxConcat(m_viewMtx, modelMtx, modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); const SLayout &lo = m_selected ? m_loSelected : m_loNormal; u16 s = (u32)i == m_range / 2 ? lo.txtCenterStyle : lo.txtSideStyle; float x; float y; float w = (u32)i == m_range / 2 ? lo.txtCenterWidth : lo.txtSideWidth; float h = m_font.lineSpacing; if ((s & FTGX_JUSTIFY_CENTER) != 0) x = -w * 0.5f; else if ((s & FTGX_JUSTIFY_RIGHT) != 0) x = -w; else x = 0.f; if ((s & FTGX_ALIGN_MIDDLE) != 0) y = -h * 0.5f; else if ((s & FTGX_ALIGN_TOP) != 0) y = -h; else y = 0.f; GX_Begin(GX_QUADS, GX_VTXFMT0, 4); GX_Position3f32(x, y, 0.f); GX_Color4u8(color.r, color.g, color.b, color.a / 2); GX_Position3f32(x + w, y, 0.f); GX_Color4u8(color.r, color.g, color.b, color.a / 2); GX_Position3f32(x + w, y + h, 0.f); GX_Color4u8(color.r, color.g, color.b, color.a / 2); GX_Position3f32(x, y + h, 0.f); GX_Color4u8(color.r, color.g, color.b, color.a / 2); GX_End(); } m_covers[i].title.setColor(color); m_font.font->reset(); m_font.font->setXScale(0.005f); m_font.font->setYScale(mirror ? 0.005f : -0.005f); GX_ClearVtxDesc(); GX_SetNumTevStages(1); GX_SetNumChans(1); 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_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR); // guMtxIdentity(modelMtx); guMtxRotAxisDeg(modelMtx, &rotAxis, m_covers[i].txtAngle); guMtxTransApply(modelMtx, modelMtx, m_covers[i].txtPos.x, mirror ? -m_covers[i].txtPos.y : m_covers[i].txtPos.y, m_covers[i].txtPos.z); guMtxConcat(m_viewMtx, modelMtx, modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); m_covers[i].title.draw(); } void CCoverFlow::_drawCover(int i, bool mirror, CCoverFlow::DrawMode dm) { Mtx modelMtx; Mtx rotMtx; Mtx modelViewMtx; Vector3D osc; Vector3D oscP; if (m_hideCover) return; if (m_covers[i].color.a == 0 || (dm == CCoverFlow::CFDR_SHADOW && m_covers[i].shadowColor.a == 0)) return; if (dm == CCoverFlow::CFDR_STENCIL && (i > 0xFF || _invisibleCover(i / m_rows, i % m_rows))) return; if ((u32)i == m_range / 2) { osc = _coverMovesA(); oscP = _coverMovesP(); } // guMtxIdentity(modelMtx); guMtxScaleApply(modelMtx, modelMtx, m_covers[i].scale.x, m_covers[i].scale.y, m_covers[i].scale.z); if (dm == CCoverFlow::CFDR_SHADOW) guMtxScaleApply(modelMtx, modelMtx, 1.f + (m_shadowScale - 1.f) * g_boxSize.y / g_boxSize.x, m_shadowScale, m_shadowScale); guMtxRotDeg(rotMtx, 'Z', m_covers[i].angle.z + osc.z); guMtxConcat(rotMtx, modelMtx, modelMtx); guMtxRotDeg(rotMtx, 'X', m_covers[i].angle.x + osc.x); guMtxConcat(rotMtx, modelMtx, modelMtx); guMtxRotDeg(rotMtx, 'Y', m_covers[i].angle.y + osc.y); guMtxConcat(rotMtx, modelMtx, modelMtx); guMtxTransApply(modelMtx, modelMtx, m_covers[i].pos.x + oscP.x, m_covers[i].pos.y + oscP.y + g_coverYCenter, m_covers[i].pos.z + oscP.z); if (mirror) guMtxScaleApply(modelMtx, modelMtx, 1.f, -1.f, 1.f); guMtxConcat(m_viewMtx, modelMtx, modelViewMtx); GX_LoadPosMtxImm(modelViewMtx, GX_PNMTX0); // Cover if (m_box) _drawCoverBox(i, mirror, dm); else _drawCoverFlat(i, mirror, dm); } const TexData *CCoverFlow::_coverTexture(int i) { if(m_items[i].texture.data == NULL) { if(m_box) return (m_items[i].state == STATE_Loading) ? &m_boxLoadingTexture : &m_boxNoCoverTexture; else return (m_items[i].state == STATE_Loading) ? &m_flatLoadingTexture : &m_flatNoCoverTexture; } return &m_items[i].texture; } void CCoverFlow::_drawCoverFlat(int i, bool mirror, CCoverFlow::DrawMode dm) { GXTexObj texObj; const TexData *tex = _coverTexture(m_covers[i].index); bool boxTex = m_items[m_covers[i].index].boxTexture && !!m_items[m_covers[i].index].texture.data; switch (dm) { case CCoverFlow::CFDR_NORMAL: { CColor color(m_covers[i].color); if (mirror) color.a = (u8)((float)color.a * m_mirrorAlpha); GX_SetTevKColor(GX_KCOLOR0, color); break; } case CCoverFlow::CFDR_STENCIL: GX_SetTevKColor(GX_KCOLOR0, CColor(i + 1, 0xFF, 0xFF, 0xFF)); break; case CCoverFlow::CFDR_SHADOW: { CColor color(m_covers[i].shadowColor); if (mirror) color.a = (u8)((float)color.a * m_mirrorAlpha); GX_SetTevKColor(GX_KCOLOR0, color); break; } } if(dm == CCoverFlow::CFDR_NORMAL) { GX_InitTexObj(&texObj, tex->data, tex->width, tex->height, tex->format, GX_CLAMP, GX_CLAMP, GX_FALSE); if(tex->maxLOD > 0) GX_InitTexObjLOD(&texObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.f, (float)tex->maxLOD, mirror ? 1.f : m_lodBias, GX_FALSE, m_edgeLOD ? GX_TRUE : GX_FALSE, m_aniso); GX_LoadTexObj(&texObj, GX_TEXMAP0); } GX_Begin(GX_QUADS, GX_VTXFMT0, g_flatCoverMeshSize); for (u32 j = 0; j < g_flatCoverMeshSize; ++j) { GX_Position3f32(g_flatCoverMesh[j].pos.x, g_flatCoverMesh[j].pos.y, g_flatCoverMesh[j].pos.z); if (dm == CCoverFlow::CFDR_NORMAL) { if (boxTex) GX_TexCoord2f32(g_flatCoverBoxTex[j].x, g_flatCoverBoxTex[j].y); else GX_TexCoord2f32(g_flatCoverMesh[j].texCoord.x, g_flatCoverMesh[j].texCoord.y); } } GX_End(); } bool CCoverFlow::checkCoverColor(const char *gameID, const char *checkID[], u32 len) { for(u32 num = 0; num < len; ++num) { if (strncmp(gameID, checkID[num], strlen(checkID[num])) == 0) return true; } return false; } u32 CCoverFlow::InternalCoverColor(const char *ID, u32 DefCaseColor) { if(checkCoverColor(ID, red, red_len)) return 0xFF0000; else if(checkCoverColor(ID, black, black_len)) return 0x000000; else if(checkCoverColor(ID, yellow, yellow_len)) return 0xFCFF00; else if(checkCoverColor(ID, greenOne, greenOne_len)) return 0x01A300; else if(checkCoverColor(ID, greenTwo, greenTwo_len)) return 0x00E360; return DefCaseColor; } void CCoverFlow::_drawCoverBox(int i, bool mirror, CCoverFlow::DrawMode dm) { CColor color; GXTexObj texObj; const TexData *tex = _coverTexture(m_covers[i].index); bool flatTex = !m_items[m_covers[i].index].boxTexture && !!m_items[m_covers[i].index].texture.data; switch (dm) { case CCoverFlow::CFDR_NORMAL: color = m_covers[i].color; if (mirror) color.a = (u8)((float)color.a * m_mirrorAlpha); GX_SetTevKColor(GX_KCOLOR0, color); break; case CCoverFlow::CFDR_STENCIL: GX_SetTevKColor(GX_KCOLOR0, CColor(i + 1, 0xFF, 0xFF, 0xFF)); break; case CCoverFlow::CFDR_SHADOW: color = m_covers[i].shadowColor; if (mirror) color.a = (u8)((float)color.a * m_mirrorAlpha); GX_SetTevKColor(GX_KCOLOR0, color); break; } /* this draws the colored spine of the box */ if(dm == CCoverFlow::CFDR_NORMAL) { // set dvd spine color texture, depending on game switch(m_items[m_covers[i].index].hdr->casecolor) { case 0x000000: case 0x181919: GX_InitTexObj(&texObj, m_dvdSkin_Black.data, m_dvdSkin_Black.width, m_dvdSkin_Black.height, m_dvdSkin_Black.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; case 0xFF0000: GX_InitTexObj(&texObj, m_dvdSkin_Red.data, m_dvdSkin_Red.width, m_dvdSkin_Red.height, m_dvdSkin_Red.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; case 0xFCFF00: GX_InitTexObj(&texObj, m_dvdSkin_Yellow.data, m_dvdSkin_Yellow.width, m_dvdSkin_Yellow.height, m_dvdSkin_Yellow.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; case 0x01A300: GX_InitTexObj(&texObj, m_dvdSkin_GreenOne.data, m_dvdSkin_GreenOne.width, m_dvdSkin_GreenOne.height, m_dvdSkin_GreenOne.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; case 0x00E360: GX_InitTexObj(&texObj, m_dvdSkin_GreenTwo.data, m_dvdSkin_GreenTwo.width, m_dvdSkin_GreenTwo.height, m_dvdSkin_GreenTwo.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; default: GX_InitTexObj(&texObj, m_dvdSkin.data, m_dvdSkin.width, m_dvdSkin.height, m_dvdSkin.format, GX_CLAMP, GX_CLAMP, GX_FALSE); break; } GX_LoadTexObj(&texObj, GX_TEXMAP0); } GX_Begin(GX_QUADS, GX_VTXFMT0, g_boxMeshQSize);// Quads for (u32 j = 0; j < g_boxMeshQSize; ++j) { GX_Position3f32(g_boxMeshQ[j].pos.x, g_boxMeshQ[j].pos.y, g_boxMeshQ[j].pos.z); if (dm == CCoverFlow::CFDR_NORMAL) GX_TexCoord2f32(g_boxMeshQ[j].texCoord.x, g_boxMeshQ[j].texCoord.y); } GX_End(); GX_Begin(GX_TRIANGLES, GX_VTXFMT0, g_boxMeshTSize);// triangles for (u32 j = 0; j < g_boxMeshTSize; ++j) { GX_Position3f32(g_boxMeshT[j].pos.x, g_boxMeshT[j].pos.y, g_boxMeshT[j].pos.z); if (dm == CCoverFlow::CFDR_NORMAL) GX_TexCoord2f32(g_boxMeshT[j].texCoord.x, g_boxMeshT[j].texCoord.y); } GX_End(); /* draw back of cover and title spine texture */ if (dm == CCoverFlow::CFDR_NORMAL) { const TexData *myTex = tex; /* if we have front cover texture only then draw back and spine textures using the default box no cover texture */ if(flatTex) myTex = &m_boxNoCoverTexture; GX_InitTexObj(&texObj, myTex->data, myTex->width, myTex->height, myTex->format, GX_CLAMP, GX_CLAMP, GX_FALSE); if (myTex->maxLOD > 0) GX_InitTexObjLOD(&texObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.f, (float)myTex->maxLOD, mirror ? 1.f : m_lodBias, GX_FALSE, m_edgeLOD ? GX_TRUE : GX_FALSE, m_aniso); GX_LoadTexObj(&texObj, GX_TEXMAP0); } GX_Begin(GX_QUADS, GX_VTXFMT0, g_boxBackCoverMeshSize); for (u32 j = 0; j < g_boxBackCoverMeshSize; ++j) { GX_Position3f32(g_boxBackCoverMesh[j].pos.x, g_boxBackCoverMesh[j].pos.y, g_boxBackCoverMesh[j].pos.z); if (dm == CCoverFlow::CFDR_NORMAL) GX_TexCoord2f32(g_boxBackCoverMesh[j].texCoord.x, g_boxBackCoverMesh[j].texCoord.y); } GX_End(); /* draw front of cover texture */ /* if front cover only we need to change texObj from box no cover texture to the front cover texture */ if (dm == CCoverFlow::CFDR_NORMAL && flatTex) { GX_InitTexObj(&texObj, tex->data, tex->width, tex->height, tex->format, GX_CLAMP, GX_CLAMP, GX_FALSE); if (tex->maxLOD > 0) GX_InitTexObjLOD(&texObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.f, (float)tex->maxLOD, mirror ? 1.f : m_lodBias, GX_FALSE, m_edgeLOD ? GX_TRUE : GX_FALSE, m_aniso); GX_LoadTexObj(&texObj, GX_TEXMAP0); } GX_Begin(GX_QUADS, GX_VTXFMT0, g_boxCoverMeshSize); for (u32 j = 0; j < g_boxCoverMeshSize; ++j) { GX_Position3f32(g_boxCoverMesh[j].pos.x, g_boxCoverMesh[j].pos.y, g_boxCoverMesh[j].pos.z); if (dm == CCoverFlow::CFDR_NORMAL) { if (flatTex) GX_TexCoord2f32(g_boxCoverFlatTex[j].x, g_boxCoverFlatTex[j].y); else GX_TexCoord2f32(g_boxCoverMesh[j].texCoord.x, g_boxCoverMesh[j].texCoord.y); } } GX_End(); } void CCoverFlow::_loadCover(int i, int item) { m_covers[i].index = item; m_covers[i].title.setText(m_font, m_items[item].hdr->title); } const char *CCoverFlow::getId(void) const { if (m_covers == NULL || m_items.empty()) return ""; return m_items[loopNum(m_covers[m_range / 2].index + m_jump, m_items.size())].hdr->id; } const char *CCoverFlow::getNextId(void) const { if (m_covers == NULL || m_items.empty()) return ""; return m_items[loopNum(m_covers[m_range / 2].index + m_jump + 1, m_items.size())].hdr->id; } const dir_discHdr * CCoverFlow::getHdr(void) const { if (m_covers == NULL || m_items.empty()) return NULL; return m_items[loopNum(m_covers[m_range / 2].index + m_jump, m_items.size())].hdr; } const dir_discHdr * CCoverFlow::getNextHdr(void) const { if (m_covers == NULL || m_items.empty()) return NULL; return m_items[loopNum(m_covers[m_range / 2].index + m_jump + 1, m_items.size())].hdr; } const dir_discHdr * CCoverFlow::getSpecificHdr(u32 place) const { if (m_covers == NULL || m_items.empty() || place >= m_items.size()) return NULL; return m_items[place].hdr; } wstringEx CCoverFlow::getTitle(void) const { if (m_covers == NULL) return L""; return m_items[m_covers[m_range / 2].index].hdr->title; } u64 CCoverFlow::getChanTitle(void) const { if (m_covers == NULL || m_items.empty()) return 0; return TITLE_ID(m_items[loopNum(m_covers[m_range / 2].index + m_jump, m_items.size())].hdr->settings[0], m_items[loopNum(m_covers[m_range / 2].index + m_jump, m_items.size())].hdr->settings[1]); } /* this is used for smallbox homebrew icon png's that have alpha transparency */ /* cover textures can't have transparency. this creates a new texture by filling the transparent parts */ void CCoverFlow::RenderTex(void) { if(m_renderingTex != NULL && m_renderingTex->data != NULL) { DrawTexture(m_renderingTex); m_vid.renderToTexture(*m_renderingTex, true); } } bool CCoverFlow::select(void) { if (m_covers == NULL || m_jump != 0) return false; if (m_selected) return true; LockMutex lock(m_mutex); int curPos = (int)m_range / 2; for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--) if ((u32)m_mouse[chan] < m_range && m_mouse[chan] != curPos) { int j; if (m_rows >= 3) j = ((m_mouse[chan] / m_rows - 1) * (m_rows - 2) + m_mouse[chan] % m_rows - 1) - ((curPos / m_rows - 1) * (m_rows - 2) + curPos % m_rows - 1); else j = m_mouse[chan] - (int)(m_range / 2); _setJump(j); } m_cameraPos += _cameraMoves(); m_covers[m_range / 2].angle += _coverMovesA(); m_covers[m_range / 2].pos += _coverMovesP(); m_selected = true; m_covers[m_range / 2].angle -= _coverMovesA(); m_covers[m_range / 2].pos -= _coverMovesP(); m_cameraPos -= _cameraMoves(); _updateAllTargets(); _playSound(m_selectSound); return true; } void CCoverFlow::cancel(void) { if (m_covers == NULL) return; LockMutex lock(m_mutex); _unselect(); _updateAllTargets(); _playSound(m_cancelSound); } u32 CCoverFlow::_currentPos(void) const { if (m_covers == NULL) return 0; return m_covers[m_range / 2].index; } void CCoverFlow::_setCurPos(u32 index) { _loadAllCovers(index); _updateAllTargets(true); } bool CCoverFlow::_setCurPosToCurItem(const char *id, const char *filename, u32 sourceNumber, bool instant) { LockMutex lock(m_mutex); u32 i, curPos = _currentPos(); if(m_items.empty() || (instant && m_covers == NULL)) return false; // for(i = 0; i < m_items.size(); ++i) { if(strlen(filename) > 0) { //homebrew folder or rom title.ext const char *name = strrchr(m_items[i].hdr->path, '/'); if(name != NULL && strcasecmp(name + 1, filename) == 0) break; else if(strcmp(m_items[i].hdr->path, filename) == 0)// scummvm break; } else if(strlen(id) > 0) { if(strcasecmp(m_items[i].hdr->id, id) == 0) break; } else { if(m_items[i].hdr->settings[0] == sourceNumber) break; } } if(i >= m_items.size()) return false; m_jump = 0; if (instant) { _loadAllCovers(i); _updateAllTargets(true); } else { int j = (int)i - (int)curPos; if (abs(j) <= (int)m_items.size() / 2) _setJump(j); else _setJump(j < 0 ? j + (int)m_items.size() : j - (int)m_items.size()); } return true; } void CCoverFlow::_updateAllTargets(bool instant) { m_targetCameraPos = m_selected ? m_loSelected.camera : m_loNormal.camera; m_targetCameraAim = m_selected ? m_loSelected.cameraAim : m_loNormal.cameraAim; for (u32 i = 0; i < m_range; ++i) _updateTarget(i, instant); } void CCoverFlow::_updateTarget(int i, bool instant) { SLayout &lo = m_selected ? m_loSelected : m_loNormal; int hcenter = m_columns / 2; int vcenter = m_rows / 2; int x = i / m_rows; int y = i % m_rows; CCover &cvr = m_covers[i]; // Left covers if (x < hcenter) { Vector3D deltaAngle(lo.leftDeltaAngle * (float)(hcenter - 1 - x)); cvr.targetAngle = lo.leftAngle + deltaAngle; cvr.targetPos = lo.leftPos + lo.leftSpacer.rotateY(deltaAngle.y * 0.5f) * (float)(hcenter - 1 - x); cvr.targetScale = lo.leftScale; if (_invisibleCover(x, y)) { cvr.targetColor = CColor(lo.endColor.r, lo.endColor.g, lo.endColor.b, 0); cvr.targetShadowColor = CColor(lo.shadowColorEnd.r, lo.shadowColorEnd.g, lo.shadowColorEnd.b, 0); } else { u8 a = (x - 1) * 0xFF / max(1, hcenter - 2); cvr.targetColor = CColor::interpolate(lo.endColor, lo.begColor, a); cvr.targetShadowColor = CColor::interpolate(lo.shadowColorEnd, lo.shadowColorBeg, a); } cvr.txtTargetAngle = lo.txtLeftAngle; cvr.txtTargetPos = lo.txtLeftPos; cvr.txtTargetColor = 0; cvr.txtColor = 0;//instant fade out if was displayed cvr.title.setFrame(lo.txtSideWidth, lo.txtSideStyle, false, instant); } // Right covers else if (x > hcenter) { Vector3D deltaAngle(lo.rightDeltaAngle * (float)(x - hcenter - 1)); cvr.targetAngle = lo.rightAngle + deltaAngle; cvr.targetPos = lo.rightPos + lo.rightSpacer.rotateY(deltaAngle.y * 0.5f) * (float)(x - hcenter - 1); cvr.targetScale = lo.rightScale; if (_invisibleCover(x, y)) { cvr.targetColor = CColor(lo.endColor.r, lo.endColor.g, lo.endColor.b, 0); cvr.targetShadowColor = CColor(lo.shadowColorEnd.r, lo.shadowColorEnd.g, lo.shadowColorEnd.b, 0); } else { u8 a = (m_columns - x - 2) * 0xFF / max(1, hcenter - 2); cvr.targetColor = CColor::interpolate(lo.endColor, lo.begColor, a); cvr.targetShadowColor = CColor::interpolate(lo.shadowColorEnd, lo.shadowColorBeg, a); } cvr.txtTargetAngle = lo.txtRightAngle; cvr.txtTargetPos = lo.txtRightPos; cvr.txtTargetColor = 0; cvr.txtColor = 0;//instant fade out if was displayed cvr.title.setFrame(lo.txtSideWidth, lo.txtSideStyle, false, instant); } // New center cover else if (y == vcenter) { cvr.targetColor = 0xFFFFFFFF; cvr.targetShadowColor = lo.shadowColorCenter; cvr.targetAngle = lo.centerAngle; cvr.targetPos = lo.centerPos; cvr.targetScale = lo.centerScale; cvr.txtTargetColor = 0xFF; cvr.txtTargetAngle = lo.txtCenterAngle; cvr.txtTargetPos = lo.txtCenterPos; cvr.title.setFrame(lo.txtCenterWidth, lo.txtCenterStyle, false, true); } else // Center of a row { cvr.targetColor = lo.begColor; cvr.targetShadowColor = lo.shadowColorBeg; cvr.targetAngle = lo.rowCenterAngle; cvr.targetPos = lo.rowCenterPos; cvr.targetScale = lo.rowCenterScale; cvr.txtTargetColor = 0; cvr.txtColor = 0;//instant fade out if was displayed if (y < vcenter) { cvr.txtTargetAngle = lo.txtLeftAngle; cvr.txtTargetPos = lo.txtLeftPos; } else // (y > vcenter) { cvr.txtTargetAngle = lo.txtRightAngle; cvr.txtTargetPos = lo.txtRightPos; } cvr.title.setFrame(lo.txtSideWidth, lo.txtSideStyle, false, instant); } // Top row if (y < vcenter) { Vector3D deltaAngle(lo.topDeltaAngle * (float)(vcenter - y - 1)); cvr.targetAngle += lo.topAngle + deltaAngle; cvr.targetPos += lo.topSpacer.rotateX(deltaAngle.x * 0.5f) * (float)(vcenter - y); if (_invisibleCover(x, y)) { cvr.targetColor = CColor(lo.endColor.r, lo.endColor.g, lo.endColor.b, 0); cvr.targetShadowColor = CColor(lo.shadowColorEnd.r, lo.shadowColorEnd.g, lo.shadowColorEnd.b, 0); } else { u8 a = (y - 1) * 0xFF / max(1, vcenter - 2); CColor c1(CColor::interpolate(lo.endColor, lo.begColor, a)); cvr.targetColor = CColor::interpolate(c1, cvr.targetColor, 0x7F); CColor c2(CColor::interpolate(lo.shadowColorEnd, lo.shadowColorBeg, a)); cvr.targetShadowColor = CColor::interpolate(c2, cvr.targetShadowColor, 0x7F); } } // Bottom row else if (y > vcenter) { Vector3D deltaAngle(lo.bottomDeltaAngle * (float)(y - vcenter - 1)); cvr.targetAngle += lo.bottomAngle + deltaAngle; cvr.targetPos += lo.bottomSpacer.rotateX(deltaAngle.x * 0.5f) * (float)(y - vcenter); if (_invisibleCover(x, y)) { cvr.targetColor = CColor(lo.endColor.r, lo.endColor.g, lo.endColor.b, 0); cvr.targetShadowColor = CColor(lo.shadowColorEnd.r, lo.shadowColorEnd.g, lo.shadowColorEnd.b, 0); } else { u8 a = (m_rows - y - 2) * 0xFF / max(1, vcenter - 2); CColor c1(CColor::interpolate(lo.endColor, lo.begColor, a)); cvr.targetColor = CColor::interpolate(c1, cvr.targetColor, 0x7F); CColor c2(CColor::interpolate(lo.shadowColorEnd, lo.shadowColorBeg, a)); cvr.targetShadowColor = CColor::interpolate(c2, cvr.targetShadowColor, 0x7F); } } else if (vcenter > 0 && !_invisibleCover(x, y) && x != hcenter) { cvr.targetColor = CColor::interpolate(lo.begColor, cvr.targetColor, 0x7F); cvr.targetShadowColor = CColor::interpolate(lo.shadowColorBeg, cvr.targetShadowColor, 0x7F); } // Mouse selection for(int chan = WPAD_MAX_WIIMOTES-1; chan >= 0; chan--) if (!m_selected && (u32)m_mouse[chan] < m_range) { if (i == m_mouse[chan]) { cvr.targetColor = 0xFFFFFFFF; cvr.targetShadowColor = lo.shadowColorCenter; cvr.txtTargetAngle = lo.txtCenterAngle; cvr.txtTargetPos = lo.txtCenterPos; cvr.txtTargetColor = 0xFF; cvr.title.setFrame(lo.txtCenterWidth, lo.txtCenterStyle, false, true); } else { cvr.targetColor = lo.mouseOffColor; cvr.targetShadowColor = lo.shadowColorOff; cvr.txtTargetAngle = m_mouse[chan] > i ? lo.txtLeftAngle : lo.txtRightAngle; cvr.txtTargetPos = m_mouse[chan] > i ? lo.txtLeftPos : lo.txtRightPos; cvr.txtTargetColor = 0; cvr.txtColor = 0;//instant fade out if was displayed cvr.title.setFrame(lo.txtSideWidth, lo.txtSideStyle, false, instant); } if (_invisibleCover(x, y)) { cvr.targetColor.a = 0; cvr.targetShadowColor.a = 0; } } // if (instant) _instantTarget(i); } void CCoverFlow::_instantTarget(int i) { CCover &cvr = m_covers[i]; cvr.angle = cvr.targetAngle; cvr.pos = cvr.targetPos; cvr.scale = cvr.targetScale; cvr.color = cvr.targetColor; cvr.shadowColor = cvr.targetShadowColor; cvr.txtAngle = cvr.txtTargetAngle; cvr.txtPos = cvr.txtTargetPos; cvr.txtColor = cvr.txtTargetColor; } bool CCoverFlow::_sortByPlayCount(CItem item1, CItem item2) { return (item1.playcount == item2.playcount) ? _sortByAlpha(item1, item2) : item1.playcount > item2.playcount; } bool CCoverFlow::_sortByLastPlayed(CItem item1, CItem item2) { return (item1.lastPlayed == item2.lastPlayed) ? _sortByPlayCount(item1, item2) : item1.lastPlayed > item2.lastPlayed; } bool CCoverFlow::_sortByGameID(CItem item1, CItem item2) { u32 s = min(strlen(item1.hdr->id), strlen(item2.hdr->id)); for(u32 k = 0; k < s; ++k) { if(toupper(item1.hdr->id[k]) > toupper(item2.hdr->id[k])) return false; else if(toupper(item1.hdr->id[k]) < toupper(item2.hdr->id[k])) return true; } return strlen(item1.hdr->id) < strlen(item2.hdr->id); } bool CCoverFlow::_sortByAlpha(CItem item1, CItem item2) { const wchar_t *first = item1.hdr->title; const wchar_t *second = item2.hdr->title; return wchar_cmp(first, second, wcslen(first), wcslen(second)); } bool CCoverFlow::_sortByPlayers(CItem item1, CItem item2) { if(item1.hdr->players == item2.hdr->players) return _sortByAlpha(item1, item2); return item1.hdr->players < item2.hdr->players; } bool CCoverFlow::_sortByWifiPlayers(CItem item1, CItem item2) { if(item1.hdr->wifi == item2.hdr->wifi) return _sortByAlpha(item1, item2); return item1.hdr->wifi < item2.hdr->wifi; } bool CCoverFlow::_sortByBtnNumbers(CItem item1, CItem item2) { if(item1.hdr->settings[0] == item2.hdr->settings[0]) return _sortByAlpha(item1, item2); return item1.hdr->settings[0] < item2.hdr->settings[0]; } bool CCoverFlow::start(const string &m_imgsDir) { if (m_items.empty()) return true; /* sort coverflow items list based on sort type */ if (m_sorting == SORT_ALPHA) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByAlpha); else if (m_sorting == SORT_PLAYCOUNT) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByPlayCount); else if (m_sorting == SORT_LASTPLAYED) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByLastPlayed); else if (m_sorting == SORT_GAMEID) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByGameID); else if (m_sorting == SORT_PLAYERS) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByPlayers); else if (m_sorting == SORT_WIFIPLAYERS) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByWifiPlayers); else if (m_sorting == SORT_BTN_NUMBERS) sort(m_items.begin(), m_items.end(), CCoverFlow::_sortByBtnNumbers); /* load the colored skin/spine images if not already done */ if(!m_dvdskin_loaded) { if(TexHandle.fromImageFile(m_dvdSkin, fmt("%s/dvdskin.jpg", m_imgsDir.c_str())) != TE_OK) return false; if(TexHandle.fromImageFile(m_dvdSkin_Red, fmt("%s/dvdskin_red.jpg", m_imgsDir.c_str())) != TE_OK) return false; if(TexHandle.fromImageFile(m_dvdSkin_Black, fmt("%s/dvdskin_black.jpg", m_imgsDir.c_str())) != TE_OK) return false; if(TexHandle.fromImageFile(m_dvdSkin_Yellow, fmt("%s/dvdskin_yellow.jpg", m_imgsDir.c_str())) != TE_OK) return false; if(TexHandle.fromImageFile(m_dvdSkin_GreenOne, fmt("%s/dvdskin_greenone.jpg", m_imgsDir.c_str())) != TE_OK) return false; if(TexHandle.fromImageFile(m_dvdSkin_GreenTwo, fmt("%s/dvdskin_greentwo.jpg", m_imgsDir.c_str())) != TE_OK) return false; m_dvdskin_loaded = true; } /* load the no pic or loading cover images if not already done */ if(!m_defcovers_loaded) { if(m_pngLoadCover.empty() || TexHandle.fromImageFile(m_boxLoadingTexture, m_pngLoadCover.c_str(), GX_TF_CMPR, 32, 512) != TE_OK) { if(TexHandle.fromImageFile(m_boxLoadingTexture, fmt("%s/loading.jpg", m_imgsDir.c_str()), GX_TF_CMPR, 32, 512) != TE_OK) return false; } if(m_pngNoCover.empty() || TexHandle.fromImageFile(m_boxNoCoverTexture, m_pngNoCover.c_str(), GX_TF_CMPR, 32, 512) != TE_OK) { if(TexHandle.fromImageFile(m_boxNoCoverTexture, fmt("%s/nopic.png", m_imgsDir.c_str()), GX_TF_CMPR, 32, 512) != TE_OK) return false; } if(m_pngLoadCoverFlat.empty() || TexHandle.fromImageFile(m_flatLoadingTexture, m_pngLoadCoverFlat.c_str(), GX_TF_CMPR, 32, 512) != TE_OK) { if(TexHandle.fromImageFile(m_flatLoadingTexture, fmt("%s/flatloading.jpg", m_imgsDir.c_str()), GX_TF_CMPR, 32, 512) != TE_OK) return false; } if(m_pngNoCoverFlat.empty() || TexHandle.fromImageFile(m_flatNoCoverTexture, m_pngNoCoverFlat.c_str(), GX_TF_CMPR, 32, 512) != TE_OK) { if(TexHandle.fromImageFile(m_flatNoCoverTexture, fmt("%s/flatnopic.png", m_imgsDir.c_str()), GX_TF_CMPR, 32, 512) != TE_OK) return false; } m_defcovers_loaded = true; } /* allocate enough memory for the covers list (m_covers) based on rows * columns (m_range) */ if(m_covers != NULL) MEM2_free(m_covers); m_covers = NULL; if(m_range > 0) { m_covers = (CCover*)MEM2_alloc(sizeof(CCover) * m_range); for(size_t i = 0; i < m_range; ++i) m_covers[i] = *(new(m_covers+i) CCover); } m_jump = 0; m_selected = false; m_moved = true; return true; } void CCoverFlow::up(void) { if (m_covers == NULL) return; if (m_jump != 0) return; LockMutex lock(m_mutex); _left(m_minDelay, 1); } void CCoverFlow::down(void) { if (m_covers == NULL) return; if (m_jump != 0) return; LockMutex lock(m_mutex); _right(m_minDelay, 1); } void CCoverFlow::left(void) { if (m_covers == NULL) return; if (m_jump != 0) return; LockMutex lock(m_mutex); _left(m_minDelay, m_rows >= 3 ? m_rows - 2 : 1); } void CCoverFlow::right(void) { if (m_covers == NULL) return; if (m_jump != 0) return; LockMutex lock(m_mutex); _right(m_minDelay, m_rows >= 3 ? m_rows - 2 : 1); } void CCoverFlow::_left(int repeatDelay, u32 step) { int prev, arrStep; if (m_delay > 0) return; m_moved = true; m_delay = repeatDelay; m_covers[m_range / 2].angle += _coverMovesA(); m_covers[m_range / 2].pos += _coverMovesP(); if (m_rows >= 3) { prev = m_covers[m_range / 2].index; // Shift the array to the right arrStep = step % (m_rows - 2) + (step / (m_rows - 2)) * m_rows; for (int i = (int)m_range - 1; i >= arrStep; --i) m_covers[i] = m_covers[i - arrStep]; _loadAllCovers(loopNum((int)prev - step, m_items.size())); _updateAllTargets(); if (arrStep % m_rows != 0) for (u32 i = 0; i < m_columns; ++i) _instantTarget(i * m_rows); if (arrStep / m_rows != 0) for (u32 i = 0; i < m_rows; ++i) _instantTarget(i); } else { prev = m_covers[0].index; // Shift the array to the right for (int i = (int)m_range - 1; i > 0; --i) m_covers[i] = m_covers[i - 1]; _loadCover(0, loopNum(prev - step, m_items.size())); _updateAllTargets(); _instantTarget(0); } _playSound(m_flipSound); m_covers[m_range / 2].angle -= _coverMovesA(); m_covers[m_range / 2].pos -= _coverMovesP(); } void CCoverFlow::_right(int repeatDelay, u32 step) { int prev, arrStep; if (m_delay > 0) return; m_moved = true; m_delay = repeatDelay; m_covers[m_range / 2].angle += _coverMovesA(); m_covers[m_range / 2].pos += _coverMovesP(); if (m_rows >= 3) { prev = m_covers[m_range / 2].index; // Shift the array to the left arrStep = step % (m_rows - 2) + (step / (m_rows - 2)) * m_rows; for (u32 i = 0; i < m_range - arrStep; ++i) m_covers[i] = m_covers[i + arrStep]; _loadAllCovers(loopNum(prev + step, m_items.size())); _updateAllTargets(); if (arrStep % m_rows != 0) for (u32 i = 0; i < m_columns; ++i) _instantTarget(i * m_rows + m_rows - 1); if (arrStep / m_rows != 0) for (u32 i = 0; i < m_rows; ++i) _instantTarget(i + (m_columns - 1) * m_rows); } else { prev = m_covers[m_range - 1].index; // Shift the array to the left for (u32 i = 0; i < m_range - 1; ++i) m_covers[i] = m_covers[i + 1]; _loadCover(m_range - 1, loopNum(prev + step, m_items.size())); _updateAllTargets(); _instantTarget(m_range - 1); } _playSound(m_flipSound); m_covers[m_range / 2].angle -= _coverMovesA(); m_covers[m_range / 2].pos -= _coverMovesP(); } void CCoverFlow::mouse(int chan, int x, int y) { if (m_covers == NULL) return; int m = m_mouse[chan]; if (x < 0 || y < 0) m_mouse[chan] = -1; else { m_vid.prepareStencil(); _draw(CCoverFlow::CFDR_STENCIL, false, false); m_vid.renderStencil(); m_mouse[chan] = m_vid.stencilVal(x, y) - 1; } if (m != m_mouse[chan]) { if ((u32)m_mouse[chan] < m_range && m_mouse[chan] >= 0) _playSound(m_hoverSound); _updateAllTargets(); } } bool CCoverFlow::mouseOver(int x, int y) { if (m_covers == NULL) return false; m_vid.prepareStencil(); _draw(CCoverFlow::CFDR_STENCIL, false, false); m_vid.renderStencil(); m_vid.prepareStencil(); _draw(CCoverFlow::CFDR_STENCIL, false, false); m_vid.renderStencil(); return m_vid.stencilVal(x, y) == (int)m_range / 2 + 1; } void CCoverFlow::setSelected(int i) { LockMutex lock(m_mutex); m_moved = true; _loadAllCovers(i); _updateAllTargets(true); select(); } void CCoverFlow::pageUp(void) { if (m_covers == NULL) return; int n, j; if (m_rows >= 3) { j = m_jump - max(1, (int)m_columns - 2); if (m_jump != 0) { n = ((int)m_items.size() - 1) / (m_rows - 2) + 1; j = j < 0 ? -(-j % n) : j % n; } j *= (int)m_rows - 2; } else { j = m_jump - max(1, (int)m_range / 2); if (m_jump != 0) { n = (int)m_items.size(); j = j < 0 ? -(-j % n) : j % n; } } _setJump(j); } void CCoverFlow::pageDown(void) { if (m_covers == NULL) return; int n, j; if (m_rows >= 3) { j = m_jump + max(1, (int)m_columns - 2); if (m_jump != 0) { n = ((int)m_items.size() - 1) / (m_rows - 2) + 1; j = j < 0 ? -(-j % n) : j % n; } j *= (int)m_rows - 2; } else { j = m_jump + max(1, (int)m_range / 2); if (m_jump != 0) { n = (int)m_items.size(); j = j < 0 ? -(-j % n) : j % n; } } _setJump(j); } void CCoverFlow::flip(bool force, bool f) { if (m_covers == NULL || !m_selected) return; LockMutex lock(m_mutex); CCoverFlow::CCover &cvr = m_covers[m_range / 2]; if (!force) f = m_loSelected.centerAngle == cvr.targetAngle && m_loSelected.centerPos == cvr.targetPos && m_loSelected.centerScale == cvr.targetScale; if (f) { cvr.targetPos = m_loSelected.centerPos + m_flipCoverPos; cvr.targetAngle = m_loSelected.centerAngle + m_flipCoverAngle; cvr.targetScale = m_loSelected.centerScale * m_flipCoverScale; cvr.txtTargetColor = m_flipCoverPos == Vector3D(0.f, 0.f, 0.f) ? 0xFF : 0; } else { cvr.targetPos = m_loSelected.centerPos; cvr.targetAngle = m_loSelected.centerAngle; cvr.targetScale = m_loSelected.centerScale; cvr.txtTargetColor = 0xFF; } } bool CCoverFlow::_invisibleCover(u32 x, u32 y) { return (m_rows > 1 && (y == 0 || y == m_rows - 1)) || x == 0 || x == m_columns - 1; } void CCoverFlow::_loadAllCovers(int i) { int r = (int)m_rows; int c = (int)m_columns; int s = (int)m_items.size(); if (m_rows >= 3) for (int j = 0; j < r; ++j) for (int k = 0; k < c; ++k) _loadCover(j + k * r, loopNum(i + (j - r / 2) + (k - c / 2) * (r - 2), s)); else for (int k = 0; k < (int)m_range; ++k) _loadCover(k, loopNum(i + k - (int)m_range / 2, s)); } void CCoverFlow::_setJump(int j) { _loadAllCovers(_currentPos()); m_jump = j; if (m_jump == 0) _updateAllTargets(); } void CCoverFlow::_completeJump(void) { if (m_rows >= 3) _loadAllCovers((int)m_covers[m_range / 2].index + m_jump); else { if (m_jump < 0) _loadAllCovers((int)m_covers[0].index + m_jump + (int)m_range / 2); else _loadAllCovers((int)m_covers[m_range - 1].index + m_jump - ((int)m_range - 1) / 2); } } void CCoverFlow::nextLetter(wchar_t *c) { if (m_covers == NULL || m_sorting == SORT_BTN_NUMBERS) { c[0] = L'\0'; return; } if (m_sorting == SORT_WIFIPLAYERS || m_sorting == SORT_PLAYERS) return nextPlayers(m_sorting == SORT_WIFIPLAYERS, c); if (m_sorting == SORT_GAMEID) return nextID(c); LockMutex lock(m_mutex); u32 i, j = 0, k = 0, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); while (!iswalnum(m_items[curPos].hdr->title[j]) && m_items[curPos].hdr->title[j+1] != L'\0') j++; c[0] = upperCaseWChar(m_items[curPos].hdr->title[j]); for (i = 1; i < n; ++i) { k = 0; while (!iswalnum(m_items[loopNum(curPos + i, n)].hdr->title[k]) && m_items[loopNum(curPos + i, n)].hdr->title[k+1] != L'\0') k++; if (upperCaseWChar(m_items[loopNum(curPos + i, n)].hdr->title[k]) != c[0]) break; } if (i < n) { _setJump(i); c[0] = upperCaseWChar(m_items[loopNum(curPos + i, n)].hdr->title[k]); } _updateAllTargets(); } void CCoverFlow::prevLetter(wchar_t *c) { if (m_covers == NULL || m_sorting == SORT_BTN_NUMBERS) { c[0] = L'\0'; return; } if (m_sorting == SORT_WIFIPLAYERS || m_sorting == SORT_PLAYERS) return prevPlayers(m_sorting == SORT_WIFIPLAYERS, c); if (m_sorting == SORT_GAMEID) return prevID(c); LockMutex lock(m_mutex); u32 i, j = 0, k = 0, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); while (!iswalnum(m_items[curPos].hdr->title[j]) && m_items[curPos].hdr->title[j+1] != L'\0') j++; c[0] = upperCaseWChar(m_items[curPos].hdr->title[j]); for (i = 1; i < n; ++i) { k = 0; while (!iswalnum(m_items[loopNum(curPos - i, n)].hdr->title[k]) && m_items[loopNum(curPos - i, n)].hdr->title[k+1] != L'\0') k++; if (upperCaseWChar(m_items[loopNum(curPos - i, n)].hdr->title[k]) != c[0]) { c[0] = upperCaseWChar(m_items[loopNum(curPos - i, n)].hdr->title[k]); while(i < n) { ++i; k = 0; while (!iswalnum(m_items[loopNum(curPos - i, n)].hdr->title[k]) && m_items[loopNum(curPos - i, n)].hdr->title[k+1] != L'\0') k++; if(upperCaseWChar(m_items[loopNum(curPos - i, n)].hdr->title[k]) != c[0]) break; } i--; break; } } if (i < n) { _setJump(-i); k = 0; while (!iswalnum(m_items[loopNum(curPos - i, n)].hdr->title[k]) && m_items[loopNum(curPos - i, n)].hdr->title[k+1] != L'\0') k++; c[0] = upperCaseWChar(m_items[loopNum(curPos - i, n)].hdr->title[k]); } _updateAllTargets(); } void CCoverFlow::nextPlayers(bool wifi, wchar_t *c) { LockMutex lock(m_mutex); u32 i, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); int players = wifi ? m_items[curPos].hdr->wifi : m_items[curPos].hdr->players; for (i = 1; i < n; ++i) if ((wifi ? m_items[loopNum(curPos + i, n)].hdr->wifi : m_items[loopNum(curPos + i, n)].hdr->players) != players) break; if (i < n) { _setJump(i); players = wifi ? m_items[loopNum(curPos + i, n)].hdr->wifi : m_items[loopNum(curPos + i, n)].hdr->players; } char p[4] = {0 ,0 ,0 ,0}; sprintf(p, "%d", players); mbstowcs(c, p, strlen(p)); _updateAllTargets(); } void CCoverFlow::prevPlayers(bool wifi, wchar_t *c) { LockMutex lock(m_mutex); u32 i, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); int players = wifi ? m_items[curPos].hdr->wifi : m_items[curPos].hdr->players; for (i = 1; i < n; ++i) if ((wifi ? m_items[loopNum(curPos - i, n)].hdr->wifi : m_items[loopNum(curPos - i, n)].hdr->players) != players) { players = wifi ? m_items[loopNum(curPos - i, n)].hdr->wifi : m_items[loopNum(curPos - i, n)].hdr->players; while(i < n && (wifi ? m_items[loopNum(curPos - i, n)].hdr->wifi : m_items[loopNum(curPos - i, n)].hdr->players) == players) ++i; i--; break; } if (i < n) { _setJump(-i); players = wifi ? m_items[loopNum(curPos - i, n)].hdr->wifi : m_items[loopNum(curPos - i, n)].hdr->players; } char p[4] = {0 ,0 ,0 ,0}; sprintf(p, "%d", players); mbstowcs(c, p, strlen(p)); _updateAllTargets(); } void CCoverFlow::nextID(wchar_t *c) { LockMutex lock(m_mutex); u32 i, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); char *system = m_items[curPos].hdr->id; for (i = 1; i < n; ++i) if (m_items[loopNum(curPos + i, n)].hdr->id[0] != system[0]) break; if (i < n) { _setJump(i); system = m_items[loopNum(curPos + i, n)].hdr->id; } system[1] = '\0'; mbstowcs(c, system, 1); _updateAllTargets(); } void CCoverFlow::prevID(wchar_t *c) { LockMutex lock(m_mutex); u32 i, n = m_items.size(); _completeJump(); u32 curPos = _currentPos(); char *system = m_items[curPos].hdr->id; for(i = 1; i < n; ++i) if(m_items[loopNum(curPos - i, n)].hdr->id[0] != system[0]) { system = m_items[loopNum(curPos - i, n)].hdr->id; while(i < n && m_items[loopNum(curPos - i, n)].hdr->id[0] == system[0]) ++i; i--; break; } if (i < n) { _setJump(-i); system = m_items[loopNum(curPos - i, n)].hdr->id; } system[1] = '\0'; mbstowcs(c, system, 1); _updateAllTargets(); } void CCoverFlow::_coverTick(int i) { float speed = m_selected ? m_selected_speed : m_normal_speed; Vector3D posDist(m_covers[i].targetPos - m_covers[i].pos); if (posDist.sqNorm() < 0.5f) speed *= 0.5f; m_covers[i].angle += (m_covers[i].targetAngle - m_covers[i].angle) * speed; m_covers[i].pos += posDist * speed; m_covers[i].scale += (m_covers[i].targetScale - m_covers[i].scale) * speed; if (m_covers[i].color != m_covers[i].targetColor) { CColor c(m_covers[i].color); m_covers[i].color = CColor::interpolate(c, m_covers[i].targetColor, 0x20); if (m_covers[i].color == c) // If the interpolation doesn't do anything because of numerical approximation, force the target color m_covers[i].color = m_covers[i].targetColor; } if (m_covers[i].shadowColor != m_covers[i].targetShadowColor) { CColor c(m_covers[i].shadowColor); m_covers[i].shadowColor = CColor::interpolate(c, m_covers[i].targetShadowColor, 0x20); if (m_covers[i].shadowColor == c) // If the interpolation doesn't do anything because of numerical approximation, force the target color m_covers[i].shadowColor = m_covers[i].targetShadowColor; } m_covers[i].txtAngle += (m_covers[i].txtTargetAngle - m_covers[i].txtAngle) * speed; m_covers[i].txtPos += (m_covers[i].txtTargetPos - m_covers[i].txtPos) * speed; int colorDist = (int)m_covers[i].txtTargetColor - (int)m_covers[i].txtColor; m_covers[i].txtColor += abs(colorDist) >= 8 ? (u8)(colorDist / 8) : (u8)colorDist; m_covers[i].title.tick(); } void CCoverFlow::_unselect(void) { if (m_selected) { m_cameraPos += _cameraMoves(); m_covers[m_range / 2].angle += _coverMovesA(); m_covers[m_range / 2].pos += _coverMovesP(); m_selected = false; m_covers[m_range / 2].angle -= _coverMovesA(); m_covers[m_range / 2].pos -= _coverMovesP(); m_cameraPos -= _cameraMoves(); m_targetCameraPos = m_loNormal.camera; m_targetCameraAim = m_loNormal.cameraAim; } } void CCoverFlow::_jump(void) { int step, delay = 2; if (m_rows >= 3) step = (u32)abs(m_jump) <= (m_rows - 2) / 2 ? 1 : m_rows - 2; else // Cheat and skip covers we wouldn't see anyway // This means the jump shouldn't be modified before it's done completely step = (u32)abs(m_jump) > m_range + 1 ? abs(m_jump) - (m_range + 1) : 1; if (m_jump < 0) { _left(delay, step); m_jump += step; } else if (m_jump > 0) { _right(delay, step); m_jump -= step; } } void CCoverFlow::tick(void) { if (m_covers == NULL) return; LockMutex lock(m_mutex); ++m_tickCount; if (m_delay > 0) --m_delay; else _jump(); for (u32 i = 0; i < m_range; ++i) _coverTick(i); m_cameraPos += (m_targetCameraPos - m_cameraPos) * 0.2f; m_cameraAim += (m_targetCameraAim - m_cameraAim) * 0.2f; } struct SWFCHeader { u8 newFmt : 1; // Was 0 in beta u8 full : 1; u8 cmpr : 1; u8 zipped : 1; u8 backCover : 1; u16 width; u16 height; u8 maxLOD; u16 backWidth; u16 backHeight; u8 backMaxLOD; public: u32 getWidth(void) const { return width * 4; } u32 getHeight(void) const { return height * 4; } u32 getBackWidth(void) const { return backWidth * 4; } u32 getBackHeight(void) const { return backHeight * 4; } SWFCHeader(void) { memset(this, 0, sizeof *this); } SWFCHeader(const TexData &tex, bool f, bool z, const TexData &backTex = TexData()) { newFmt = 1; full = f ? 1 : 0; cmpr = tex.format == GX_TF_CMPR ? 1 : 0; zipped = z ? 1 : 0; width = tex.width / 4; height = tex.height / 4; maxLOD = tex.maxLOD; backCover = !!backTex.data ? 1 : 0; backWidth = backTex.width / 4; backHeight = backTex.height / 4; backMaxLOD = backTex.maxLOD; } }; bool CCoverFlow::cacheCoverFile(const char *wfcPath, const char *coverPath, bool full) { TexData tex; tex.thread = true;// lets TexHandle know this texture is a cover image and in case its a homebrew icon.png m_renderingTex = &tex;// only used if cover has alpha transparency - homebrew icon.png and sourceflow smallbox u8 textureFmt = m_compressTextures ? GX_TF_CMPR : GX_TF_RGBA8;// always GX_TF_CMPR if(TexHandle.fromImageFile(tex, coverPath, textureFmt, 32) != TE_OK) { m_renderingTex = NULL; return false; } m_renderingTex = NULL; tex.thread = false; u32 bufSize = fixGX_GetTexBufferSize(tex.width, tex.height, tex.format, tex.maxLOD > 0 ? GX_TRUE : GX_FALSE, tex.maxLOD); if(tex.data != NULL) { FILE *file = fopen(wfcPath, "wb"); if(file != NULL) { SWFCHeader header(tex, full, false); fwrite(&header, 1, sizeof(header), file); fwrite(tex.data, 1, bufSize, file); fclose(file); } } TexHandle.Cleanup(tex); return true; } bool CCoverFlow::cacheCoverBuffer(const char *wfcPath, const u8 *png, bool full) { TexData tex; u8 textureFmt = m_compressTextures ? GX_TF_CMPR : GX_TF_RGB565;// always GX_TF_CMPR if(TexHandle.fromPNG(tex, png, textureFmt, 32) != TE_OK) return false; u32 bufSize = fixGX_GetTexBufferSize(tex.width, tex.height, tex.format, tex.maxLOD > 0 ? GX_TRUE : GX_FALSE, tex.maxLOD); if(tex.data != NULL) { FILE *file = fopen(wfcPath, "wb"); if(file != NULL) { SWFCHeader header(tex, full, false); fwrite(&header, 1, sizeof(header), file); fwrite(tex.data, 1, bufSize, file); fclose(file); } } TexHandle.Cleanup(tex); return true; } bool CCoverFlow::fullCoverCached(const char *wfcPath) { bool found = false; FILE *file = fopen(wfcPath, "rb"); if(file != NULL) { SWFCHeader header; found = fread(&header, 1, sizeof header, file) == sizeof header && header.full != 0 && m_compressTextures == (header.cmpr != 0) && header.getWidth() >= 8 && header.getHeight() >= 8 && header.getWidth() <= 1090 && header.getHeight() <= 1090; fclose(file); } return found; } bool CCoverFlow::_calcTexLQLOD(TexData &tex) { bool done = false; while (tex.width > 512 && tex.height > 512 && tex.maxLOD > 0) { --tex.maxLOD; tex.width >>= 1;// divide by 2 tex.height >>= 1;// divide by 2 done = true; } return done; } void CCoverFlow::_dropHQLOD(int i) { if ((u32)i >= m_items.size()) return; LockMutex lock(m_mutex); const TexData &prevTex = m_items[i].texture; if(prevTex.data == NULL) return; TexData newTex; newTex.maxLOD = prevTex.maxLOD; newTex.width = prevTex.width; newTex.height = prevTex.height; newTex.format = prevTex.format; if (!CCoverFlow::_calcTexLQLOD(newTex)) return; u32 prevTexLen = fixGX_GetTexBufferSize(prevTex.width, prevTex.height, prevTex.format, prevTex.maxLOD > 0 ? GX_TRUE : GX_FALSE, prevTex.maxLOD); u32 newTexLen = fixGX_GetTexBufferSize(newTex.width, newTex.height, newTex.format, newTex.maxLOD > 0 ? GX_TRUE : GX_FALSE, newTex.maxLOD); newTex.data = (u8*)MEM2_alloc(newTexLen); if(newTex.data == NULL) return; memcpy(newTex.data, prevTex.data + (prevTexLen - newTexLen), newTexLen); DCFlushRange(newTex.data, newTexLen); TexHandle.Cleanup(m_items[i].texture); m_items[i].texture = newTex; } const char *CCoverFlow::getFilenameId(const dir_discHdr *curHdr) { if(NoGameID(curHdr->type)) { if(strrchr(curHdr->path, '/') != NULL) return strrchr(curHdr->path, '/') + 1;// app folder or rom name.ext else return curHdr->path;// title for scummvm } return curHdr->id;// ID for Wii, GC, & Channels } CCoverFlow::CLRet CCoverFlow::_loadCoverTex(u32 i, bool box, bool hq, bool blankBoxCover) { if(!m_loadingCovers) return CL_ERROR; if(box && m_smallBox)// prevent smallbox from loading full box cover return CL_ERROR; if(blankBoxCover && m_items[i].hdr->type == TYPE_SOURCE)// blank covers not used for sourceflow return CL_ERROR; bool allocFailed = false; /* try to find the wfc texture file in the cache folder */ if(!m_cachePath.empty()) { char wfcTitle[128]; wfcTitle[127] = '\0'; const char *wfcCoverDir = NULL; char *full_path = (char*)MEM2_alloc(MAX_FAT_PATH+1); if(full_path == NULL) return CL_NOMEM; memset(full_path, 0, MAX_FAT_PATH+1); /* get title for wfc file */ if(blankBoxCover) { const char *blankCoverPath = mainMenu.getBlankCoverPath(m_items[i].hdr); if(blankCoverPath != NULL && strrchr(blankCoverPath, '/') != NULL) strncpy(wfcTitle, strrchr(blankCoverPath, '/') + 1, sizeof(wfcTitle) - 1); else return CL_ERROR; } else strncpy(wfcTitle, getFilenameId(m_items[i].hdr), sizeof(wfcTitle) - 1); /* get coverfolder for plugins, sourceflow, and homebrew */ if(m_items[i].hdr->type == TYPE_PLUGIN && m_pluginCacheFolders) wfcCoverDir = m_plugin.GetCoverFolderName(m_items[i].hdr->settings[0]); if(m_items[i].hdr->type == TYPE_SOURCE) wfcCoverDir = "sourceflow"; if(m_items[i].hdr->type == TYPE_HOMEBREW) wfcCoverDir = "homebrew"; /* set full path of wfc file */ if(wfcCoverDir != NULL) { if(m_smallBox) strncpy(full_path, fmt("%s/%s/%s_small.wfc", m_cachePath.c_str(), wfcCoverDir, wfcTitle), MAX_FAT_PATH); else strncpy(full_path, fmt("%s/%s/%s.wfc", m_cachePath.c_str(), wfcCoverDir, wfcTitle), MAX_FAT_PATH); } else { if(m_smallBox) strncpy(full_path, fmt("%s/%s_small.wfc", m_cachePath.c_str(), wfcTitle), MAX_FAT_PATH); else strncpy(full_path, fmt("%s/%s.wfc", m_cachePath.c_str(), wfcTitle), MAX_FAT_PATH); } DCFlushRange(full_path, MAX_FAT_PATH+1); /* load wfc file */ FILE *fp; fp = fopen(full_path, "rb"); free(full_path); if(fp != NULL)//if wfc chache file is found { bool success = false; if(fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return CL_ERROR; } u32 fileSize = ftell(fp); if(fseek(fp, 0, SEEK_SET) != 0) { fclose(fp); return CL_ERROR; } SWFCHeader header; if(fileSize > sizeof(header)) { if(fread(&header, 1, sizeof(header), fp) != sizeof(header)) { fclose(fp); return CL_ERROR; } DCFlushRange(&header, sizeof(header)); //make sure wfc cache file matches what we want if(header.newFmt == 1 && (header.full != 0) == box && (header.cmpr != 0) == m_compressTextures) { TexData tex; tex.format = header.cmpr != 0 ? GX_TF_CMPR : GX_TF_RGB565; tex.width = header.getWidth(); tex.height = header.getHeight(); tex.maxLOD = header.maxLOD; /* note bufSize and texLen will be the same if cover is HQ */ u32 bufSize = fixGX_GetTexBufferSize(tex.width, tex.height, tex.format, tex.maxLOD > 0 ? GX_TRUE : GX_FALSE, tex.maxLOD); if(!hq) CCoverFlow::_calcTexLQLOD(tex); u32 texLen = fixGX_GetTexBufferSize(tex.width, tex.height, tex.format, tex.maxLOD > 0 ? GX_TRUE : GX_FALSE, tex.maxLOD); tex.data = (u8*)MEM2_alloc(texLen); if(tex.data == NULL) allocFailed = true; else { /* if not HQ cover then skip (bufSize - texLen) texture data after header */ fseek(fp, sizeof(header) + (bufSize - texLen), SEEK_SET); if(fread(tex.data, 1, texLen, fp) == texLen) { DCFlushRange(tex.data, texLen); LockMutex lock(m_mutex); TexHandle.Cleanup(m_items[i].texture); m_items[i].texture = tex; m_items[i].state = STATE_Ready; m_items[i].boxTexture = header.full != 0; success = true; } } if(!success && tex.data != NULL) { free(tex.data); tex.data = NULL; } } } fclose(fp); if(success) return CL_OK; } } if(allocFailed) return CL_NOMEM; return CL_ERROR; } void * CCoverFlow::_coverLoader(void *obj) { CCoverFlow *cf = static_cast(obj); cf->m_coverThrdBusy = true; CLRet ret; u32 firstItem; bool update; u32 i, j; bool hq_req = cf->m_useHQcover; bool cur_pos_hq = false; u32 bufferSize = min(cf->m_numBufCovers * max(2ul, cf->m_rows), 80ul); while(cf->m_loadingCovers) { update = cf->m_moved; cf->m_moved = false; firstItem = cf->m_covers[cf->m_range / 2].index; for(j = cf->m_items.size(); j >= bufferSize && !cf->m_moved && update; --j) { i = loopNum((j & 1) ? firstItem - (j + 1) / 2 : firstItem + j / 2, cf->m_items.size()); if(cf->m_items[i].state != STATE_Loading) { LWP_MutexLock(cf->m_mutex); TexHandle.Cleanup(cf->m_items[i].texture); cf->m_items[i].state = STATE_Loading; LWP_MutexUnlock(cf->m_mutex); } } ret = CL_OK; bool hq_done = false; /* we try 3 passes to get the full cover. because the cover loader randomly skips the wfc file for unknown reasons */ /* first time is full cover only */ /* second time is full then front */ /* third is full then front then custom blank cover */ for(u8 k = 1; k < 4 && !cf->m_moved && update && ret != CL_NOMEM; k++) { for(j = 0; j <= bufferSize && !cf->m_moved && update && ret != CL_NOMEM; ++j) { i = loopNum((j & 1) ? firstItem - (j + 1) / 2 : firstItem + j / 2, cf->m_items.size()); cur_pos_hq = (hq_req && i == firstItem); if(!cur_pos_hq && cf->m_items[i].state != STATE_Loading) continue; if(cur_pos_hq && hq_done) continue; if(((ret = cf->_loadCoverTex(i, true, cur_pos_hq, false)) == CL_ERROR) && k > 1) { if(((ret = cf->_loadCoverTex(i, false, cur_pos_hq, false)) == CL_ERROR) && k > 2) { if((ret = cf->_loadCoverTex(i, true, cur_pos_hq, true)) == CL_ERROR) cf->m_items[i].state = STATE_NoCover; } } hq_done = (hq_done == false && ret == CL_OK && cur_pos_hq); } } if(ret == CL_NOMEM && bufferSize > 3) bufferSize -= 2; } cf->m_coverThrdBusy = false; return 0; }