mirror of
https://github.com/wiidev/usbloadergx.git
synced 2024-11-05 11:05:06 +01:00
fa4b1d162b
the "GameCube Source" setting has USB priority. * Fixed Playlog writing when using Hermes cIOS v4 (untested) (Requires AHB access). * Fixed EmuNAND when using cIOS revision 65535 (issue 2225) * Added Nintendont support: 1. Select Nintendont's boot.dol folder in userpath settings. 2. Set the "GameCube Mode" setting to Nintendont. 3. Nintendont share some of DIOS MIOS (Lite) settings. * Added sections in the Loader settings (Wii/gamecube/Devolution/DIOS MIOS/Nintendont). * Updated the GameCube game settings to display only the selected GameCube mode related settings. * Updated some menus to support more controller's input: - Prevent GC/CC X and Y buttons to change row number in Wall layout (use d-pad up/down only) - Added GC/CC support to carousel's arrow button - Added GC/CC support to Wall/Carousel continuous scroll (+/- on CC, L/R on GC) - Added GC support L/R and Start buttons in the settings/homebrew browser. - Added D-pad support in listing windows if not pointing the screen. The cursor now moves with the selection (not very good with high Overscan value) (issue 2093) * Changed the StartupProcess to speed up launch time by using AHB access to read config files. IOS argument in meta.xml has priority over AHB detection. * Added IOS58 + AHB support for launching the loader without cIOS (Wii games and EmuNAND still require cIOS). * Added a Loader's IOS setting (now Loader and Games use two separate settings: loader can use 58 and games 249). * Added LibruntimeIOSPatch to patch IOS58 and Hermes v4 to get ISFS access and enable Banner mode, Channel's title and System font with these IOSes (Requires AHB access) * Added a delete prompt if downloaded cheat file is empty. * Force all launched homebrew to reload to IOS58 if available. * Changed Gecko.c to send logs to wifigecko too. * Changed wifigecko IP to send logs to all IP 192.168.0.x * Updated French translation.
460 lines
12 KiB
C++
460 lines
12 KiB
C++
/****************************************************************************
|
|
* libwiigui
|
|
*
|
|
* gui_gamecarousel.cpp
|
|
*
|
|
* GUI class definitions
|
|
***************************************************************************/
|
|
|
|
#include "gui.h"
|
|
#include "wpad.h"
|
|
#include "menu.h"
|
|
|
|
#include <unistd.h>
|
|
#include "gui_image_async.h"
|
|
#include "gui_gamecarousel.h"
|
|
#include "usbloader/GameList.h"
|
|
#include "settings/GameTitles.h"
|
|
#include "settings/CSettings.h"
|
|
#include "GUI/LoadCoverImage.h"
|
|
#include "themes/CTheme.h"
|
|
#include "utils/tools.h"
|
|
#include "main.h"
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <sstream>
|
|
|
|
#define SCALE 0.8f
|
|
#define DEG_OFFSET 7
|
|
#define RADIUS 780
|
|
#define IN_SPEED 175
|
|
#define SHIFT_SPEED 75
|
|
#define SPEED_STEP 4
|
|
#define SPEED_LIMIT 250
|
|
|
|
static inline int OFFSETLIMIT(int Offset, int gameCnt)
|
|
{
|
|
while (Offset < 0)
|
|
Offset += gameCnt;
|
|
return Offset % gameCnt;
|
|
}
|
|
#define GetGameIndex(pageEntry, listOffset, gameCnt) OFFSETLIMIT(listOffset+pageEntry, gameCnt)
|
|
static GuiImageData *GameCarouselLoadCoverImage(void * Arg)
|
|
{
|
|
return LoadCoverImage((struct discHdr *) Arg, true, false);
|
|
}
|
|
/**
|
|
* Constructor for the GuiGameCarousel class.
|
|
*/
|
|
GuiGameCarousel::GuiGameCarousel(int w, int h, const char *themePath, int offset) :
|
|
noCover(Resources::GetFile("nocover.png"), Resources::GetFileSize("nocover.png"))
|
|
{
|
|
width = w;
|
|
height = h;
|
|
pagesize = (gameList.size() < 11) ? gameList.size() : 11;
|
|
listOffset = (gameList.size() < 11) ? LIMIT(offset, 0, MAX(0, gameList.size()-1)) : LIMIT(offset, 0, MAX(0, gameList.size()-1))-2;
|
|
selectable = true;
|
|
selectedItem = -1;
|
|
clickedItem = -1;
|
|
|
|
speed = 0;
|
|
|
|
trigA = new GuiTrigger;
|
|
trigA->SetSimpleTrigger(-1, WPAD_BUTTON_A | WPAD_CLASSIC_BUTTON_A, PAD_BUTTON_A);
|
|
trigL = new GuiTrigger;
|
|
trigL->SetButtonOnlyTrigger(-1, WPAD_BUTTON_LEFT | WPAD_CLASSIC_BUTTON_LEFT, PAD_BUTTON_LEFT);
|
|
trigR = new GuiTrigger;
|
|
trigR->SetButtonOnlyTrigger(-1, WPAD_BUTTON_RIGHT | WPAD_CLASSIC_BUTTON_RIGHT, PAD_BUTTON_RIGHT);
|
|
trigPlus = new GuiTrigger;
|
|
trigPlus->SetButtonOnlyTrigger(-1, WPAD_BUTTON_PLUS | WPAD_CLASSIC_BUTTON_PLUS, PAD_TRIGGER_R);
|
|
trigMinus = new GuiTrigger;
|
|
trigMinus->SetButtonOnlyTrigger(-1, WPAD_BUTTON_MINUS | WPAD_CLASSIC_BUTTON_MINUS, PAD_TRIGGER_L);
|
|
|
|
imgLeft = Resources::GetImageData("startgame_arrow_left.png");
|
|
imgRight = Resources::GetImageData("startgame_arrow_right.png");
|
|
|
|
btnLeftImg = new GuiImage(imgLeft);
|
|
if (Settings.wsprompt == ON) btnLeftImg->SetWidescreen(Settings.widescreen);
|
|
btnLeft = new GuiButton(imgLeft->GetWidth(), imgLeft->GetHeight());
|
|
btnLeft->SetAlignment(thAlign("left - carousel layout left arrow align hor"), thAlign("top - carousel layout left arrow align ver"));
|
|
btnLeft->SetPosition(thInt("20 - carousel layout left arrow pos x"), thInt("65 - carousel layout left arrow pos y"));
|
|
btnLeft->SetParent(this);
|
|
btnLeft->SetImage(btnLeftImg);
|
|
btnLeft->SetSoundOver(btnSoundOver);
|
|
btnLeft->SetTrigger(trigA);
|
|
btnLeft->SetTrigger(trigL);
|
|
btnLeft->SetTrigger(trigMinus);
|
|
btnLeft->SetEffectGrow();
|
|
|
|
btnRightImg = new GuiImage(imgRight);
|
|
if (Settings.wsprompt == ON) btnRightImg->SetWidescreen(Settings.widescreen);
|
|
btnRight = new GuiButton(imgRight->GetWidth(), imgRight->GetHeight());
|
|
btnRight->SetParent(this);
|
|
btnRight->SetAlignment(thAlign("right - carousel layout right arrow align hor"), thAlign("top - carousel layout right arrow align ver"));
|
|
btnRight->SetPosition(thInt("-20 - carousel layout right arrow pos x"), thInt("65 - carousel layout right arrow pos y"));
|
|
btnRight->SetImage(btnRightImg);
|
|
btnRight->SetSoundOver(btnSoundOver);
|
|
btnRight->SetTrigger(trigA);
|
|
btnRight->SetTrigger(trigR);
|
|
btnRight->SetTrigger(trigPlus);
|
|
btnRight->SetEffectGrow();
|
|
|
|
gamename = new GuiText(" ", 18, thColor("r=55 g=190 b=237 a=255 - carousel game name text color"));
|
|
gamename->SetParent(this);
|
|
gamename->SetAlignment(ALIGN_CENTER, ALIGN_TOP);
|
|
gamename->SetPosition(0, 330);
|
|
gamename->SetMaxWidth(280, DOTTED);
|
|
|
|
gameIndex = new int[pagesize];
|
|
game.resize(pagesize);
|
|
coverImg.resize(pagesize);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
/**
|
|
* Destructor for the GuiGameCarousel class.
|
|
*/
|
|
GuiGameCarousel::~GuiGameCarousel()
|
|
{
|
|
delete imgRight;
|
|
delete imgLeft;
|
|
delete btnLeftImg;
|
|
delete btnRightImg;
|
|
delete btnRight;
|
|
delete btnLeft;
|
|
|
|
delete trigA;
|
|
delete trigL;
|
|
delete trigR;
|
|
delete trigPlus;
|
|
delete trigMinus;
|
|
delete gamename;
|
|
|
|
GuiImageAsync::ClearQueue();
|
|
|
|
for (u32 i = 0; i < game.size(); ++i)
|
|
delete coverImg[i];
|
|
for (u32 i = 0; i < game.size(); ++i)
|
|
delete game[i];
|
|
|
|
delete[] gameIndex;
|
|
|
|
}
|
|
|
|
void GuiGameCarousel::setListOffset(int off)
|
|
{
|
|
LOCK( this );
|
|
if(gameList.size() < 11)
|
|
listOffset = MIN(off, gameList.size()-1);
|
|
else
|
|
listOffset = MIN(off, gameList.size()) - 2;
|
|
|
|
Refresh();
|
|
}
|
|
|
|
int GuiGameCarousel::getListOffset() const
|
|
{
|
|
if(gameList.size() < 11)
|
|
return listOffset;
|
|
else
|
|
return (listOffset + 2) % gameList.size();
|
|
}
|
|
|
|
void GuiGameCarousel::SetSelectedOption(int ind)
|
|
{
|
|
LOCK(this);
|
|
selectedItem = LIMIT(ind, 0, MIN(pagesize, MAX(0, gameList.size()-1)));
|
|
}
|
|
|
|
void GuiGameCarousel::Refresh()
|
|
{
|
|
for (int i = 0; i < pagesize; i++)
|
|
{
|
|
//------------------------
|
|
// Index
|
|
//------------------------
|
|
gameIndex[i] = GetGameIndex( i, listOffset, gameList.size() );
|
|
|
|
//------------------------
|
|
// Image
|
|
//------------------------
|
|
delete coverImg[i];
|
|
coverImg[i] = new (std::nothrow) GuiImageAsync(GameCarouselLoadCoverImage, gameList[gameIndex[i]], sizeof(struct discHdr), &noCover);
|
|
if (coverImg[i]) coverImg[i]->SetWidescreen(Settings.widescreen);
|
|
|
|
//------------------------
|
|
// GameButton
|
|
//------------------------
|
|
delete game[i];
|
|
game[i] = new GuiButton(122, 244);
|
|
game[i]->SetParent(this);
|
|
game[i]->SetAlignment(ALIGN_CENTER, ALIGN_MIDDLE);
|
|
game[i]->SetPosition(0, 740);
|
|
game[i]->SetImage(coverImg[i]);
|
|
game[i]->SetScale(SCALE);
|
|
game[i]->SetRumble(false);
|
|
game[i]->SetTrigger(trigA);
|
|
game[i]->SetSoundClick(btnSoundClick);
|
|
game[i]->SetClickable(true);
|
|
game[i]->SetEffect(EFFECT_GOROUND, IN_SPEED, 90 - (pagesize - 2 * i - 1) * DEG_OFFSET / 2, RADIUS, 180, 1, 0, RADIUS);
|
|
}
|
|
}
|
|
|
|
void GuiGameCarousel::SetFocus(int f)
|
|
{
|
|
LOCK( this );
|
|
if (!gameList.size()) return;
|
|
|
|
for (int i = 0; i < pagesize; i++)
|
|
game[i]->ResetState();
|
|
|
|
if (f == 1 && selectedItem >= 0) game[selectedItem]->SetState(STATE_SELECTED);
|
|
}
|
|
|
|
void GuiGameCarousel::ResetState()
|
|
{
|
|
LOCK( this );
|
|
if (state != STATE_DISABLED)
|
|
{
|
|
state = STATE_DEFAULT;
|
|
stateChan = -1;
|
|
}
|
|
|
|
for (int i = 0; i < pagesize; i++)
|
|
{
|
|
game[i]->ResetState();
|
|
}
|
|
}
|
|
|
|
int GuiGameCarousel::GetClickedOption()
|
|
{
|
|
LOCK( this );
|
|
int found = -1;
|
|
if (clickedItem >= 0)
|
|
{
|
|
for (int i = pagesize - 1; i >= 0; i--)
|
|
game[i]->ResetState();
|
|
|
|
game[clickedItem]->SetState(STATE_SELECTED);
|
|
found = gameIndex[clickedItem];
|
|
clickedItem = -1;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
int GuiGameCarousel::GetSelectedOption()
|
|
{
|
|
LOCK( this );
|
|
int found = -1;
|
|
for (int i = 0; i < pagesize; i++)
|
|
{
|
|
if (game[i]->GetState() == STATE_SELECTED)
|
|
{
|
|
game[i]->SetState(STATE_SELECTED);
|
|
found = gameIndex[i];
|
|
break;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Draw the button on screen
|
|
*/
|
|
void GuiGameCarousel::Draw()
|
|
{
|
|
LOCK( this );
|
|
if (!this->IsVisible() || !gameList.size()) return;
|
|
|
|
for (int i = 0; i < pagesize; i++)
|
|
game[i]->Draw();
|
|
|
|
gamename->Draw();
|
|
|
|
if (gameList.size() > 6)
|
|
{
|
|
btnRight->Draw();
|
|
btnLeft->Draw();
|
|
}
|
|
|
|
//!Draw tooltip after the Images to have it on top
|
|
if (Settings.tooltips == ON)
|
|
for (int i = 0; i < pagesize; i++)
|
|
game[i]->DrawTooltip();
|
|
|
|
this->UpdateEffects();
|
|
}
|
|
|
|
void GuiGameCarousel::Update(GuiTrigger * t)
|
|
{
|
|
LOCK( this );
|
|
if (state == STATE_DISABLED || !t || !gameList.size() || !pagesize) return;
|
|
|
|
btnRight->Update(t);
|
|
btnLeft->Update(t);
|
|
|
|
if ((game[0]->GetEffect() & EFFECT_GOROUND) || (game[pagesize - 1]->GetEffect() & EFFECT_GOROUND))
|
|
{
|
|
return; // skip when rotate
|
|
}
|
|
|
|
// find selected + clicked
|
|
int selectedItem_old = selectedItem;
|
|
selectedItem = -1;
|
|
clickedItem = -1;
|
|
for (int i = pagesize - 1; i >= 0; i--)
|
|
{
|
|
game[i]->Update(t);
|
|
if (game[i]->GetState() == STATE_SELECTED)
|
|
{
|
|
selectedItem = i;
|
|
}
|
|
if (game[i]->GetState() == STATE_CLICKED)
|
|
{
|
|
clickedItem = i;
|
|
}
|
|
|
|
}
|
|
|
|
/// OnOver-Effect + GameText + Tooltop
|
|
if (selectedItem_old != selectedItem)
|
|
{
|
|
if (selectedItem >= 0)
|
|
{
|
|
game[selectedItem]->SetEffect(EFFECT_SCALE, 1, 130);
|
|
gamename->SetText(GameTitles.GetTitle(gameList[gameIndex[selectedItem]]));
|
|
}
|
|
else gamename->SetText((char*) NULL);
|
|
if (selectedItem_old >= 0) game[selectedItem_old]->SetEffect(EFFECT_SCALE, -1, 100);
|
|
}
|
|
// navigation
|
|
if (gameList.size() > 6)
|
|
{
|
|
|
|
int newspeed = 0;
|
|
// Left/Right Navigation
|
|
if (btnLeft->GetState() == STATE_CLICKED)
|
|
{
|
|
u32 buttons = t->wpad.btns_h;
|
|
u32 buttonsPAD = t->pad.btns_h;
|
|
if (!((buttons & WPAD_BUTTON_A) || (buttons & WPAD_BUTTON_MINUS) ||
|
|
(buttons & WPAD_CLASSIC_BUTTON_A) || (buttons & WPAD_CLASSIC_BUTTON_MINUS) ||
|
|
(buttonsPAD & PAD_BUTTON_A) || (buttonsPAD & PAD_TRIGGER_L) || t->Left()))
|
|
{
|
|
btnLeft->ResetState();
|
|
return;
|
|
}
|
|
|
|
if (Settings.xflip == XFLIP_SYSMENU || Settings.xflip == XFLIP_YES || Settings.xflip == XFLIP_DISK3D)
|
|
newspeed = SHIFT_SPEED;
|
|
else newspeed = -SHIFT_SPEED;
|
|
}
|
|
else if (btnRight->GetState() == STATE_CLICKED)
|
|
{
|
|
u32 buttons = t->wpad.btns_h;
|
|
u32 buttonsPAD = t->pad.btns_h;
|
|
if (!((buttons & WPAD_BUTTON_A) || (buttons & WPAD_BUTTON_PLUS) ||
|
|
(buttons & WPAD_CLASSIC_BUTTON_A) || (buttons & WPAD_CLASSIC_BUTTON_PLUS) ||
|
|
(buttonsPAD & PAD_BUTTON_A) || (buttonsPAD & PAD_TRIGGER_R) || t->Right()))
|
|
{
|
|
btnRight->ResetState();
|
|
return;
|
|
}
|
|
if (Settings.xflip == XFLIP_SYSMENU || Settings.xflip == XFLIP_YES || Settings.xflip == XFLIP_DISK3D)
|
|
newspeed = -SHIFT_SPEED;
|
|
else newspeed = SHIFT_SPEED;
|
|
}
|
|
if (newspeed)
|
|
{
|
|
if (speed == 0)
|
|
speed = newspeed;
|
|
else if (speed > 0)
|
|
{
|
|
if ((speed += SPEED_STEP) > SPEED_LIMIT) speed = SPEED_LIMIT;
|
|
}
|
|
else
|
|
{
|
|
if ((speed -= SPEED_STEP) < -SPEED_LIMIT) speed = -SPEED_LIMIT;
|
|
}
|
|
}
|
|
else speed = 0;
|
|
|
|
if (speed > 0) // rotate right
|
|
{
|
|
GuiButton *tmpButton;
|
|
listOffset = OFFSETLIMIT(listOffset - 1, gameList.size()); // set the new listOffset
|
|
// Save right Button + TollTip and destroy right Image + Image-Data
|
|
delete coverImg[pagesize - 1];
|
|
coverImg[pagesize - 1] = NULL;
|
|
game[pagesize - 1]->SetImage(NULL);
|
|
tmpButton = game[pagesize - 1];
|
|
|
|
// Move all Page-Entries one step right
|
|
for (int i = pagesize - 1; i >= 1; i--)
|
|
{
|
|
coverImg[i] = coverImg[i - 1];
|
|
game[i] = game[i - 1];
|
|
gameIndex[i] = gameIndex[i - 1];
|
|
}
|
|
// set saved Button & gameIndex to right
|
|
gameIndex[0] = listOffset;
|
|
coverImg[0] = new GuiImageAsync(GameCarouselLoadCoverImage, gameList[gameIndex[0]], sizeof(struct discHdr),
|
|
&noCover);
|
|
coverImg[0] ->SetWidescreen(Settings.widescreen);
|
|
|
|
game[0] = tmpButton;
|
|
game[0] ->SetImage(coverImg[0]);
|
|
|
|
for (int i = 0; i < pagesize; i++)
|
|
{
|
|
game[i]->StopEffect();
|
|
game[i]->ResetState();
|
|
game[i]->SetEffect(EFFECT_GOROUND, speed, DEG_OFFSET, RADIUS, 270 - (pagesize - 2 * i + 1) * DEG_OFFSET
|
|
/ 2, 1, 0, RADIUS);
|
|
game[i]->UpdateEffects(); // rotate one step for liquid scrolling
|
|
}
|
|
}
|
|
else if (speed < 0) // rotate left
|
|
{
|
|
GuiButton *tmpButton;
|
|
listOffset = OFFSETLIMIT(listOffset + 1, gameList.size()); // set the new listOffset
|
|
// Save left Button + TollTip and destroy left Image + Image-Data
|
|
delete coverImg[0];
|
|
coverImg[0] = NULL;
|
|
game[0]->SetImage(NULL);
|
|
tmpButton = game[0];
|
|
|
|
// Move all Page-Entries one step left
|
|
for (int i = 0; i < (pagesize - 1); i++)
|
|
{
|
|
coverImg[i] = coverImg[i + 1];
|
|
game[i] = game[i + 1];
|
|
gameIndex[i] = gameIndex[i + 1];
|
|
}
|
|
// set saved Button & gameIndex to right
|
|
int ii = pagesize - 1;
|
|
gameIndex[ii] = OFFSETLIMIT(listOffset + ii, gameList.size());
|
|
coverImg[ii] = new GuiImageAsync(GameCarouselLoadCoverImage, gameList[gameIndex[ii]],
|
|
sizeof(struct discHdr), &noCover);
|
|
coverImg[ii] ->SetWidescreen(Settings.widescreen);
|
|
|
|
game[ii] = tmpButton;
|
|
game[ii] ->SetImage(coverImg[ii]);
|
|
|
|
for (int i = 0; i < pagesize; i++)
|
|
{
|
|
game[i]->StopEffect();
|
|
game[i]->ResetState();
|
|
game[i]->SetEffect(EFFECT_GOROUND, speed, DEG_OFFSET, RADIUS, 270 - (pagesize - 2 * i - 3) * DEG_OFFSET
|
|
/ 2, 1, 0, RADIUS);
|
|
game[i]->UpdateEffects(); // rotate one step for liquid scrolling
|
|
}
|
|
}
|
|
|
|
}
|
|
if (updateCB) updateCB(this);
|
|
}
|
|
|