mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-10-21 07:02:57 -05:00
Finalize text menu logic. Build with -Werror for better standards.
This commit is contained in:
20
Include/AppStates/ExtrasMenuState.hpp
Normal file
20
Include/AppStates/ExtrasMenuState.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "AppStates/AppState.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "UI/Menu.hpp"
|
||||
|
||||
class ExtrasMenuState : public AppState
|
||||
{
|
||||
public:
|
||||
ExtrasMenuState(void);
|
||||
~ExtrasMenuState() {};
|
||||
|
||||
void Update(void);
|
||||
void Render(void);
|
||||
|
||||
private:
|
||||
// Actual menu
|
||||
UI::Menu m_ExtrasMenu;
|
||||
// Render target
|
||||
SDL::SharedTexture m_RenderTarget;
|
||||
};
|
||||
@@ -17,4 +17,6 @@ class SettingsState : public AppState
|
||||
UI::Menu m_SettingsMenu;
|
||||
// Render target.
|
||||
SDL::SharedTexture m_RenderTarget = nullptr;
|
||||
// Control guide X coordinate.
|
||||
int m_ControlGuideX = 0;
|
||||
};
|
||||
|
||||
25
Include/AppStates/TextTitleSelectState.hpp
Normal file
25
Include/AppStates/TextTitleSelectState.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "AppStates/TitleSelectCommon.hpp"
|
||||
#include "Data/Data.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "UI/Menu.hpp"
|
||||
|
||||
class TextTitleSelectState : public TitleSelectCommon
|
||||
{
|
||||
public:
|
||||
TextTitleSelectState(Data::User *User);
|
||||
~TextTitleSelectState() {};
|
||||
|
||||
void Update(void);
|
||||
void Render(void);
|
||||
|
||||
void Refresh(void);
|
||||
|
||||
private:
|
||||
// Pointer to user to save.
|
||||
Data::User *m_User;
|
||||
// Title select menu
|
||||
UI::Menu m_TitleSelectMenu;
|
||||
// Render target
|
||||
SDL::SharedTexture m_RenderTarget;
|
||||
};
|
||||
20
Include/AppStates/TitleSelectCommon.hpp
Normal file
20
Include/AppStates/TitleSelectCommon.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "AppStates/AppState.hpp"
|
||||
|
||||
class TitleSelectCommon : public AppState
|
||||
{
|
||||
public:
|
||||
TitleSelectCommon(void);
|
||||
virtual ~TitleSelectCommon() {};
|
||||
|
||||
virtual void Update(void) = 0;
|
||||
virtual void Render(void) = 0;
|
||||
|
||||
virtual void Refresh(void) = 0;
|
||||
|
||||
void RenderControlGuide(void);
|
||||
|
||||
protected:
|
||||
// X coordinate for control guide. Shared between all instances. Only should be calculated once.
|
||||
static inline int m_TitleControlsX = 0;
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
#include "AppStates/AppState.hpp"
|
||||
#include "AppStates/TitleSelectCommon.hpp"
|
||||
#include "Data/Data.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "UI/TitleView.hpp"
|
||||
|
||||
class TitleSelectState : public AppState
|
||||
class TitleSelectState : public TitleSelectCommon
|
||||
{
|
||||
public:
|
||||
TitleSelectState(Data::User *User);
|
||||
@@ -13,6 +13,8 @@ class TitleSelectState : public AppState
|
||||
void Update(void);
|
||||
void Render(void);
|
||||
|
||||
void Refresh(void);
|
||||
|
||||
private:
|
||||
// Save pointer to user
|
||||
Data::User *m_User = nullptr;
|
||||
@@ -20,6 +22,4 @@ class TitleSelectState : public AppState
|
||||
SDL::SharedTexture m_RenderTarget = nullptr;
|
||||
// Title select view
|
||||
UI::TitleView m_TitleView;
|
||||
// X coordinate for control guide. Shared between all instances. Only should be calculated once.
|
||||
static inline int m_TitleControlsX = 0;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Config
|
||||
void Save(void);
|
||||
// Retrieves a value with its key.
|
||||
uint8_t GetByKey(std::string_view Key);
|
||||
// Retrieves value by index.
|
||||
uint8_t GetByIndex(int Index);
|
||||
// Gets the working directory.
|
||||
FsLib::Path GetWorkingDirectory(void);
|
||||
// Gets the UI's transition scaling
|
||||
@@ -33,6 +35,8 @@ namespace Config
|
||||
static constexpr std::string_view WorkingDirectory = "WorkingDirectory";
|
||||
static constexpr std::string_view IncludeDeviceSaves = "IncludeDeviceSaves";
|
||||
static constexpr std::string_view AutoBackupOnRestore = "AutoBackupOnRestore";
|
||||
static constexpr std::string_view AutoNameBackups = "AutoNameBackups";
|
||||
static constexpr std::string_view AutoUpload = "AutoUploadToRemote";
|
||||
static constexpr std::string_view HoldForDeletion = "HoldForDeletion";
|
||||
static constexpr std::string_view HoldForRestoration = "HoldForRestoration";
|
||||
static constexpr std::string_view HoldForOverwrite = "HoldForOverWrite";
|
||||
@@ -40,13 +44,12 @@ namespace Config
|
||||
static constexpr std::string_view ListAccountSystemSaves = "ListAccountSystemSaves";
|
||||
static constexpr std::string_view AllowSystemSaveWriting = "AllowSystemSaveWriting";
|
||||
static constexpr std::string_view ExportToZip = "ExportToZip";
|
||||
static constexpr std::string_view TitleSortType = "TitleSortType";
|
||||
static constexpr std::string_view JKSMTextMode = "JKSMTextMode";
|
||||
static constexpr std::string_view ForceEnglish = "ForceEnglish";
|
||||
static constexpr std::string_view EnableTrashBin = "EnableTrash";
|
||||
static constexpr std::string_view AutoNameBackups = "AutoNameBackups";
|
||||
static constexpr std::string_view TitleSortType = "TitleSortType";
|
||||
static constexpr std::string_view UIAnimationScaling = "UIAnimationScaling";
|
||||
static constexpr std::string_view Favorites = "Favorites";
|
||||
static constexpr std::string_view BlackList = "BlackList";
|
||||
static constexpr std::string_view AutoUpload = "AutoUploadToRemote";
|
||||
static constexpr std::string_view UIAnimationScaling = "UIAnimationScaling";
|
||||
} // namespace Keys
|
||||
} // namespace Config
|
||||
|
||||
@@ -6,7 +6,7 @@ static inline bool operator==(AccountUid AccountID1, AccountUid AccountID2)
|
||||
return (AccountID1.uid[0] == AccountID2.uid[0]) && (AccountID1.uid[1] == AccountID2.uid[1]);
|
||||
}
|
||||
|
||||
static inline u128 AccountUIDToU128(AccountUid AccountID)
|
||||
static inline bool operator==(AccountUid AccountID, u128 u128ID)
|
||||
{
|
||||
return (static_cast<u128>(AccountID.uid[0]) << 64 | AccountID.uid[1]);
|
||||
return AccountID.uid[0] == (u128ID >> 64 & 0xFFFFFFFFFFFFFFFF) && AccountID.uid[1] == (u128ID & 0xFFFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Strings
|
||||
static constexpr std::string_view SaveDataTypes = "SaveDataTypes";
|
||||
static constexpr std::string_view MainMenuNames = "MainMenuNames";
|
||||
static constexpr std::string_view SettingsMenu = "SettingsMenu";
|
||||
static constexpr std::string_view ExtrasMenu = "ExtrasMenu";
|
||||
static constexpr std::string_view OnOff = "OnOff";
|
||||
} // namespace Names
|
||||
} // namespace Strings
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace UI
|
||||
class IconMenu : public UI::Menu
|
||||
{
|
||||
public:
|
||||
IconMenu(int X, int Y, int ScrollLength, int RenderTargetHeight);
|
||||
IconMenu(int X, int Y, int RenderTargetHeight);
|
||||
~IconMenu() {};
|
||||
|
||||
void Update(bool HasFocus);
|
||||
|
||||
@@ -10,51 +10,44 @@ namespace UI
|
||||
class Menu : public UI::Element
|
||||
{
|
||||
public:
|
||||
// Coordinates to render to and dimensions of menu.
|
||||
Menu(int X, int Y, int Width, int FontSize, int ScrollLength, int RenderTargetHeight);
|
||||
Menu(int X, int Y, int Width, int FontSize, int RenderTargetHeight);
|
||||
~Menu() {};
|
||||
|
||||
// Required functions from Element.
|
||||
void Update(bool HasFocus);
|
||||
void Render(SDL_Texture *Target, bool HasFocus);
|
||||
|
||||
// Adds an option to the menu.
|
||||
void AddOption(std::string_view NewOption);
|
||||
|
||||
// Returns the index of the selected item.
|
||||
int GetSelected(void) const;
|
||||
// Sets the selected option
|
||||
void SetSelected(int Selected);
|
||||
// Resets the menu.
|
||||
void Reset(void);
|
||||
|
||||
protected:
|
||||
// X and Y coordinates.
|
||||
double m_X, m_Y;
|
||||
// Font size
|
||||
int m_FontSize;
|
||||
// Selected option.
|
||||
int m_Selected = 0;
|
||||
// Color mod for rendering bounding box.
|
||||
// Color pulse
|
||||
UI::ColorMod m_ColorMod;
|
||||
// Actual length of the menu.
|
||||
int m_OptionsLength = -1;
|
||||
// Height of options.
|
||||
// Calculated height of the options.
|
||||
int m_OptionHeight;
|
||||
// Total height of menu in pixels.
|
||||
int m_MenuHeight = 0;
|
||||
// A small target to render the option to so it can't draw text outside of the bounding area.
|
||||
// Render target for options so they can't render outside the bounding area.
|
||||
SDL::SharedTexture m_OptionTarget = nullptr;
|
||||
|
||||
private:
|
||||
// These are used for the scrolling effect.
|
||||
double m_OriginalY, m_TargetY;
|
||||
// How many options before scrolling starts happening.
|
||||
int m_ScrollLength, m_MenuRenderLength;
|
||||
// The height of the render target for scrolling calculations.
|
||||
int m_RenderTargetHeight = 0;
|
||||
// Width.
|
||||
// Need to preserve the original Y.
|
||||
double m_OriginalY;
|
||||
// This is the TargetY the menu catches up to so-to-speak.
|
||||
double m_TargetY;
|
||||
// This is calculated according to the render target's length.
|
||||
int m_ScrollLength;
|
||||
// Maximum display length of options.
|
||||
int m_Width;
|
||||
// Font size.
|
||||
int m_FontSize;
|
||||
// Vertical size of the destination render target in pixels.
|
||||
int m_RenderTargetHeight;
|
||||
// Maximum number of display options render target can show.
|
||||
int m_MaxDisplayOptions;
|
||||
// Vector of options.
|
||||
std::vector<std::string> m_Options;
|
||||
};
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace UI
|
||||
// Saves pointer to the user passed.
|
||||
Data::User *m_User = nullptr;
|
||||
// Y coordinate.
|
||||
double m_Y = 38;
|
||||
double m_Y = 28.0f;
|
||||
// Currently highlighted/selected title.
|
||||
int m_Selected = 0;
|
||||
// This is to save the X and Y coordinates so the selected icon can be drawn last.
|
||||
double m_SelectedX, m_SelectedY = 38.0f;
|
||||
double m_SelectedX, m_SelectedY = 28.0f;
|
||||
// Color mod for pulse.
|
||||
UI::ColorMod m_ColorMod;
|
||||
// Vector of tiles.
|
||||
|
||||
Submodule Libraries/SDLLib updated: 33e12628aa...3ffe1c9038
6
Makefile
6
Makefile
@@ -47,9 +47,9 @@ ICON := icon.jpg
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
override CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
override CFLAGS += `sdl2-config --cflags` `freetype-config --cflags` `curl-config --cflags`
|
||||
override CFLAGS += -g -Wall -O2 -ffunction-sections -ffast-math $(ARCH) $(DEFINES)
|
||||
CFLAGS := $(INCLUDE) -D__SWITCH__ `sdl2-config --cflags` `freetype-config --cflags` \
|
||||
`curl-config --cflags` -g -Wall -Werror -O2 -ffunction-sections -ffast-math \
|
||||
$(ARCH) $(DEFINES)
|
||||
|
||||
CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=c++23
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"Include Device Saves with users: %s",
|
||||
"Auto-backup on restore: %s",
|
||||
"Auto-name backups: %s",
|
||||
"Auto-upload backups to remote storage: %s",
|
||||
"Hold to delete backups: %s",
|
||||
"Hold to restore backups: %s",
|
||||
"Hold to overwrite backups: %s",
|
||||
@@ -31,9 +32,21 @@
|
||||
"Show account system saves: %s",
|
||||
"Enable writing to system saves and NAND: %s",
|
||||
"Export saves to ZIP: %s",
|
||||
"Title sort type: %s",
|
||||
"Text menu (JKSM) mode: %s",
|
||||
"Force English: %s",
|
||||
"Enable trash bin: %s",
|
||||
"Animation scaling: %s"
|
||||
"Animation scaling: %.02f"
|
||||
],
|
||||
"ExtrasMenu" : [
|
||||
"Reload Titles",
|
||||
"SD to SD Browser",
|
||||
"BIS: ProfInfoF",
|
||||
"BIS: Safe",
|
||||
"BIS: System",
|
||||
"BIS: User",
|
||||
"Terminate Process",
|
||||
"Mount System Save"
|
||||
],
|
||||
"OnOff" : [
|
||||
"Off",
|
||||
|
||||
BIN
RomFS/Textures/ExtrasIcon.png
Normal file
BIN
RomFS/Textures/ExtrasIcon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
39
Source/AppStates/ExtrasMenuState.cpp
Normal file
39
Source/AppStates/ExtrasMenuState.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "AppStates/ExtrasMenuState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "Input.hpp"
|
||||
#include "Strings.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget";
|
||||
}
|
||||
|
||||
ExtrasMenuState::ExtrasMenuState(void)
|
||||
: m_ExtrasMenu(32, 8, 1000, 32, 555),
|
||||
m_RenderTarget(SDL::TextureManager::CreateLoadTexture(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET))
|
||||
{
|
||||
const char *ExtrasString = nullptr;
|
||||
int CurrentString = 0;
|
||||
while ((ExtrasString = Strings::GetByName(Strings::Names::ExtrasMenu, CurrentString++)) != nullptr)
|
||||
{
|
||||
m_ExtrasMenu.AddOption(ExtrasString);
|
||||
}
|
||||
}
|
||||
|
||||
void ExtrasMenuState::Update(void)
|
||||
{
|
||||
m_ExtrasMenu.Update(AppState::HasFocus());
|
||||
|
||||
if (Input::ButtonPressed(HidNpadButton_B))
|
||||
{
|
||||
AppState::Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
void ExtrasMenuState::Render(void)
|
||||
{
|
||||
m_RenderTarget->Clear(Colors::Transparent);
|
||||
m_ExtrasMenu.Render(m_RenderTarget->Get(), AppState::HasFocus());
|
||||
m_RenderTarget->Render(NULL, 201, 91);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "AppStates/MainMenuState.hpp"
|
||||
#include "AppStates/ExtrasMenuState.hpp"
|
||||
#include "AppStates/SettingsState.hpp"
|
||||
#include "AppStates/TitleSelectState.hpp"
|
||||
#include "Colors.hpp"
|
||||
@@ -9,32 +10,27 @@
|
||||
#include "SDL.hpp"
|
||||
#include "Strings.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Size of the font to use to make tweaking this easier.
|
||||
static int ICON_FONT_SIZE = 50;
|
||||
} // namespace
|
||||
|
||||
MainMenuState::MainMenuState(void)
|
||||
: m_RenderTarget(SDL::TextureManager::CreateLoadTexture("MainMenuTarget", 200, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)),
|
||||
m_Background(SDL::TextureManager::CreateLoadTexture("MainMenuBackground", "romfs:/Textures/MenuBackground.png")),
|
||||
m_MainMenu(50, 15, 1, 555), m_ControlGuide(Strings::GetByName(Strings::Names::ControlGuides, 0)),
|
||||
m_ControlGuideX(1220 - SDL::Text::GetWidth(22, m_ControlGuide))
|
||||
m_Background(SDL::TextureManager::CreateLoadTexture("MainMenuBackground", "romfs:/Textures/MenuBackground.png")), m_MainMenu(50, 15, 555),
|
||||
m_ControlGuide(Strings::GetByName(Strings::Names::ControlGuides, 0)), m_ControlGuideX(1220 - SDL::Text::GetWidth(22, m_ControlGuide))
|
||||
{
|
||||
// Fetch user list.
|
||||
Data::GetUsers(m_Users);
|
||||
|
||||
// Loop through and add icons to menu
|
||||
// Loop through add user's icon to menu and create states.
|
||||
for (size_t i = 0; i < m_Users.size(); i++)
|
||||
{
|
||||
m_MainMenu.AddOption(m_Users.at(i)->GetSharedIcon());
|
||||
m_States.push_back(std::make_shared<TitleSelectState>(m_Users.at(i)));
|
||||
}
|
||||
// Add the settings and extras.
|
||||
m_States.push_back(std::make_shared<SettingsState>());
|
||||
m_States.push_back(std::make_shared<ExtrasMenuState>());
|
||||
|
||||
// Create icons for the other two.
|
||||
m_SettingsIcon = SDL::TextureManager::CreateLoadTexture("SettingsIcon", "romfs:/Textures/SettingsIcon.png");
|
||||
m_ExtrasIcon = SDL::TextureManager::CreateLoadTexture("ExtrasIcon", 256, 256, 0);
|
||||
m_ExtrasIcon = SDL::TextureManager::CreateLoadTexture("ExtrasIcon", "romfs:/Textures/ExtrasIcon.png");
|
||||
|
||||
// Finally add them to the end.
|
||||
m_MainMenu.AddOption(m_SettingsIcon);
|
||||
@@ -45,13 +41,13 @@ void MainMenuState::Update(void)
|
||||
{
|
||||
m_MainMenu.Update(AppState::HasFocus());
|
||||
|
||||
if (Input::ButtonPressed(HidNpadButton_A) && m_MainMenu.GetSelected() < m_Users.size() &&
|
||||
if (Input::ButtonPressed(HidNpadButton_A) && m_MainMenu.GetSelected() < static_cast<int>(m_Users.size()) &&
|
||||
m_Users.at(m_MainMenu.GetSelected())->GetTotalDataEntries() > 0)
|
||||
{
|
||||
m_States.at(m_MainMenu.GetSelected())->Reactivate();
|
||||
JKSV::PushState(m_States.at(m_MainMenu.GetSelected()));
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_A) && m_MainMenu.GetSelected() >= m_Users.size())
|
||||
else if (Input::ButtonPressed(HidNpadButton_A) && m_MainMenu.GetSelected() >= static_cast<int>(m_Users.size()))
|
||||
{
|
||||
m_States.at(m_MainMenu.GetSelected())->Reactivate();
|
||||
JKSV::PushState(m_States.at(m_MainMenu.GetSelected()));
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "AppStates/SettingsState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "Config.hpp"
|
||||
#include "Input.hpp"
|
||||
#include "StringUtil.hpp"
|
||||
#include "Strings.hpp"
|
||||
|
||||
namespace
|
||||
@@ -9,17 +11,28 @@ namespace
|
||||
constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget";
|
||||
} // namespace
|
||||
|
||||
static inline const char *GetValueText(uint8_t Value)
|
||||
{
|
||||
return Value == 1 ? Strings::GetByName(Strings::Names::OnOff, 0) : Strings::GetByName(Strings::Names::OnOff, 1);
|
||||
}
|
||||
|
||||
SettingsState::SettingsState(void)
|
||||
: m_SettingsMenu(32, 18, 1002, 32, 4, 555),
|
||||
: m_SettingsMenu(32, 8, 1000, 32, 555),
|
||||
m_RenderTarget(SDL::TextureManager::CreateLoadTexture(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET))
|
||||
{
|
||||
// Loop through strings and add them until nullptr.
|
||||
int CurrentString = 0;
|
||||
// Add the first two, because they don't have values to display.
|
||||
m_SettingsMenu.AddOption(Strings::GetByName(Strings::Names::SettingsMenu, 0));
|
||||
m_SettingsMenu.AddOption(Strings::GetByName(Strings::Names::SettingsMenu, 1));
|
||||
|
||||
int CurrentString = 2;
|
||||
const char *SettingsString = nullptr;
|
||||
while ((SettingsString = Strings::GetByName(Strings::Names::SettingsMenu, CurrentString++)) != nullptr)
|
||||
while (CurrentString < 16 && (SettingsString = Strings::GetByName(Strings::Names::SettingsMenu, CurrentString++)) != nullptr)
|
||||
{
|
||||
m_SettingsMenu.AddOption(SettingsString);
|
||||
m_SettingsMenu.AddOption(StringUtil::GetFormattedString(SettingsString, GetValueText(Config::GetByIndex(CurrentString - 1))));
|
||||
}
|
||||
// Add the scaling
|
||||
m_SettingsMenu.AddOption(
|
||||
StringUtil::GetFormattedString(Strings::GetByName(Strings::Names::SettingsMenu, 17), Config::GetAnimationScaling()));
|
||||
}
|
||||
|
||||
void SettingsState::Update(void)
|
||||
|
||||
38
Source/AppStates/TextTitleSelectState.cpp
Normal file
38
Source/AppStates/TextTitleSelectState.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "AppStates/TextTitleSelectState.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include <string_view>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget";
|
||||
}
|
||||
|
||||
TextTitleSelectState::TextTitleSelectState(Data::User *User)
|
||||
: TitleSelectCommon(), m_User(User), m_TitleSelectMenu(32, 8, 1000, 20, 555),
|
||||
m_RenderTarget(SDL::TextureManager::CreateLoadTexture(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET))
|
||||
{
|
||||
TextTitleSelectState::Refresh();
|
||||
}
|
||||
|
||||
void TextTitleSelectState::Update(void)
|
||||
{
|
||||
m_TitleSelectMenu.Update(AppState::HasFocus());
|
||||
}
|
||||
|
||||
void TextTitleSelectState::Render(void)
|
||||
{
|
||||
m_RenderTarget->Clear(Colors::Transparent);
|
||||
m_TitleSelectMenu.Render(m_RenderTarget->Get(), AppState::HasFocus());
|
||||
TitleSelectCommon::RenderControlGuide();
|
||||
m_RenderTarget->Render(NULL, 201, 91);
|
||||
}
|
||||
|
||||
void TextTitleSelectState::Refresh(void)
|
||||
{
|
||||
m_TitleSelectMenu.Reset();
|
||||
for (size_t i = 0; i < m_User->GetTotalDataEntries(); i++)
|
||||
{
|
||||
m_TitleSelectMenu.AddOption(Data::GetTitleInfoByID(m_User->GetApplicationIDAt(i))->GetTitle());
|
||||
}
|
||||
}
|
||||
26
Source/AppStates/TitleSelectCommon.cpp
Normal file
26
Source/AppStates/TitleSelectCommon.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "AppStates/TitleSelectCommon.hpp"
|
||||
#include "Colors.hpp"
|
||||
#include "SDL.hpp"
|
||||
#include "Strings.hpp"
|
||||
|
||||
TitleSelectCommon::TitleSelectCommon(void)
|
||||
{
|
||||
if (m_TitleControlsX == 0)
|
||||
{
|
||||
m_TitleControlsX = 1220 - SDL::Text::GetWidth(22, Strings::GetByName(Strings::Names::ControlGuides, 1));
|
||||
}
|
||||
}
|
||||
|
||||
void TitleSelectCommon::RenderControlGuide(void)
|
||||
{
|
||||
if (AppState::HasFocus())
|
||||
{
|
||||
SDL::Text::Render(NULL,
|
||||
m_TitleControlsX,
|
||||
673,
|
||||
22,
|
||||
SDL::Text::NO_TEXT_WRAP,
|
||||
Colors::White,
|
||||
Strings::GetByName(Strings::Names::ControlGuides, 1));
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,9 @@ namespace
|
||||
} // namespace
|
||||
|
||||
TitleSelectState::TitleSelectState(Data::User *User)
|
||||
: m_User(User),
|
||||
: TitleSelectCommon(), m_User(User),
|
||||
m_RenderTarget(SDL::TextureManager::CreateLoadTexture(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET)),
|
||||
m_TitleView(m_User)
|
||||
{
|
||||
if (m_TitleControlsX == 0)
|
||||
{
|
||||
m_TitleControlsX = 1220 - SDL::Text::GetWidth(22, Strings::GetByName(Strings::Names::ControlGuides, 1));
|
||||
}
|
||||
}
|
||||
m_TitleView(m_User) {};
|
||||
|
||||
void TitleSelectState::Update(void)
|
||||
{
|
||||
@@ -44,16 +38,10 @@ void TitleSelectState::Render(void)
|
||||
{
|
||||
m_RenderTarget->Clear(Colors::Transparent);
|
||||
m_TitleView.Render(m_RenderTarget->Get(), AppState::HasFocus());
|
||||
|
||||
if (AppState::HasFocus())
|
||||
{
|
||||
SDL::Text::Render(NULL,
|
||||
m_TitleControlsX,
|
||||
673,
|
||||
22,
|
||||
SDL::Text::NO_TEXT_WRAP,
|
||||
Colors::White,
|
||||
Strings::GetByName(Strings::Names::ControlGuides, 1));
|
||||
}
|
||||
TitleSelectCommon::RenderControlGuide();
|
||||
m_RenderTarget->Render(NULL, 201, 91);
|
||||
}
|
||||
|
||||
void TitleSelectState::Refresh(void)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
@@ -14,8 +13,8 @@ namespace
|
||||
// Config path(s)
|
||||
const char *CONFIG_FOLDER = "sdmc:/config/JKSV";
|
||||
const char *CONFIG_PATH = "sdmc:/config/JKSV/JKSV.json";
|
||||
// Map of config values
|
||||
std::unordered_map<std::string, uint8_t> s_ConfigMap;
|
||||
// Vector to preserve order now.
|
||||
std::vector<std::pair<std::string, uint8_t>> s_ConfigVector;
|
||||
// Working directory
|
||||
FsLib::Path s_WorkingDirectory;
|
||||
// UI animation scaling.
|
||||
@@ -86,7 +85,7 @@ void Config::Initialize(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
s_ConfigMap[KeyName] = json_object_get_uint64(ConfigValue);
|
||||
s_ConfigVector.push_back(std::make_pair(KeyName, json_object_get_uint64(ConfigValue)));
|
||||
}
|
||||
json_object_iter_next(&ConfigIterator);
|
||||
}
|
||||
@@ -95,20 +94,21 @@ void Config::Initialize(void)
|
||||
void Config::ResetToDefault(void)
|
||||
{
|
||||
s_WorkingDirectory = "sdmc:/JKSV";
|
||||
s_ConfigMap[Config::Keys::IncludeDeviceSaves.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::AutoBackupOnRestore.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::HoldForDeletion.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::HoldForRestoration.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::HoldForOverwrite.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::OnlyListMountable.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::ListAccountSystemSaves.data()] = 0;
|
||||
s_ConfigMap[Config::Keys::AllowSystemSaveWriting.data()] = 0;
|
||||
s_ConfigMap[Config::Keys::ExportToZip.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::ForceEnglish.data()] = 0;
|
||||
s_ConfigMap[Config::Keys::EnableTrashBin.data()] = 1;
|
||||
s_ConfigMap[Config::Keys::AutoNameBackups.data()] = 0;
|
||||
s_ConfigMap[Config::Keys::TitleSortType.data()] = 0;
|
||||
s_ConfigMap[Config::Keys::AutoUpload.data()] = 0;
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::IncludeDeviceSaves.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::AutoBackupOnRestore.data(), 1));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::AutoNameBackups.data(), 1));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::AutoUpload.data(), 1));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::HoldForDeletion.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::HoldForRestoration.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::HoldForOverwrite.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::OnlyListMountable.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::ListAccountSystemSaves.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::AllowSystemSaveWriting.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::ExportToZip.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::TitleSortType.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::JKSMTextMode.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::ForceEnglish.data(), 0));
|
||||
s_ConfigVector.push_back(std::make_pair(Config::Keys::EnableTrashBin.data(), 0));
|
||||
s_UIAnimationScaling = 2.5f;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ void Config::Save(void)
|
||||
json_object_object_add(ConfigJSON.get(), Config::Keys::WorkingDirectory.data(), WorkingDirectory);
|
||||
|
||||
// Loop through map and add it.
|
||||
for (auto &[Key, Value] : s_ConfigMap)
|
||||
for (auto &[Key, Value] : s_ConfigVector)
|
||||
{
|
||||
json_object *JsonValue = json_object_new_uint64(Value);
|
||||
json_object_object_add(ConfigJSON.get(), Key.c_str(), JsonValue);
|
||||
@@ -157,11 +157,23 @@ void Config::Save(void)
|
||||
|
||||
uint8_t Config::GetByKey(std::string_view Key)
|
||||
{
|
||||
if (s_ConfigMap.find(Key.data()) == s_ConfigMap.end())
|
||||
auto FindKey = std::find_if(s_ConfigVector.begin(), s_ConfigVector.end(), [Key](const std::pair<std::string, uint8_t> &ConfigPair) {
|
||||
return Key == ConfigPair.first;
|
||||
});
|
||||
if (FindKey == s_ConfigVector.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return s_ConfigMap.at(Key.data());
|
||||
return FindKey->second;
|
||||
}
|
||||
|
||||
uint8_t Config::GetByIndex(int Index)
|
||||
{
|
||||
if (Index < 0 || Index >= static_cast<int>(s_ConfigVector.size()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return s_ConfigVector.at(Index).second;
|
||||
}
|
||||
|
||||
FsLib::Path Config::GetWorkingDirectory(void)
|
||||
|
||||
@@ -80,6 +80,38 @@ bool Data::Initialize(void)
|
||||
|
||||
while (R_SUCCEEDED(fsSaveDataInfoReaderRead(&SaveInfoReader, &SaveInfo, 1, &TotalEntries)) && TotalEntries > 0)
|
||||
{
|
||||
// Skip this stuff
|
||||
if (!Config::GetByKey(Config::Keys::ListAccountSystemSaves) && SaveInfo.save_data_type == FsSaveDataType_System &&
|
||||
SaveInfo.uid != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (SaveInfo.save_data_type)
|
||||
{
|
||||
case FsSaveDataType_Bcat:
|
||||
{
|
||||
SaveInfo.uid = {FsSaveDataType_Bcat};
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Device:
|
||||
{
|
||||
SaveInfo.uid = {FsSaveDataType_Device};
|
||||
}
|
||||
break;
|
||||
|
||||
case FsSaveDataType_Cache:
|
||||
{
|
||||
SaveInfo.uid = {FsSaveDataType_Cache};
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
auto FindUser = std::find_if(s_UserVector.begin(), s_UserVector.end(), [&SaveInfo](UserIDPair &IDPair) {
|
||||
return IDPair.first == SaveInfo.uid;
|
||||
});
|
||||
|
||||
@@ -26,8 +26,8 @@ Data::TitleInfo::TitleInfo(uint64_t ApplicationID)
|
||||
std::memset(&m_NACP, 0x00, sizeof(NacpStruct));
|
||||
|
||||
// Sprintf title ids to language entries for safety.
|
||||
sprintf(m_NACP.lang[SetLanguage_ENUS].name, "%016llX", ApplicationID);
|
||||
sprintf(m_PathSafeTitle, "%016llX", ApplicationID);
|
||||
sprintf(m_NACP.lang[SetLanguage_ENUS].name, "%016lX", ApplicationID);
|
||||
sprintf(m_PathSafeTitle, "%016lX", ApplicationID);
|
||||
|
||||
// Create a place holder icon.
|
||||
int TextX = 128 - (SDL::Text::GetWidth(48, ApplicationIDHex.c_str()) / 2);
|
||||
@@ -42,7 +42,7 @@ Data::TitleInfo::TitleInfo(uint64_t ApplicationID)
|
||||
// Get a path safe version of the title.
|
||||
if (!StringUtil::SanitizeStringForPath(LanguageEntry->name, m_PathSafeTitle, 0x200))
|
||||
{
|
||||
std::sprintf(m_PathSafeTitle, "%016llX", ApplicationID);
|
||||
std::sprintf(m_PathSafeTitle, "%016lX", ApplicationID);
|
||||
}
|
||||
// Load the icon.
|
||||
m_Icon = SDL::TextureManager::CreateLoadTexture(LanguageEntry->name, NsControlData.icon, NsAppControlSize - sizeof(NacpStruct));
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
namespace
|
||||
{
|
||||
constexpr uint8_t BUILD_MON = 12;
|
||||
constexpr uint8_t BUILD_DAY = 5;
|
||||
constexpr uint8_t BUILD_DAY = 9;
|
||||
constexpr uint16_t BUILD_YEAR = 2024;
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "Colors.hpp"
|
||||
#include "UI/RenderFunctions.hpp"
|
||||
|
||||
UI::IconMenu::IconMenu(int X, int Y, int ScrollLength, int RenderTargetHeight) : Menu(X, Y, 152, 88, ScrollLength, RenderTargetHeight) {};
|
||||
UI::IconMenu::IconMenu(int X, int Y, int RenderTargetHeight) : Menu(X, Y, 152, 80, RenderTargetHeight) {};
|
||||
|
||||
void UI::IconMenu::Update(bool HasFocus)
|
||||
{
|
||||
@@ -16,7 +16,7 @@ void UI::IconMenu::Render(SDL_Texture *Target, bool HasFocus)
|
||||
m_ColorMod.Update();
|
||||
}
|
||||
|
||||
for (int i = 0, TempY = m_Y; i <= m_OptionsLength; i++, TempY += m_OptionHeight)
|
||||
for (int i = 0, TempY = m_Y; i < static_cast<int>(m_Options.size()); i++, TempY += m_OptionHeight)
|
||||
{
|
||||
// Clear target.
|
||||
m_OptionTarget->Clear(Colors::Transparent);
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
#include "UI/RenderFunctions.hpp"
|
||||
#include <cmath>
|
||||
|
||||
UI::Menu::Menu(int X, int Y, int Width, int FontSize, int ScrollLength, int RenderTargetHeight)
|
||||
: m_X(X), m_Y(Y), m_OriginalY(Y), m_FontSize(FontSize), m_ScrollLength(ScrollLength), m_MenuRenderLength(ScrollLength * 2), m_Width(Width),
|
||||
m_OptionHeight(std::ceil(static_cast<double>(FontSize) * 1.8f)), m_TargetY(Y), m_RenderTargetHeight(RenderTargetHeight)
|
||||
UI::Menu::Menu(int X, int Y, int Width, int FontSize, int RenderTargetHeight)
|
||||
: m_X(X), m_Y(Y), m_OptionHeight(std::ceil(static_cast<double>(FontSize) * 1.8f)), m_OriginalY(Y), m_TargetY(Y), m_Width(Width),
|
||||
m_FontSize(FontSize), m_RenderTargetHeight(RenderTargetHeight)
|
||||
{
|
||||
// Create render target for options
|
||||
static int MenuID = 0;
|
||||
// Create target for menu option
|
||||
std::string MenuTargetName = "Menu_" + std::to_string(MenuID++);
|
||||
m_OptionTarget =
|
||||
SDL::TextureManager::CreateLoadTexture(MenuTargetName, m_Width, m_OptionHeight, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET);
|
||||
|
||||
// Calculate around how many options can be shown on the render target at once.
|
||||
m_MaxDisplayOptions = (RenderTargetHeight - m_OriginalY) / m_OptionHeight;
|
||||
m_ScrollLength = std::floor(static_cast<double>(m_MaxDisplayOptions) / 2.0f);
|
||||
}
|
||||
|
||||
void UI::Menu::Update(bool HasFocus)
|
||||
@@ -24,36 +28,43 @@ void UI::Menu::Update(bool HasFocus)
|
||||
return;
|
||||
}
|
||||
|
||||
int PreviousSelected = m_Selected;
|
||||
int OptionsSize = m_Options.size();
|
||||
if (Input::ButtonPressed(HidNpadButton_AnyUp) && --m_Selected < 0)
|
||||
{
|
||||
m_Selected = m_OptionsLength;
|
||||
m_Selected = OptionsSize - 1;
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_AnyDown) && ++m_Selected > m_OptionsLength)
|
||||
else if (Input::ButtonPressed(HidNpadButton_AnyDown) && ++m_Selected >= static_cast<int>(m_Options.size()))
|
||||
{
|
||||
m_Selected = 0;
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_Left) && (m_Selected -= m_ScrollLength) < 0)
|
||||
else if (Input::ButtonPressed(HidNpadButton_AnyLeft) && (m_Selected -= m_ScrollLength) < 0)
|
||||
{
|
||||
m_Selected = 0;
|
||||
}
|
||||
else if (Input::ButtonPressed(HidNpadButton_AnyRight) && (m_Selected += m_ScrollLength) > m_OptionsLength)
|
||||
else if (Input::ButtonPressed(HidNpadButton_AnyRight) && (m_Selected += m_ScrollLength) >= static_cast<int>(m_Options.size()))
|
||||
{
|
||||
m_Selected = m_OptionsLength;
|
||||
m_Selected = OptionsSize - 1;
|
||||
}
|
||||
|
||||
// To do: Refine this further. It's so close.
|
||||
if (PreviousSelected != m_Selected && m_Selected < m_ScrollLength)
|
||||
// Don't bother continuing further if there's no reason to scroll.
|
||||
if (static_cast<int>(m_Options.size()) <= m_MaxDisplayOptions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Selected < m_ScrollLength)
|
||||
{
|
||||
m_TargetY = m_OriginalY;
|
||||
}
|
||||
else if (PreviousSelected != m_Selected && m_Selected >= m_ScrollLength &&
|
||||
(m_Selected > PreviousSelected || m_Selected < PreviousSelected) && m_Selected < (m_OptionsLength - m_ScrollLength))
|
||||
else if (m_Selected >= static_cast<int>(m_Options.size()) - (m_MaxDisplayOptions - m_ScrollLength))
|
||||
{
|
||||
m_TargetY = m_OriginalY - ((m_Options.size() - m_MaxDisplayOptions) * m_OptionHeight);
|
||||
}
|
||||
else if (m_Selected >= m_ScrollLength)
|
||||
{
|
||||
m_TargetY = m_OriginalY - ((m_Selected - m_ScrollLength) * m_OptionHeight);
|
||||
}
|
||||
|
||||
|
||||
if (m_Y != m_TargetY)
|
||||
{
|
||||
m_Y += std::ceil((m_TargetY - m_Y) / Config::GetAnimationScaling());
|
||||
@@ -72,12 +83,13 @@ void UI::Menu::Render(SDL_Texture *Target, bool HasFocus)
|
||||
// I hate doing this.
|
||||
int TargetHeight = 0;
|
||||
SDL_QueryTexture(Target, NULL, NULL, NULL, &TargetHeight);
|
||||
for (int i = 0, TempY = m_Y; i <= m_OptionsLength; i++, TempY += m_OptionHeight)
|
||||
for (int i = 0, TempY = m_Y; i < static_cast<int>(m_Options.size()); i++, TempY += m_OptionHeight)
|
||||
{
|
||||
if (TempY < 0 || TempY > TargetHeight)
|
||||
if (TempY < -m_FontSize)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Clear target texture.
|
||||
m_OptionTarget->Clear(Colors::Transparent);
|
||||
|
||||
@@ -93,11 +105,11 @@ void UI::Menu::Render(SDL_Texture *Target, bool HasFocus)
|
||||
}
|
||||
// Render text to target.
|
||||
SDL::Text::Render(m_OptionTarget->Get(),
|
||||
20,
|
||||
28,
|
||||
(m_OptionHeight / 2) - (m_FontSize / 2),
|
||||
m_FontSize,
|
||||
SDL::Text::NO_TEXT_WRAP,
|
||||
i == m_Selected ? Colors::BlueGreen : Colors::White,
|
||||
i == m_Selected && HasFocus ? Colors::BlueGreen : Colors::White,
|
||||
m_Options.at(i).c_str());
|
||||
// Render target to target
|
||||
m_OptionTarget->Render(Target, m_X, TempY);
|
||||
@@ -107,8 +119,6 @@ void UI::Menu::Render(SDL_Texture *Target, bool HasFocus)
|
||||
void UI::Menu::AddOption(std::string_view NewOption)
|
||||
{
|
||||
m_Options.push_back(NewOption.data());
|
||||
++m_OptionsLength;
|
||||
m_MenuHeight += m_OptionHeight;
|
||||
}
|
||||
|
||||
int UI::Menu::GetSelected(void) const
|
||||
@@ -123,6 +133,5 @@ void UI::Menu::SetSelected(int Selected)
|
||||
|
||||
void UI::Menu::Reset(void)
|
||||
{
|
||||
m_OptionsLength = -1;
|
||||
m_Options.clear();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ void UI::RenderBoundingBox(SDL_Texture *Target, int X, int Y, int Width, int Hei
|
||||
}
|
||||
|
||||
// Setup color.
|
||||
SDL::Color RenderMod = {(0x88 + ColorMod) << 16 | (0xC5 + (ColorMod / 2)) << 8 | 0xFF};
|
||||
SDL::Color RenderMod = {static_cast<uint32_t>((0x88 + ColorMod) << 16 | (0xC5 + (ColorMod / 2)) << 8 | 0xFF)};
|
||||
|
||||
// This shouldn't fail, but I don't really care if it does.
|
||||
SDL_SetTextureColorMod(s_MenuBoundingCorners->Get(), RenderMod.RGBA[3], RenderMod.RGBA[2], RenderMod.RGBA[1]);
|
||||
|
||||
@@ -55,18 +55,18 @@ void UI::TitleView::Update(bool HasFocus)
|
||||
}
|
||||
|
||||
double Scaling = Config::GetAnimationScaling();
|
||||
if (m_SelectedY > 280.0f)
|
||||
if (m_SelectedY > 388.0f)
|
||||
{
|
||||
m_Y += std::ceil((280.0f - m_SelectedY) / Scaling);
|
||||
m_Y += std::ceil((388.0f - m_SelectedY) / Scaling);
|
||||
}
|
||||
else if (m_SelectedY < 38.0f)
|
||||
else if (m_SelectedY < 28.0f)
|
||||
{
|
||||
m_Y += std::ceil((38.0f - m_SelectedY) / Scaling);
|
||||
m_Y += std::ceil((28.0f - m_SelectedY) / Scaling);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_TitleTiles.size(); i++)
|
||||
{
|
||||
m_TitleTiles.at(i).Update(m_Selected == i ? true : false);
|
||||
m_TitleTiles.at(i).Update(m_Selected == static_cast<int>(i) ? true : false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user