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}
|
add_executable(${PROJECT_NAME}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
src/lib/debug/debugScreen.c
|
src/lib/debug/debugScreen.c
|
||||||
src/lib/subsonic.hpp
|
src/subsonic.hpp
|
||||||
|
src/settings.hpp
|
||||||
|
|
||||||
src/lib/dr_mp3.h
|
src/lib/dr_mp3.h
|
||||||
src/lib/dr_flac.h
|
src/lib/dr_flac.h
|
||||||
src/lib/audio.cpp
|
src/lib/audio.cpp
|
||||||
|
|
@ -42,6 +44,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||||
SceDisplay_stub
|
SceDisplay_stub
|
||||||
SceAudio_stub
|
SceAudio_stub
|
||||||
SceTouch_stub
|
SceTouch_stub
|
||||||
|
SceIme_stub
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
${OPENSSL_LIBRARIES}
|
${OPENSSL_LIBRARIES}
|
||||||
c
|
c
|
||||||
|
|
|
||||||
1
emulator.sh
Executable file
1
emulator.sh
Executable file
|
|
@ -0,0 +1 @@
|
||||||
|
/home/deck/Downloads/ubuntu-latest/Vita3K make/subsonic.vpk
|
||||||
672
src/main.cpp
672
src/main.cpp
|
|
@ -1,8 +1,11 @@
|
||||||
#include <SDL2/SDL.h>
|
#include <SDL2/SDL.h>
|
||||||
#include <SDL2/SDL_audio.h>
|
#include <SDL2/SDL_audio.h>
|
||||||
#include <SDL2/SDL_hints.h>
|
#include <SDL2/SDL_hints.h>
|
||||||
|
#include <SDL2/SDL_log.h>
|
||||||
#include <SDL2/SDL_messagebox.h>
|
#include <SDL2/SDL_messagebox.h>
|
||||||
|
#include <SDL2/SDL_rect.h>
|
||||||
#include <SDL2/SDL_stdinc.h>
|
#include <SDL2/SDL_stdinc.h>
|
||||||
|
#include <SDL2/SDL_touch.h>
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <SDL2/SDL_ttf.h>
|
||||||
#include <SDL2/SDL_image.h>
|
#include <SDL2/SDL_image.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
@ -11,9 +14,14 @@
|
||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include "lib/subsonic.hpp"
|
#include "subsonic.hpp"
|
||||||
#include "lib/audio.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_W 960
|
||||||
#define SCREEN_H 544
|
#define SCREEN_H 544
|
||||||
|
|
||||||
|
|
@ -44,6 +52,14 @@ struct AudioQueueEntry {
|
||||||
std::string id;
|
std::string id;
|
||||||
bool isFlac;
|
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<ArtRequest> gArtRequestQ;
|
||||||
std::queue<ArtResult> gArtResultQ;
|
std::queue<ArtResult> gArtResultQ;
|
||||||
|
|
@ -68,18 +84,16 @@ int lastY = 0;
|
||||||
float velocity = 0.0f; // current scrolling speed
|
float velocity = 0.0f; // current scrolling speed
|
||||||
float friction = 0.92f; // deceleration factor (lower = more friction)
|
float friction = 0.92f; // deceleration factor (lower = more friction)
|
||||||
bool inertial = false; // true after releasing a drag
|
bool inertial = false; // true after releasing a drag
|
||||||
std::string selectedAlbumId = "";
|
bool settingsOpened = false;
|
||||||
|
|
||||||
std::vector<AudioQueueEntry> gPlayQueue; // holds Subsonic track IDs
|
std::vector<AudioQueueEntry> gPlayQueue; // holds Subsonic track IDs
|
||||||
int gQueueIndex = -1; // index in queue; -1 = not playing
|
int gQueueIndex = -1; // index in queue; -1 = not playing
|
||||||
|
|
||||||
const int LEFT_X = 0;
|
const int LEFT_X = 0;
|
||||||
const int LEFT_Y = 60; // below the tabs
|
const int LEFT_Y = 60; // below the tabs
|
||||||
const int LEFT_W = 350;
|
const int LEFT_W = 350;
|
||||||
const int LEFT_H = SCREEN_H - LEFT_Y;
|
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;
|
std::map<std::string, SDL_Texture*> gArtCache;
|
||||||
|
|
||||||
void log(SDL_Window* win, const char* fmt, ...) {
|
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_Rect dst = {LEFT_X, LEFT_Y, LEFT_W, LEFT_H};
|
||||||
SDL_RenderCopy(rd, listTex, NULL, &dst);
|
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(
|
void renderRightPanel(
|
||||||
SDL_Renderer* rd,
|
SDL_Renderer* rd,
|
||||||
TTF_Font* fontBig, TTF_Font* fontMed,
|
TTF_Font* fontBig, TTF_Font* fontMed,
|
||||||
|
|
@ -309,14 +521,6 @@ void drawButton(SDL_Renderer* rd, TTF_Font* font, Button& b) {
|
||||||
SDL_DestroyTexture(tt);
|
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]) {
|
void renderTabs(SDL_Renderer* rd, TTF_Font* fontMed, Tab tabs[3]) {
|
||||||
for (int i=0;i<3;i++){
|
for (int i=0;i<3;i++){
|
||||||
SDL_Rect r = {tabs[i].x,tabs[i].y,tabs[i].w,tabs[i].h};
|
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;
|
if (maxScroll < 0) maxScroll = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loadArtists(
|
||||||
void loadAlbums(
|
std::vector<Item>& items,
|
||||||
std::vector<Item>& items,
|
const SubsonicClient& ss
|
||||||
const SubsonicClient& ss)
|
) {
|
||||||
{
|
|
||||||
std::string err;
|
std::string err;
|
||||||
auto allAlbums = ss.getAllAlbums(&err);
|
auto allArtists = ss.getArtists(&err);
|
||||||
if (!err.empty())
|
if (!err.empty())
|
||||||
SDL_Log("getAlbum error: %s", err.c_str());
|
SDL_Log("getArtists error: %s", err.c_str());
|
||||||
|
|
||||||
items.clear();
|
items.clear();
|
||||||
items.reserve(allAlbums.size());
|
items.reserve(allArtists.size());
|
||||||
|
|
||||||
const int artSize = 80;
|
const int artSize = 80;
|
||||||
const int rowH = 100;
|
const int rowH = 100;
|
||||||
const int baseY = 70;
|
const int baseY = 70;
|
||||||
|
|
||||||
for (int k = 0; k < allAlbums.size(); k++) {
|
for (int k = 0; k < allArtists.size(); k++) {
|
||||||
const auto& a = allAlbums[k];
|
const auto& a = allArtists[k];
|
||||||
|
|
||||||
Item it;
|
Item it;
|
||||||
int y = baseY + k * rowH;
|
int y = baseY + k * rowH;
|
||||||
|
|
||||||
it.art = {20, y, artSize, artSize};
|
it.art = {20, y, artSize, artSize};
|
||||||
it.title = a.name;
|
it.title = a.name;
|
||||||
it.artist = a.artist;
|
it.artist = "";
|
||||||
it.coverId = a.coverArt;
|
it.coverId = a.coverArt;
|
||||||
it.id = a.id;
|
it.id = a.id;
|
||||||
it.isTrack = false;
|
it.isTrack = false;
|
||||||
|
|
@ -421,19 +624,70 @@ void loadAlbums(
|
||||||
maxScroll = items.size() * rowH - LEFT_H;
|
maxScroll = items.size() * rowH - LEFT_H;
|
||||||
if (maxScroll < 0) maxScroll = 0;
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
|
Settings settings;
|
||||||
|
settings.load();
|
||||||
|
|
||||||
SubsonicClient ss(
|
SubsonicClient ss(
|
||||||
"http://192.168.8.102:4747",
|
settings.server,
|
||||||
"admin",
|
settings.user,
|
||||||
"admin",
|
settings.pass,
|
||||||
"vita-player",
|
"vita-player",
|
||||||
"1.16.1"
|
"1.16.1"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
SDL_setenv("VITA_DISABLE_TOUCH_BACK", "1", 1);
|
SDL_setenv("VITA_DISABLE_TOUCH_BACK", "1", 1);
|
||||||
|
|
||||||
Audio_Init();
|
Audio_Init();
|
||||||
|
|
@ -491,9 +745,31 @@ int main()
|
||||||
|
|
||||||
int controlsCenter = sliderBar.x + sliderBar.w/2;
|
int controlsCenter = sliderBar.x + sliderBar.w/2;
|
||||||
|
|
||||||
Button btnBack = { controlsCenter - 150, 340, 80, 50, "back" };
|
Button btnBack = { controlsCenter - 150, 340, 80, 50, "Back" };
|
||||||
Button btnPlay = { controlsCenter - 40, 340, 80, 50, "play" };
|
Button btnPlay = { controlsCenter - 40, 340, 80, 50, "Play" };
|
||||||
Button btnNext = { controlsCenter + 70, 340, 80, 50, "next" };
|
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;
|
bool running=true;
|
||||||
|
|
||||||
|
|
@ -504,6 +780,11 @@ int main()
|
||||||
bool touch=false;
|
bool touch=false;
|
||||||
|
|
||||||
while(SDL_PollEvent(&e)){
|
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_QUIT) running=false;
|
||||||
if (e.type == SDL_FINGERMOTION && dragging) {
|
if (e.type == SDL_FINGERMOTION && dragging) {
|
||||||
int newY = e.tfinger.y * SCREEN_H;
|
int newY = e.tfinger.y * SCREEN_H;
|
||||||
|
|
@ -542,102 +823,158 @@ int main()
|
||||||
|
|
||||||
if (touch)
|
if (touch)
|
||||||
{
|
{
|
||||||
// Tabs
|
if(settingsOpened) {
|
||||||
for (int i=0;i<3;i++){
|
SDL_Rect rB = {btnSettingsBack.x,btnSettingsBack.y,btnSettingsBack.w,btnSettingsBack.h};
|
||||||
SDL_Rect r = {tabs[i].x,tabs[i].y,tabs[i].w,tabs[i].h};
|
SDL_Rect rS = {btnSettingsSave.x,btnSettingsSave.y,btnSettingsSave.w,btnSettingsSave.h};
|
||||||
if (hit(tx,ty,r)){
|
|
||||||
for(int j=0;j<3;j++) tabs[j].active=false;
|
|
||||||
tabs[i].active=true;
|
|
||||||
if (strcmp(tabs[i].label, "albums") == 0) {
|
|
||||||
loadAlbums(items, ss);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if(hit(tx, ty,rB)) {
|
||||||
|
settingsOpened = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < items.size(); i++) {
|
if(hit(tx,ty,rS)) {
|
||||||
SDL_Rect r = items[i].art;
|
settings.pass = password.text;
|
||||||
r.y -= scrollOffset;
|
settings.server = serverUrl.text;
|
||||||
if (hit(tx, ty, {LEFT_X + r.x, r.y, r.w, r.h})) {
|
settings.user = username.text;
|
||||||
if (tabs[0].active) {
|
|
||||||
selectedAlbumId = items[i].id;
|
|
||||||
loadTracks(items, ss, selectedAlbumId, win);
|
|
||||||
|
|
||||||
// switch to Tracks tab
|
settings.save();
|
||||||
for (int j = 0; j < 3; j++) tabs[j].active = false;
|
|
||||||
tabs[2].active = true;
|
|
||||||
} else if(tabs[2].active) {
|
|
||||||
gPlayQueue.clear();
|
|
||||||
for (int t = i; t < items.size(); t++) {
|
|
||||||
if (items[t].isTrack)
|
|
||||||
gPlayQueue.push_back( AudioQueueEntry{
|
|
||||||
items[t].id,
|
|
||||||
items[t].contentType=="audio/flac"||items[t].contentType=="audio/x-flac",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
gQueueIndex = 0; // start at selected track
|
|
||||||
sel = items[i];
|
|
||||||
auto q = gPlayQueue[gQueueIndex];
|
|
||||||
if(q.isFlac) {
|
|
||||||
Audio_Play(ss.streamUrl(q.id).c_str());
|
|
||||||
} else {
|
|
||||||
Audio_PlayMP3(ss.streamUrl(q.id).c_str());
|
|
||||||
}
|
|
||||||
btnPlay.label = "Pause";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
// Tabs
|
||||||
|
|
||||||
// Slider
|
// THE LAST TAB IS ONLY NAVIGATED TO IN CODE!!
|
||||||
if (hit(tx,ty,sliderBar)){
|
for (int i=0;i<2;i++){
|
||||||
float rel = (tx - sliderBar.x) / (float)sliderBar.w;
|
SDL_Rect r = {tabs[i].x,tabs[i].y,tabs[i].w,tabs[i].h};
|
||||||
if (rel<0) rel=0;
|
if (hit(tx,ty,r)){
|
||||||
if (rel>1) rel=1;
|
for(int j=0;j<3;j++) tabs[j].active=false;
|
||||||
sliderVal = rel;
|
tabs[i].active=true;
|
||||||
}
|
if (strcmp(tabs[i].label, "albums") == 0) {
|
||||||
|
loadAlbums(items, ss);
|
||||||
|
}
|
||||||
|
if (strcmp(tabs[i].label, "artists") == 0) {
|
||||||
|
loadArtists(items, ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Controls
|
for (int i = 0; i < items.size(); i++) {
|
||||||
SDL_Rect rB = {btnBack.x,btnBack.y,btnBack.w,btnBack.h};
|
SDL_Rect r = items[i].art;
|
||||||
SDL_Rect rP = {btnPlay.x,btnPlay.y,btnPlay.w,btnPlay.h};
|
r.y -= scrollOffset;
|
||||||
SDL_Rect rN = {btnNext.x,btnNext.y,btnNext.w,btnNext.h};
|
if (hit(tx, ty, {LEFT_X + r.x, r.y, r.w, r.h})) {
|
||||||
|
if (tabs[0].active) {
|
||||||
|
loadTracks(items, ss, items[i].id, win);
|
||||||
|
|
||||||
if (hit(tx,ty,rB)) {
|
// switch to Tracks tab
|
||||||
// Previous track in queue
|
for (int j = 0; j < 3; j++) tabs[j].active = false;
|
||||||
if (gQueueIndex > 0) {
|
tabs[2].active = true;
|
||||||
gQueueIndex--;
|
} else if(tabs[1].active) {
|
||||||
auto prevId = gPlayQueue[gQueueIndex];
|
loadAlbums(items, ss, items[i].id);
|
||||||
|
|
||||||
// Update UI selection
|
for (int j = 0; j < 3; j++) tabs[j].active = false;
|
||||||
for (auto &it : items)
|
tabs[0].active = true;
|
||||||
if (it.id == prevId.id)
|
} else if(tabs[2].active) {
|
||||||
sel = it;
|
gPlayQueue.clear();
|
||||||
|
for (int t = i; t < items.size(); t++) {
|
||||||
|
if (items[t].isTrack)
|
||||||
|
gPlayQueue.push_back( AudioQueueEntry{
|
||||||
|
items[t].id,
|
||||||
|
items[t].contentType=="audio/flac"||items[t].contentType=="audio/x-flac",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gQueueIndex = 0; // start at selected track
|
||||||
|
sel = items[i];
|
||||||
|
auto q = gPlayQueue[gQueueIndex];
|
||||||
|
if(q.isFlac) {
|
||||||
|
Audio_Play(ss.streamUrl(q.id).c_str());
|
||||||
|
} else {
|
||||||
|
Audio_PlayMP3(ss.streamUrl(q.id).c_str());
|
||||||
|
}
|
||||||
|
btnPlay.label = "Pause";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
if (hit(tx,ty,sliderBar)){
|
||||||
|
float rel = (tx - sliderBar.x) / (float)sliderBar.w;
|
||||||
|
if (rel<0) rel=0;
|
||||||
|
if (rel>1) rel=1;
|
||||||
|
sliderVal = rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controls
|
||||||
|
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
|
||||||
|
if (gQueueIndex > 0) {
|
||||||
|
gQueueIndex--;
|
||||||
|
auto prevId = gPlayQueue[gQueueIndex];
|
||||||
|
|
||||||
|
// Update UI selection
|
||||||
|
for (auto &it : items)
|
||||||
|
if (it.id == prevId.id)
|
||||||
|
sel = it;
|
||||||
|
|
||||||
if(prevId.isFlac) {
|
if(prevId.isFlac) {
|
||||||
Audio_Play(ss.streamUrl(prevId.id).c_str());
|
Audio_Play(ss.streamUrl(prevId.id).c_str());
|
||||||
} else {
|
} else {
|
||||||
Audio_PlayMP3(ss.streamUrl(prevId.id).c_str());
|
Audio_PlayMP3(ss.streamUrl(prevId.id).c_str());
|
||||||
}
|
}
|
||||||
btnPlay.label = "Pause";
|
btnPlay.label = "Pause";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hit(tx,ty,rP)) {
|
if (hit(tx,ty,rP)) {
|
||||||
if(!sel.id.empty()) {
|
if(!sel.id.empty()) {
|
||||||
if (strcmp(btnPlay.label, "Pause") == 0) {
|
if (strcmp(btnPlay.label, "Pause") == 0) {
|
||||||
Audio_Pause();
|
Audio_Pause();
|
||||||
btnPlay.label = "Play";
|
btnPlay.label = "Play";
|
||||||
} else if(Audio_IsPaused()) {
|
} else if(Audio_IsPaused()) {
|
||||||
|
btnPlay.label = "Pause";
|
||||||
|
Audio_Resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hit(tx,ty,rN)) {
|
||||||
|
if (gQueueIndex >= 0 && gQueueIndex + 1 < gPlayQueue.size()) {
|
||||||
|
gQueueIndex++;
|
||||||
|
auto nextId = gPlayQueue[gQueueIndex];
|
||||||
|
|
||||||
|
for (auto &it : items)
|
||||||
|
if (it.id == nextId.id)
|
||||||
|
sel = it;
|
||||||
|
|
||||||
|
if(nextId.isFlac) {
|
||||||
|
Audio_Play(ss.streamUrl(nextId.id).c_str());
|
||||||
|
} else {
|
||||||
|
Audio_PlayMP3(ss.streamUrl(nextId.id).c_str());
|
||||||
|
}
|
||||||
btnPlay.label = "Pause";
|
btnPlay.label = "Pause";
|
||||||
Audio_Resume();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hit(tx,ty,rN)) {
|
}
|
||||||
|
|
||||||
|
if(!settingsOpened) {
|
||||||
|
float prog = Audio_GetProgress();
|
||||||
|
|
||||||
|
if (Audio_IsPlaying()) {
|
||||||
|
sliderVal = prog;
|
||||||
|
} else {
|
||||||
|
// Track finished → advance queue
|
||||||
if (gQueueIndex >= 0 && gQueueIndex + 1 < gPlayQueue.size()) {
|
if (gQueueIndex >= 0 && gQueueIndex + 1 < gPlayQueue.size()) {
|
||||||
|
|
||||||
gQueueIndex++;
|
gQueueIndex++;
|
||||||
auto nextId = gPlayQueue[gQueueIndex];
|
auto nextId = gPlayQueue[gQueueIndex];
|
||||||
|
|
||||||
|
// update UI selection
|
||||||
for (auto &it : items)
|
for (auto &it : items)
|
||||||
if (it.id == nextId.id)
|
if (it.id == nextId.id)
|
||||||
sel = it;
|
sel = it;
|
||||||
|
|
@ -648,87 +985,74 @@ int main()
|
||||||
Audio_PlayMP3(ss.streamUrl(nextId.id).c_str());
|
Audio_PlayMP3(ss.streamUrl(nextId.id).c_str());
|
||||||
}
|
}
|
||||||
btnPlay.label = "Pause";
|
btnPlay.label = "Pause";
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float prog = Audio_GetProgress();
|
|
||||||
|
|
||||||
if (Audio_IsPlaying()) {
|
|
||||||
sliderVal = prog;
|
|
||||||
} else {
|
|
||||||
// Track finished → advance queue
|
|
||||||
if (gQueueIndex >= 0 && gQueueIndex + 1 < gPlayQueue.size()) {
|
|
||||||
|
|
||||||
gQueueIndex++;
|
|
||||||
auto nextId = gPlayQueue[gQueueIndex];
|
|
||||||
|
|
||||||
// update UI selection
|
|
||||||
for (auto &it : items)
|
|
||||||
if (it.id == nextId.id)
|
|
||||||
sel = it;
|
|
||||||
|
|
||||||
if(nextId.isFlac) {
|
|
||||||
Audio_Play(ss.streamUrl(nextId.id).c_str());
|
|
||||||
} else {
|
} else {
|
||||||
Audio_PlayMP3(ss.streamUrl(nextId.id).c_str());
|
// queue finished
|
||||||
|
btnPlay.label = "Play";
|
||||||
|
gQueueIndex = -1;
|
||||||
|
sel = {};
|
||||||
}
|
}
|
||||||
btnPlay.label = "Pause";
|
|
||||||
} else {
|
|
||||||
// queue finished
|
|
||||||
btnPlay.label = "Play";
|
|
||||||
gQueueIndex = -1;
|
|
||||||
sel = {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// DRAW UI
|
// DRAW UI
|
||||||
// -------------------------
|
// -------------------------
|
||||||
SDL_SetRenderDrawColor(rd,25,25,35,255);
|
SDL_SetRenderDrawColor(rd,25,25,35,255);
|
||||||
SDL_RenderClear(rd);
|
SDL_RenderClear(rd);
|
||||||
|
|
||||||
processPendingArt(rd);
|
processPendingArt(rd);
|
||||||
renderTabs(rd, fontMed, tabs);
|
if(settingsOpened) {
|
||||||
|
drawButton(rd, fontSmall, btnSettingsBack);
|
||||||
|
drawInputBox(rd, fontSmall, serverUrl);
|
||||||
|
drawInputBox(rd, fontSmall, username);
|
||||||
|
drawInputBox(rd, fontSmall, password);
|
||||||
|
|
||||||
// INERTIA SCROLLING UPDATE
|
drawButton(rd, fontSmall, btnSettingsSave);
|
||||||
if (inertial && !dragging) {
|
} else {
|
||||||
|
renderTabs(rd, fontMed, tabs);
|
||||||
|
|
||||||
// apply inertia
|
renderLeftList(rd, listTex, albumTex, fontSmall,
|
||||||
scrollOffset += (int)velocity;
|
items, scrollOffset, ss);
|
||||||
|
|
||||||
// decay the velocity
|
if (!sel.id.empty()) {
|
||||||
velocity *= friction;
|
renderRightPanel(rd, fontBig, fontMed,
|
||||||
|
selArt,albumTex, ss);
|
||||||
// stop when very slow
|
|
||||||
if (fabs(velocity) < 0.3f)
|
|
||||||
inertial = false;
|
|
||||||
|
|
||||||
// clamp
|
|
||||||
if (scrollOffset < 0) {
|
|
||||||
scrollOffset = 0;
|
|
||||||
inertial = false;
|
|
||||||
velocity = 0;
|
|
||||||
}
|
}
|
||||||
if (scrollOffset > maxScroll) {
|
// Slider
|
||||||
scrollOffset = maxScroll;
|
renderSlider(rd, sliderBar, sliderVal);
|
||||||
inertial = false;
|
|
||||||
velocity = 0;
|
// Controls
|
||||||
|
drawButton(rd, fontSmall, btnBack);
|
||||||
|
drawButton(rd, fontSmall, btnPlay);
|
||||||
|
drawButton(rd, fontSmall, btnNext);
|
||||||
|
drawButton(rd, fontSmall, btnSettings);
|
||||||
|
|
||||||
|
// INERTIA SCROLLING UPDATE
|
||||||
|
if (inertial && !dragging) {
|
||||||
|
|
||||||
|
// apply inertia
|
||||||
|
scrollOffset += (int)velocity;
|
||||||
|
|
||||||
|
// decay the velocity
|
||||||
|
velocity *= friction;
|
||||||
|
|
||||||
|
// stop when very slow
|
||||||
|
if (fabs(velocity) < 0.3f)
|
||||||
|
inertial = false;
|
||||||
|
|
||||||
|
// clamp
|
||||||
|
if (scrollOffset < 0) {
|
||||||
|
scrollOffset = 0;
|
||||||
|
inertial = false;
|
||||||
|
velocity = 0;
|
||||||
|
}
|
||||||
|
if (scrollOffset > maxScroll) {
|
||||||
|
scrollOffset = maxScroll;
|
||||||
|
inertial = false;
|
||||||
|
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_RenderPresent(rd);
|
||||||
SDL_Delay(16);
|
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