lots of more code
This commit is contained in:
parent
57b0832144
commit
d245a92811
5 changed files with 579 additions and 175 deletions
|
|
@ -28,7 +28,9 @@ find_package(SDL2 REQUIRED)
|
|||
add_executable(${PROJECT_NAME}
|
||||
src/main.cpp
|
||||
src/lib/debug/debugScreen.c
|
||||
src/lib/subsonic.hpp
|
||||
src/subsonic.hpp
|
||||
src/settings.hpp
|
||||
|
||||
src/lib/dr_mp3.h
|
||||
src/lib/dr_flac.h
|
||||
src/lib/audio.cpp
|
||||
|
|
@ -42,6 +44,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||
SceDisplay_stub
|
||||
SceAudio_stub
|
||||
SceTouch_stub
|
||||
SceIme_stub
|
||||
${CURL_LIBRARIES}
|
||||
${OPENSSL_LIBRARIES}
|
||||
c
|
||||
|
|
|
|||
1
emulator.sh
Executable file
1
emulator.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
/home/deck/Downloads/ubuntu-latest/Vita3K make/subsonic.vpk
|
||||
422
src/main.cpp
422
src/main.cpp
|
|
@ -1,8 +1,11 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_audio.h>
|
||||
#include <SDL2/SDL_hints.h>
|
||||
#include <SDL2/SDL_log.h>
|
||||
#include <SDL2/SDL_messagebox.h>
|
||||
#include <SDL2/SDL_rect.h>
|
||||
#include <SDL2/SDL_stdinc.h>
|
||||
#include <SDL2/SDL_touch.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <cstddef>
|
||||
|
|
@ -11,9 +14,14 @@
|
|||
|
||||
#include <queue>
|
||||
|
||||
#include "lib/subsonic.hpp"
|
||||
#include "subsonic.hpp"
|
||||
#include "lib/audio.hpp"
|
||||
|
||||
#include "settings.hpp"
|
||||
#include <map>
|
||||
#include <cmath> // for fabsf if you want
|
||||
#include <psp2/libime.h>
|
||||
#include <psp2/ime_dialog.h>
|
||||
#include <psp2/io/stat.h>
|
||||
#define SCREEN_W 960
|
||||
#define SCREEN_H 544
|
||||
|
||||
|
|
@ -44,6 +52,14 @@ struct AudioQueueEntry {
|
|||
std::string id;
|
||||
bool isFlac;
|
||||
};
|
||||
struct InputBox {
|
||||
int x, y, w, h;
|
||||
std::string text;
|
||||
bool focused = false;
|
||||
int caretPos = 0; // caret index
|
||||
Uint32 lastBlink = 0; // for caret blinking
|
||||
bool caretVisible = true;
|
||||
};
|
||||
|
||||
std::queue<ArtRequest> gArtRequestQ;
|
||||
std::queue<ArtResult> gArtResultQ;
|
||||
|
|
@ -68,18 +84,16 @@ int lastY = 0;
|
|||
float velocity = 0.0f; // current scrolling speed
|
||||
float friction = 0.92f; // deceleration factor (lower = more friction)
|
||||
bool inertial = false; // true after releasing a drag
|
||||
std::string selectedAlbumId = "";
|
||||
bool settingsOpened = false;
|
||||
|
||||
std::vector<AudioQueueEntry> gPlayQueue; // holds Subsonic track IDs
|
||||
int gQueueIndex = -1; // index in queue; -1 = not playing
|
||||
|
||||
const int LEFT_X = 0;
|
||||
const int LEFT_Y = 60; // below the tabs
|
||||
const int LEFT_W = 350;
|
||||
const int LEFT_H = SCREEN_H - LEFT_Y;
|
||||
|
||||
#include <map>
|
||||
#include <cmath> // for fabsf if you want
|
||||
|
||||
// Simple cache: coverArt ID -> SDL_Texture*
|
||||
std::map<std::string, SDL_Texture*> gArtCache;
|
||||
|
||||
void log(SDL_Window* win, const char* fmt, ...) {
|
||||
|
|
@ -253,6 +267,204 @@ void renderLeftList(
|
|||
SDL_Rect dst = {LEFT_X, LEFT_Y, LEFT_W, LEFT_H};
|
||||
SDL_RenderCopy(rd, listTex, NULL, &dst);
|
||||
}
|
||||
|
||||
void drawInputBox(SDL_Renderer* rd, TTF_Font* font, InputBox& b)
|
||||
{
|
||||
// background
|
||||
if (b.focused)
|
||||
SDL_SetRenderDrawColor(rd, 80, 80, 120, 255); // focused color
|
||||
else
|
||||
SDL_SetRenderDrawColor(rd, 60, 60, 90, 255);
|
||||
|
||||
SDL_Rect r = { b.x, b.y, b.w, b.h };
|
||||
SDL_RenderFillRect(rd, &r);
|
||||
|
||||
// text texture
|
||||
SDL_Texture* tt = nullptr;
|
||||
int tw = 0, th = 0;
|
||||
if (!b.text.empty()) {
|
||||
tt = text(rd, font, b.text.c_str(), {255,255,255});
|
||||
SDL_QueryTexture(tt, NULL, NULL, &tw, &th);
|
||||
}
|
||||
|
||||
int textX = r.x + 6; // left padding
|
||||
int textY = r.y + (r.h - th) / 2;
|
||||
|
||||
if (tt) {
|
||||
SDL_Rect h = SDL_Rect{ textX, textY, tw, th };
|
||||
SDL_RenderCopy(rd, tt, NULL, &h);
|
||||
}
|
||||
if (tt)
|
||||
SDL_DestroyTexture(tt);
|
||||
|
||||
// caret blinking
|
||||
Uint32 now = SDL_GetTicks();
|
||||
if (now - b.lastBlink > 500) {
|
||||
b.caretVisible = !b.caretVisible;
|
||||
b.lastBlink = now;
|
||||
}
|
||||
|
||||
// caret drawing
|
||||
if (b.focused && b.caretVisible) {
|
||||
// measure text up to caret
|
||||
std::string left = b.text.substr(0, b.caretPos);
|
||||
int caretX = textX;
|
||||
|
||||
if (!left.empty()) {
|
||||
TTF_SizeUTF8(font, left.c_str(), &caretX, NULL);
|
||||
caretX += textX;
|
||||
}
|
||||
|
||||
SDL_SetRenderDrawColor(rd, 255, 255, 255, 255);
|
||||
SDL_RenderDrawLine(rd, caretX, r.y + 5, caretX, r.y + r.h - 5);
|
||||
}
|
||||
}
|
||||
void utf8_to_utf16(SceWChar16* dst, const char* src, size_t max)
|
||||
{
|
||||
size_t di = 0;
|
||||
for (size_t si = 0; src[si] && di + 1 < max; )
|
||||
{
|
||||
unsigned char c = src[si++];
|
||||
|
||||
// 1-byte ASCII fast path
|
||||
if (c < 0x80) {
|
||||
dst[di++] = c;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2-byte UTF-8 → UTF-16
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
unsigned char c2 = src[si++];
|
||||
dst[di++] = ((c & 0x1F) << 6) | (c2 & 0x3F);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3-byte UTF-8 → UTF-16
|
||||
if ((c & 0xF0) == 0xE0) {
|
||||
unsigned char c2 = src[si++];
|
||||
unsigned char c3 = src[si++];
|
||||
dst[di++] = ((c & 0x0F) << 12) |
|
||||
((c2 & 0x3F) << 6) |
|
||||
(c3 & 0x3F);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Unsupported (skip)
|
||||
}
|
||||
|
||||
dst[di] = 0;
|
||||
}
|
||||
void utf16_to_utf8(const SceWChar16* src, uint8_t* dst)
|
||||
{
|
||||
size_t di = 0;
|
||||
|
||||
for (size_t si = 0; src[si]; si++)
|
||||
{
|
||||
uint16_t v = src[si];
|
||||
|
||||
if (v < 0x80) {
|
||||
dst[di++] = (uint8_t)v;
|
||||
}
|
||||
else if (v < 0x800) {
|
||||
dst[di++] = 0xC0 | (v >> 6);
|
||||
dst[di++] = 0x80 | (v & 0x3F);
|
||||
}
|
||||
else {
|
||||
dst[di++] = 0xE0 | (v >> 12);
|
||||
dst[di++] = 0x80 | ((v >> 6) & 0x3F);
|
||||
dst[di++] = 0x80 | (v & 0x3F);
|
||||
}
|
||||
}
|
||||
|
||||
dst[di] = 0;
|
||||
}
|
||||
|
||||
// Global input buffer (UTF-16, where IME writes text)
|
||||
static SceWChar16 gImeBuf[SCE_IME_DIALOG_MAX_TEXT_LENGTH + 1];
|
||||
|
||||
// Open IME dialog (set title + current text)
|
||||
static void ShowIME(const char* titleUtf8, const char* initUtf8)
|
||||
{
|
||||
SceImeDialogParam p;
|
||||
sceImeDialogParamInit(&p);
|
||||
|
||||
static SceWChar16 title[64];
|
||||
static SceWChar16 init[256];
|
||||
|
||||
utf8_to_utf16(title, titleUtf8, 63);
|
||||
utf8_to_utf16(init, initUtf8, 255);
|
||||
|
||||
p.supportedLanguages = 0;
|
||||
p.languagesForced = SCE_FALSE;
|
||||
p.type = SCE_IME_TYPE_DEFAULT;
|
||||
p.option = 0;
|
||||
p.textBoxMode = SCE_IME_DIALOG_TEXTBOX_MODE_WITH_CLEAR;
|
||||
p.maxTextLength = SCE_IME_DIALOG_MAX_TEXT_LENGTH;
|
||||
p.title = title;
|
||||
p.initialText = init;
|
||||
p.inputTextBuffer = gImeBuf;
|
||||
|
||||
sceImeDialogInit(&p);
|
||||
}
|
||||
|
||||
// Poll IME; returns true when finished and fills utf8Out
|
||||
static bool PollIME(char* utf8Out, int maxlen)
|
||||
{
|
||||
if (sceImeDialogGetStatus() != SCE_COMMON_DIALOG_STATUS_FINISHED)
|
||||
return false;
|
||||
|
||||
SceImeDialogResult r;
|
||||
SDL_memset(&r, 0, sizeof(r));
|
||||
sceImeDialogGetResult(&r);
|
||||
|
||||
uint8_t tmp[512];
|
||||
utf16_to_utf8(gImeBuf, tmp); // <-- text is in gImeBuf, not in SceImeDialogResult
|
||||
|
||||
SDL_strlcpy(utf8Out, (char*)tmp, maxlen);
|
||||
|
||||
// if you care about which button:
|
||||
// if (r.button == SCE_IME_DIALOG_BUTTON_ENTER) { ... }
|
||||
|
||||
sceImeDialogTerm();
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleInputBoxEvent(InputBox& box, const SDL_Event& e)
|
||||
{
|
||||
// Click = focus + open IME keyboard
|
||||
if (e.type == SDL_FINGERDOWN) {
|
||||
int mx = e.tfinger.x * SCREEN_W;
|
||||
int my = e.tfinger.y * SCREEN_H;
|
||||
|
||||
bool inside = (mx >= box.x && mx <= box.x + box.w &&
|
||||
my >= box.y && my <= box.y + box.h);
|
||||
|
||||
if (inside) {
|
||||
box.focused = true;
|
||||
ShowIME("testing.. testing..", box.text.c_str());
|
||||
char* result;
|
||||
while(true) {
|
||||
if(PollIME(result, 256)) {
|
||||
// Replace the entire input content
|
||||
box.text = result;
|
||||
|
||||
box.caretPos = box.text.size();
|
||||
box.lastBlink = SDL_GetTicks();
|
||||
box.caretVisible = true;
|
||||
|
||||
box.focused = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
box.focused = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!box.focused)
|
||||
return;
|
||||
}
|
||||
|
||||
void renderRightPanel(
|
||||
SDL_Renderer* rd,
|
||||
TTF_Font* fontBig, TTF_Font* fontMed,
|
||||
|
|
@ -309,14 +521,6 @@ void drawButton(SDL_Renderer* rd, TTF_Font* font, Button& b) {
|
|||
SDL_DestroyTexture(tt);
|
||||
}
|
||||
|
||||
void renderControls(SDL_Renderer* rd, TTF_Font* fontSmall,
|
||||
Button& b1, Button& b2, Button& b3)
|
||||
{
|
||||
drawButton(rd, fontSmall, b1);
|
||||
drawButton(rd, fontSmall, b2);
|
||||
drawButton(rd, fontSmall, b3);
|
||||
}
|
||||
|
||||
void renderTabs(SDL_Renderer* rd, TTF_Font* fontMed, Tab tabs[3]) {
|
||||
for (int i=0;i<3;i++){
|
||||
SDL_Rect r = {tabs[i].x,tabs[i].y,tabs[i].w,tabs[i].h};
|
||||
|
|
@ -381,32 +585,31 @@ void loadTracks(
|
|||
if (maxScroll < 0) maxScroll = 0;
|
||||
}
|
||||
|
||||
|
||||
void loadAlbums(
|
||||
void loadArtists(
|
||||
std::vector<Item>& items,
|
||||
const SubsonicClient& ss)
|
||||
{
|
||||
const SubsonicClient& ss
|
||||
) {
|
||||
std::string err;
|
||||
auto allAlbums = ss.getAllAlbums(&err);
|
||||
auto allArtists = ss.getArtists(&err);
|
||||
if (!err.empty())
|
||||
SDL_Log("getAlbum error: %s", err.c_str());
|
||||
SDL_Log("getArtists error: %s", err.c_str());
|
||||
|
||||
items.clear();
|
||||
items.reserve(allAlbums.size());
|
||||
items.reserve(allArtists.size());
|
||||
|
||||
const int artSize = 80;
|
||||
const int rowH = 100;
|
||||
const int baseY = 70;
|
||||
|
||||
for (int k = 0; k < allAlbums.size(); k++) {
|
||||
const auto& a = allAlbums[k];
|
||||
for (int k = 0; k < allArtists.size(); k++) {
|
||||
const auto& a = allArtists[k];
|
||||
|
||||
Item it;
|
||||
int y = baseY + k * rowH;
|
||||
|
||||
it.art = {20, y, artSize, artSize};
|
||||
it.title = a.name;
|
||||
it.artist = a.artist;
|
||||
it.artist = "";
|
||||
it.coverId = a.coverArt;
|
||||
it.id = a.id;
|
||||
it.isTrack = false;
|
||||
|
|
@ -421,19 +624,70 @@ void loadAlbums(
|
|||
maxScroll = items.size() * rowH - LEFT_H;
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
}
|
||||
void loadAlbums(
|
||||
std::vector<Item>& items,
|
||||
const SubsonicClient& ss,
|
||||
std::string artist = ""
|
||||
) {
|
||||
std::string err;
|
||||
auto allAlbums = ss.getAllAlbums(&err);
|
||||
if (!err.empty())
|
||||
SDL_Log("getAlbum error: %s", err.c_str());
|
||||
|
||||
items.clear();
|
||||
|
||||
const int artSize = 80;
|
||||
const int rowH = 100;
|
||||
const int baseY = 70;
|
||||
|
||||
int k2 = 0;
|
||||
|
||||
for (int k = 0; k < allAlbums.size(); k++) {
|
||||
const auto& a = allAlbums[k];
|
||||
|
||||
if(!artist.empty()) {
|
||||
if(a.artistId != artist) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Item it;
|
||||
int y = baseY + k2 * rowH;
|
||||
|
||||
it.art = {20, y, artSize, artSize};
|
||||
it.title = a.name;
|
||||
it.artist = a.artist;
|
||||
it.coverId = a.coverArt;
|
||||
it.id = a.id;
|
||||
it.isTrack = false;
|
||||
|
||||
it.titleRect = {20 + artSize + 15, y + 10, 200, 20};
|
||||
it.artistRect = {20 + artSize + 15, y + 35, 200, 20};
|
||||
|
||||
items.push_back(it);
|
||||
|
||||
k2=k2+1;
|
||||
}
|
||||
|
||||
scrollOffset = 0;
|
||||
maxScroll = items.size() * rowH - LEFT_H;
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
Settings settings;
|
||||
settings.load();
|
||||
|
||||
SubsonicClient ss(
|
||||
"http://192.168.8.102:4747",
|
||||
"admin",
|
||||
"admin",
|
||||
settings.server,
|
||||
settings.user,
|
||||
settings.pass,
|
||||
"vita-player",
|
||||
"1.16.1"
|
||||
);
|
||||
|
||||
|
||||
SDL_setenv("VITA_DISABLE_TOUCH_BACK", "1", 1);
|
||||
|
||||
Audio_Init();
|
||||
|
|
@ -491,9 +745,31 @@ int main()
|
|||
|
||||
int controlsCenter = sliderBar.x + sliderBar.w/2;
|
||||
|
||||
Button btnBack = { controlsCenter - 150, 340, 80, 50, "back" };
|
||||
Button btnPlay = { controlsCenter - 40, 340, 80, 50, "play" };
|
||||
Button btnNext = { controlsCenter + 70, 340, 80, 50, "next" };
|
||||
Button btnBack = { controlsCenter - 150, 340, 80, 50, "Back" };
|
||||
Button btnPlay = { controlsCenter - 40, 340, 80, 50, "Play" };
|
||||
Button btnNext = { controlsCenter + 70, 340, 80, 50, "Next" };
|
||||
Button btnSettings = { SCREEN_W-80, 10, 70, 50, "Settings" };
|
||||
|
||||
|
||||
Button btnSettingsBack = { 10, 10, 80, 50, "Back" };
|
||||
|
||||
InputBox serverUrl {
|
||||
10,10+50+10,350,30,
|
||||
settings.server
|
||||
};
|
||||
|
||||
InputBox password {
|
||||
10,10+50+10+30+10,350,30,
|
||||
settings.user
|
||||
};
|
||||
InputBox username {
|
||||
10,10+50+10+30+10+30+10,350,30,
|
||||
settings.pass
|
||||
};
|
||||
|
||||
Button btnSettingsSave = {
|
||||
10, 10+50+10+30+10+30+10+30+20,80,50,"Save"
|
||||
};
|
||||
|
||||
bool running=true;
|
||||
|
||||
|
|
@ -504,6 +780,11 @@ int main()
|
|||
bool touch=false;
|
||||
|
||||
while(SDL_PollEvent(&e)){
|
||||
if(settingsOpened) {
|
||||
handleInputBoxEvent(serverUrl, e);
|
||||
handleInputBoxEvent(password, e);
|
||||
handleInputBoxEvent(username, e);
|
||||
}
|
||||
if(e.type==SDL_QUIT) running=false;
|
||||
if (e.type == SDL_FINGERMOTION && dragging) {
|
||||
int newY = e.tfinger.y * SCREEN_H;
|
||||
|
|
@ -542,8 +823,26 @@ int main()
|
|||
|
||||
if (touch)
|
||||
{
|
||||
if(settingsOpened) {
|
||||
SDL_Rect rB = {btnSettingsBack.x,btnSettingsBack.y,btnSettingsBack.w,btnSettingsBack.h};
|
||||
SDL_Rect rS = {btnSettingsSave.x,btnSettingsSave.y,btnSettingsSave.w,btnSettingsSave.h};
|
||||
|
||||
if(hit(tx, ty,rB)) {
|
||||
settingsOpened = false;
|
||||
}
|
||||
|
||||
if(hit(tx,ty,rS)) {
|
||||
settings.pass = password.text;
|
||||
settings.server = serverUrl.text;
|
||||
settings.user = username.text;
|
||||
|
||||
settings.save();
|
||||
}
|
||||
} else {
|
||||
// Tabs
|
||||
for (int i=0;i<3;i++){
|
||||
|
||||
// THE LAST TAB IS ONLY NAVIGATED TO IN CODE!!
|
||||
for (int i=0;i<2;i++){
|
||||
SDL_Rect r = {tabs[i].x,tabs[i].y,tabs[i].w,tabs[i].h};
|
||||
if (hit(tx,ty,r)){
|
||||
for(int j=0;j<3;j++) tabs[j].active=false;
|
||||
|
|
@ -551,7 +850,9 @@ int main()
|
|||
if (strcmp(tabs[i].label, "albums") == 0) {
|
||||
loadAlbums(items, ss);
|
||||
}
|
||||
|
||||
if (strcmp(tabs[i].label, "artists") == 0) {
|
||||
loadArtists(items, ss);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -560,12 +861,16 @@ int main()
|
|||
r.y -= scrollOffset;
|
||||
if (hit(tx, ty, {LEFT_X + r.x, r.y, r.w, r.h})) {
|
||||
if (tabs[0].active) {
|
||||
selectedAlbumId = items[i].id;
|
||||
loadTracks(items, ss, selectedAlbumId, win);
|
||||
loadTracks(items, ss, items[i].id, win);
|
||||
|
||||
// switch to Tracks tab
|
||||
for (int j = 0; j < 3; j++) tabs[j].active = false;
|
||||
tabs[2].active = true;
|
||||
} else if(tabs[1].active) {
|
||||
loadAlbums(items, ss, items[i].id);
|
||||
|
||||
for (int j = 0; j < 3; j++) tabs[j].active = false;
|
||||
tabs[0].active = true;
|
||||
} else if(tabs[2].active) {
|
||||
gPlayQueue.clear();
|
||||
for (int t = i; t < items.size(); t++) {
|
||||
|
|
@ -602,6 +907,10 @@ int main()
|
|||
SDL_Rect rB = {btnBack.x,btnBack.y,btnBack.w,btnBack.h};
|
||||
SDL_Rect rP = {btnPlay.x,btnPlay.y,btnPlay.w,btnPlay.h};
|
||||
SDL_Rect rN = {btnNext.x,btnNext.y,btnNext.w,btnNext.h};
|
||||
SDL_Rect rS = {btnSettings.x,btnSettings.y,btnSettings.w,btnSettings.h};
|
||||
if(hit(tx, ty,rS)) {
|
||||
settingsOpened = true;
|
||||
}
|
||||
|
||||
if (hit(tx,ty,rB)) {
|
||||
// Previous track in queue
|
||||
|
|
@ -651,7 +960,9 @@ int main()
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!settingsOpened) {
|
||||
float prog = Audio_GetProgress();
|
||||
|
||||
if (Audio_IsPlaying()) {
|
||||
|
|
@ -681,16 +992,41 @@ int main()
|
|||
sel = {};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// DRAW UI
|
||||
// -------------------------
|
||||
SDL_SetRenderDrawColor(rd,25,25,35,255);
|
||||
SDL_RenderClear(rd);
|
||||
|
||||
processPendingArt(rd);
|
||||
if(settingsOpened) {
|
||||
drawButton(rd, fontSmall, btnSettingsBack);
|
||||
drawInputBox(rd, fontSmall, serverUrl);
|
||||
drawInputBox(rd, fontSmall, username);
|
||||
drawInputBox(rd, fontSmall, password);
|
||||
|
||||
drawButton(rd, fontSmall, btnSettingsSave);
|
||||
} else {
|
||||
renderTabs(rd, fontMed, tabs);
|
||||
|
||||
renderLeftList(rd, listTex, albumTex, fontSmall,
|
||||
items, scrollOffset, ss);
|
||||
|
||||
if (!sel.id.empty()) {
|
||||
renderRightPanel(rd, fontBig, fontMed,
|
||||
selArt,albumTex, ss);
|
||||
}
|
||||
// Slider
|
||||
renderSlider(rd, sliderBar, sliderVal);
|
||||
|
||||
// Controls
|
||||
drawButton(rd, fontSmall, btnBack);
|
||||
drawButton(rd, fontSmall, btnPlay);
|
||||
drawButton(rd, fontSmall, btnNext);
|
||||
drawButton(rd, fontSmall, btnSettings);
|
||||
|
||||
// INERTIA SCROLLING UPDATE
|
||||
if (inertial && !dragging) {
|
||||
|
||||
|
|
@ -716,19 +1052,7 @@ int main()
|
|||
velocity = 0;
|
||||
}
|
||||
}
|
||||
renderLeftList(rd, listTex, albumTex, fontSmall,
|
||||
items, scrollOffset, ss);
|
||||
|
||||
if (!sel.id.empty()) {
|
||||
renderRightPanel(rd, fontBig, fontMed,
|
||||
selArt,albumTex, ss);
|
||||
}
|
||||
// Slider
|
||||
renderSlider(rd, sliderBar, sliderVal);
|
||||
|
||||
// Controls
|
||||
renderControls(rd, fontSmall, btnBack, btnPlay, btnNext);
|
||||
|
||||
SDL_RenderPresent(rd);
|
||||
SDL_Delay(16);
|
||||
}
|
||||
|
|
|
|||
76
src/settings.hpp
Normal file
76
src/settings.hpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
#include <jsoncpp/json/json.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <psp2/io/fcntl.h>
|
||||
#include <psp2/io/stat.h>
|
||||
|
||||
class Settings {
|
||||
public:
|
||||
// ---- Stored values ----
|
||||
int volume = 80;
|
||||
std::string server = "http://192.168.8.102:4747";
|
||||
std::string user = "admin";
|
||||
std::string pass = "admin";
|
||||
|
||||
// ---- File paths ----
|
||||
static constexpr const char* PATH_DIR = "ux0:/data/subsonic_vita";
|
||||
static constexpr const char* PATH_FILE = "ux0:/data/subsonic_vita/settings.json";
|
||||
|
||||
Settings() {}
|
||||
|
||||
// ================================================================
|
||||
// Save to JSON file
|
||||
// ================================================================
|
||||
void save() const
|
||||
{
|
||||
sceIoMkdir(PATH_DIR, 0777);
|
||||
|
||||
Json::Value v;
|
||||
v["volume"] = volume;
|
||||
v["server"] = server;
|
||||
v["user"] = user;
|
||||
v["pass"] = pass;
|
||||
|
||||
Json::StreamWriterBuilder b;
|
||||
std::string out = Json::writeString(b, v);
|
||||
|
||||
SceUID fd = sceIoOpen(PATH_FILE,
|
||||
SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC,
|
||||
0666);
|
||||
if (fd >= 0) {
|
||||
sceIoWrite(fd, out.c_str(), out.size());
|
||||
sceIoClose(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Load from JSON file
|
||||
// ================================================================
|
||||
void load()
|
||||
{
|
||||
SceUID fd = sceIoOpen(PATH_FILE, SCE_O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
return; // no file → defaults stay
|
||||
|
||||
char buf[4096];
|
||||
int n = sceIoRead(fd, buf, sizeof(buf) - 1);
|
||||
sceIoClose(fd);
|
||||
if (n <= 0)
|
||||
return;
|
||||
buf[n] = 0;
|
||||
|
||||
Json::Value root;
|
||||
Json::CharReaderBuilder b;
|
||||
std::string errs;
|
||||
|
||||
std::istringstream ss(buf);
|
||||
if (!Json::parseFromStream(b, ss, &root, &errs))
|
||||
return;
|
||||
|
||||
if (root.isMember("volume")) volume = root["volume"].asInt();
|
||||
if (root.isMember("server")) server = root["server"].asString();
|
||||
if (root.isMember("user")) user = root["user"].asString();
|
||||
if (root.isMember("pass")) pass = root["pass"].asString();
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue