mirror of
https://github.com/TheCriicom/omega-dashboard-ps5.git
synced 2026-10-17 12:01:28 +02:00
- Cambia posizione dalle opzioni di un gioco, di una cartella o di un'app di Omega: la tessera si solleva, si porta dove si vuole con le frecce (L2/R2 in testa o in fondo) e le altre scorrono per farle posto; ✕ salva, ◯ rimette tutto com'era. Funziona in tutte le modalità del menu. L'ordine sta in home-layout.txt e resta dopo il riavvio; i giochi nuovi vanno in testa. Impostazioni › Home e giochi › Ordine della home riporta l'ordine automatico. - Community, Store, Browser, Installa PKG, Aggiornamenti e App mobile si spostano e si nascondono come le altre tessere. - Le tessere della fila scorrono al nuovo posto quando la fila cambia invece di saltare. - Server: il proxy non riusa più una connessione che l'api sta chiudendo (keep-alive 65 s sull'api, 30 s di inattività sul proxy): era la causa dei 502 "socket hang up".
1430 lines
79 KiB
C
1430 lines
79 KiB
C
// Omega UI — pannelli sopra la home: menu e conferme, Centro di controllo,
|
||
// Game Base, notifiche, profilo, scelta avatar, chat, ricerca, impostazioni e
|
||
// lettore di notizie.
|
||
#include "app.h"
|
||
#include "servers.h"
|
||
#include <arpa/inet.h>
|
||
#include <errno.h>
|
||
#include <fcntl.h>
|
||
#include <math.h>
|
||
#include <netinet/in.h>
|
||
#include <poll.h>
|
||
#include <stdlib.h>
|
||
#include <sys/socket.h>
|
||
#include <time.h>
|
||
#include <unistd.h>
|
||
|
||
// ------------------------------------------------------------------ utilità --
|
||
static void backdrop(float t, int strength) { fill_rect(0, 0, SCREEN_W, SCREEN_H, RGB(2, 4, 10), (int)(strength * t)); }
|
||
|
||
// Pannello che entra da destra; ritorna la x. Il velo scuro copre solo la parte
|
||
// lasciata libera: fondere tutto lo schermo a ogni fotogramma costa troppo al
|
||
// renderer software.
|
||
static int side_panel(float t, int w) {
|
||
float e = ease_out(t);
|
||
int x = SCREEN_W - (int)(w * e);
|
||
fill_rect(0, 0, x, SCREEN_H, RGB(2, 4, 10), (int)(110 * t));
|
||
grad_h(x - 60, 0, 60, SCREEN_H, C_BLACK, 0, C_BLACK, (int)(120 * t));
|
||
fill_rect(x, 0, w, SCREEN_H, RGB(16, 19, 30), t > 0.98f ? 255 : (int)(250 * t));
|
||
fill_rect(x, 0, 2, SCREEN_H, RGB(70, 84, 120), (int)(90 * t));
|
||
return x;
|
||
}
|
||
|
||
static void row_bg(int x, int y, int w, int h, float fa, int alpha) {
|
||
if (fa <= 0.01f) return;
|
||
fill_rrect(x, y, w, h, 18, C_WHITE, (int)(alpha * 0.11f * fa));
|
||
stroke_rrect(x - 3, y - 3, w + 6, h + 6, 21, 3, C_WHITE, (int)(alpha * fa));
|
||
}
|
||
|
||
static void status_dot(int cx, int cy, const char *status, int playing, int alpha) {
|
||
Col c = playing ? C_ACC2 : !strcmp(status, "online") ? C_OK : !strcmp(status, "away") ? C_WARN : !strcmp(status, "dnd") ? C_ERR : C_FAINT;
|
||
fill_circle(cx, cy, 11, RGB(16, 19, 30), alpha);
|
||
fill_circle(cx, cy, 7, c, alpha);
|
||
}
|
||
|
||
static void friend_status(const Friend *f, char *out, size_t n) {
|
||
if (f->game_id[0]) { char t[64]; play_time(f->started, t, sizeof t); snprintf(out, n, "%s \xC2\xB7 %s", f->game_name[0] ? f->game_name : f->game_id, t); }
|
||
else if (friend_online(f)) snprintf(out, n, "%s%s%s", !strcmp(f->status, "away") ? _("Assente") : !strcmp(f->status, "dnd") ? _("Non disturbare") : _("Online"),
|
||
f->status_msg[0] ? " \xC2\xB7 " : "", f->status_msg);
|
||
else if (f->last_seen[0]) { char t[64]; rel_time(f->last_seen, t, sizeof t); snprintf(out, n, _("Offline \xC2\xB7 visto %s"), t); }
|
||
else snprintf(out, n, "%s", _("Offline"));
|
||
}
|
||
|
||
// scorrimento verticale di una lista che tiene visibile la riga selezionata
|
||
static float list_scroll(float *cur, int sel, int row_h, int visible_h) {
|
||
float target = 0;
|
||
int y = sel * row_h;
|
||
if (y + row_h > visible_h) target = (float)(y + row_h - visible_h + row_h / 2);
|
||
*cur = approach(*cur, target, 14.0f);
|
||
return *cur;
|
||
}
|
||
|
||
// --------------------------------------------------------------------- menu --
|
||
static char mn_title[160]; static char mn_items[32][160]; static int mn_n, mn_sel; static MenuFn mn_fn; static void *mn_ud;
|
||
static float mn_anim;
|
||
|
||
void menu_open(const char *title, const char **items, int n, MenuFn fn, void *ud) {
|
||
snprintf(mn_title, sizeof mn_title, "%s", title ? title : "");
|
||
mn_n = n > 32 ? 32 : n;
|
||
for (int i = 0; i < mn_n; i++) snprintf(mn_items[i], sizeof mn_items[i], "%s", items[i]);
|
||
mn_sel = 0; mn_anim = 0; mn_fn = fn; mn_ud = ud;
|
||
ov_push(OV_MENU);
|
||
}
|
||
|
||
// voce selezionata all'apertura (es. la scelta attuale)
|
||
void menu_select(int i) { if (i >= 0 && i < mn_n) mn_sel = i, mn_anim = (float)i; }
|
||
|
||
void menu_draw(float t) {
|
||
backdrop(t, 120);
|
||
mn_anim = approach(mn_anim, (float)mn_sel, 18.0f);
|
||
// largo quanto la voce più lunga (le scelte della Privacy portano il valore accanto)
|
||
int w = 640, rh = 82, h = 110 + mn_n * rh + 30;
|
||
for (int i = 0; i < mn_n; i++) { int tw = text_w(font(W_MED, 28), mn_items[i]) + 130; if (tw > w) w = tw; }
|
||
if (w > 1180) w = 1180;
|
||
if (h > SCREEN_H - 120) h = SCREEN_H - 120;
|
||
int x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 50);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 30, 40, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 30, C_PANEL, a);
|
||
stroke_rrect(x, y, w, h, 30, 1, RGB(80, 92, 125), a / 2);
|
||
draw_text_fit(font(W_MED, 30), mn_title, x + 40, y + 36, w - 80, C_TXT, a, AL_L);
|
||
int ly = y + 104;
|
||
static float sc; float off = list_scroll(&sc, mn_sel, rh, h - 140);
|
||
SDL_Rect clip = { x, ly - 8, w, h - 120 };
|
||
SDL_RenderSetClipRect(R, &clip);
|
||
for (int i = 0; i < mn_n; i++) {
|
||
int ry = ly + i * rh - (int)off;
|
||
float fa = clampf(1 - fabsf(mn_anim - i), 0, 1);
|
||
row_bg(x + 20, ry, w - 40, rh - 10, fa, a);
|
||
draw_text_fit(font(mn_sel == i ? W_MED : W_REG, 28), mn_items[i], x + 48, ry + 18, w - 120, C_TXT, a, AL_L);
|
||
}
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
}
|
||
|
||
void menu_input(int b) {
|
||
if (b == B_UP && mn_sel > 0) mn_sel--;
|
||
else if (b == B_DOWN && mn_sel < mn_n - 1) mn_sel++;
|
||
else if (b == B_O) ov_pop();
|
||
else if (b == B_X) { MenuFn fn = mn_fn; void *ud = mn_ud; int s = mn_sel; ov_pop(); if (fn) fn(s, ud); }
|
||
}
|
||
|
||
// ----------------------------------------------------------------- conferma --
|
||
static char cf_msg[512], cf_yes[96]; static MenuFn cf_fn; static void *cf_ud; static int cf_sel;
|
||
void confirm_open(const char *msg, const char *yes, MenuFn fn, void *ud) {
|
||
snprintf(cf_msg, sizeof cf_msg, "%s", msg); snprintf(cf_yes, sizeof cf_yes, "%s", yes);
|
||
cf_fn = fn; cf_ud = ud; cf_sel = 1;
|
||
ov_push(OV_CONFIRM);
|
||
}
|
||
void confirm_draw(float t) {
|
||
backdrop(t, 150);
|
||
int w = 820, h = 330, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 40);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 30, 40, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 30, C_PANEL, a);
|
||
draw_text_wrap(font(W_REG, 32), cf_msg, x + 50, y + 50, w - 100, 3, 42, C_TXT, a);
|
||
int bw = 340, by = y + h - 120;
|
||
for (int i = 0; i < 2; i++) {
|
||
int bx = x + 50 + i * (bw + 40);
|
||
int foc = cf_sel == i;
|
||
if (foc) shadow_rrect(bx, by, bw, 76, 38, 16, a / 2);
|
||
fill_rrect(bx, by, bw, 76, 38, foc ? C_WHITE : RGB(48, 54, 72), a);
|
||
draw_text(font(W_MED, 28), i == 0 ? cf_yes : _("Annulla"), bx + bw / 2, by + 22, foc ? RGB(10, 12, 20) : C_TXT, a, AL_C);
|
||
}
|
||
}
|
||
void confirm_input(int b) {
|
||
if (b == B_LEFT) cf_sel = 0; else if (b == B_RIGHT) cf_sel = 1;
|
||
else if (b == B_O) ov_pop();
|
||
else if (b == B_X) { MenuFn fn = cf_fn; void *ud = cf_ud; int s = cf_sel; ov_pop(); if (s == 0 && fn) fn(0, ud); }
|
||
}
|
||
|
||
// ------------------------------------------------------ Centro di controllo --
|
||
enum { CC_NOTIF, CC_GB, CC_COMMUNITY, CC_PARTY, CC_MSG, CC_MUSIC, CC_BROWSER, CC_PROFILE, CC_SETTINGS, CC_POWER, CC_N };
|
||
static int cc_sel; static float cc_anim;
|
||
|
||
// Riposo, riavvio e spegnimento: le stesse richieste del menu di sistema
|
||
// (libSceSystemService), così la console chiude giochi e dischi in ordine.
|
||
// Prima: niente se si sta scrivendo (installazione, spostamento, copia,
|
||
// salvataggi); poi giochi esterni smontati, sync() e il servizio avvisato
|
||
// (prepare_power) perché chiuda i suoi lavori. Se la console rifiuta, o non si
|
||
// spegne entro un minuto (o si sveglia dal riposo), il servizio riprende.
|
||
#ifdef PS5
|
||
int sceSystemStateMgrEnterStandby(void);
|
||
int sceSystemStateMgrReboot(void);
|
||
int sceSystemStateMgrTurnOff(void);
|
||
#endif
|
||
#ifndef MSG_NOSIGNAL
|
||
#define MSG_NOSIGNAL 0
|
||
#endif
|
||
static SDL_atomic_t power_req; // 1 dalla richiesta finché il servizio non riprende
|
||
int power_pending(void) { return SDL_AtomicGet(&power_req) != 0; }
|
||
|
||
// POST /v1/cmd al servizio con una socket propria e al massimo ms in tutto: il
|
||
// servizio può non esserci o essere bloccato. Stato HTTP o -1.
|
||
static int svc_cmd(const char *cmd, int ms) {
|
||
int s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) return -1;
|
||
#ifdef SO_NOSIGPIPE
|
||
{ int one = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one); }
|
||
#endif
|
||
struct sockaddr_in a; memset(&a, 0, sizeof a);
|
||
a.sin_family = AF_INET; a.sin_port = htons(9095); a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||
fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);
|
||
Uint32 end = SDL_GetTicks() + (Uint32)ms; int st = -1;
|
||
struct pollfd p = { s, POLLOUT, 0 };
|
||
if (connect(s, (struct sockaddr *)&a, sizeof a) == 0 || (errno == EINPROGRESS && poll(&p, 1, ms) == 1)) {
|
||
int e = 0; socklen_t el = sizeof e; getsockopt(s, SOL_SOCKET, SO_ERROR, &e, &el);
|
||
char body[96], req[320]; snprintf(body, sizeof body, "{\"cmd\":\"%s\"}", cmd);
|
||
int n = snprintf(req, sizeof req, "POST /v1/cmd HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/json\r\nContent-Length: %d\r\nConnection: close\r\n\r\n%s", (int)strlen(body), body);
|
||
if (!e && send(s, req, (size_t)n, MSG_NOSIGNAL) == n) {
|
||
char r[64]; size_t got = 0; p.events = POLLIN;
|
||
while (got < sizeof r - 1) {
|
||
int left = (int)(end - SDL_GetTicks()); if (left <= 0 || poll(&p, 1, left) != 1) break;
|
||
ssize_t k = recv(s, r + got, sizeof r - 1 - got, 0); if (k <= 0) break;
|
||
got += (size_t)k;
|
||
}
|
||
r[got] = 0;
|
||
if (got >= 12 && !strncmp(r, "HTTP/1.", 7)) st = atoi(r + 9);
|
||
}
|
||
}
|
||
close(s);
|
||
return st;
|
||
}
|
||
static int power_watch(void *ud) {
|
||
(void)ud; time_t t0 = time(NULL);
|
||
while (time(NULL) - t0 < 60) SDL_Delay(1000); // dal riposo l'orologio salta avanti: si esce subito
|
||
omega_log("alimentazione: ancora accesa, il servizio riprende (%d)", svc_cmd("resume_power", 4000));
|
||
SDL_AtomicSet(&power_req, 0);
|
||
return 0;
|
||
}
|
||
static void power_do(int idx, void *ud) {
|
||
if (idx != 0) return;
|
||
int what = (int)(intptr_t)ud, rc = -1;
|
||
if (power_pending()) return;
|
||
if (install_busy()) { set_msg(_("C'è già un'installazione in corso"), 1); return; }
|
||
if (storage_busy()) { set_msg(_("C'è già uno spostamento in corso"), 1); return; }
|
||
if (files_busy()) { set_msg(_("C'è già una copia in corso"), 1); return; }
|
||
if (saves_busy()) { set_msg(_("Attendi: c'è già un'operazione sui salvataggi in corso"), 1); return; }
|
||
omega_log("alimentazione: %s", what == 0 ? "riposo" : what == 1 ? "riavvio" : "spegnimento");
|
||
SDL_AtomicSet(&power_req, 1);
|
||
drives_unmount_all();
|
||
sync();
|
||
omega_log("alimentazione: servizio avvisato (%d)", svc_cmd("prepare_power", 4000));
|
||
#ifdef PS5
|
||
rc = what == 0 ? sceSystemStateMgrEnterStandby() : what == 1 ? sceSystemStateMgrReboot() : sceSystemStateMgrTurnOff();
|
||
#endif
|
||
if (rc != 0) {
|
||
svc_cmd("resume_power", 4000); SDL_AtomicSet(&power_req, 0);
|
||
char m[120]; snprintf(m, sizeof m, _("La console ha rifiutato la richiesta (0x%x)"), (unsigned)rc); set_msg(m, 1);
|
||
return;
|
||
}
|
||
SDL_Thread *t = SDL_CreateThread(power_watch, "power", NULL);
|
||
if (t) SDL_DetachThread(t); else SDL_AtomicSet(&power_req, 0);
|
||
}
|
||
static void power_menu(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx == 0) power_do(0, (void *)(intptr_t)0);
|
||
else if (idx == 1) confirm_open(_("Riavviare la console? Il jailbreak andrà caricato di nuovo."), _("Riavvia"), power_do, (void *)(intptr_t)1);
|
||
else if (idx == 2) confirm_open(_("Spegnere la console? Il jailbreak andrà caricato di nuovo alla prossima accensione."), _("Spegni"), power_do, (void *)(intptr_t)2);
|
||
else if (idx == 3) app_quit();
|
||
else if (idx == 4) do_logout();
|
||
}
|
||
|
||
void cc_draw(float t) {
|
||
backdrop(t, 120);
|
||
cc_anim = approach(cc_anim, (float)cc_sel, 16.0f);
|
||
float e = ease_out(t);
|
||
int h = 470, y = SCREEN_H - (int)(h * e);
|
||
int a = (int)(255 * t);
|
||
grad_v(0, y - 120, SCREEN_W, 120, RGB(10, 13, 22), 0, RGB(10, 13, 22), (int)(220 * t));
|
||
fill_rect(0, y, SCREEN_W, h, RGB(10, 13, 22), (int)(235 * t));
|
||
char clock[16], date[96] = ""; time_t tt = time(NULL); struct tm *lt = localtime(&tt);
|
||
snprintf(clock, sizeof clock, "%02d:%02d", lt ? lt->tm_hour : 0, lt ? lt->tm_min : 0);
|
||
if (lt) date_long(date, sizeof date, lt->tm_wday, lt->tm_mday, lt->tm_mon);
|
||
draw_text(font(W_LIGHT, 64), clock, 110, y + 26, C_WHITE, a, AL_L);
|
||
draw_text(font(W_REG, 28), date, 290, y + 56, C_DIM, a, AL_L);
|
||
draw_avatar(S.me, S.my_avatar, SCREEN_W - 150, y + 62, 64, a);
|
||
draw_text(font(W_MED, 28), S.me, SCREEN_W - 196, y + 46, C_TXT, a, AL_R);
|
||
|
||
static const char *lbl[CC_N] = { N_("Notifiche"), N_("Game Base"), N_("Community"), N_("Party"), N_("Messaggi"), N_("Musica"), N_("Browser"), N_("Profilo"), N_("Impostazioni"), N_("Spegni") };
|
||
static const int ico[CC_N] = { IC_BELL, IC_FRIENDS, IC_NEWS, IC_PARTY, IC_CHAT, IC_MUSIC, IC_GLOBE, IC_USER, IC_GEAR, IC_POWER };
|
||
int cw = 172, ch = 222, gap = 12, x0 = (SCREEN_W - (CC_N * cw + (CC_N - 1) * gap)) / 2, cy = y + 140;
|
||
for (int i = 0; i < CC_N; i++) {
|
||
int cx = x0 + i * (cw + gap);
|
||
float fa = clampf(1 - fabsf(cc_anim - i), 0, 1);
|
||
int lift = (int)(14 * fa);
|
||
int foc = cc_sel == i;
|
||
if (foc) shadow_rrect(cx, cy - lift, cw, ch, 28, 22, a * 60 / 100);
|
||
fill_rrect(cx, cy - lift, cw, ch, 28, mix(RGB(30, 35, 50), RGB(235, 238, 245), fa), a);
|
||
Col fg = mix(C_TXT, RGB(14, 16, 24), fa);
|
||
if (i == CC_MUSIC && music_now_line()) music_mini(cx + cw / 2 - 30, cy - lift + 52, a);
|
||
if (i == CC_MUSIC && music_now_line()) { draw_icon(music_playing() ? IC_PAUSE : IC_PLAY, cx + cw / 2 + 24, cy - lift + 82, 40, fg, a); }
|
||
else draw_icon(ico[i], cx + cw / 2, cy - lift + 82, 70, fg, a);
|
||
draw_text_fit(font(W_MED, 26), _(lbl[i]), cx + cw / 2, cy - lift + 140, cw - 16, fg, a, AL_C);
|
||
char st[48] = "";
|
||
switch (i) {
|
||
case CC_NOTIF: if (S.unread_notif) snprintf(st, sizeof st, _("%d nuove"), S.unread_notif); break;
|
||
case CC_GB: snprintf(st, sizeof st, _("%d online"), friends_online_count()); break;
|
||
case CC_COMMUNITY: if (S.unread_groups) snprintf(st, sizeof st, _("%d nei gruppi"), S.unread_groups); else snprintf(st, sizeof st, "%s", _("Bacheca e gruppi")); break;
|
||
case CC_PARTY: snprintf(st, sizeof st, "%s", S.party.active ? (voice_state() == 1 ? _("Voce attiva") : _("Attivo")) : S.ninv ? _("Invito!") : _("Nessuno")); break;
|
||
case CC_MSG: if (S.unread_msg) snprintf(st, sizeof st, _("%d non letti"), S.unread_msg); break;
|
||
case CC_PROFILE: snprintf(st, sizeof st, "%s", S.me); break;
|
||
case CC_MUSIC: snprintf(st, sizeof st, "%s", music_now_line() ? music_now_line() : _("Radio e file")); break;
|
||
}
|
||
if (st[0]) draw_text_fit(font(W_REG, 21), st, cx + cw / 2, cy - lift + 176, cw - 24, mix(C_DIM, RGB(70, 76, 96), fa), a, AL_C);
|
||
int badge = i == CC_NOTIF ? S.unread_notif : i == CC_MSG ? S.unread_msg : i == CC_GB ? S.in_req : i == CC_PARTY ? S.ninv : i == CC_COMMUNITY ? S.unread_groups : 0;
|
||
if (badge) draw_badge(cx + cw - 26, cy - lift + 26, badge, a);
|
||
}
|
||
int ic[3] = { IC_BTN_X, IC_BTN_O, IC_BTN_SQ };
|
||
const char *lb[3] = { _("Apri"), _("Chiudi"), music_playing() ? _("Pausa musica") : _("Riprendi musica") };
|
||
hints(ic, lb, music_now_line() ? 3 : 2, a);
|
||
}
|
||
|
||
void cc_input(int b) {
|
||
if (b == B_SQ) { music_toggle(); return; }
|
||
if (b == B_LEFT && cc_sel > 0) cc_sel--;
|
||
else if (b == B_RIGHT && cc_sel < CC_N - 1) cc_sel++;
|
||
else if (b == B_O || b == B_OPT) ov_pop();
|
||
else if (b == B_X) {
|
||
ov_pop();
|
||
switch (cc_sel) {
|
||
case CC_NOTIF: social_load_notifications(); ov_push(OV_NOTIF); break;
|
||
case CC_GB: gb_open(0); break;
|
||
case CC_COMMUNITY: community_open(0); break;
|
||
case CC_PARTY: gb_open(2); break;
|
||
case CC_MSG: gb_open(3); break;
|
||
case CC_MUSIC: music_open(); break;
|
||
case CC_BROWSER: browser_open(NULL); break;
|
||
case CC_PROFILE: profile_open(S.me); break;
|
||
case CC_SETTINGS: ov_push(OV_SETTINGS); break;
|
||
case CC_POWER: { const char *it[] = { _("Modalità riposo"), _("Riavvia la console"), _("Spegni la console"), _("Chiudi Omega"), _("Esci dall'account") }; menu_open(_("Opzioni di alimentazione"), it, 5, power_menu, NULL); break; }
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------- Game Base --
|
||
enum { GB_FRIENDS, GB_REQ, GB_PARTY, GB_MSG, GB_NTABS };
|
||
enum { IT_ACTION, IT_FRIEND, IT_REQ_IN, IT_REQ_OUT, IT_HEADER, IT_PMEMBER, IT_PINVITE, IT_CONV, IT_EMPTY, IT_PARTYCARD };
|
||
typedef struct { int type; int idx; int icon; char title[100]; char sub[200]; char oid[32]; int avatar; int badge; } GbItem;
|
||
static int gb_tab, gb_zone = 1, gb_sel; static float gb_tab_anim, gb_sel_anim, gb_scroll;
|
||
static GbItem gbi[200]; static int ngbi;
|
||
|
||
static void add_item(int type, int idx, int icon, const char *title, const char *sub, const char *oid, int avatar, int badge) {
|
||
if (ngbi >= 200) return;
|
||
GbItem *it = &gbi[ngbi++]; memset(it, 0, sizeof *it);
|
||
it->type = type; it->idx = idx; it->icon = icon; it->avatar = avatar; it->badge = badge;
|
||
snprintf(it->title, sizeof it->title, "%s", title ? title : "");
|
||
snprintf(it->sub, sizeof it->sub, "%s", sub ? sub : "");
|
||
snprintf(it->oid, sizeof it->oid, "%s", oid ? oid : "");
|
||
}
|
||
static int selectable(const GbItem *it) { return it->type != IT_HEADER && it->type != IT_EMPTY && it->type != IT_PARTYCARD; }
|
||
|
||
static void gb_build(void) {
|
||
ngbi = 0;
|
||
char sub[200];
|
||
switch (gb_tab) {
|
||
case GB_FRIENDS:
|
||
add_item(IT_ACTION, 0, IC_ADDUSER, _("Aggiungi amici"), _("Cerca un ID online"), NULL, 0, 0);
|
||
snprintf(sub, sizeof sub, _("Amici \xC2\xB7 %d online su %d"), friends_online_count(), S.nfriends);
|
||
add_item(IT_HEADER, 0, -1, sub, NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.nfriends; i++) { friend_status(&S.friends[i], sub, sizeof sub); add_item(IT_FRIEND, i, -1, S.friends[i].oid, sub, S.friends[i].oid, S.friends[i].avatar, 0); }
|
||
if (!S.nfriends) add_item(IT_EMPTY, 0, IC_FRIENDS, _("Ancora nessun amico"), _("Aggiungi qualcuno con il suo ID online."), NULL, 0, 0);
|
||
break;
|
||
case GB_REQ:
|
||
snprintf(sub, sizeof sub, _("Ricevute \xC2\xB7 %d"), S.nin);
|
||
add_item(IT_HEADER, 0, -1, sub, NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.nin; i++) { char t[64]; rel_time(S.in[i].when, t, sizeof t); snprintf(sub, sizeof sub, _("Vuole diventare tuo amico \xC2\xB7 %s"), t); add_item(IT_REQ_IN, i, -1, S.in[i].oid, sub, S.in[i].oid, S.in[i].avatar, 0); }
|
||
if (!S.nin) add_item(IT_EMPTY, 0, IC_ADDUSER, _("Nessuna richiesta ricevuta"), NULL, NULL, 0, 0);
|
||
snprintf(sub, sizeof sub, _("Inviate \xC2\xB7 %d"), S.nout);
|
||
add_item(IT_HEADER, 0, -1, sub, NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.nout; i++) { char t[64]; rel_time(S.out[i].when, t, sizeof t); snprintf(sub, sizeof sub, _("In attesa di risposta \xC2\xB7 %s"), t); add_item(IT_REQ_OUT, i, -1, S.out[i].oid, sub, S.out[i].oid, S.out[i].avatar, 0); }
|
||
if (!S.nout) add_item(IT_EMPTY, 0, IC_SEND, _("Nessuna richiesta inviata"), NULL, NULL, 0, 0);
|
||
break;
|
||
case GB_PARTY:
|
||
if (S.party.active) {
|
||
add_item(IT_PARTYCARD, 0, -1, S.party.name, NULL, NULL, 0, 0);
|
||
int muted = 0; for (int i = 0; i < S.party.nmembers; i++) if (!strcasecmp(S.party.members[i].oid, S.me)) muted = S.party.members[i].muted;
|
||
add_item(IT_ACTION, 10, IC_CHAT, _("Chat del party"), S.npmsg ? S.pmsg[S.npmsg - 1].body : _("Scrivi al gruppo"), NULL, 0, 0);
|
||
add_item(IT_ACTION, 11, IC_ADDUSER, _("Invita amici"), _("Solo amici"), NULL, 0, 0);
|
||
add_item(IT_ACTION, 12, muted ? IC_MICOFF : IC_MIC, muted ? _("Attiva microfono") : _("Disattiva microfono"), NULL, NULL, 0, 0);
|
||
add_item(IT_ACTION, 13, IC_EXIT, _("Lascia il party"), NULL, NULL, 0, 0);
|
||
snprintf(sub, sizeof sub, _("Membri \xC2\xB7 %d"), S.party.nmembers);
|
||
add_item(IT_HEADER, 0, -1, sub, NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.party.nmembers; i++) {
|
||
PartyMember *m = &S.party.members[i];
|
||
snprintf(sub, sizeof sub, "%s%s%s", m->owner ? _("Leader") : _("Membro"), m->game_name[0] ? " \xC2\xB7 " : "", m->game_name);
|
||
add_item(IT_PMEMBER, i, -1, m->oid, sub, m->oid, m->avatar, 0);
|
||
}
|
||
for (int i = 0; i < S.party.ninvited; i++) add_item(IT_PMEMBER, 100 + i, -1, S.party.invited[i].oid, _("Invitato \xC2\xB7 in attesa"), S.party.invited[i].oid, S.party.invited[i].avatar, 0);
|
||
} else {
|
||
add_item(IT_ACTION, 20, IC_PARTY, _("Avvia un party"), _("Crea un gruppo e invita i tuoi amici"), NULL, 0, 0);
|
||
}
|
||
snprintf(sub, sizeof sub, _("Inviti ricevuti \xC2\xB7 %d"), S.ninv);
|
||
add_item(IT_HEADER, 0, -1, sub, NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.ninv; i++) {
|
||
snprintf(sub, sizeof sub, S.inv[i].members == 1 ? _("Invito da %s \xC2\xB7 %d membro") : _("Invito da %s \xC2\xB7 %d membri"), S.inv[i].from, S.inv[i].members);
|
||
add_item(IT_PINVITE, i, -1, S.inv[i].name, sub, S.inv[i].from, S.inv[i].avatar, 0);
|
||
}
|
||
if (!S.ninv) add_item(IT_EMPTY, 0, IC_PARTY, _("Nessun invito"), NULL, NULL, 0, 0);
|
||
break;
|
||
case GB_MSG:
|
||
add_item(IT_ACTION, 30, IC_PLUS, _("Nuovo messaggio"), _("Scrivi a un amico"), NULL, 0, 0);
|
||
add_item(IT_HEADER, 0, -1, _("Conversazioni"), NULL, NULL, 0, 0);
|
||
for (int i = 0; i < S.nconv; i++) {
|
||
char t[64]; rel_time(S.conv[i].when, t, sizeof t);
|
||
snprintf(sub, sizeof sub, S.conv[i].from_me ? _("Tu: %s") : "%s", S.conv[i].last);
|
||
add_item(IT_CONV, i, -1, S.conv[i].oid, sub, S.conv[i].oid, S.conv[i].avatar, S.conv[i].unread);
|
||
}
|
||
if (!S.nconv) add_item(IT_EMPTY, 0, IC_CHAT, _("Nessuna conversazione"), _("I messaggi con i tuoi amici appariranno qui."), NULL, 0, 0);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void gb_fix_sel(int dir) {
|
||
if (!ngbi) { gb_sel = 0; return; }
|
||
if (gb_sel >= ngbi) gb_sel = ngbi - 1;
|
||
if (gb_sel < 0) gb_sel = 0;
|
||
int guard = 0;
|
||
while (!selectable(&gbi[gb_sel]) && guard++ < ngbi) {
|
||
gb_sel += dir ? dir : 1;
|
||
if (gb_sel >= ngbi) { gb_sel = ngbi - 1; dir = -1; }
|
||
if (gb_sel < 0) { gb_sel = 0; dir = 1; }
|
||
}
|
||
}
|
||
|
||
void gb_open(int tab) {
|
||
gb_tab = tab; gb_zone = 1; gb_sel = 0; gb_scroll = 0;
|
||
social_load_friends(); social_load_conversations(); social_sync_now();
|
||
gb_build(); gb_fix_sel(1);
|
||
ov_push(OV_GAMEBASE);
|
||
}
|
||
|
||
static int item_height(const GbItem *it) {
|
||
switch (it->type) { case IT_HEADER: return 70; case IT_EMPTY: return 150; case IT_PARTYCARD: return 262; default: return 104; }
|
||
}
|
||
|
||
static void draw_partycard(int x, int y, int w, int alpha) {
|
||
news_art(NULL, x, y, w, 242, 24, alpha);
|
||
draw_icon(IC_PARTY, x + 70, y + 70, 64, C_WHITE, alpha);
|
||
draw_text_fit(font(W_MED, 34), S.party.name, x + 130, y + 40, w - 160, C_WHITE, alpha, AL_L);
|
||
char mm[48], m[128]; snprintf(mm, sizeof mm, S.party.nmembers == 1 ? _("%d membro") : _("%d membri"), S.party.nmembers);
|
||
snprintf(m, sizeof m, "%s \xC2\xB7 %s", mm, S.party.owner ? _("sei il leader") : _("sei nel party"));
|
||
draw_text(font(W_REG, 24), m, x + 130, y + 88, RGB(220, 230, 255), alpha, AL_L);
|
||
const char *vs = voice_state() == 1 ? _("Voce attiva") : voice_state() == 2 ? _("Solo ascolto: microfono non disponibile") : _("Voce in avvio...");
|
||
draw_icon(voice_state() == 1 ? IC_MIC : IC_MICOFF, x + w - 60, y + 50, 30, C_WHITE, alpha);
|
||
draw_text(font(W_REG, 20), vs, x + w - 84, y + 38, RGB(220, 230, 255), alpha, AL_R);
|
||
// avatar dei membri; un anello animato segnala chi sta parlando
|
||
for (int i = 0; i < S.party.nmembers && i < 8; i++) {
|
||
int cx = x + 60 + i * 56, cy = y + 152;
|
||
PartyMember *pm = &S.party.members[i];
|
||
if (!pm->muted && voice_speaking(pm->oid)) { ring(cx, cy, 29, 4, C_OK, alpha); ring(cx, cy, 33 + (int)(4 * sinf((float)g_time * 9)), 2, C_OK, alpha / 2); }
|
||
else if (pm->muted) { fill_circle(cx + 17, cy + 17, 11, RGB(160, 40, 50), alpha); draw_icon(IC_MICOFF, cx + 17, cy + 17, 14, C_WHITE, alpha); }
|
||
draw_avatar(pm->oid, pm->avatar, cx, cy, 48, alpha);
|
||
}
|
||
// in gioco la voce la porta avanti il servizio: come si comanda da lì
|
||
static int onion = -1; if (onion < 0) onion = strstr(hen_name(), "OnionHEN") != NULL;
|
||
draw_text_fit(font(W_REG, 20), onion ? _("In gioco: microfono con L2+R3 › Plugin › Omega; gli altri si sentono con Omega aperta")
|
||
: _("In gioco: microfono dal menu di OnionHEN (L2 + R3); gli altri si sentono con Omega aperta"),
|
||
x + 34, y + 200, w - 68, RGB(200, 212, 245), alpha, AL_L);
|
||
}
|
||
|
||
void gb_draw(float t) {
|
||
gb_build();
|
||
if (gb_zone == 1) gb_fix_sel(1);
|
||
int w = 820, x = side_panel(t, w), a = (int)(255 * t);
|
||
int px = x + 50, pw = w - 100;
|
||
draw_icon(IC_FRIENDS, px + 22, 74, 44, C_TXT, a);
|
||
draw_text(font(W_LIGHT, 44), _("Game Base"), px + 60, 46, C_WHITE, a, AL_L);
|
||
const char *tabs[GB_NTABS] = { _("Amici"), _("Richieste"), _("Party"), _("Messaggi") };
|
||
int badges[GB_NTABS] = { 0, S.in_req, S.ninv, S.unread_msg };
|
||
gb_tab_anim = approach(gb_tab_anim, (float)gb_tab, 16.0f);
|
||
int tx = px, ty = 130;
|
||
for (int i = 0; i < GB_NTABS; i++) {
|
||
TTF_Font *f = font(gb_tab == i ? W_MED : W_REG, 28);
|
||
int tw = text_w(f, tabs[i]) + 40;
|
||
if (gb_zone == 0 && gb_tab == i) fill_rrect(tx, ty, tw, 56, 28, C_WHITE, a);
|
||
else if (gb_tab == i) fill_rrect(tx, ty, tw, 56, 28, C_WHITE, a * 18 / 100);
|
||
draw_text(f, tabs[i], tx + 20, ty + 12, gb_zone == 0 && gb_tab == i ? RGB(12, 14, 22) : gb_tab == i ? C_WHITE : C_DIM, a, AL_L);
|
||
if (badges[i]) draw_badge(tx + tw - 4, ty + 2, badges[i], a);
|
||
tx += tw + 12;
|
||
}
|
||
int ly = 220, lh = SCREEN_H - ly - 90;
|
||
int sel_y = 0; for (int i = 0; i < gb_sel && i < ngbi; i++) sel_y += item_height(&gbi[i]);
|
||
float target = sel_y + 104 > lh ? (float)(sel_y + 104 - lh + 60) : 0;
|
||
gb_scroll = approach(gb_scroll, gb_zone == 1 ? target : 0, 14.0f);
|
||
gb_sel_anim = approach(gb_sel_anim, (float)gb_sel, 20.0f);
|
||
SDL_Rect clip = { x, ly - 10, w, lh + 10 };
|
||
SDL_RenderSetClipRect(R, &clip);
|
||
int y = ly - (int)gb_scroll;
|
||
for (int i = 0; i < ngbi; i++) {
|
||
GbItem *it = &gbi[i];
|
||
int ih = item_height(it);
|
||
if (y + ih < ly - 20 || y > ly + lh) { y += ih; continue; }
|
||
float fa = gb_zone == 1 ? clampf(1 - fabsf(gb_sel_anim - i), 0, 1) : 0;
|
||
int foc = gb_zone == 1 && gb_sel == i;
|
||
switch (it->type) {
|
||
case IT_HEADER:
|
||
draw_text(font(W_MED, 24), it->title, px + 8, y + 30, C_DIM, a, AL_L);
|
||
break;
|
||
case IT_EMPTY:
|
||
fill_rrect(px, y + 10, pw, ih - 20, 20, RGB(255, 255, 255), a * 5 / 100);
|
||
draw_icon(it->icon, px + 70, y + ih / 2, 50, C_FAINT, a);
|
||
draw_text(font(W_MED, 27), it->title, px + 130, y + (it->sub[0] ? 40 : 56), C_DIM, a, AL_L);
|
||
if (it->sub[0]) draw_text_fit(font(W_REG, 22), it->sub, px + 130, y + 80, pw - 160, C_FAINT, a, AL_L);
|
||
break;
|
||
case IT_PARTYCARD:
|
||
draw_partycard(px, y, pw, a);
|
||
break;
|
||
default: {
|
||
row_bg(px, y + 6, pw, ih - 12, fa, a);
|
||
int cx = px + 54, cy = y + ih / 2;
|
||
if (it->oid[0]) {
|
||
draw_avatar(it->oid, it->avatar, cx, cy, 68, a);
|
||
if (it->type == IT_FRIEND) { const Friend *f = &S.friends[it->idx]; if (friend_online(f)) status_dot(cx + 24, cy + 24, f->status, f->game_id[0] != 0, a); }
|
||
} else {
|
||
fill_circle(cx, cy, 34, foc ? C_WHITE : RGB(44, 52, 72), a);
|
||
draw_icon(it->icon, cx, cy, 36, foc ? RGB(12, 14, 22) : C_TXT, a);
|
||
}
|
||
int tx2 = px + 110, tw2 = pw - 150 - (it->badge ? 50 : 0);
|
||
draw_text_fit(font(W_MED, 28), it->title, tx2, y + (it->sub[0] ? 22 : 36), tw2, C_TXT, a, AL_L);
|
||
if (it->sub[0]) {
|
||
Col sc = C_DIM;
|
||
if (it->type == IT_FRIEND && S.friends[it->idx].game_id[0]) sc = C_ACC2;
|
||
else if (it->type == IT_FRIEND && friend_online(&S.friends[it->idx])) sc = C_OK;
|
||
draw_text_fit(font(W_REG, 22), it->sub, tx2, y + 60, tw2, sc, a, AL_L);
|
||
}
|
||
if (it->badge) draw_badge(px + pw - 40, cy, it->badge, a);
|
||
else if (it->type == IT_REQ_IN && foc) { draw_icon(IC_BTN_X, px + pw - 110, cy, 22, C_TXT, a); draw_text(font(W_REG, 20), _("Opzioni"), px + pw - 92, cy - 12, C_DIM, a, AL_L); }
|
||
else if (it->type != IT_ACTION) draw_icon(IC_ARROW_R, px + pw - 34, cy, 24, C_FAINT, a);
|
||
break;
|
||
}
|
||
}
|
||
y += ih;
|
||
}
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
const int ic[] = { IC_BTN_X, IC_BTN_O };
|
||
const char *lb[] = { _("Seleziona"), _("Indietro") };
|
||
hints(ic, lb, 2, a);
|
||
}
|
||
|
||
// azioni della Game Base
|
||
static char act_oid[32]; static char act_party[24];
|
||
static void friend_menu_cb(int idx, void *ud) {
|
||
(void)ud;
|
||
const Friend *f = friend_find(act_oid);
|
||
switch (idx) {
|
||
case 0: profile_open(act_oid); break;
|
||
case 1: chat_open(act_oid, f ? f->avatar : 0); break;
|
||
case 2: if (S.party.active) social_party_invite(act_oid); else social_party_create(); break;
|
||
}
|
||
}
|
||
static void remove_yes(int idx, void *ud) { (void)idx; (void)ud; social_friend_remove(act_oid); }
|
||
static void req_in_cb(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx == 0) social_friend_accept(act_oid);
|
||
else if (idx == 1) social_friend_decline(act_oid);
|
||
else profile_open(act_oid);
|
||
}
|
||
static void req_out_cb(int idx, void *ud) { (void)ud; if (idx == 0) social_friend_remove(act_oid); else profile_open(act_oid); }
|
||
static void pinvite_cb(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx == 0) social_party_join(act_party);
|
||
else if (idx == 1) social_party_decline(act_party);
|
||
}
|
||
static void leave_yes(int idx, void *ud) { (void)idx; (void)ud; social_party_leave(); }
|
||
|
||
static char inv_names[16][40]; static int ninv_names;
|
||
static void invite_pick(int idx, void *ud) { (void)ud; if (idx >= 0 && idx < ninv_names) social_party_invite(inv_names[idx]); }
|
||
static void open_invite_menu(void) {
|
||
static const char *items[16]; ninv_names = 0;
|
||
for (int i = 0; i < S.nfriends && ninv_names < 16; i++) {
|
||
int in = 0;
|
||
for (int k = 0; k < S.party.nmembers; k++) if (!strcasecmp(S.party.members[k].oid, S.friends[i].oid)) in = 1;
|
||
if (in) continue;
|
||
snprintf(inv_names[ninv_names], sizeof inv_names[0], "%s", S.friends[i].oid);
|
||
items[ninv_names] = inv_names[ninv_names]; ninv_names++;
|
||
}
|
||
if (!ninv_names) { set_msg(_("Nessun amico da invitare"), 0); return; }
|
||
menu_open(_("Invita al party"), items, ninv_names, invite_pick, NULL);
|
||
}
|
||
static char msg_names[64][40]; static int nmsg_names;
|
||
static void newmsg_pick(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx >= 0 && idx < nmsg_names) { const Friend *f = friend_find(msg_names[idx]); chat_open(msg_names[idx], f ? f->avatar : 0); }
|
||
}
|
||
static void open_newmsg_menu(void) {
|
||
static const char *items[16]; nmsg_names = 0;
|
||
for (int i = 0; i < S.nfriends && nmsg_names < 16; i++) { snprintf(msg_names[nmsg_names], sizeof msg_names[0], "%s", S.friends[i].oid); items[nmsg_names] = msg_names[nmsg_names]; nmsg_names++; }
|
||
if (!nmsg_names) { set_msg(_("Aggiungi un amico per scrivergli"), 0); return; }
|
||
menu_open(_("Scrivi a"), items, nmsg_names, newmsg_pick, NULL);
|
||
}
|
||
|
||
static void friend_menu_dispatch(int idx, void *ud) {
|
||
if (idx == 3) { confirm_open(_("Vuoi davvero rimuovere questo amico? Non vedrete più l'uno le attività dell'altro."), _("Rimuovi"), remove_yes, NULL); return; }
|
||
friend_menu_cb(idx, ud);
|
||
}
|
||
|
||
static void gb_activate(void) {
|
||
if (gb_sel < 0 || gb_sel >= ngbi) return;
|
||
GbItem *it = &gbi[gb_sel];
|
||
snprintf(act_oid, sizeof act_oid, "%s", it->oid);
|
||
switch (it->type) {
|
||
case IT_ACTION:
|
||
switch (it->idx) {
|
||
case 0: search_open(); break;
|
||
case 10: chat_open_party(); break;
|
||
case 11: open_invite_menu(); break;
|
||
case 12: { int muted = 0; for (int i = 0; i < S.party.nmembers; i++) if (!strcasecmp(S.party.members[i].oid, S.me)) muted = S.party.members[i].muted; social_party_mute(!muted); break; }
|
||
case 13: confirm_open(_("Vuoi lasciare il party?"), _("Lascia"), leave_yes, NULL); break;
|
||
case 20: social_party_create(); break;
|
||
case 30: open_newmsg_menu(); break;
|
||
}
|
||
break;
|
||
case IT_FRIEND: {
|
||
const char *it1[] = { _("Visualizza profilo"), _("Invia messaggio"), _("Invita al party"), _("Rimuovi amico") };
|
||
const char *it2[] = { _("Visualizza profilo"), _("Invia messaggio"), _("Avvia un party"), _("Rimuovi amico") };
|
||
menu_open(it->oid, S.party.active ? it1 : it2, 4, friend_menu_dispatch, NULL);
|
||
break;
|
||
}
|
||
case IT_REQ_IN: { const char *m[] = { _("Accetta"), _("Rifiuta"), _("Visualizza profilo") }; menu_open(it->oid, m, 3, req_in_cb, NULL); break; }
|
||
case IT_REQ_OUT: { const char *m[] = { _("Annulla richiesta"), _("Visualizza profilo") }; menu_open(it->oid, m, 2, req_out_cb, NULL); break; }
|
||
case IT_PMEMBER: if (strcasecmp(it->oid, S.me)) profile_open(it->oid); else profile_open(S.me); break;
|
||
case IT_PINVITE: {
|
||
snprintf(act_party, sizeof act_party, "%s", S.inv[it->idx].party_id);
|
||
const char *m[] = { _("Unisciti al party"), _("Rifiuta invito") };
|
||
menu_open(S.inv[it->idx].name, m, 2, pinvite_cb, NULL);
|
||
break;
|
||
}
|
||
case IT_CONV: chat_open(it->oid, it->avatar); break;
|
||
}
|
||
}
|
||
|
||
void gb_input(int b) {
|
||
gb_build();
|
||
if (b == B_O || b == B_TRI) { ov_pop(); return; }
|
||
if (b == B_L1 || b == B_R1) {
|
||
gb_tab = (gb_tab + (b == B_R1 ? 1 : GB_NTABS - 1)) % GB_NTABS; gb_sel = 0; gb_scroll = 0;
|
||
gb_build(); gb_fix_sel(1);
|
||
return;
|
||
}
|
||
if (gb_zone == 0) {
|
||
if (b == B_LEFT && gb_tab > 0) { gb_tab--; gb_sel = 0; }
|
||
else if (b == B_RIGHT && gb_tab < GB_NTABS - 1) { gb_tab++; gb_sel = 0; }
|
||
else if (b == B_DOWN || b == B_X) { gb_zone = 1; gb_build(); gb_sel = 0; gb_fix_sel(1); }
|
||
if (gb_tab == GB_MSG) social_load_conversations();
|
||
return;
|
||
}
|
||
if (b == B_UP) {
|
||
int s = gb_sel - 1;
|
||
while (s >= 0 && !selectable(&gbi[s])) s--;
|
||
if (s < 0) gb_zone = 0; else gb_sel = s;
|
||
} else if (b == B_DOWN) {
|
||
int s = gb_sel + 1;
|
||
while (s < ngbi && !selectable(&gbi[s])) s++;
|
||
if (s < ngbi) gb_sel = s;
|
||
} else if (b == B_LEFT || b == B_RIGHT) {
|
||
gb_tab = (gb_tab + (b == B_RIGHT ? 1 : GB_NTABS - 1)) % GB_NTABS; gb_sel = 0; gb_scroll = 0; gb_build(); gb_fix_sel(1);
|
||
if (gb_tab == GB_MSG) social_load_conversations();
|
||
} else if (b == B_X) gb_activate();
|
||
}
|
||
|
||
// ---------------------------------------------------------------- notifiche --
|
||
static int nt_sel; static float nt_anim, nt_scroll; static int nt_marked;
|
||
|
||
void notif_draw(float t) {
|
||
int w = 780, x = side_panel(t, w), a = (int)(255 * t);
|
||
int px = x + 50, pw = w - 100;
|
||
draw_icon(IC_BELL, px + 22, 74, 44, C_TXT, a);
|
||
draw_text(font(W_LIGHT, 44), _("Notifiche"), px + 60, 46, C_WHITE, a, AL_L);
|
||
if (!nt_marked && t > 0.9f && S.nnotif) { social_mark_notif_read(); nt_marked = 1; }
|
||
if (nt_sel >= S.nnotif) nt_sel = S.nnotif - 1;
|
||
if (nt_sel < 0) nt_sel = 0;
|
||
nt_anim = approach(nt_anim, (float)nt_sel, 20.0f);
|
||
int ly = 150, rh = 128, lh = SCREEN_H - ly - 90;
|
||
float off = list_scroll(&nt_scroll, nt_sel, rh, lh);
|
||
SDL_Rect clip = { x, ly - 10, w, lh + 10 };
|
||
SDL_RenderSetClipRect(R, &clip);
|
||
if (!S.nnotif) {
|
||
draw_icon(IC_BELL, px + pw / 2, ly + 160, 90, C_FAINT, a);
|
||
draw_text(font(W_MED, 30), _("Nessuna notifica"), px + pw / 2, ly + 240, C_DIM, a, AL_C);
|
||
draw_text(font(W_REG, 24), _("Richieste, messaggi e inviti appariranno qui."), px + pw / 2, ly + 286, C_FAINT, a, AL_C);
|
||
}
|
||
for (int i = 0; i < S.nnotif; i++) {
|
||
int y = ly + i * rh - (int)off;
|
||
if (y + rh < ly - 20 || y > ly + lh) continue;
|
||
Notif *n = &S.notif[i];
|
||
float fa = clampf(1 - fabsf(nt_anim - i), 0, 1);
|
||
row_bg(px, y + 6, pw, rh - 12, fa, a);
|
||
if (!n->read) fill_circle(px + 18, y + rh / 2, 6, C_ACC2, a);
|
||
int cx = px + 74, cy = y + rh / 2;
|
||
if (n->actor[0]) { draw_avatar(n->actor, n->avatar, cx, cy, 70, a); fill_circle(cx + 25, cy + 25, 16, C_ACC, a); draw_icon(notif_icon(n->type), cx + 25, cy + 25, 18, C_WHITE, a); }
|
||
else { fill_circle(cx, cy, 35, C_ACC, a); draw_icon(notif_icon(n->type), cx, cy, 36, C_WHITE, a); }
|
||
int tx = px + 130, tw = pw - 160;
|
||
if (!strcmp(n->type, "message")) {
|
||
draw_text_fit(font(W_MED, 26), n->title, tx, y + 22, tw, C_TXT, a, AL_L);
|
||
draw_text_fit(font(W_REG, 23), n->body, tx, y + 56, tw, C_DIM, a, AL_L);
|
||
} else {
|
||
draw_text_wrap(font(W_MED, 25), n->title, tx, y + 18, tw, 2, 31, C_TXT, a);
|
||
}
|
||
char tm[64]; rel_time(n->when, tm, sizeof tm);
|
||
draw_text(font(W_REG, 20), tm, tx, y + rh - 38, C_FAINT, a, AL_L);
|
||
}
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
const int ic[] = { IC_BTN_X, IC_BTN_SQ, IC_BTN_O };
|
||
const char *lb[] = { _("Apri"), _("Cancella tutte"), _("Indietro") };
|
||
hints(ic, lb, 3, a);
|
||
}
|
||
|
||
static void clear_yes(int idx, void *ud) { (void)idx; (void)ud; social_clear_notif(); }
|
||
|
||
void notif_input(int b) {
|
||
if (b == B_O) { ov_pop(); nt_marked = 0; return; }
|
||
if (b == B_UP && nt_sel > 0) nt_sel--;
|
||
else if (b == B_DOWN && nt_sel < S.nnotif - 1) nt_sel++;
|
||
else if (b == B_SQ) { if (S.nnotif) confirm_open(_("Cancellare tutte le notifiche?"), _("Cancella"), clear_yes, NULL); }
|
||
else if (b == B_X && S.nnotif) {
|
||
Notif *n = &S.notif[nt_sel];
|
||
nt_marked = 0;
|
||
if (!strcmp(n->type, "friend_request")) { ov_pop(); gb_open(1); }
|
||
else if (!strcmp(n->type, "message")) { const Friend *f = friend_find(n->ref); ov_pop(); chat_open(n->ref, f ? f->avatar : n->avatar); }
|
||
else if (!strcmp(n->type, "party_invite")) { ov_pop(); gb_open(2); }
|
||
else if (!strcmp(n->type, "post_like") || !strcmp(n->type, "post_comment")) { ov_pop(); community_open_post(n->ref); }
|
||
else if (!strcmp(n->type, "game_invite")) {
|
||
int k = -1; for (int i = 0; i < napps; i++) if (!strcmp(apps[i].tid, n->ref)) k = i;
|
||
ov_pop();
|
||
if (k >= 0) { ov_clear(); launch_app(k); } else set_msg(_("Questo gioco non è installato su questa console"), 1);
|
||
}
|
||
else if (n->actor[0]) { ov_pop(); profile_open(n->actor); }
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------------ profilo --
|
||
static int pr_sel; static float pr_anim;
|
||
enum { PA_MSG, PA_PARTY, PA_REMOVE, PA_ADD, PA_ACCEPT, PA_DECLINE, PA_CANCEL, PA_BIO, PA_AVATAR, PA_COVER, PA_STATUS, PA_MORE, PA_UNBLOCK, PA_TROPHIES };
|
||
#define PR_MAX_ACTIONS 6
|
||
static int pr_actions(int *out, const char **lbl, int *icons) {
|
||
int n = 0;
|
||
#define ADD(id, l, ic) do { out[n] = id; lbl[n] = l; icons[n] = ic; n++; } while (0)
|
||
// i trofei: i propri sempre, quelli degli altri se li mostrano
|
||
int trophies = PR.loaded && !PR.blocked && (!strcmp(PR.relation, "self") || !PR.trophies.hidden);
|
||
if (!strcmp(PR.relation, "self")) { ADD(PA_STATUS, _("Stato"), IC_CHECK); ADD(PA_TROPHIES, _("Trofei"), IC_STAR); ADD(PA_BIO, _("Modifica bio"), IC_NEWS); ADD(PA_AVATAR, _("Avatar"), IC_USER); ADD(PA_COVER, _("Copertina"), IC_ALBUM); }
|
||
else if (PR.blocked) { ADD(PA_UNBLOCK, _("Sblocca"), IC_CHECK); }
|
||
else if (!strcmp(PR.relation, "friend")) { ADD(PA_MSG, _("Messaggio"), IC_CHAT); ADD(PA_PARTY, S.party.active ? _("Invita al party") : _("Avvia party"), IC_PARTY); ADD(PA_REMOVE, _("Rimuovi"), IC_CLOSE); ADD(PA_MORE, P_("profilo", "Altro"), IC_MORE); }
|
||
else if (!strcmp(PR.relation, "incoming")) { ADD(PA_ACCEPT, _("Accetta richiesta"), IC_CHECK); ADD(PA_DECLINE, _("Rifiuta"), IC_CLOSE); }
|
||
else if (!strcmp(PR.relation, "outgoing")) { ADD(PA_CANCEL, _("Annulla richiesta"), IC_CLOSE); }
|
||
else if (PR.loaded) { ADD(PA_ADD, _("Aggiungi amico"), IC_ADDUSER); }
|
||
if (trophies && strcmp(PR.relation, "self")) ADD(PA_TROPHIES, _("Trofei"), IC_STAR);
|
||
if (PR.loaded && !PR.blocked && strcmp(PR.relation, "self") && strcmp(PR.relation, "friend")) ADD(PA_MORE, P_("profilo", "Altro"), IC_MORE);
|
||
#undef ADD
|
||
return n;
|
||
}
|
||
|
||
void profile_draw(float t) {
|
||
backdrop(t, 160);
|
||
int w = 1560, h = 920, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 60);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 34, 50, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 34, RGB(18, 21, 32), a);
|
||
News fake; memset(&fake, 0, sizeof fake); fake.color = PR.avatar % 8;
|
||
news_art(&fake, x, y, w, 260, -34, a);
|
||
// copertina personalizzata (foto o video in loop) sopra la grafica generata
|
||
if (PR.cover_media[0]) draw_media_frames(PR.cover_media, PR.cover_frames, x, y, w, 260, 1170, 195, -26, 1, a);
|
||
int ax = x + 170, ay = y + 260;
|
||
fill_circle(ax, ay, 118, RGB(18, 21, 32), a);
|
||
draw_avatar(PR.oid, PR.avatar, ax, ay, 220, a);
|
||
int online = PR.status[0] && strcmp(PR.status, "offline");
|
||
if (online) { fill_circle(ax + 78, ay + 78, 20, RGB(18, 21, 32), a); fill_circle(ax + 78, ay + 78, 14, PR.game_name[0] ? C_ACC2 : C_OK, a); }
|
||
draw_text(font(W_LIGHT, 60), PR.oid, x + 320, y + 270, C_WHITE, a, AL_L);
|
||
char st[200];
|
||
if (PR.loading && !PR.loaded) snprintf(st, sizeof st, "%s", _("Caricamento..."));
|
||
else if (PR.game_name[0]) { char tm[64]; play_time(PR.started, tm, sizeof tm); snprintf(st, sizeof st, _("Sta giocando a %s \xC2\xB7 %s"), PR.game_name, tm); }
|
||
else snprintf(st, sizeof st, "%s", online ? _("Online") : _("Offline"));
|
||
int sw = draw_text(font(W_REG, 28), st, x + 324, y + 346, online ? (PR.game_name[0] ? C_ACC2 : C_OK) : C_DIM, a, AL_L);
|
||
if (PR.status_msg[0]) { char q[90]; snprintf(q, sizeof q, "\xE2\x80\x9C%s\xE2\x80\x9D", PR.status_msg); draw_text_fit(font(W_REG, 26), q, x + 350 + sw, y + 348, w - 760 - sw, C_TXT, a, AL_L); }
|
||
const char *rel = !strcmp(PR.relation, "friend") ? _("Amico") : !strcmp(PR.relation, "self") ? _("Sei tu") : !strcmp(PR.relation, "incoming") ? _("Ti ha inviato una richiesta") : !strcmp(PR.relation, "outgoing") ? _("Richiesta inviata") : PR.loaded ? _("Non siete amici") : "";
|
||
if (rel[0]) {
|
||
int rw = text_w(font(W_MED, 22), rel) + 40;
|
||
fill_rrect(x + w - rw - 60, y + 286, rw, 44, 22, C_WHITE, a * 14 / 100);
|
||
draw_text(font(W_MED, 22), rel, x + w - rw / 2 - 60, y + 296, C_TXT, a, AL_C);
|
||
}
|
||
int ids[PR_MAX_ACTIONS], icons[PR_MAX_ACTIONS]; const char *lbl[PR_MAX_ACTIONS];
|
||
int n = pr_actions(ids, lbl, icons);
|
||
if (pr_sel >= n) pr_sel = n ? n - 1 : 0;
|
||
pr_anim = approach(pr_anim, (float)pr_sel, 18.0f);
|
||
int bx = x + 60, by = y + 420;
|
||
for (int i = 0; i < n; i++) {
|
||
float fa = clampf(1 - fabsf(pr_anim - i), 0, 1);
|
||
int bw = pill(bx, by, 76, lbl[i], icons[i], pr_sel == i, fa, a);
|
||
if (pr_sel == i) focus_ring(bx, by, bw, 76, 38, 0.5f + 0.5f * sinf((float)g_time * 3.2f), a);
|
||
bx += bw + 20;
|
||
}
|
||
// colonne: info, giochi, attività
|
||
int cy = y + 540;
|
||
int c1 = x + 60, c2 = x + 560, c3 = x + 1060;
|
||
draw_text(font(W_MED, 26), _("Informazioni"), c1, cy, C_DIM, a, AL_L);
|
||
draw_text_wrap(font(W_REG, 27), PR.about[0] ? PR.about : (strcmp(PR.relation, "self") ? _("Nessuna bio.") : _("Aggiungi una bio con \"Modifica bio\".")), c1, cy + 46, 440, 4, 36, PR.about[0] ? C_TXT : C_FAINT, a);
|
||
char line[160]; snprintf(line, sizeof line, PR.friends_count == 1 ? _("%d amico") : _("%d amici"), PR.friends_count);
|
||
draw_icon(IC_FRIENDS, c1 + 16, cy + 222, 28, C_DIM, a);
|
||
draw_text(font(W_REG, 25), line, c1 + 44, cy + 206, C_TXT, a, AL_L);
|
||
if (PR.mutual_friends && strcmp(PR.relation, "self")) {
|
||
char ref[64]; snprintf(ref, sizeof ref, _("%d amici"), 100); // larghezza di riferimento della riga sopra
|
||
snprintf(line, sizeof line, _("%d in comune"), PR.mutual_friends);
|
||
int lw = text_w(font(W_REG, 25), ref) + 70;
|
||
for (int i = 0; i < PR.nmutual && i < 3; i++) draw_avatar(PR.mutual[i], 0, c1 + lw + i * 26, cy + 222, 34, a);
|
||
draw_text(font(W_REG, 22), line, c1 + lw + 3 * 26 + 10, cy + 210, C_DIM, a, AL_L);
|
||
}
|
||
if (PR.total_seconds > 0) {
|
||
snprintf(line, sizeof line, _("%ld ore di gioco su Omega"), (PR.total_seconds + 1800) / 3600);
|
||
draw_icon(IC_CLOCK, c1 + 16, cy + 314, 28, C_DIM, a);
|
||
draw_text(font(W_REG, 25), line, c1 + 44, cy + 298, C_TXT, a, AL_L);
|
||
}
|
||
if (PR.loaded && !PR.blocked && !PR.trophies.hidden && PR.trophies.sets)
|
||
trophy_counts(c1 + 2, cy + 342, PR.trophies.p, PR.trophies.g, PR.trophies.s, PR.trophies.b, a);
|
||
if (PR.created[0]) {
|
||
int Y, M, D;
|
||
if (sscanf(PR.created, "%d-%d-%d", &Y, &M, &D) == 3 && M >= 1 && M <= 12) {
|
||
char dt[64]; date_short(dt, sizeof dt, D, M - 1, Y);
|
||
snprintf(line, sizeof line, _("Su Omega dal %s"), dt);
|
||
draw_icon(IC_STAR, c1 + 16, cy + 268, 28, C_DIM, a);
|
||
draw_text(font(W_REG, 25), line, c1 + 44, cy + 252, C_TXT, a, AL_L);
|
||
}
|
||
}
|
||
draw_text(font(W_MED, 26), _("Giocati di recente"), c2, cy, C_DIM, a, AL_L);
|
||
if (!PR.ngames) draw_text(font(W_REG, 24), strcmp(PR.relation, "friend") && strcmp(PR.relation, "self") ? _("Visibile solo agli amici") : _("Nessun gioco ancora"), c2, cy + 50, C_FAINT, a, AL_L);
|
||
for (int i = 0; i < PR.ngames && i < 5; i++) {
|
||
int yy = cy + 50 + i * 62;
|
||
AppEntry *ap = NULL; for (int k = 0; k < napps; k++) if (!strcmp(apps[k].tid, PR.games[i].game_id)) ap = &apps[k];
|
||
if (ap && ap->tex) draw_tex(ap->tex, c2, yy, 50, 50, a); else { fill_rrect(c2, yy, 50, 50, 10, RGB(40, 48, 70), a); draw_icon(IC_GAMEPAD, c2 + 25, yy + 25, 28, C_DIM, a); }
|
||
draw_text_fit(font(W_MED, 24), PR.games[i].game_name[0] ? PR.games[i].game_name : PR.games[i].game_id, c2 + 66, yy, 360, C_TXT, a, AL_L);
|
||
char tm[64]; rel_time(PR.games[i].last, tm, sizeof tm);
|
||
draw_text(font(W_REG, 20), tm, c2 + 66, yy + 28, C_FAINT, a, AL_L);
|
||
}
|
||
draw_text(font(W_MED, 26), _("Attività"), c3, cy, C_DIM, a, AL_L);
|
||
if (!PR.nrecent) draw_text(font(W_REG, 24), _("Nessuna attività"), c3, cy + 50, C_FAINT, a, AL_L);
|
||
for (int i = 0; i < PR.nrecent && i < 5; i++) {
|
||
int yy = cy + 50 + i * 62;
|
||
Activity *ac = &PR.recent[i];
|
||
char l[160];
|
||
if (!strcmp(ac->type, "game_start")) snprintf(l, sizeof l, _("Ha giocato a %s"), ac->game_name);
|
||
else if (!strcmp(ac->type, "online")) snprintf(l, sizeof l, "%s", _("È stato online"));
|
||
else snprintf(l, sizeof l, "%s", ac->detail);
|
||
fill_circle(c3 + 10, yy + 16, 5, C_ACC2, a);
|
||
draw_text_fit(font(W_REG, 24), l, c3 + 28, yy, 420, C_TXT, a, AL_L);
|
||
char tm[64]; rel_time(ac->when, tm, sizeof tm);
|
||
draw_text(font(W_REG, 20), tm, c3 + 28, yy + 28, C_FAINT, a, AL_L);
|
||
}
|
||
if (PR.loading) draw_spinner(x + w - 60, y + 60, 14, a);
|
||
}
|
||
|
||
static void remove_from_profile(int idx, void *ud) { (void)idx; (void)ud; social_friend_remove(PR.oid); }
|
||
static void block_yes(int idx, void *ud) { (void)idx; (void)ud; social_block(PR.oid, 1); }
|
||
static const char *USER_REASONS[] = { "spam", "molestie", "contenuto_offensivo", "impersonificazione", "altro" };
|
||
static void report_user_pick(int idx, void *ud) { (void)ud; if (idx >= 0 && idx < 5) social_report_user(PR.oid, USER_REASONS[idx]); }
|
||
static void more_pick(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx == 0) confirm_open(_("Bloccarlo? Non potrà più scriverti, invitarti né vedere i tuoi post, e smetterete di essere amici."), _("Blocca"), block_yes, NULL);
|
||
else if (idx == 1) { const char *r[] = { _("Spam"), _("Molestie"), _("Contenuto offensivo"), _("Si spaccia per un altro"), _("Altro") }; menu_open(_("Segnala utente"), r, 5, report_user_pick, NULL); }
|
||
}
|
||
|
||
void profile_input(int b) {
|
||
int ids[PR_MAX_ACTIONS], icons[PR_MAX_ACTIONS]; const char *lbl[PR_MAX_ACTIONS];
|
||
int n = pr_actions(ids, lbl, icons);
|
||
if (b == B_O) { ov_pop(); return; }
|
||
if (b == B_LEFT && pr_sel > 0) pr_sel--;
|
||
else if (b == B_RIGHT && pr_sel < n - 1) pr_sel++;
|
||
else if (b == B_X && n) {
|
||
switch (ids[pr_sel]) {
|
||
case PA_MSG: chat_open(PR.oid, PR.avatar); break;
|
||
case PA_PARTY: if (S.party.active) social_party_invite(PR.oid); else { social_party_create(); set_msg(_("Party creato: ora invita dal profilo"), 0); } break;
|
||
case PA_REMOVE: confirm_open(_("Vuoi davvero rimuovere questo amico?"), _("Rimuovi"), remove_from_profile, NULL); break;
|
||
case PA_ADD: social_friend_request(PR.oid); snprintf(PR.relation, sizeof PR.relation, "outgoing"); break;
|
||
case PA_ACCEPT: social_friend_accept(PR.oid); break;
|
||
case PA_DECLINE: social_friend_decline(PR.oid); break;
|
||
case PA_CANCEL: social_friend_remove(PR.oid); snprintf(PR.relation, sizeof PR.relation, "none"); break;
|
||
case PA_BIO: { char bio[168]; snprintf(bio, sizeof bio, "%s", PR.about); if (edit_text(_("La tua bio"), bio, sizeof bio, 0)) { snprintf(PR.about, sizeof PR.about, "%s", bio); social_profile_update(bio, 0); } break; }
|
||
case PA_AVATAR: ov_push(OV_AVATAR); break;
|
||
case PA_COVER: gallery_open(1); break;
|
||
case PA_STATUS: status_menu(); break;
|
||
case PA_UNBLOCK: social_block(PR.oid, 0); break;
|
||
case PA_TROPHIES: trophies_open(PR.oid); break;
|
||
case PA_MORE: { const char *m[] = { _("Blocca"), _("Segnala") }; menu_open(PR.oid, m, 2, more_pick, NULL); break; }
|
||
}
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------ scelta avatar --
|
||
// Quattro righe da 8 personaggi illustrati (0-31), una riga da 16 colori
|
||
// (32-47), poi il tasto per una foto o un video personale (48).
|
||
#define AV_CHARS AV_ART_COUNT
|
||
#define AV_COLORS 16
|
||
#define AV_CUSTOM (AV_CHARS + AV_COLORS)
|
||
static int av_sel = -1;
|
||
static int av_index(int sel) { return sel < AV_CHARS ? AV_ART_FIRST + sel : sel - AV_CHARS; }
|
||
void avatar_draw(float t) {
|
||
backdrop(t, 140);
|
||
if (av_sel < 0) { av_sel = AV_CUSTOM; for (int i = 0; i < AV_CUSTOM; i++) if (av_index(i) == S.my_avatar) av_sel = i; }
|
||
int w = 1320, h = 940, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 40);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 30, 40, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 30, C_PANEL, a);
|
||
draw_text(font(W_LIGHT, 44), _("Scegli il tuo avatar"), x + 60, y + 36, C_WHITE, a, AL_L);
|
||
draw_text(font(W_MED, 24), _("Personaggi"), x + 64, y + 104, C_DIM, a, AL_L);
|
||
draw_text(font(W_MED, 24), _("Colori"), x + 64, y + 640, C_DIM, a, AL_L);
|
||
float pulse = 0.5f + 0.5f * sinf((float)g_time * 4);
|
||
int has_custom = media_of(S.me, NULL) != NULL;
|
||
for (int i = 0; i < AV_CUSTOM; i++) {
|
||
int chr = i < AV_CHARS, foc = av_sel == i, idx = av_index(i);
|
||
int cx = chr ? x + 135 + (i % 8) * 150 : x + 90 + (i - AV_CHARS) * 76;
|
||
int cy = chr ? y + 196 + (i / 8) * 122 : y + 718;
|
||
int sz = chr ? (foc ? 112 : 98) : (foc ? 64 : 54);
|
||
if (foc) ring(cx, cy, sz / 2 + 8, 4, C_WHITE, (int)(a * (0.7f + 0.3f * pulse)));
|
||
// oid vuoto: draw_avatar altrimenti mostrerebbe la foto personalizzata
|
||
if (chr) { char none[2] = ""; draw_avatar(none, idx, cx, cy, sz, a); }
|
||
else draw_avatar_color(S.me, idx, cx, cy, sz, a);
|
||
if (idx == S.my_avatar && !has_custom) {
|
||
int o = chr ? 38 : 20, r = chr ? 16 : 11;
|
||
fill_circle(cx + o, cy + o, r, C_OK, a); draw_icon(IC_CHECK, cx + o, cy + o, r * 13 / 10, C_WHITE, a);
|
||
}
|
||
}
|
||
int by = y + h - 120, bw = 560, bx = x + w / 2 - bw / 2, foc = av_sel == AV_CUSTOM;
|
||
if (foc) shadow_rrect(bx, by, bw, 80, 40, 16, a / 2);
|
||
fill_rrect(bx, by, bw, 80, 40, foc ? C_WHITE : RGB(48, 54, 72), a);
|
||
draw_icon(IC_PLAY, bx + 60, by + 40, 30, foc ? RGB(12, 14, 22) : C_TXT, a);
|
||
draw_text(font(W_MED, 28), has_custom ? _("Cambia foto o video personale") : _("Usa una tua foto o un video"), bx + 100, by + 22, foc ? RGB(12, 14, 22) : C_TXT, a, AL_L);
|
||
}
|
||
void avatar_input(int b) {
|
||
if (b == B_O) { av_sel = -1; ov_pop(); return; }
|
||
if (av_sel == AV_CUSTOM) {
|
||
if (b == B_UP) av_sel = AV_CHARS + AV_COLORS / 2;
|
||
else if (b == B_X) { av_sel = -1; ov_pop(); gallery_open(0); }
|
||
return;
|
||
}
|
||
if (av_sel >= AV_CHARS) { // riga dei colori
|
||
int c = av_sel - AV_CHARS;
|
||
if (b == B_LEFT && c > 0) av_sel--;
|
||
else if (b == B_RIGHT && c < AV_COLORS - 1) av_sel++;
|
||
else if (b == B_UP) av_sel = AV_CHARS - 8 + c / 2;
|
||
else if (b == B_DOWN) av_sel = AV_CUSTOM;
|
||
} else { // personaggi, 8 per riga
|
||
if (b == B_LEFT && av_sel % 8 > 0) av_sel--;
|
||
else if (b == B_RIGHT && av_sel % 8 < 7) av_sel++;
|
||
else if (b == B_UP && av_sel >= 8) av_sel -= 8;
|
||
else if (b == B_DOWN) av_sel = av_sel + 8 < AV_CHARS ? av_sel + 8 : AV_CHARS + (av_sel % 8) * 2;
|
||
}
|
||
if (b == B_X) {
|
||
int idx = av_index(av_sel);
|
||
// scegliere un avatar Omega toglie la foto o il video personale
|
||
if (media_of(S.me, NULL)) { net_req(HTTP_POST, OMEGA_API "/media/clear?kind=avatar", "{}", NULL, NULL); media_note(S.me, "", 0); }
|
||
social_profile_update(NULL, idx); PR.avatar = idx; av_sel = -1; ov_pop();
|
||
}
|
||
}
|
||
|
||
// --------------------------------------------------------------------- chat --
|
||
void chat_draw(float t) {
|
||
int w = 980, x = side_panel(t, w), a = (int)(255 * t);
|
||
int px = x + 50, pw = w - 100;
|
||
if (CH.party) {
|
||
fill_circle(px + 40, 80, 40, C_ACC, a); draw_icon(IC_PARTY, px + 40, 80, 44, C_WHITE, a);
|
||
draw_text_fit(font(W_MED, 34), CH.oid, px + 100, 48, pw - 120, C_WHITE, a, AL_L);
|
||
char m[128]; snprintf(m, sizeof m, _("Chat del party \xC2\xB7 %d membri"), S.party.nmembers);
|
||
draw_text(font(W_REG, 23), m, px + 100, 92, C_DIM, a, AL_L);
|
||
} else {
|
||
draw_avatar(CH.oid, CH.avatar, px + 40, 80, 80, a);
|
||
const Friend *f = friend_find(CH.oid);
|
||
if (f && friend_online(f)) status_dot(px + 68, 108, f->status, f->game_id[0] != 0, a);
|
||
draw_text(font(W_MED, 34), CH.oid, px + 100, 48, C_WHITE, a, AL_L);
|
||
char st[160]; snprintf(st, sizeof st, "%s", _("Offline")); if (f) friend_status(f, st, sizeof st);
|
||
draw_text_fit(font(W_REG, 23), st, px + 100, 92, pw - 120, f && friend_online(f) ? C_OK : C_DIM, a, AL_L);
|
||
}
|
||
fill_rect(px, 150, pw, 1, RGB(70, 84, 120), a / 2);
|
||
// messaggi disegnati dal basso verso l'alto
|
||
int top = 170, bottom = SCREEN_H - 190;
|
||
CH.scroll = approach(CH.scroll, CH.scroll_t, 14.0f);
|
||
SDL_Rect clip = { x, top, w, bottom - top };
|
||
SDL_RenderSetClipRect(R, &clip);
|
||
TTF_Font *bf = font(W_REG, 26);
|
||
int y = bottom - 30 + (int)CH.scroll;
|
||
if (CH.loading && !CH.nmsg) draw_spinner(px + pw / 2, (top + bottom) / 2, 18, a);
|
||
if (!CH.loading && !CH.nmsg) {
|
||
draw_icon(IC_CHAT, px + pw / 2, (top + bottom) / 2 - 40, 80, C_FAINT, a);
|
||
draw_text(font(W_REG, 26), _("Nessun messaggio: scrivi il primo!"), px + pw / 2, (top + bottom) / 2 + 20, C_DIM, a, AL_C);
|
||
}
|
||
int maxbw = pw * 72 / 100;
|
||
for (int i = CH.nmsg - 1; i >= 0; i--) {
|
||
Msg *m = &CH.msg[i];
|
||
if (m->system) {
|
||
y -= 54;
|
||
if (y < bottom && y > top - 60) draw_text_fit(font(W_REG, 21), m->body + 3, px + pw / 2, y + 12, pw - 40, C_FAINT, a, AL_C);
|
||
continue;
|
||
}
|
||
int tw = text_w(bf, m->body);
|
||
int lines = tw / (maxbw - 48) + 1; if (lines > 8) lines = 8;
|
||
int bw = lines > 1 ? maxbw : tw + 48; if (bw < 90) bw = 90;
|
||
int show_name = CH.party && !m->mine && (i == 0 || strcmp(CH.msg[i - 1].sender, m->sender) || CH.msg[i - 1].system);
|
||
int bh = lines * 34 + 30 + (show_name ? 30 : 0);
|
||
y -= bh + 14;
|
||
if (y > bottom || y + bh < top - 40) continue;
|
||
int bx = m->mine ? px + pw - bw : px + (CH.party ? 60 : 0);
|
||
if (CH.party && !m->mine && show_name) draw_avatar(m->sender, m->avatar, px + 24, y + 26, 44, a);
|
||
fill_rrect(bx, y, bw, bh, 24, m->mine ? C_ACC : RGB(42, 48, 66), a);
|
||
int ty = y + 14;
|
||
if (show_name) { draw_text(font(W_MED, 21), m->sender, bx + 24, ty, avatar_col(m->avatar), a, AL_L); ty += 30; }
|
||
draw_text_wrap(bf, m->body, bx + 24, ty, bw - 48, 8, 34, C_WHITE, !strcmp(m->id, "pending") ? a * 70 / 100 : a);
|
||
char tm[64]; rel_time(m->when, tm, sizeof tm);
|
||
if (i == CH.nmsg - 1 && tm[0]) draw_text(font(W_REG, 18), tm, m->mine ? bx + bw : bx, y + bh + 4, C_FAINT, a, m->mine ? AL_R : AL_L);
|
||
}
|
||
int content_top = y;
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
float maxs = (float)(top - content_top); if (maxs < 0) maxs = 0;
|
||
if (CH.scroll_t > maxs + 20) CH.scroll_t = maxs + 20;
|
||
int iy = SCREEN_H - 160;
|
||
fill_rrect(px, iy, pw, 84, 42, RGB(36, 42, 60), a);
|
||
draw_icon(IC_BTN_X, px + 44, iy + 42, 26, C_TXT, a);
|
||
draw_text(font(W_REG, 26), _("Scrivi un messaggio..."), px + 80, iy + 26, C_DIM, a, AL_L);
|
||
fill_circle(px + pw - 42, iy + 42, 30, C_ACC, a);
|
||
draw_icon(IC_SEND, px + pw - 40, iy + 42, 30, C_WHITE, a);
|
||
const int ic[] = { IC_BTN_X, IC_BTN_O };
|
||
const char *lb[] = { _("Scrivi"), _("Indietro") };
|
||
hints(ic, lb, 2, a);
|
||
}
|
||
|
||
void chat_input(int b) {
|
||
if (b == B_O) { ov_pop(); social_load_conversations(); return; }
|
||
if (b == B_UP) CH.scroll_t += 120;
|
||
else if (b == B_DOWN) { CH.scroll_t -= 120; if (CH.scroll_t < 0) CH.scroll_t = 0; }
|
||
else if (b == B_X) {
|
||
char txt[500] = "";
|
||
if (edit_text(CH.party ? _("Messaggio al party") : _("Messaggio"), txt, sizeof txt, 0) && txt[0]) chat_send(txt);
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------------ ricerca --
|
||
static char sr_q[64]; static int sr_sel; static float sr_anim, sr_scroll;
|
||
void search_open(void) {
|
||
sr_q[0] = 0; sr_sel = 0; S.nusers = 0;
|
||
social_search("");
|
||
ov_push(OV_SEARCH);
|
||
}
|
||
|
||
static const char *rel_label(const char *r) {
|
||
if (!strcmp(r, "friend")) return _("Amico");
|
||
if (!strcmp(r, "outgoing")) return _("Richiesta inviata");
|
||
if (!strcmp(r, "incoming")) return _("Ti ha chiesto l'amicizia");
|
||
return "";
|
||
}
|
||
|
||
void search_draw(float t) {
|
||
backdrop(t, 150);
|
||
int w = 1100, h = 900, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 50);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 34, 50, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 34, C_PANEL, a);
|
||
draw_text(font(W_LIGHT, 46), _("Aggiungi amici"), x + 60, y + 44, C_WHITE, a, AL_L);
|
||
int fy = y + 130, focf = sr_sel == 0;
|
||
fill_rrect(x + 60, fy, w - 120, 90, 45, focf ? RGB(52, 60, 84) : RGB(36, 42, 60), a);
|
||
if (focf) stroke_rrect(x + 56, fy - 4, w - 112, 98, 49, 3, C_WHITE, a);
|
||
draw_icon(IC_SEARCH, x + 112, fy + 45, 36, C_TXT, a);
|
||
draw_text(font(sr_q[0] ? W_REG : W_LIGHT, 30), sr_q[0] ? sr_q : _("Cerca per ID online"), x + 150, fy + 26, sr_q[0] ? C_TXT : C_DIM, a, AL_L);
|
||
if (S.users_loading) draw_spinner(x + w - 110, fy + 45, 14, a);
|
||
draw_text(font(W_MED, 24), sr_q[0] ? _("Risultati") : _("Suggeriti"), x + 64, fy + 120, C_DIM, a, AL_L);
|
||
int ly = fy + 170, rh = 104, lh = y + h - 40 - ly;
|
||
sr_anim = approach(sr_anim, (float)(sr_sel - 1), 20.0f);
|
||
float off = list_scroll(&sr_scroll, sr_sel > 0 ? sr_sel - 1 : 0, rh, lh);
|
||
SDL_Rect clip = { x, ly - 8, w, lh + 8 };
|
||
SDL_RenderSetClipRect(R, &clip);
|
||
if (!S.users_loading && !S.nusers) draw_text(font(W_REG, 26), sr_q[0] ? _("Nessun utente trovato") : _("Nessun suggerimento"), x + w / 2, ly + 60, C_FAINT, a, AL_C);
|
||
for (int i = 0; i < S.nusers; i++) {
|
||
int ry = ly + i * rh - (int)off;
|
||
if (ry + rh < ly - 20 || ry > ly + lh) continue;
|
||
UserRef *u = &S.users[i];
|
||
float fa = clampf(1 - fabsf(sr_anim - i), 0, 1);
|
||
row_bg(x + 60, ry, w - 120, rh - 12, fa, a);
|
||
draw_avatar(u->oid, u->avatar, x + 116, ry + (rh - 12) / 2, 66, a);
|
||
draw_text(font(W_MED, 28), u->oid, x + 170, ry + 18, C_TXT, a, AL_L);
|
||
const char *rl = rel_label(u->relation);
|
||
if (rl[0]) draw_text(font(W_REG, 22), rl, x + 170, ry + 54, !strcmp(u->relation, "friend") ? C_OK : C_DIM, a, AL_L);
|
||
const char *act = !strcmp(u->relation, "none") ? _("Aggiungi") : !strcmp(u->relation, "incoming") ? _("Accetta") : !strcmp(u->relation, "friend") ? _("Messaggio") : _("Annulla");
|
||
int ic = !strcmp(u->relation, "none") ? IC_ADDUSER : !strcmp(u->relation, "incoming") ? IC_CHECK : !strcmp(u->relation, "friend") ? IC_CHAT : IC_CLOSE;
|
||
int pw2 = text_w(font(W_MED, 25), act) + 90;
|
||
pill(x + w - 80 - pw2, ry + 18, 56, act, ic, sr_sel == i + 1, sr_sel == i + 1 ? 1.0f : 0.0f, a);
|
||
}
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
const int ic[] = { IC_BTN_X, IC_BTN_TRI, IC_BTN_O };
|
||
const char *lb[] = { _("Seleziona"), _("Profilo"), _("Indietro") };
|
||
hints(ic, lb, 3, a);
|
||
}
|
||
|
||
void search_input(int b) {
|
||
if (b == B_O) { ov_pop(); return; }
|
||
if (b == B_UP && sr_sel > 0) sr_sel--;
|
||
else if (b == B_DOWN && sr_sel < S.nusers) sr_sel++;
|
||
else if (b == B_TRI && sr_sel > 0) profile_open(S.users[sr_sel - 1].oid);
|
||
else if (b == B_X) {
|
||
if (sr_sel == 0) {
|
||
char q[64]; snprintf(q, sizeof q, "%s", sr_q);
|
||
if (edit_text(_("Cerca ID online"), q, sizeof q, 0)) { snprintf(sr_q, sizeof sr_q, "%s", q); S.nusers = 0; social_search(sr_q); sr_sel = 0; }
|
||
} else {
|
||
UserRef *u = &S.users[sr_sel - 1];
|
||
if (!strcmp(u->relation, "none")) { social_friend_request(u->oid); snprintf(u->relation, sizeof u->relation, "outgoing"); }
|
||
else if (!strcmp(u->relation, "incoming")) { social_friend_accept(u->oid); snprintf(u->relation, sizeof u->relation, "friend"); }
|
||
else if (!strcmp(u->relation, "friend")) chat_open(u->oid, u->avatar);
|
||
else if (!strcmp(u->relation, "outgoing")) { social_friend_remove(u->oid); snprintf(u->relation, sizeof u->relation, "none"); }
|
||
}
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------- impostazioni --
|
||
static int st_sel; static float st_anim;
|
||
static void st_logout_yes(int idx, void *ud) { (void)idx; (void)ud; do_logout(); }
|
||
static void st_quit_yes(int idx, void *ud) { (void)idx; (void)ud; app_quit(); }
|
||
static void audio_pick(int idx, void *ud);
|
||
static void open_audio_menu(void) {
|
||
static char l0[96], l1[96], l2[96];
|
||
static const char *items[3] = { l0, l1, l2 };
|
||
snprintf(l0, sizeof l0, "%s", audio_music_on() ? _("Musica di sottofondo: attiva") : _("Musica di sottofondo: spenta"));
|
||
snprintf(l1, sizeof l1, "%s", audio_sfx_on() ? _("Effetti sonori: attivi") : _("Effetti sonori: spenti"));
|
||
snprintf(l2, sizeof l2, _("Volume musica: %d%%"), audio_music_level());
|
||
menu_open(_("Audio"), items, 3, audio_pick, NULL);
|
||
}
|
||
static void audio_pick(int idx, void *ud) {
|
||
(void)ud;
|
||
if (idx == 0) audio_set(!audio_music_on(), -1, -1);
|
||
else if (idx == 1) audio_set(-1, !audio_sfx_on(), -1);
|
||
else { int l = audio_music_level(); l = l >= 80 ? 30 : l >= 50 ? 80 : 55; audio_set(-1, -1, l); }
|
||
open_audio_menu(); // il menu resta aperto con i valori aggiornati
|
||
}
|
||
static void theme_pick(int idx, void *ud) { (void)ud; theme_apply(idx, 1); char m[96]; snprintf(m, sizeof m, _("Tema: %s"), theme_name(idx)); set_msg(m, 0); }
|
||
|
||
// Lingua: "Automatica" segue la console; la scelta vale subito e si salva.
|
||
static void lang_pick(int idx, void *ud) {
|
||
(void)ud;
|
||
i18n_set(idx <= 0 ? "auto" : i18n_lang_code(idx - 1));
|
||
social_sync_now(); // i testi che arrivano dal server tornano nella lingua nuova
|
||
}
|
||
static void open_lang_menu(void) {
|
||
static char autol[96];
|
||
static const char *items[32];
|
||
int n = i18n_count(), sel = 0;
|
||
snprintf(autol, sizeof autol, "%s", _("Automatica (console)"));
|
||
items[0] = autol;
|
||
for (int i = 0; i < n && i < 31; i++) {
|
||
items[i + 1] = i18n_lang_name(i18n_lang_code(i));
|
||
if (!strcmp(i18n_pref(), i18n_lang_code(i))) sel = i + 1;
|
||
}
|
||
menu_open(_("Lingua"), items, n + 1, lang_pick, NULL);
|
||
menu_select(sel);
|
||
}
|
||
|
||
// Impostazioni in sezioni: a sinistra Account, Aspetto, Home e giochi, Sistema,
|
||
// Aiuto; a destra le voci della sezione con il valore attuale. Prima era un
|
||
// unico elenco di 15 righe: ora ogni cosa sta dove la si cerca.
|
||
enum {
|
||
SO_PROFILE, SO_AVATAR, SO_BIO, SO_NOTIFY, SO_PRIVACY, SO_LOGOUT,
|
||
SO_CUSTOM, SO_STYLE, SO_THEME, SO_LANG, SO_AUDIO,
|
||
SO_HOME, SO_HIDDEN, SO_ORDER, SO_PATHS, SO_STORAGE, SO_SAVES, SO_PKGS, SO_GAMEUPD, SO_AUTOMOUNT,
|
||
SO_SYSTEM, SO_REMOTE, SO_SERVER, SO_FILES,
|
||
SO_WHY, SO_FEEDBACK, SO_ABOUT, SO_QUIT, N_OPT
|
||
};
|
||
static const struct { const char *name; int ic; int first, n; } ST_SECT[] = {
|
||
{ N_("Account"), IC_USER, SO_PROFILE, 6 }, { N_("Aspetto"), IC_BRUSH, SO_CUSTOM, 5 }, { N_("Home e giochi"), IC_HOME, SO_HOME, 9 },
|
||
{ N_("Sistema"), IC_GEAR, SO_SYSTEM, 4 }, { N_("Aiuto"), IC_IDEA, SO_WHY, 4 },
|
||
};
|
||
#define ST_NSECT (int)(sizeof ST_SECT / sizeof *ST_SECT)
|
||
static int st_sect, st_col; static float st_sect_anim; // st_col: 0 sezioni, 1 voci
|
||
static void order_reset_yes(int idx, void *ud) { (void)ud; if (idx == 0) { layout_order_reset(); set_msg(_("Ordine automatico ripristinato"), 0); } }
|
||
static const char *st_label(int o) {
|
||
static const char *L[N_OPT] = { N_("Il mio profilo"), N_("Cambia avatar"), N_("Modifica bio"), N_("Notifiche"), N_("Privacy"), N_("Esci dall'account"),
|
||
N_("Personalizza"), N_("Modalità del menu"), N_("Tema"), N_("Lingua"), N_("Audio"),
|
||
N_("Omega come Home"), N_("App nascoste"), N_("Ordine della home"), N_("Cartelle di giochi e PKG"), N_("Archivio e spostamenti"), N_("Salvataggi online"), N_("Installa PKG"), N_("Aggiornamenti dei giochi"), N_("Montaggio automatico"),
|
||
N_("Sistema e strumenti"), N_("App mobile"), N_("Server"), N_("Gestore dei file"),
|
||
N_("Perché Omega"), N_("Segnala un bug o chiedi una funzione"), N_("Informazioni su Omega"), N_("Chiudi Omega") };
|
||
return _(L[o]);
|
||
}
|
||
static int st_icon(int o) {
|
||
static const int I[N_OPT] = { IC_USER, IC_STAR, IC_NEWS, IC_BELL, IC_SHIELD, IC_EXIT, IC_BRUSH, IC_GRID, IC_GEAR, IC_CHAT, IC_VOLUME,
|
||
IC_HOME, IC_CLOSE, IC_GRID, IC_FOLDER, IC_DRIVE, IC_CLOUD, IC_BOX, IC_DOWNLOAD, IC_USB, IC_FOLDER, IC_GLOBE, IC_CLOUD, IC_FOLDER, IC_HEART, IC_BUG, IC_MORE, IC_POWER };
|
||
return I[o];
|
||
}
|
||
static void st_value(int o, char *v, size_t n) {
|
||
v[0] = 0;
|
||
switch (o) {
|
||
case SO_AUDIO: snprintf(v, n, _("Musica %s \xC2\xB7 Effetti %s"), audio_music_on() ? _("s\xC3\xAC") : _("no"), audio_sfx_on() ? _("s\xC3\xAC") : _("no")); break;
|
||
case SO_THEME: snprintf(v, n, "%s", theme_name(g_theme)); break;
|
||
case SO_LANG: snprintf(v, n, "%s", i18n_lang_name(i18n_code())); break;
|
||
case SO_SERVER: snprintf(v, n, "%s", server_label()); break;
|
||
case SO_HOME: snprintf(v, n, "%s", home_mode() == 1 ? _("s\xC3\xAC") : _("no")); break;
|
||
case SO_HIDDEN: snprintf(v, n, "%d", layout_hidden_count()); break;
|
||
case SO_ORDER: snprintf(v, n, "%s", layout_order_custom() ? _("Scelto da te") : _("Automatico")); break;
|
||
case SO_STYLE: snprintf(v, n, "%s", hs_name(g_prefs.home_style)); break;
|
||
case SO_AUTOMOUNT: snprintf(v, n, "%s", g_prefs.automount ? _("s\xC3\xAC") : _("no")); break;
|
||
case SO_SAVES: snprintf(v, n, "%s", g_prefs.save_auto ? _("automatici") : _("manuali")); break;
|
||
case SO_PATHS: { char g[32][300], k[32][300]; snprintf(v, n, _("%d giochi \xC2\xB7 %d pkg"), paths_list(0, g, 32), paths_list(1, k, 32)); break; }
|
||
}
|
||
}
|
||
|
||
void settings_draw(float t) {
|
||
int w = 1240, x = side_panel(t, w), a = (int)(255 * t);
|
||
int px = x + 50, pw = w - 100;
|
||
draw_icon(IC_GEAR, px + 22, 74, 44, C_TXT, a);
|
||
draw_text(font(W_LIGHT, 44), _("Impostazioni"), px + 60, 46, C_WHITE, a, AL_L);
|
||
int y = 132;
|
||
fill_rrect(px, y, pw, 112, 24, RGB(255, 255, 255), a * 6 / 100);
|
||
draw_avatar(S.me, S.my_avatar, px + 66, y + 56, 80, a);
|
||
draw_text_fit(font(W_MED, 32), S.me, px + 130, y + 18, pw - 170, C_TXT, a, AL_L);
|
||
char info[160];
|
||
snprintf(info, sizeof info, _("Amici: %d \xC2\xB7 online: %d \xC2\xB7 giochi: %d"), S.nfriends, friends_online_count(), napps);
|
||
draw_text_fit(font(W_REG, 22), info, px + 130, y + 64, pw - 170, C_DIM, a, AL_L);
|
||
y += 136;
|
||
// sezioni
|
||
int sw = 330;
|
||
st_sect_anim = approach(st_sect_anim, (float)st_sect, 20.0f);
|
||
for (int i = 0; i < ST_NSECT; i++) {
|
||
int ry = y + i * 74, on = i == st_sect;
|
||
float fa = clampf(1 - fabsf(st_sect_anim - i), 0, 1);
|
||
Col bg = on ? (st_col == 0 ? C_WHITE : RGB(52, 60, 82)) : RGB(255, 255, 255);
|
||
fill_rrect(px, ry, sw, 62, 20, bg, on ? a : (int)(a * (4 + 6 * fa) / 100));
|
||
Col fg = on && st_col == 0 ? RGB(12, 14, 22) : C_TXT;
|
||
draw_icon(ST_SECT[i].ic, px + 40, ry + 31, 26, fg, a);
|
||
draw_text_fit(font(on ? W_MED : W_REG, 27), _(ST_SECT[i].name), px + 76, ry + 14, sw - 96, fg, a, AL_L);
|
||
}
|
||
// voci della sezione
|
||
int ix = px + sw + 30, iw = pw - sw - 30, rs = 78, rh = 66;
|
||
st_anim = approach(st_anim, (float)st_sel, 20.0f);
|
||
const int f0 = ST_SECT[st_sect].first, nn = ST_SECT[st_sect].n;
|
||
for (int k = 0; k < nn; k++) {
|
||
int o = f0 + k, ry = y + k * rs;
|
||
float fa = st_col ? clampf(1 - fabsf(st_anim - k), 0, 1) : 0;
|
||
row_bg(ix, ry, iw, rh, fa, a);
|
||
int on = st_col && st_sel == k;
|
||
fill_circle(ix + 48, ry + rh / 2, 24, on ? C_WHITE : RGB(44, 52, 72), a);
|
||
draw_icon(st_icon(o), ix + 48, ry + rh / 2, 25, on ? RGB(12, 14, 22) : C_TXT, a);
|
||
char v[128]; st_value(o, v, sizeof v);
|
||
int vw = v[0] ? draw_text_fit(font(W_REG, 23), v, ix + iw - 30, ry + (rh - TTF_FontHeight(font(W_REG, 23))) / 2, 340, C_DIM, a, AL_R) : 0;
|
||
TTF_Font *fo = font(W_MED, 28);
|
||
draw_text_fit(fo, st_label(o), ix + 92, ry + (rh - TTF_FontHeight(fo)) / 2, iw - 92 - 30 - (vw ? vw + 24 : 0), o == SO_QUIT || o == SO_LOGOUT ? C_ERR : C_TXT, a, AL_L);
|
||
}
|
||
const int ic[] = { IC_BTN_X, IC_BTN_O };
|
||
const char *lb[] = { _("Seleziona"), _("Indietro") };
|
||
hints(ic, lb, 2, a);
|
||
}
|
||
|
||
// ------------------------------------------------- bug e richieste --
|
||
static int fb_kind;
|
||
static void fb_done(int st, JVal *j, const char *raw, void *ud) {
|
||
(void)j; (void)raw; (void)ud;
|
||
set_msg(st == 201 ? _("Grazie! Il messaggio è arrivato allo sviluppatore") : st == 429 ? _("Troppi messaggi: riprova più tardi") : _("Invio non riuscito: controlla la rete"), st != 201);
|
||
}
|
||
static void fb_send(int with_logs) {
|
||
char text[900] = "";
|
||
if (!edit_text(fb_kind ? _("Descrivi la funzione che vorresti") : _("Descrivi il problema: cosa facevi e cosa è successo"), text, sizeof text, 0) || strlen(text) < 4) return;
|
||
size_t cap = with_logs ? 200 * 1024 : 4096, o = 0; char *b = malloc(cap); if (!b) return;
|
||
char esc[1900]; json_escape(esc, sizeof esc, text);
|
||
o += (size_t)snprintf(b, cap, "{\"kind\":\"%s\",\"text\":\"%s\",\"app_version\":\"%s\",\"lang\":\"%s\"", fb_kind ? "idea" : "bug", esc, OMEGA_VERSION, i18n_code());
|
||
if (with_logs) {
|
||
// le ultime righe del registro aiutano a capire il problema (niente password)
|
||
size_t len = 0; char *lg = file_read(OMEGA_LOG, 2 * 1024 * 1024, &len);
|
||
if (lg) {
|
||
const char *from = len > 60000 ? lg + len - 60000 : lg;
|
||
o += (size_t)snprintf(b + o, cap - o, ",\"log\":\"");
|
||
for (const unsigned char *c = (const unsigned char *)from; *c && o + 8 < cap - 4; c++) {
|
||
if (*c == '"' || *c == '\\') { b[o++] = '\\'; b[o++] = (char)*c; }
|
||
else if (*c == '\n') { b[o++] = '\\'; b[o++] = 'n'; }
|
||
else if (*c >= 0x20) b[o++] = (char)*c;
|
||
}
|
||
b[o++] = '"';
|
||
free(lg);
|
||
}
|
||
}
|
||
if (o + 2 < cap) { b[o++] = '}'; b[o] = 0; }
|
||
net_req(HTTP_POST, OMEGA_API "/feedback", b, fb_done, NULL);
|
||
free(b);
|
||
set_msg(_("Invio..."), 0);
|
||
}
|
||
static void fb_logs(int idx, void *ud) { (void)ud; if (idx >= 0) fb_send(idx == 0); }
|
||
static void fb_pick(int idx, void *ud) {
|
||
(void)ud; if (idx < 0) return;
|
||
fb_kind = idx;
|
||
if (idx == 0) { static const char *it[2]; it[0] = _("Sì, allega il registro (consigliato)"); it[1] = _("No, solo il testo"); menu_open(_("Allegare il registro di Omega? Aiuta a trovare il problema"), it, 2, fb_logs, NULL); }
|
||
else fb_send(0);
|
||
}
|
||
void feedback_open(void) {
|
||
static const char *it[2]; it[0] = _("Segnala un bug"); it[1] = _("Chiedi una funzione nuova");
|
||
menu_open(_("Scrivi allo sviluppatore"), it, 2, fb_pick, NULL);
|
||
}
|
||
|
||
static void style_pick(int idx, void *ud) {
|
||
(void)ud; if (idx < 0 || idx > 5) return;
|
||
int n = 0; const PrefDef *t = prefs_table(&n);
|
||
for (int i = 0; i < n; i++) if (!strcmp(t[i].key, "home_style")) pref_set(&t[i], idx);
|
||
}
|
||
static void automount_toggle(void) {
|
||
int n = 0; const PrefDef *t = prefs_table(&n);
|
||
for (int i = 0; i < n; i++) if (!strcmp(t[i].key, "automount")) pref_set(&t[i], !g_prefs.automount);
|
||
set_msg(g_prefs.automount ? _("Montaggio automatico attivo: i giochi dei dischi si registrano da soli") : _("Montaggio automatico spento"), 0);
|
||
}
|
||
|
||
void settings_input(int b) {
|
||
int nn = ST_SECT[st_sect].n;
|
||
if (st_col == 0) {
|
||
if (b == B_O) { ov_pop(); return; }
|
||
if (b == B_UP && st_sect > 0) { st_sect--; st_sel = 0; sfx_play(SFX_MOVE); }
|
||
else if (b == B_DOWN && st_sect < ST_NSECT - 1) { st_sect++; st_sel = 0; sfx_play(SFX_MOVE); }
|
||
else if (b == B_RIGHT || b == B_X) { st_col = 1; st_sel = 0; sfx_play(SFX_SELECT); }
|
||
return;
|
||
}
|
||
if (b == B_O || b == B_LEFT) { st_col = 0; return; }
|
||
if (b == B_UP && st_sel > 0) { st_sel--; sfx_play(SFX_MOVE); }
|
||
else if (b == B_DOWN && st_sel < nn - 1) { st_sel++; sfx_play(SFX_MOVE); }
|
||
else if (b == B_X) {
|
||
switch (ST_SECT[st_sect].first + st_sel) {
|
||
case SO_PROFILE: profile_open(S.me); break;
|
||
case SO_CUSTOM: custom_open(); break;
|
||
case SO_AVATAR: ov_push(OV_AVATAR); break;
|
||
case SO_BIO: { char bio[168]; snprintf(bio, sizeof bio, "%s", S.my_about); if (edit_text(_("La tua bio"), bio, sizeof bio, 0)) social_profile_update(bio, 0); break; }
|
||
case SO_THEME: {
|
||
static const char *names[N_THEMES];
|
||
for (int i = 0; i < N_THEMES; i++) names[i] = theme_name(i);
|
||
menu_open(_("Tema"), names, N_THEMES, theme_pick, NULL);
|
||
break;
|
||
}
|
||
case SO_STYLE: { static const char *it[6]; for (int i = 0; i < 6; i++) it[i] = hs_name(i); menu_open(_("Modalità del menu"), it, 6, style_pick, NULL); menu_select(g_prefs.home_style); break; }
|
||
case SO_LANG: open_lang_menu(); break;
|
||
case SO_AUDIO: open_audio_menu(); break;
|
||
case SO_SERVER: server_menu(); break;
|
||
case SO_HOME: home_mode_menu(); break;
|
||
case SO_HIDDEN: hidden_menu(); break;
|
||
case SO_ORDER:
|
||
if (layout_order_custom()) confirm_open(_("Tornare all'ordine automatico? Le tessere si rimettono da sole, con i giochi usati di recente davanti."), _("Ripristina"), order_reset_yes, NULL);
|
||
else set_msg(_("Per scegliere l'ordine: opzioni di una tessera in home \xE2\x80\xBA Cambia posizione"), 0);
|
||
break;
|
||
case SO_PATHS: paths_menu(); break;
|
||
case SO_PKGS: pkgs_open(); break;
|
||
case SO_GAMEUPD: gameupd_open(); break;
|
||
case SO_STORAGE: storage_open(); break;
|
||
case SO_SAVES: saves_open(NULL); break;
|
||
case SO_AUTOMOUNT: automount_toggle(); break;
|
||
case SO_SYSTEM: system_open(); break;
|
||
case SO_REMOTE: mobile_open(); break;
|
||
case SO_FILES: files_open(NULL); break;
|
||
case SO_PRIVACY: privacy_menu(); break;
|
||
case SO_NOTIFY: notifprefs_open(); break;
|
||
case SO_WHY: ov_push(OV_WHY); break;
|
||
case SO_FEEDBACK: feedback_open(); break;
|
||
case SO_ABOUT: ov_push(OV_ABOUT); break;
|
||
case SO_LOGOUT: confirm_open(_("Vuoi uscire dal tuo account Omega?"), _("Esci"), st_logout_yes, NULL); break;
|
||
case SO_QUIT: confirm_open(_("Chiudere Omega e tornare alla home di sistema?"), _("Chiudi"), st_quit_yes, NULL); break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------- perché Omega --
|
||
// I motivi per usare la dash, detti in breve: si apre da Impostazioni › Aiuto
|
||
// e dalla finestra delle novità.
|
||
static float why_scroll; static int why_sel;
|
||
static const struct { int ic; const char *t, *d; } WHY[] = {
|
||
{ IC_GAMEPAD, N_("Tutti i tuoi giochi in un posto"), N_("Giochi installati, homebrew, payload e giochi sui dischi esterni nella stessa fila, con cartelle, ordine e app nascoste.") },
|
||
{ IC_USB, N_("Dischi e chiavette senza pensieri"), N_("Colleghi un disco e i suoi giochi compaiono da soli, anche nella Home della console: il montaggio automatico fa il lavoro di ShadowMount.") },
|
||
{ IC_BOX, N_("Installi i PKG con un tasto"), N_("Da chiavette, dischi, cartelle a scelta o dal PC via Wi-Fi, con l'icona del gioco e la barra che avanza come sulla PS4, in coda uno alla volta.") },
|
||
{ IC_FRIENDS, N_("Amici, party e chat"), N_("Vedi chi gioca e a cosa, parli in party con la voce, scrivi messaggi e inviti gli amici: un social pensato per la console con jailbreak.") },
|
||
{ IC_STORE, N_("Uno Store della community"), N_("Homebrew pubblicati dagli utenti con voti, stelle, commenti, novità degli amici e liste dei desideri; e La mia libreria per i tuoi backup.") },
|
||
{ IC_TROPHY, N_("Trofei e record"), N_("I trofei della console nel profilo e in classifica, il tempo di gioco della settimana e le maratone di tutti gli iscritti.") },
|
||
{ IC_CHAT, N_("Omega anche sul telefono"), N_("Con l'App mobile chatti, entri nei party, vedi amici, classifiche e Store anche a console spenta, e carichi i giochi dal PC.") },
|
||
{ IC_MUSIC, N_("Musica e radio"), N_("Radio, file e server personali, con il lettore che continua anche fuori dalla home e i comandi nel menu in gioco.") },
|
||
{ IC_GRID, N_("Fatta a modo tuo"), N_("Sei modalità del menu (Omega, PS4, XMB, Griglia, Carosello, Cinema), temi, sfondi, suoni e più di trenta opzioni.") },
|
||
{ IC_HEART, N_("Gratis, aperta e nella tua lingua"), N_("Sorgente GPL scaricabile, 27 lingue, nessun account Sony, aggiornamenti automatici e un tasto per segnalare bug o chiedere funzioni.") },
|
||
};
|
||
#define NWHY (int)(sizeof WHY / sizeof *WHY)
|
||
void why_draw(float t) {
|
||
backdrop(t, 185);
|
||
int w = 1500, h = 900, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 40), a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 34, 40, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 34, RGB(16, 20, 34), a);
|
||
glow(x + 160, y + 120, 320, C_ACC, a * 20 / 100);
|
||
draw_logo(x + 110, y + 104, 96, a);
|
||
draw_text(font(W_LIGHT, 50), _("Perché Omega"), x + 190, y + 58, C_WHITE, a, AL_L);
|
||
draw_text(font(W_REG, 24), _("Dieci buoni motivi per usarla come Home"), x + 192, y + 124, C_DIM, a, AL_L);
|
||
int cw = (w - 150) / 2, ch = 150, top = y + 196, vis = h - 280;
|
||
int row = why_sel / 2;
|
||
why_scroll = approach(why_scroll, (float)(row > 1 ? (row - 1) * (ch + 18) : 0), 12.0f);
|
||
SDL_Rect clip = { x, top - 6, w, vis + 12 }; SDL_RenderSetClipRect(R, &clip);
|
||
for (int i = 0; i < NWHY; i++) {
|
||
int cx = x + 50 + (i % 2) * (cw + 50), cy = top + (i / 2) * (ch + 18) - (int)why_scroll;
|
||
if (cy + ch < top - 6 || cy > top + vis) continue;
|
||
int on = i == why_sel;
|
||
fill_rrect(cx, cy, cw, ch, 22, C_WHITE, a * (on ? 12 : 5) / 100);
|
||
if (on) stroke_rrect(cx - 3, cy - 3, cw + 6, ch + 6, 25, 3, C_WHITE, a);
|
||
fill_circle(cx + 56, cy + 56, 34, mix(C_ACC, C_WHITE, 0.1f), a);
|
||
draw_icon(WHY[i].ic, cx + 56, cy + 56, 32, C_WHITE, a);
|
||
draw_text_fit(font(W_MED, 28), _(WHY[i].t), cx + 110, cy + 20, cw - 130, C_WHITE, a, AL_L);
|
||
draw_text_wrap(font(W_REG, 21), _(WHY[i].d), cx + 110, cy + 60, cw - 130, 3, 26, C_DIM, a);
|
||
}
|
||
SDL_RenderSetClipRect(R, NULL);
|
||
const int ic[] = { IC_BTN_O };
|
||
const char *lb[] = { _("Chiudi") };
|
||
hints(ic, lb, 1, a);
|
||
}
|
||
void why_input(int b) {
|
||
if (b == B_O || b == B_X) { ov_pop(); return; }
|
||
if (b == B_LEFT && why_sel % 2) why_sel--;
|
||
else if (b == B_RIGHT && why_sel % 2 == 0 && why_sel + 1 < NWHY) why_sel++;
|
||
else if (b == B_UP && why_sel >= 2) why_sel -= 2;
|
||
else if (b == B_DOWN && why_sel + 2 < NWHY) why_sel += 2;
|
||
}
|
||
|
||
// --------------------------------------------------------- informazioni --
|
||
// Crediti: chi sviluppa Omega, licenza, sorgente e dichiarazione di non affiliazione.
|
||
void about_draw(float t) {
|
||
backdrop(t, 160);
|
||
int w = 1040, h = 740, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 50);
|
||
int a = (int)(255 * t), cx = SCREEN_W / 2;
|
||
shadow_rrect(x, y, w, h, 34, 50, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 34, C_PANEL, a);
|
||
stroke_rrect(x, y, w, h, 34, 1, RGB(80, 92, 125), a / 2);
|
||
glow(cx, y + 150, 190, RGB(40, 120, 255), a * 45 / 100);
|
||
draw_logo(cx, y + 150, 170, a);
|
||
draw_text(font(W_LIGHT, 30), "O M E G A", cx, y + 252, C_TXT, a, AL_C);
|
||
char l[256];
|
||
snprintf(l, sizeof l, _("Versione %s"), OMEGA_VERSION);
|
||
draw_text(font(W_REG, 24), l, cx, y + 300, C_DIM, a, AL_C);
|
||
fill_rect(x + 90, y + 356, w - 180, 1, RGB(80, 92, 125), a / 2);
|
||
draw_text_fit(font(W_MED, 32), _("Sviluppato da TheCriicom"), cx, y + 386, w - 140, C_WHITE, a, AL_C);
|
||
draw_text(font(W_REG, 26), "outlinedigital.it", cx, y + 434, C_ACC2, a, AL_C);
|
||
draw_text_fit(font(W_REG, 25), _("Software libero \xE2\x80\x94 GPL-3.0-or-later"), cx, y + 500, w - 140, C_TXT, a, AL_C);
|
||
snprintf(l, sizeof l, _("Il codice sorgente \xC3\xA8 su %s/source"), srv_host(srv_get(srv_current())));
|
||
draw_text_fit(font(W_REG, 23), l, cx, y + 540, w - 140, C_DIM, a, AL_C);
|
||
const char *disc = _("Omega \xC3\xA8 un progetto indipendente: non \xC3\xA8 affiliato a Sony Interactive Entertainment e non usa i suoi account n\xC3\xA9 i suoi server.");
|
||
draw_text_wrap_al(font(W_REG, 22), disc, cx, y + 608, w - 220, 3, 32, C_FAINT, a, AL_C);
|
||
const int ic[] = { IC_BTN_O };
|
||
const char *lb[] = { _("Indietro") };
|
||
hints(ic, lb, 1, a);
|
||
}
|
||
void about_input(int b) { if (b == B_O || b == B_X) ov_pop(); }
|
||
|
||
// ------------------------------------------------------------------ notizie --
|
||
static News nw;
|
||
void news_open(const News *n) { if (!n) return; nw = *n; ov_push(OV_NEWS); }
|
||
void news_draw(float t) {
|
||
backdrop(t, 170);
|
||
int w = 1300, h = 860, x = SCREEN_W / 2 - w / 2, y = SCREEN_H / 2 - h / 2 + (int)((1 - ease_out(t)) * 60);
|
||
int a = (int)(255 * t);
|
||
shadow_rrect(x, y, w, h, 34, 50, a * 70 / 100);
|
||
fill_rrect(x, y, w, h, 34, RGB(18, 21, 32), a);
|
||
news_art(&nw, x, y, w, 380, -34, a);
|
||
grad_v(x, y + 200, w, 180, RGB(18, 21, 32), 0, RGB(18, 21, 32), a);
|
||
int tw = text_w(font(W_BOLD, 20), nw.tag) + 30;
|
||
fill_rrect(x + 60, y + 50, tw, 38, 19, C_BLACK, a * 45 / 100);
|
||
draw_text(font(W_BOLD, 20), nw.tag, x + 75, y + 57, C_WHITE, a, AL_L);
|
||
draw_text_wrap(font(W_LIGHT, 54), nw.title, x + 60, y + 300, w - 120, 2, 64, C_WHITE, a);
|
||
char tm[64]; rel_time(nw.when, tm, sizeof tm);
|
||
draw_text(font(W_REG, 24), tm, x + 62, y + 440, C_FAINT, a, AL_L);
|
||
draw_text_wrap(font(W_REG, 30), nw.body, x + 60, y + 500, w - 120, 7, 42, C_TXT, a);
|
||
if (nw.link[0]) {
|
||
draw_text(font(W_MED, 22), _("Leggi l'articolo completo su:"), x + 62, y + h - 110, C_DIM, a, AL_L);
|
||
draw_text_fit(font(W_REG, 22), nw.link, x + 62, y + h - 76, w - 124, C_ACC2, a, AL_L);
|
||
}
|
||
if (nw.link[0]) { const int ic[] = { IC_BTN_X, IC_BTN_O }; const char *lb[] = { _("Apri nel browser"), _("Chiudi") }; hints(ic, lb, 2, a); }
|
||
else { const int ic[] = { IC_BTN_O }; const char *lb[] = { _("Chiudi") }; hints(ic, lb, 1, a); }
|
||
}
|
||
void news_input(int b) {
|
||
if (b == B_O) ov_pop();
|
||
else if (b == B_X && nw.link[0]) { ov_pop(); browser_open(nw.link); }
|
||
}
|