Compare commits
2
Commits
07890191cf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a240b082 | ||
|
|
c296ff5ade |
@@ -49,6 +49,7 @@ target_link_libraries(${PROJECT_NAME}
|
|||||||
SceNetCtl_stub
|
SceNetCtl_stub
|
||||||
SceAvPlayer_stub
|
SceAvPlayer_stub
|
||||||
SceAudio_stub
|
SceAudio_stub
|
||||||
|
ScePhotoExport_stub
|
||||||
)
|
)
|
||||||
|
|
||||||
# UNSAFE: reading the camera folders (ux0:picture, ux0:video) is outside
|
# UNSAFE: reading the camera folders (ux0:picture, ux0:video) is outside
|
||||||
|
|||||||
@@ -81,6 +81,29 @@ handheld.
|
|||||||
| SELECT | Sync overview |
|
| SELECT | Sync overview |
|
||||||
| START | Exit |
|
| START | Exit |
|
||||||
|
|
||||||
|
**Albums**
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
| --- | --- |
|
||||||
|
| D‑pad / drag | Move selection / scroll |
|
||||||
|
| X | Open the album |
|
||||||
|
| □ (or tap the search bar) | Search albums by name |
|
||||||
|
| SELECT (or tap the chip) | Sort: modified newest → oldest → name A‑Z → Z‑A |
|
||||||
|
| △ | Reload the album list |
|
||||||
|
| O | Clear the search, then back |
|
||||||
|
|
||||||
|
Cover shape — **Vertical** (3:4), **Square** or **Panoramic** (16:9) — is
|
||||||
|
picked on the *Server & Backup* page (**L / R** or tap) and saved as
|
||||||
|
`covershape=` in `config.txt`.
|
||||||
|
|
||||||
|
**Inside an album**
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
| --- | --- |
|
||||||
|
| SELECT | Order: newest → oldest → file name A‑Z → Z‑A (name orders drop the month headers) |
|
||||||
|
| L / R | Jump a month (or, sorted by name, an initial letter) |
|
||||||
|
| O | Back to the album list |
|
||||||
|
|
||||||
**Places map**
|
**Places map**
|
||||||
|
|
||||||
| Button | Action |
|
| Button | Action |
|
||||||
@@ -96,8 +119,10 @@ handheld.
|
|||||||
| Button | Action |
|
| Button | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| ← / → | Previous / next |
|
| ← / → | Previous / next |
|
||||||
| Right stick / L · R | Zoom · left stick pans |
|
| R / L | Turn the viewer 90° clockwise / counter‑clockwise — hold the Vita upright and portrait photos fill the screen (d‑pad, sticks and touch follow the turn) |
|
||||||
|
| Right stick | Zoom · left stick pans |
|
||||||
| X | Play video / retry a failed load |
|
| X | Play video / retry a failed load |
|
||||||
|
| △ (or tap "Save to Vita") | Save the photo to the Vita's Photos app (JPEG/PNG original, else Immich's full-size JPEG) |
|
||||||
| O | Back |
|
| O | Back |
|
||||||
|
|
||||||
**Video playback** — X pause/resume · ←/→ seek ±10 s · O stop.
|
**Video playback** — X pause/resume · ←/→ seek ±10 s · O stop.
|
||||||
|
|||||||
+248
-44
@@ -9,7 +9,11 @@
|
|||||||
/* albums page */
|
/* albums page */
|
||||||
/* */
|
/* */
|
||||||
/* Opened from the albums button on the top bar. A grid of album cards */
|
/* Opened from the albums button on the top bar. A grid of album cards */
|
||||||
/* (cover, name, item count). Covers are ordinary server thumbnails: */
|
/* (cover, name, item count). The grid shows a VIEW of the list: */
|
||||||
|
/* g_alb_view holds g_album_* indices filtered by the name search */
|
||||||
|
/* (SQUARE / tap the pill) and sorted by name or last modification */
|
||||||
|
/* (SELECT / tap the chip). g_alb_sel is a position in that view. */
|
||||||
|
/* Covers are ordinary server thumbnails: */
|
||||||
/* each album's cover id becomes a g_asset entry (asset_for_id) and the */
|
/* each album's cover id becomes a g_asset entry (asset_for_id) and the */
|
||||||
/* shared thumbnail worker loads it into g_thumb[]. Only rows near the */
|
/* shared thumbnail worker loads it into g_thumb[]. Only rows near the */
|
||||||
/* viewport keep their cover texture (libraries can have hundreds of */
|
/* viewport keep their cover texture (libraries can have hundreds of */
|
||||||
@@ -19,26 +23,128 @@
|
|||||||
|
|
||||||
#define ALB_COLS 4
|
#define ALB_COLS 4
|
||||||
#define ALB_CW 210.0f /* card width */
|
#define ALB_CW 210.0f /* card width */
|
||||||
#define ALB_COVH 158.0f /* cover height (4:3) */
|
#define ALB_COVH alb_cover_h() /* cover height: the cover shape */
|
||||||
#define ALB_GAP 20.0f
|
#define ALB_GAP 20.0f
|
||||||
#define ALB_ROWH (ALB_COVH + 62.0f) /* cover + two text lines + gap */
|
#define ALB_ROWH (ALB_COVH + 62.0f) /* cover + two text lines + gap */
|
||||||
#define ALB_TOP 64.0f /* below the page header */
|
#define ALB_TOP 64.0f /* below the page header */
|
||||||
#define ALB_X0 ((SCREEN_W - ALB_COLS * ALB_CW - (ALB_COLS - 1) * ALB_GAP) / 2)
|
#define ALB_X0 ((SCREEN_W - ALB_COLS * ALB_CW - (ALB_COLS - 1) * ALB_GAP) / 2)
|
||||||
#define ALB_GLYPH 44 /* album glyph bake size on this page */
|
#define ALB_GLYPH 44 /* album glyph bake size on this page */
|
||||||
|
|
||||||
enum { ALB_NONE, ALB_BACK, ALB_OPEN, ALB_REFRESH };
|
/* header controls: the search pill and the sort chip */
|
||||||
|
#define ALB_HY 9.0f /* top of pill + chip */
|
||||||
|
#define ALB_HH 38.0f /* their height */
|
||||||
|
#define ALB_SX 190.0f /* search pill */
|
||||||
|
#define ALB_SW 380.0f
|
||||||
|
#define ALB_OX (ALB_SX + ALB_SW + 14.0f) /* sort chip */
|
||||||
|
#define ALB_OW 230.0f
|
||||||
|
|
||||||
static int g_alb_sel; /* selected album */
|
/* cover height for the chosen shape: 3:4, 1:1 or 16:9 */
|
||||||
|
static float alb_cover_h(void)
|
||||||
|
{
|
||||||
|
return g_cover_shape == COVER_VERTICAL ? 280.0f :
|
||||||
|
g_cover_shape == COVER_WIDE ? 118.0f : 210.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { ALB_NONE, ALB_BACK, ALB_OPEN, ALB_REFRESH, ALB_SEARCH };
|
||||||
|
|
||||||
|
/* sort orders, cycled by SELECT */
|
||||||
|
enum { ALB_SORT_MOD_NEW, ALB_SORT_MOD_OLD, ALB_SORT_NAME_AZ, ALB_SORT_NAME_ZA,
|
||||||
|
ALB_NSORT };
|
||||||
|
static const char *const alb_sort_label[ALB_NSORT] = {
|
||||||
|
"Modified: newest", "Modified: oldest", "Name: A-Z", "Name: Z-A",
|
||||||
|
};
|
||||||
|
|
||||||
|
static int g_alb_sel; /* selected position in the view */
|
||||||
static float g_alb_scroll, g_alb_target;
|
static float g_alb_scroll, g_alb_target;
|
||||||
static int g_alb_touch, g_alb_dragged;
|
static int g_alb_touch, g_alb_dragged;
|
||||||
static float g_alb_tx, g_alb_ty, g_alb_t0y, g_alb_s0;
|
static float g_alb_tx, g_alb_ty, g_alb_t0y, g_alb_s0;
|
||||||
|
|
||||||
|
static int g_alb_sort; /* ALB_SORT_* */
|
||||||
|
static char g_alb_query[128]; /* name filter, "" = all albums */
|
||||||
|
static int *g_alb_view; /* g_album_* indices, filtered+sorted */
|
||||||
|
static int g_alb_vlen, g_alb_vcap;
|
||||||
|
|
||||||
static float alb_content_h(void)
|
static float alb_content_h(void)
|
||||||
{
|
{
|
||||||
int rows = (g_album_len + ALB_COLS - 1) / ALB_COLS;
|
int rows = (g_alb_vlen + ALB_COLS - 1) / ALB_COLS;
|
||||||
return ALB_TOP + rows * ALB_ROWH;
|
return ALB_TOP + rows * ALB_ROWH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ASCII case-insensitive substring test (other UTF-8 bytes match exactly) */
|
||||||
|
static int alb_name_match(const char *name, const char *q)
|
||||||
|
{
|
||||||
|
if (!q[0])
|
||||||
|
return 1;
|
||||||
|
for (; *name; name++) {
|
||||||
|
const char *a = name, *b = q;
|
||||||
|
while (*a && *b && tolower((unsigned char)*a) ==
|
||||||
|
tolower((unsigned char)*b)) {
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
if (!*b)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int alb_cmp(const void *pa, const void *pb)
|
||||||
|
{
|
||||||
|
int a = *(const int *)pa, b = *(const int *)pb, r = 0;
|
||||||
|
switch (g_alb_sort) {
|
||||||
|
case ALB_SORT_MOD_NEW:
|
||||||
|
r = strcmp(g_album_updated[b], g_album_updated[a]);
|
||||||
|
break;
|
||||||
|
case ALB_SORT_MOD_OLD:
|
||||||
|
r = strcmp(g_album_updated[a], g_album_updated[b]);
|
||||||
|
break;
|
||||||
|
case ALB_SORT_NAME_AZ:
|
||||||
|
r = strcasecmp(g_album_name[a], g_album_name[b]);
|
||||||
|
break;
|
||||||
|
case ALB_SORT_NAME_ZA:
|
||||||
|
r = strcasecmp(g_album_name[b], g_album_name[a]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return r ? r : a - b; /* ties keep the server's order */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* rebuild g_alb_view from the list, the query and the sort order. the
|
||||||
|
* selected album stays selected when it is still in the view (a refresh);
|
||||||
|
* callers wanting the first card set g_alb_sel = -1 first. */
|
||||||
|
static void albums_rebuild_view(void)
|
||||||
|
{
|
||||||
|
int keep = (g_alb_sel >= 0 && g_alb_sel < g_alb_vlen) ?
|
||||||
|
g_alb_view[g_alb_sel] : -1;
|
||||||
|
g_album_list_dirty = 0;
|
||||||
|
g_alb_vlen = 0;
|
||||||
|
if (g_album_len > g_alb_vcap) {
|
||||||
|
int cap = GROW(g_alb_view, g_alb_vcap, g_album_len);
|
||||||
|
if (!cap)
|
||||||
|
return;
|
||||||
|
g_alb_vcap = cap;
|
||||||
|
}
|
||||||
|
for (int a = 0; a < g_album_len; a++)
|
||||||
|
if (alb_name_match(g_album_name[a], g_alb_query))
|
||||||
|
g_alb_view[g_alb_vlen++] = a;
|
||||||
|
qsort(g_alb_view, g_alb_vlen, sizeof(g_alb_view[0]), alb_cmp);
|
||||||
|
|
||||||
|
g_alb_sel = 0;
|
||||||
|
for (int v = 0; v < g_alb_vlen; v++)
|
||||||
|
if (g_alb_view[v] == keep)
|
||||||
|
g_alb_sel = v;
|
||||||
|
log_line("albums view: %d of %d (sort %d, query \"%s\")", g_alb_vlen,
|
||||||
|
g_album_len, g_alb_sort, g_alb_query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* new name filter (from the keyboard); "" shows every album */
|
||||||
|
static void albums_set_query(const char *q)
|
||||||
|
{
|
||||||
|
snprintf(g_alb_query, sizeof(g_alb_query), "%s", q);
|
||||||
|
g_alb_sel = -1; /* start at the first match */
|
||||||
|
albums_rebuild_view();
|
||||||
|
g_alb_scroll = g_alb_target = 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void alb_card_xy(int a, float *x, float *y)
|
static void alb_card_xy(int a, float *x, float *y)
|
||||||
{
|
{
|
||||||
*x = ALB_X0 + (a % ALB_COLS) * (ALB_CW + ALB_GAP);
|
*x = ALB_X0 + (a % ALB_COLS) * (ALB_CW + ALB_GAP);
|
||||||
@@ -89,12 +195,22 @@ static void albums_pump_covers(int first, int last)
|
|||||||
|
|
||||||
/* rows more than one row outside the viewport drop their texture */
|
/* rows more than one row outside the viewport drop their texture */
|
||||||
int keep0 = first - ALB_COLS, keep1 = last + ALB_COLS;
|
int keep0 = first - ALB_COLS, keep1 = last + ALB_COLS;
|
||||||
for (int a = 0; a < g_album_len; a++) {
|
if (keep0 < 0)
|
||||||
if (a >= keep0 && a <= keep1)
|
keep0 = 0;
|
||||||
|
if (keep1 >= g_alb_vlen)
|
||||||
|
keep1 = g_alb_vlen - 1;
|
||||||
|
for (int v = 0; v < g_alb_vlen; v++) {
|
||||||
|
if (v >= keep0 && v <= keep1)
|
||||||
continue;
|
continue;
|
||||||
int idx = g_album_cover[a];
|
int idx = g_album_cover[g_alb_view[v]];
|
||||||
if (idx >= 0 && g_thumb[idx] &&
|
if (idx < 0 || !g_thumb[idx] ||
|
||||||
!(g_req_state != REQ_IDLE && g_req_idx == idx)) {
|
(g_req_state != REQ_IDLE && g_req_idx == idx))
|
||||||
|
continue;
|
||||||
|
/* albums can share a cover photo: keep it if a kept card uses it */
|
||||||
|
int shared = 0;
|
||||||
|
for (int k = keep0; k <= keep1 && !shared; k++)
|
||||||
|
shared = g_album_cover[g_alb_view[k]] == idx;
|
||||||
|
if (!shared) {
|
||||||
tex_release(g_thumb[idx]);
|
tex_release(g_thumb[idx]);
|
||||||
g_thumb[idx] = NULL;
|
g_thumb[idx] = NULL;
|
||||||
}
|
}
|
||||||
@@ -103,8 +219,11 @@ static void albums_pump_covers(int first, int last)
|
|||||||
if (g_req_state != REQ_IDLE)
|
if (g_req_state != REQ_IDLE)
|
||||||
return;
|
return;
|
||||||
for (int k = -1; k <= last - first; k++) {
|
for (int k = -1; k <= last - first; k++) {
|
||||||
int a = (k < 0) ? g_alb_sel : first + k; /* selection first */
|
int v = (k < 0) ? g_alb_sel : first + k; /* selection first */
|
||||||
if (a < 0 || a >= g_album_len || !g_album_thumb[a][0])
|
if (v < 0 || v >= g_alb_vlen)
|
||||||
|
continue;
|
||||||
|
int a = g_alb_view[v];
|
||||||
|
if (!g_album_thumb[a][0])
|
||||||
continue;
|
continue;
|
||||||
if (g_album_cover[a] < 0)
|
if (g_album_cover[a] < 0)
|
||||||
g_album_cover[a] = asset_for_id(g_album_thumb[a]);
|
g_album_cover[a] = asset_for_id(g_album_thumb[a]);
|
||||||
@@ -132,7 +251,7 @@ static void draw_album_card(int a, float x, float y, int selected)
|
|||||||
draw_sel_outline(x - 5, y - 5, ALB_CW + 10, ALB_COVH + 10);
|
draw_sel_outline(x - 5, y - 5, ALB_CW + 10, ALB_COVH + 10);
|
||||||
vita2d_draw_rectangle(x, y, ALB_CW, ALB_COVH, card);
|
vita2d_draw_rectangle(x, y, ALB_CW, ALB_COVH, card);
|
||||||
if (tex) {
|
if (tex) {
|
||||||
/* crop to fill the 4:3 cover box */
|
/* crop to fill the cover box */
|
||||||
float tw = vita2d_texture_get_width(tex);
|
float tw = vita2d_texture_get_width(tex);
|
||||||
float th = vita2d_texture_get_height(tex);
|
float th = vita2d_texture_get_height(tex);
|
||||||
float s = fmaxf(ALB_CW / tw, ALB_COVH / th);
|
float s = fmaxf(ALB_CW / tw, ALB_COVH / th);
|
||||||
@@ -160,14 +279,89 @@ static void draw_album_card(int a, float x, float y, int selected)
|
|||||||
draw_text(x, y + ALB_COVH + 44, RGBA8(140, 140, 148, 255), 0.8f, meta);
|
draw_text(x, y + ALB_COVH + 44, RGBA8(140, 140, 148, 255), 0.8f, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* header: search pill (query or placeholder) and the sort chip */
|
||||||
|
static void draw_albums_header(int n)
|
||||||
|
{
|
||||||
|
vita2d_draw_rectangle(0, 0, SCREEN_W, ALB_TOP - 8, RGBA8(16, 16, 16, 255));
|
||||||
|
int gs = 24;
|
||||||
|
vita2d_texture *og = icon_tex(ICON_CIRCLE, gs);
|
||||||
|
if (og)
|
||||||
|
vita2d_draw_texture_tint(og, 18, 16, RGBA8(235, 90, 85, 255));
|
||||||
|
/* same bake size as the card placeholder: icon_tex caches one
|
||||||
|
* size per glyph, so mixing sizes in a frame re-bakes (and frees)
|
||||||
|
* the texture mid-frame */
|
||||||
|
vita2d_texture *ag = icon_tex(ICON_ALBUM, ALB_GLYPH);
|
||||||
|
if (ag)
|
||||||
|
vita2d_draw_texture_tint_scale(ag, 56, 14, 28.0f / ALB_GLYPH,
|
||||||
|
28.0f / ALB_GLYPH,
|
||||||
|
RGBA8(235, 235, 240, 255));
|
||||||
|
draw_text(94, 37, RGBA8(235, 235, 240, 255), 1.1f, "Albums");
|
||||||
|
|
||||||
|
uint32_t field = RGBA8(38, 38, 42, 255);
|
||||||
|
uint32_t dim = RGBA8(140, 140, 148, 255);
|
||||||
|
uint32_t txt = RGBA8(235, 235, 240, 255);
|
||||||
|
float ty = ALB_HY + ALB_HH - 12;
|
||||||
|
|
||||||
|
/* search pill: [search] query ... [] (or O to clear) */
|
||||||
|
vita2d_texture *pm = rounded_mask_tex((int)ALB_SW, (int)ALB_HH, 10.0f);
|
||||||
|
if (pm)
|
||||||
|
vita2d_draw_texture_tint(pm, ALB_SX, ALB_HY, field);
|
||||||
|
else
|
||||||
|
vita2d_draw_rectangle(ALB_SX, ALB_HY, ALB_SW, ALB_HH, field);
|
||||||
|
vita2d_texture *si = icon_tex(ICON_SEARCH, 24);
|
||||||
|
if (si)
|
||||||
|
vita2d_draw_texture_tint(si, ALB_SX + 10, ALB_HY + (ALB_HH - 24) / 2,
|
||||||
|
RGBA8(190, 190, 196, 255));
|
||||||
|
char line[96];
|
||||||
|
if (g_alb_query[0]) {
|
||||||
|
alb_fit(line, sizeof(line), g_alb_query, 0.9f, (int)ALB_SW - 84);
|
||||||
|
draw_text(ALB_SX + 42, ty, txt, 0.9f, line);
|
||||||
|
if (og) /* O clears the filter */
|
||||||
|
vita2d_draw_texture_tint(og, ALB_SX + ALB_SW - 34,
|
||||||
|
ALB_HY + (ALB_HH - gs) / 2,
|
||||||
|
RGBA8(235, 90, 85, 255));
|
||||||
|
} else {
|
||||||
|
draw_text(ALB_SX + 42, ty, dim, 0.9f, "Search albums");
|
||||||
|
}
|
||||||
|
if (!g_alb_query[0]) {
|
||||||
|
vita2d_texture *sq = icon_tex(ICON_SQUARE, 20);
|
||||||
|
if (sq)
|
||||||
|
vita2d_draw_texture_tint(sq, ALB_SX + ALB_SW - 32,
|
||||||
|
ALB_HY + (ALB_HH - 20) / 2,
|
||||||
|
RGBA8(232, 120, 175, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sort chip: SELECT + the current order */
|
||||||
|
vita2d_texture *cm = rounded_mask_tex((int)ALB_OW, (int)ALB_HH, 10.0f);
|
||||||
|
if (cm)
|
||||||
|
vita2d_draw_texture_tint(cm, ALB_OX, ALB_HY, field);
|
||||||
|
else
|
||||||
|
vita2d_draw_rectangle(ALB_OX, ALB_HY, ALB_OW, ALB_HH, field);
|
||||||
|
draw_text(ALB_OX + 12, ty - 1, dim, 0.7f, "SELECT");
|
||||||
|
draw_text(ALB_OX + 12 + text_width(0.7f, "SELECT") + 10, ty, txt, 0.9f,
|
||||||
|
alb_sort_label[g_alb_sort]);
|
||||||
|
|
||||||
|
char cnt[64];
|
||||||
|
if (g_alb_query[0])
|
||||||
|
snprintf(cnt, sizeof(cnt), "%d of %d", n, g_album_len);
|
||||||
|
else
|
||||||
|
snprintf(cnt, sizeof(cnt), "%d album%s", n, n == 1 ? "" : "s");
|
||||||
|
draw_text(SCREEN_W - 18 - text_width(0.8f, cnt), 36, dim, 0.8f, cnt);
|
||||||
|
}
|
||||||
|
|
||||||
/* one frame of the album list: input, cover loading and drawing. returns
|
/* one frame of the album list: input, cover loading and drawing. returns
|
||||||
* ALB_OPEN (open g_alb_sel), ALB_BACK, ALB_REFRESH or ALB_NONE. */
|
* ALB_OPEN (open view position g_alb_sel), ALB_BACK, ALB_REFRESH,
|
||||||
|
* ALB_SEARCH (bring up the keyboard) or ALB_NONE. */
|
||||||
static int albums_page(unsigned int pressed, unsigned int nav)
|
static int albums_page(unsigned int pressed, unsigned int nav)
|
||||||
{
|
{
|
||||||
int action = ALB_NONE;
|
int action = ALB_NONE;
|
||||||
int n = g_album_len;
|
if (g_album_list_dirty)
|
||||||
|
albums_rebuild_view();
|
||||||
|
int n = g_alb_vlen;
|
||||||
if (g_alb_sel >= n)
|
if (g_alb_sel >= n)
|
||||||
g_alb_sel = n > 0 ? n - 1 : 0;
|
g_alb_sel = n > 0 ? n - 1 : 0;
|
||||||
|
if (g_alb_sel < 0)
|
||||||
|
g_alb_sel = 0;
|
||||||
|
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
if (nav & SCE_CTRL_RIGHT && g_alb_sel + 1 < n)
|
if (nav & SCE_CTRL_RIGHT && g_alb_sel + 1 < n)
|
||||||
@@ -183,10 +377,17 @@ static int albums_page(unsigned int pressed, unsigned int nav)
|
|||||||
if (pressed & SCE_CTRL_CROSS)
|
if (pressed & SCE_CTRL_CROSS)
|
||||||
action = ALB_OPEN;
|
action = ALB_OPEN;
|
||||||
}
|
}
|
||||||
if (pressed & SCE_CTRL_CIRCLE)
|
int do_sort = (pressed & SCE_CTRL_SELECT) != 0;
|
||||||
|
if (pressed & SCE_CTRL_CIRCLE) {
|
||||||
|
if (g_alb_query[0])
|
||||||
|
albums_set_query(""); /* O first clears the filter */
|
||||||
|
else
|
||||||
action = ALB_BACK;
|
action = ALB_BACK;
|
||||||
|
}
|
||||||
if (pressed & SCE_CTRL_TRIANGLE)
|
if (pressed & SCE_CTRL_TRIANGLE)
|
||||||
action = ALB_REFRESH;
|
action = ALB_REFRESH;
|
||||||
|
if (pressed & SCE_CTRL_SQUARE)
|
||||||
|
action = ALB_SEARCH;
|
||||||
|
|
||||||
float maxs = alb_content_h() - SCREEN_H + 16;
|
float maxs = alb_content_h() - SCREEN_H + 16;
|
||||||
if (maxs < 0)
|
if (maxs < 0)
|
||||||
@@ -216,8 +417,18 @@ static int albums_page(unsigned int pressed, unsigned int nav)
|
|||||||
g_alb_ty = ty;
|
g_alb_ty = ty;
|
||||||
} else if (g_alb_touch) {
|
} else if (g_alb_touch) {
|
||||||
g_alb_touch = 0;
|
g_alb_touch = 0;
|
||||||
if (!g_alb_dragged && g_alb_ty < ALB_TOP - 8 && g_alb_tx < 140) {
|
if (!g_alb_dragged && g_alb_ty < ALB_TOP - 8) {
|
||||||
action = ALB_BACK; /* tap on the header's back label */
|
/* header taps: back label, search pill (its O end
|
||||||
|
* clears an active filter), sort chip */
|
||||||
|
if (g_alb_tx < 140)
|
||||||
|
action = ALB_BACK;
|
||||||
|
else if (g_alb_tx >= ALB_SX && g_alb_tx < ALB_SX + ALB_SW) {
|
||||||
|
if (g_alb_query[0] && g_alb_tx > ALB_SX + ALB_SW - 48)
|
||||||
|
albums_set_query("");
|
||||||
|
else
|
||||||
|
action = ALB_SEARCH;
|
||||||
|
} else if (g_alb_tx >= ALB_OX && g_alb_tx < ALB_OX + ALB_OW)
|
||||||
|
do_sort = 1;
|
||||||
} else if (!g_alb_dragged) {
|
} else if (!g_alb_dragged) {
|
||||||
float cy = g_alb_ty + g_alb_scroll - ALB_TOP;
|
float cy = g_alb_ty + g_alb_scroll - ALB_TOP;
|
||||||
int row = (int)(cy / ALB_ROWH);
|
int row = (int)(cy / ALB_ROWH);
|
||||||
@@ -234,6 +445,13 @@ static int albums_page(unsigned int pressed, unsigned int nav)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (do_sort) {
|
||||||
|
/* a new order starts from its top */
|
||||||
|
g_alb_sort = (g_alb_sort + 1) % ALB_NSORT;
|
||||||
|
g_alb_sel = -1;
|
||||||
|
albums_rebuild_view();
|
||||||
|
g_alb_scroll = g_alb_target = 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* keep the selected card on screen (d-pad), with an eased scroll */
|
/* keep the selected card on screen (d-pad), with an eased scroll */
|
||||||
if (!g_alb_touch) {
|
if (!g_alb_touch) {
|
||||||
@@ -263,37 +481,23 @@ static int albums_page(unsigned int pressed, unsigned int nav)
|
|||||||
vita2d_start_drawing();
|
vita2d_start_drawing();
|
||||||
vita2d_clear_screen();
|
vita2d_clear_screen();
|
||||||
vita2d_draw_rectangle(0, 0, SCREEN_W, SCREEN_H, RGBA8(16, 16, 16, 255));
|
vita2d_draw_rectangle(0, 0, SCREEN_W, SCREEN_H, RGBA8(16, 16, 16, 255));
|
||||||
for (int a = first; a <= last && a < n; a++) {
|
for (int v = first; v <= last && v < n; v++) {
|
||||||
float x, y;
|
float x, y;
|
||||||
alb_card_xy(a, &x, &y);
|
alb_card_xy(v, &x, &y);
|
||||||
draw_album_card(a, x, y - g_alb_scroll, a == g_alb_sel);
|
draw_album_card(g_alb_view[v], x, y - g_alb_scroll,
|
||||||
|
v == g_alb_sel);
|
||||||
}
|
}
|
||||||
if (n == 0)
|
if (n == 0 && g_alb_query[0]) {
|
||||||
|
char msg[160];
|
||||||
|
snprintf(msg, sizeof(msg), "No albums match \"%.64s\"", g_alb_query);
|
||||||
|
draw_centered(SCREEN_H / 2, RGBA8(160, 160, 165, 255), msg);
|
||||||
|
} else if (n == 0) {
|
||||||
draw_centered(SCREEN_H / 2, RGBA8(160, 160, 165, 255),
|
draw_centered(SCREEN_H / 2, RGBA8(160, 160, 165, 255),
|
||||||
"No albums on this account");
|
"No albums on this account");
|
||||||
|
|
||||||
/* header: back label, title, count (drawn last: cards scroll under it) */
|
|
||||||
vita2d_draw_rectangle(0, 0, SCREEN_W, ALB_TOP - 8, RGBA8(16, 16, 16, 255));
|
|
||||||
{
|
|
||||||
int gs = 24;
|
|
||||||
vita2d_texture *og = icon_tex(ICON_CIRCLE, gs);
|
|
||||||
if (og)
|
|
||||||
vita2d_draw_texture_tint(og, 18, 16, RGBA8(235, 90, 85, 255));
|
|
||||||
/* same bake size as the card placeholder: icon_tex caches one
|
|
||||||
* size per glyph, so mixing sizes in a frame re-bakes (and frees)
|
|
||||||
* the texture mid-frame */
|
|
||||||
vita2d_texture *ag = icon_tex(ICON_ALBUM, ALB_GLYPH);
|
|
||||||
if (ag)
|
|
||||||
vita2d_draw_texture_tint_scale(ag, 56, 14, 28.0f / ALB_GLYPH,
|
|
||||||
28.0f / ALB_GLYPH,
|
|
||||||
RGBA8(235, 235, 240, 255));
|
|
||||||
draw_text(94, 37, RGBA8(235, 235, 240, 255), 1.1f, "Albums");
|
|
||||||
char cnt[64];
|
|
||||||
snprintf(cnt, sizeof(cnt), "%d album%s X open /\\ refresh",
|
|
||||||
n, n == 1 ? "" : "s");
|
|
||||||
draw_text(SCREEN_W - 18 - text_width(0.8f, cnt), 36,
|
|
||||||
RGBA8(140, 140, 148, 255), 0.8f, cnt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* drawn last: cards scroll under it */
|
||||||
|
draw_albums_header(n);
|
||||||
vita2d_end_drawing();
|
vita2d_end_drawing();
|
||||||
vita2d_swap_buffers();
|
vita2d_swap_buffers();
|
||||||
return action;
|
return action;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ static int parse_assets(const char *js, size_t jslen, char *err, size_t errlen)
|
|||||||
added++;
|
added++;
|
||||||
g_asset_ids[cur][0] = '\0';
|
g_asset_ids[cur][0] = '\0';
|
||||||
g_asset_dates[cur][0] = '\0';
|
g_asset_dates[cur][0] = '\0';
|
||||||
|
g_asset_names[cur][0] = '\0';
|
||||||
g_asset_ratio[cur] = 0.0f;
|
g_asset_ratio[cur] = 0.0f;
|
||||||
g_asset_rot[cur] = 0;
|
g_asset_rot[cur] = 0;
|
||||||
g_asset_is_video[cur] = 0;
|
g_asset_is_video[cur] = 0;
|
||||||
@@ -136,6 +137,10 @@ static int parse_assets(const char *js, size_t jslen, char *err, size_t errlen)
|
|||||||
else if (klen == 4 && !strncmp(k, "type", 4))
|
else if (klen == 4 && !strncmp(k, "type", 4))
|
||||||
g_asset_is_video[cur] =
|
g_asset_is_video[cur] =
|
||||||
(vlen == 5 && !strncmp(js + val->start, "VIDEO", 5));
|
(vlen == 5 && !strncmp(js + val->start, "VIDEO", 5));
|
||||||
|
else if (klen == 16 && !strncmp(k, "originalFileName", 16))
|
||||||
|
/* may be cut mid UTF-8 sequence; only sorted, never drawn */
|
||||||
|
snprintf(g_asset_names[cur], sizeof(g_asset_names[0]),
|
||||||
|
"%.*s", vlen, js + val->start);
|
||||||
}
|
}
|
||||||
if (cur >= 0 && exw > 0 && exh > 0)
|
if (cur >= 0 && exw > 0 && exh > 0)
|
||||||
g_asset_ratio[cur] = (exori >= 5 && exori <= 8) ?
|
g_asset_ratio[cur] = (exori >= 5 && exori <= 8) ?
|
||||||
@@ -175,6 +180,8 @@ static int dedup_new_assets(int before)
|
|||||||
sizeof(g_asset_ids[0]));
|
sizeof(g_asset_ids[0]));
|
||||||
memcpy(g_asset_dates[i], g_asset_dates[n],
|
memcpy(g_asset_dates[i], g_asset_dates[n],
|
||||||
sizeof(g_asset_dates[0]));
|
sizeof(g_asset_dates[0]));
|
||||||
|
memcpy(g_asset_names[i], g_asset_names[n],
|
||||||
|
sizeof(g_asset_names[0]));
|
||||||
g_asset_ratio[i] = g_asset_ratio[n];
|
g_asset_ratio[i] = g_asset_ratio[n];
|
||||||
g_asset_rot[i] = g_asset_rot[n];
|
g_asset_rot[i] = g_asset_rot[n];
|
||||||
g_asset_is_video[i] = g_asset_is_video[n];
|
g_asset_is_video[i] = g_asset_is_video[n];
|
||||||
@@ -468,6 +475,7 @@ static int parse_albums(const char *js, size_t len)
|
|||||||
g_album_nassets[cur] = 0;
|
g_album_nassets[cur] = 0;
|
||||||
g_album_shared[cur] = 0;
|
g_album_shared[cur] = 0;
|
||||||
g_album_has_link[cur] = 0;
|
g_album_has_link[cur] = 0;
|
||||||
|
g_album_updated[cur][0] = '\0';
|
||||||
g_album_cover[cur] = -1;
|
g_album_cover[cur] = -1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -501,6 +509,11 @@ static int parse_albums(const char *js, size_t len)
|
|||||||
g_album_shared[cur] = (unsigned char)json_true(js, v);
|
g_album_shared[cur] = (unsigned char)json_true(js, v);
|
||||||
else if (kl == 13 && !strncmp(k, "hasSharedLink", 13))
|
else if (kl == 13 && !strncmp(k, "hasSharedLink", 13))
|
||||||
g_album_has_link[cur] = (unsigned char)json_true(js, v);
|
g_album_has_link[cur] = (unsigned char)json_true(js, v);
|
||||||
|
else if (kl == 9 && !strncmp(k, "updatedAt", 9) &&
|
||||||
|
v->type == JSMN_STRING &&
|
||||||
|
vl < (int)sizeof(g_album_updated[0]))
|
||||||
|
snprintf(g_album_updated[cur], sizeof(g_album_updated[0]),
|
||||||
|
"%.*s", vl, js + v->start);
|
||||||
}
|
}
|
||||||
free(t);
|
free(t);
|
||||||
|
|
||||||
@@ -516,6 +529,8 @@ static int parse_albums(const char *js, size_t len)
|
|||||||
g_album_nassets[w] = g_album_nassets[r];
|
g_album_nassets[w] = g_album_nassets[r];
|
||||||
g_album_shared[w] = g_album_shared[r];
|
g_album_shared[w] = g_album_shared[r];
|
||||||
g_album_has_link[w] = g_album_has_link[r];
|
g_album_has_link[w] = g_album_has_link[r];
|
||||||
|
memcpy(g_album_updated[w], g_album_updated[r],
|
||||||
|
sizeof(g_album_updated[0]));
|
||||||
g_album_cover[w] = g_album_cover[r];
|
g_album_cover[w] = g_album_cover[r];
|
||||||
}
|
}
|
||||||
w++;
|
w++;
|
||||||
@@ -553,6 +568,7 @@ static void fetch_albums(void)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log_line("albums: %d fetched", n);
|
log_line("albums: %d fetched", n);
|
||||||
|
g_album_list_dirty = 1;
|
||||||
__sync_synchronize();
|
__sync_synchronize();
|
||||||
g_album_state = 1;
|
g_album_state = 1;
|
||||||
}
|
}
|
||||||
@@ -633,6 +649,8 @@ static int fetch_album_assets(int a)
|
|||||||
!strcmp(g_asset_ids[j], g_asset_ids[i])) {
|
!strcmp(g_asset_ids[j], g_asset_ids[i])) {
|
||||||
memcpy(g_asset_dates[j], g_asset_dates[i],
|
memcpy(g_asset_dates[j], g_asset_dates[i],
|
||||||
sizeof(g_asset_dates[0]));
|
sizeof(g_asset_dates[0]));
|
||||||
|
memcpy(g_asset_names[j], g_asset_names[i],
|
||||||
|
sizeof(g_asset_names[0]));
|
||||||
g_asset_ratio[j] = g_asset_ratio[i];
|
g_asset_ratio[j] = g_asset_ratio[i];
|
||||||
g_asset_rot[j] = g_asset_rot[i];
|
g_asset_rot[j] = g_asset_rot[i];
|
||||||
g_asset_is_video[j] = g_asset_is_video[i];
|
g_asset_is_video[j] = g_asset_is_video[i];
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ static char g_boot_status[48] = "Loading...";
|
|||||||
static volatile int g_boot_done;
|
static volatile int g_boot_done;
|
||||||
static volatile int g_boot_login_failed;
|
static volatile int g_boot_login_failed;
|
||||||
#ifdef VIMM_TEST_INPUT
|
#ifdef VIMM_TEST_INPUT
|
||||||
|
/* a pseudo button: hold the right stick up (zoom in) */
|
||||||
|
#define TEST_RSTICK_UP 0x80000000u
|
||||||
/* TEST BUILDS ONLY (-DVIMM_TEST_INPUT): scripted buttons for emulator runs
|
/* TEST BUILDS ONLY (-DVIMM_TEST_INPUT): scripted buttons for emulator runs
|
||||||
* where synthetic key events can't reach Vita3K. Lines in
|
* where synthetic key events can't reach Vita3K. Lines in
|
||||||
* ux0:/data/vitaimmich/test_input.txt: "x", "down:3" (repeat), "wait:60"
|
* ux0:/data/vitaimmich/test_input.txt: "x", "down:3" (repeat), "wait:60"
|
||||||
@@ -53,6 +55,7 @@ static unsigned int test_input_buttons(void)
|
|||||||
{ "left", SCE_CTRL_LEFT }, { "right", SCE_CTRL_RIGHT },
|
{ "left", SCE_CTRL_LEFT }, { "right", SCE_CTRL_RIGHT },
|
||||||
{ "l", SCE_CTRL_LTRIGGER }, { "r", SCE_CTRL_RTRIGGER },
|
{ "l", SCE_CTRL_LTRIGGER }, { "r", SCE_CTRL_RTRIGGER },
|
||||||
{ "sel", SCE_CTRL_SELECT }, { "start", SCE_CTRL_START },
|
{ "sel", SCE_CTRL_SELECT }, { "start", SCE_CTRL_START },
|
||||||
|
{ "rup", TEST_RSTICK_UP },
|
||||||
};
|
};
|
||||||
if (!strcmp(name, "wait")) {
|
if (!strcmp(name, "wait")) {
|
||||||
queue_btn[qn] = 0; queue_len[qn++] = n;
|
queue_btn[qn] = 0; queue_len[qn++] = n;
|
||||||
@@ -191,6 +194,8 @@ static void reset_for_account_change(void)
|
|||||||
g_album_fetching = 0;
|
g_album_fetching = 0;
|
||||||
g_alb_sel = 0;
|
g_alb_sel = 0;
|
||||||
g_alb_scroll = g_alb_target = 0;
|
g_alb_scroll = g_alb_target = 0;
|
||||||
|
g_alb_vlen = 0;
|
||||||
|
g_alb_query[0] = '\0';
|
||||||
g_srv_state = 0; /* re-fetch the cloud page's server info */
|
g_srv_state = 0; /* re-fetch the cloud page's server info */
|
||||||
g_srv_fetching = 0;
|
g_srv_fetching = 0;
|
||||||
g_map_state = 0; /* re-fetch the new account's map markers */
|
g_map_state = 0; /* re-fetch the new account's map markers */
|
||||||
@@ -236,6 +241,8 @@ int main(void)
|
|||||||
/* on-screen keyboard (smart search). the IME runs as a common dialog;
|
/* on-screen keyboard (smart search). the IME runs as a common dialog;
|
||||||
* the config tells it the system language + enter/cancel button map */
|
* the config tells it the system language + enter/cancel button map */
|
||||||
sceSysmoduleLoadModule(SCE_SYSMODULE_IME);
|
sceSysmoduleLoadModule(SCE_SYSMODULE_IME);
|
||||||
|
/* Save to Vita: copies photos into the Photos app */
|
||||||
|
sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT);
|
||||||
{
|
{
|
||||||
SceCommonDialogConfigParam cfg;
|
SceCommonDialogConfigParam cfg;
|
||||||
sceCommonDialogConfigParamInit(&cfg);
|
sceCommonDialogConfigParamInit(&cfg);
|
||||||
@@ -414,10 +421,15 @@ int main(void)
|
|||||||
int pf_pending = -1; /* slot being fetched right now, -1 none */
|
int pf_pending = -1; /* slot being fetched right now, -1 none */
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
view_leave(); /* only the photo viewer may be turned */
|
||||||
SceCtrlData pad;
|
SceCtrlData pad;
|
||||||
sceCtrlPeekBufferPositive(0, &pad, 1);
|
sceCtrlPeekBufferPositive(0, &pad, 1);
|
||||||
#ifdef VIMM_TEST_INPUT
|
#ifdef VIMM_TEST_INPUT
|
||||||
pad.buttons |= test_input_buttons();
|
pad.buttons |= test_input_buttons();
|
||||||
|
if (pad.buttons & TEST_RSTICK_UP) {
|
||||||
|
pad.buttons &= ~TEST_RSTICK_UP;
|
||||||
|
pad.ry = 0;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
unsigned int pressed = pad.buttons & ~prev_buttons;
|
unsigned int pressed = pad.buttons & ~prev_buttons;
|
||||||
prev_buttons = pad.buttons;
|
prev_buttons = pad.buttons;
|
||||||
@@ -474,6 +486,22 @@ int main(void)
|
|||||||
if (held_frames > 18 && held_frames % 5 == 0)
|
if (held_frames > 18 && held_frames % 5 == 0)
|
||||||
nav |= pad.buttons & dirs;
|
nav |= pad.buttons & dirs;
|
||||||
|
|
||||||
|
/* SELECT inside an album cycles its order (date / name); the
|
||||||
|
* grid restarts at the top of the new order */
|
||||||
|
if ((pressed & SCE_CTRL_SELECT) && mode == MODE_GRID &&
|
||||||
|
g_album_active) {
|
||||||
|
g_album_sort = (g_album_sort + 1) % ASORT_COUNT;
|
||||||
|
rebuild_display();
|
||||||
|
log_line("album sort: %s (first: %s, %s)",
|
||||||
|
g_album_sort_label[g_album_sort],
|
||||||
|
g_disp_count > 0 ? g_asset_names[g_disp[0].idx] : "-",
|
||||||
|
g_disp_count > 1 ? g_asset_names[g_disp[1].idx] : "-");
|
||||||
|
sel = 0;
|
||||||
|
scroll = target = 0;
|
||||||
|
grid_last_sel = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* SELECT opens the cloud / backup page from the grid or detail */
|
/* SELECT opens the cloud / backup page from the grid or detail */
|
||||||
if ((pressed & SCE_CTRL_SELECT) &&
|
if ((pressed & SCE_CTRL_SELECT) &&
|
||||||
(mode == MODE_GRID || mode == MODE_DETAIL)) {
|
(mode == MODE_GRID || mode == MODE_DETAIL)) {
|
||||||
@@ -1103,13 +1131,25 @@ int main(void)
|
|||||||
int is_local = (it.src == SRC_LOCAL);
|
int is_local = (it.src == SRC_LOCAL);
|
||||||
int is_video = disp_is_video(sel);
|
int is_video = disp_is_video(sel);
|
||||||
|
|
||||||
/* zoom with the triggers or the right stick (push up to
|
/* R / L turn the viewer 90 degrees clockwise / counter-
|
||||||
* zoom in, down to zoom out), continuous while held */
|
* clockwise (hold the Vita upright for portrait photos);
|
||||||
|
* from here on the frame works in the turned screen:
|
||||||
|
* SCREEN_W/H, d-pad, sticks and touch all follow it */
|
||||||
|
if (!slide_anim && !touch_active &&
|
||||||
|
(pressed & (SCE_CTRL_RTRIGGER | SCE_CTRL_LTRIGGER))) {
|
||||||
|
view_turn((pressed & SCE_CTRL_RTRIGGER) ? 90 : -90);
|
||||||
|
zoom = 1.0f;
|
||||||
|
panx = pany = 0.0f;
|
||||||
|
}
|
||||||
|
view_enter();
|
||||||
|
nav = view_buttons(nav);
|
||||||
|
pad.buttons = view_buttons(pad.buttons);
|
||||||
|
view_stick(&pad.lx, &pad.ly);
|
||||||
|
view_stick(&pad.rx, &pad.ry);
|
||||||
|
|
||||||
|
/* zoom with the right stick (push up to zoom in, down
|
||||||
|
* to zoom out), continuous while held */
|
||||||
if (!is_video && detail_tex) {
|
if (!is_video && detail_tex) {
|
||||||
if (pad.buttons & SCE_CTRL_RTRIGGER)
|
|
||||||
zoom *= 1.04f;
|
|
||||||
if (pad.buttons & SCE_CTRL_LTRIGGER)
|
|
||||||
zoom /= 1.04f;
|
|
||||||
float rz = (128 - pad.ry) / 128.0f; /* up positive */
|
float rz = (128 - pad.ry) / 128.0f; /* up positive */
|
||||||
if (rz > 0.18f || rz < -0.18f)
|
if (rz > 0.18f || rz < -0.18f)
|
||||||
zoom *= 1.0f + rz * 0.05f;
|
zoom *= 1.0f + rz * 0.05f;
|
||||||
@@ -1121,6 +1161,10 @@ int main(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
int zoomed = (zoom > 1.001f);
|
int zoomed = (zoom > 1.001f);
|
||||||
|
/* Triangle (or the button) saves a server photo to the
|
||||||
|
* Photos app; local photos already live on the Vita */
|
||||||
|
int can_save = !is_local && !is_video;
|
||||||
|
int do_save = can_save && (pressed & SCE_CTRL_TRIANGLE);
|
||||||
|
|
||||||
if (zoomed) {
|
if (zoomed) {
|
||||||
/* pan with the left stick + d-pad; don't browse */
|
/* pan with the left stick + d-pad; don't browse */
|
||||||
@@ -1157,6 +1201,7 @@ int main(void)
|
|||||||
if (td.reportNum > 0) {
|
if (td.reportNum > 0) {
|
||||||
float tx = td.report[0].x * 0.5f;
|
float tx = td.report[0].x * 0.5f;
|
||||||
float ty = td.report[0].y * 0.5f;
|
float ty = td.report[0].y * 0.5f;
|
||||||
|
view_touch(&tx, &ty);
|
||||||
if (!touch_active) {
|
if (!touch_active) {
|
||||||
touch_active = 1;
|
touch_active = 1;
|
||||||
touch_dragged = 0;
|
touch_dragged = 0;
|
||||||
@@ -1199,9 +1244,14 @@ int main(void)
|
|||||||
else
|
else
|
||||||
slide_goal = 0; /* spring back */
|
slide_goal = 0; /* spring back */
|
||||||
slide_anim = 1;
|
slide_anim = 1;
|
||||||
|
} else if (!touch_dragged && can_save &&
|
||||||
|
!zoomed && !slide_anim &&
|
||||||
|
save_button_hit(touch_x, touch_y)) {
|
||||||
|
do_save = 1;
|
||||||
} else if (!touch_dragged && is_video &&
|
} else if (!touch_dragged && is_video &&
|
||||||
!slide_anim) {
|
!slide_anim) {
|
||||||
/* tap on the poster plays it */
|
/* tap on the poster plays it */
|
||||||
|
view_leave();
|
||||||
g_dl_bg = detail_tex ?
|
g_dl_bg = detail_tex ?
|
||||||
detail_tex :
|
detail_tex :
|
||||||
disp_thumb(sel);
|
disp_thumb(sel);
|
||||||
@@ -1259,6 +1309,7 @@ int main(void)
|
|||||||
if (pressed & SCE_CTRL_CROSS) {
|
if (pressed & SCE_CTRL_CROSS) {
|
||||||
if (is_video) {
|
if (is_video) {
|
||||||
/* play even if the poster fetch failed */
|
/* play even if the poster fetch failed */
|
||||||
|
view_leave();
|
||||||
g_dl_bg = detail_tex ?
|
g_dl_bg = detail_tex ?
|
||||||
detail_tex : disp_thumb(sel);
|
detail_tex : disp_thumb(sel);
|
||||||
g_video_rot = 0;
|
g_video_rot = 0;
|
||||||
@@ -1280,6 +1331,20 @@ int main(void)
|
|||||||
if (detail_failed)
|
if (detail_failed)
|
||||||
detail_idx = -1; /* retry */
|
detail_idx = -1; /* retry */
|
||||||
}
|
}
|
||||||
|
if (do_save && !slide_anim) {
|
||||||
|
view_leave();
|
||||||
|
g_dl_bg = detail_tex ? detail_tex : disp_thumb(sel);
|
||||||
|
save_photo_to_vita(it.idx);
|
||||||
|
g_dl_bg = NULL;
|
||||||
|
/* buttons used on the progress screen (O cancels)
|
||||||
|
* must not also act here */
|
||||||
|
do {
|
||||||
|
sceCtrlPeekBufferPositive(0, &pad, 1);
|
||||||
|
sceKernelDelayThread(10 * 1000);
|
||||||
|
} while (pad.buttons);
|
||||||
|
prev_buttons = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/* left/right may have moved the selection; refresh the
|
/* left/right may have moved the selection; refresh the
|
||||||
* item we load + draw below (the X handler above acted
|
* item we load + draw below (the X handler above acted
|
||||||
@@ -1287,6 +1352,7 @@ int main(void)
|
|||||||
it = g_disp[sel];
|
it = g_disp[sel];
|
||||||
is_local = (it.src == SRC_LOCAL);
|
is_local = (it.src == SRC_LOCAL);
|
||||||
is_video = disp_is_video(sel);
|
is_video = disp_is_video(sel);
|
||||||
|
can_save = !is_local && !is_video;
|
||||||
|
|
||||||
/* collect a finished neighbor prefetch (and drop it
|
/* collect a finished neighbor prefetch (and drop it
|
||||||
* if browsing already moved past its neighborhood) */
|
* if browsing already moved past its neighborhood) */
|
||||||
@@ -1452,8 +1518,7 @@ int main(void)
|
|||||||
}
|
}
|
||||||
pf_skip:
|
pf_skip:
|
||||||
|
|
||||||
vita2d_start_drawing();
|
view_begin_drawing();
|
||||||
vita2d_clear_screen();
|
|
||||||
|
|
||||||
int sliding = (slide_anim ||
|
int sliding = (slide_anim ||
|
||||||
slide_x > 0.5f || slide_x < -0.5f);
|
slide_x > 0.5f || slide_x < -0.5f);
|
||||||
@@ -1529,23 +1594,25 @@ pf_skip:
|
|||||||
char hud[200];
|
char hud[200];
|
||||||
if (zoom > 1.001f)
|
if (zoom > 1.001f)
|
||||||
snprintf(hud, sizeof(hud),
|
snprintf(hud, sizeof(hud),
|
||||||
"%d / %d %.0f%% stick/d-pad pan L/R zoom O back",
|
"%d / %d %.0f%% stick/d-pad pan R-stick zoom O back",
|
||||||
sel + 1, g_disp_count, zoom * 100.0f);
|
sel + 1, g_disp_count, zoom * 100.0f);
|
||||||
else
|
else
|
||||||
snprintf(hud, sizeof(hud),
|
snprintf(hud, sizeof(hud),
|
||||||
"%d / %d %.19s < > browse L/R zoom %sO back",
|
"%d / %d %.19s < > browse L/R turn %sO back",
|
||||||
sel + 1, g_disp_count,
|
sel + 1, g_disp_count,
|
||||||
g_disp_count > 0 ? disp_date(sel) : "",
|
g_disp_count > 0 ? disp_date(sel) : "",
|
||||||
xhint);
|
xhint);
|
||||||
draw_hud(hud);
|
draw_hud(hud);
|
||||||
|
if (can_save && !zoomed && !sliding)
|
||||||
|
draw_save_button();
|
||||||
|
draw_toast();
|
||||||
/* throbber centred over the preview while the full-res
|
/* throbber centred over the preview while the full-res
|
||||||
* image is still loading */
|
* image is still loading */
|
||||||
if (detail_loading && disp_thumb(sel) && !zoomed && !sliding)
|
if (detail_loading && disp_thumb(sel) && !zoomed && !sliding)
|
||||||
draw_throbber(SCREEN_W / 2.0f, SCREEN_H / 2.0f,
|
draw_throbber(SCREEN_W / 2.0f, SCREEN_H / 2.0f,
|
||||||
24.0f, frame);
|
24.0f, frame);
|
||||||
|
|
||||||
vita2d_end_drawing();
|
view_end_drawing();
|
||||||
vita2d_swap_buffers();
|
|
||||||
} else if (mode == MODE_CLOUD) {
|
} else if (mode == MODE_CLOUD) {
|
||||||
/* server + backup overview, opened from the cloud
|
/* server + backup overview, opened from the cloud
|
||||||
* button on the search bar */
|
* button on the search bar */
|
||||||
@@ -1591,6 +1658,14 @@ pf_skip:
|
|||||||
int do_toggle = (pressed & SCE_CTRL_SQUARE) != 0;
|
int do_toggle = (pressed & SCE_CTRL_SQUARE) != 0;
|
||||||
int do_back = (pressed & SCE_CTRL_CIRCLE) != 0;
|
int do_back = (pressed & SCE_CTRL_CIRCLE) != 0;
|
||||||
int do_logout = 0;
|
int do_logout = 0;
|
||||||
|
/* album cover shape picker (right column): three
|
||||||
|
* segments, tap one or step with L / R */
|
||||||
|
float shx = 584, shy = 306, shw = 120, shh = 40;
|
||||||
|
int new_shape = g_cover_shape;
|
||||||
|
if (pressed & SCE_CTRL_LTRIGGER)
|
||||||
|
new_shape = (new_shape + COVER_COUNT - 1) % COVER_COUNT;
|
||||||
|
if (pressed & SCE_CTRL_RTRIGGER)
|
||||||
|
new_shape = (new_shape + 1) % COVER_COUNT;
|
||||||
/* O / Back button in the top-right corner */
|
/* O / Back button in the top-right corner */
|
||||||
float bkd = 36, bkcx = SCREEN_W - 30, bkcy = 36;
|
float bkd = 36, bkcx = SCREEN_W - 30, bkcy = 36;
|
||||||
/* Log out button in the top-left corner */
|
/* Log out button in the top-left corner */
|
||||||
@@ -1626,6 +1701,10 @@ pf_skip:
|
|||||||
touch_y >= loy &&
|
touch_y >= loy &&
|
||||||
touch_y <= loy + loh) {
|
touch_y <= loy + loh) {
|
||||||
do_logout = 1; /* top-left Log out */
|
do_logout = 1; /* top-left Log out */
|
||||||
|
} else if (touch_y >= shy && touch_y <= shy + shh &&
|
||||||
|
touch_x >= shx &&
|
||||||
|
touch_x < shx + COVER_COUNT * shw) {
|
||||||
|
new_shape = (int)((touch_x - shx) / shw);
|
||||||
} else if (touch_y >= tgy - 6 &&
|
} else if (touch_y >= tgy - 6 &&
|
||||||
touch_y <= tgy + tgh + 6 &&
|
touch_y <= tgy + tgh + 6 &&
|
||||||
touch_x >= 60 && touch_x <= tgx + tgw) {
|
touch_x >= 60 && touch_x <= tgx + tgw) {
|
||||||
@@ -1688,6 +1767,12 @@ pf_skip:
|
|||||||
log_line("cloud: auto-upload %s",
|
log_line("cloud: auto-upload %s",
|
||||||
g_autobackup ? "on" : "off");
|
g_autobackup ? "on" : "off");
|
||||||
}
|
}
|
||||||
|
if (new_shape != g_cover_shape) {
|
||||||
|
g_cover_shape = new_shape;
|
||||||
|
save_config();
|
||||||
|
log_line("cloud: album covers %s",
|
||||||
|
g_cover_shape_key[g_cover_shape]);
|
||||||
|
}
|
||||||
if (do_upload) {
|
if (do_upload) {
|
||||||
int q = queue_all_uploads();
|
int q = queue_all_uploads();
|
||||||
log_line("cloud: queued %d uploads", q);
|
log_line("cloud: queued %d uploads", q);
|
||||||
@@ -1791,6 +1876,39 @@ pf_skip:
|
|||||||
draw_ps_button(tgx - 23, tgy + tgh / 2, 38, ICON_SQUARE,
|
draw_ps_button(tgx - 23, tgy + tgh / 2, 38, ICON_SQUARE,
|
||||||
RGBA8(232, 120, 175, 255), 31);
|
RGBA8(232, 120, 175, 255), 31);
|
||||||
|
|
||||||
|
/* album cover shape: segmented control, the chosen
|
||||||
|
* shape highlighted, each with a small shape preview */
|
||||||
|
draw_text(shx, shy - 14, RGBA8(255, 255, 255, 255), 1.0f,
|
||||||
|
"Album covers");
|
||||||
|
for (int k = 0; k < COVER_COUNT; k++) {
|
||||||
|
float sx = shx + k * shw;
|
||||||
|
int on = (k == g_cover_shape);
|
||||||
|
vita2d_texture *seg = rounded_mask_tex((int)shw - 6,
|
||||||
|
(int)shh, 10.0f);
|
||||||
|
uint32_t sc = on ? RGBA8(94, 110, 215, 255)
|
||||||
|
: RGBA8(46, 46, 52, 255);
|
||||||
|
if (seg)
|
||||||
|
vita2d_draw_texture_tint(seg, sx, shy, sc);
|
||||||
|
else
|
||||||
|
vita2d_draw_rectangle(sx, shy, shw - 6, shh, sc);
|
||||||
|
/* the shape itself, 20 px tall at most */
|
||||||
|
float pw = k == COVER_VERTICAL ? 15 :
|
||||||
|
k == COVER_SQUARE ? 20 : 26;
|
||||||
|
float ph = k == COVER_WIDE ? 15 : 20;
|
||||||
|
vita2d_draw_rectangle(sx + 8, shy + (shh - ph) / 2,
|
||||||
|
pw, ph,
|
||||||
|
on ? RGBA8(235, 235, 245, 255)
|
||||||
|
: RGBA8(130, 130, 138, 255));
|
||||||
|
draw_text(sx + 38, shy + shh - 14,
|
||||||
|
on ? RGBA8(245, 245, 250, 255) : dim, 0.7f,
|
||||||
|
g_cover_shape_label[k]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const char *h = "L / R";
|
||||||
|
draw_text(shx + COVER_COUNT * shw - 6 -
|
||||||
|
text_width(0.75f, h), shy - 14, dim, 0.75f, h);
|
||||||
|
}
|
||||||
|
|
||||||
/* See details + Upload all buttons */
|
/* See details + Upload all buttons */
|
||||||
vita2d_texture *db = rounded_mask_tex((int)dbw, (int)bth, 10.0f);
|
vita2d_texture *db = rounded_mask_tex((int)dbw, (int)bth, 10.0f);
|
||||||
if (db)
|
if (db)
|
||||||
@@ -1992,7 +2110,12 @@ pf_skip:
|
|||||||
} else if (act == ALB_REFRESH) {
|
} else if (act == ALB_REFRESH) {
|
||||||
albums_release_covers();
|
albums_release_covers();
|
||||||
g_album_state = 0;
|
g_album_state = 0;
|
||||||
} else if (act == ALB_OPEN && g_alb_sel < g_album_len) {
|
} else if (act == ALB_SEARCH) {
|
||||||
|
char q[128] = "";
|
||||||
|
if (ime_input("Search albums", g_alb_query, q,
|
||||||
|
sizeof(q), 0))
|
||||||
|
albums_set_query(q);
|
||||||
|
} else if (act == ALB_OPEN && g_alb_sel < g_alb_vlen) {
|
||||||
/* fetch the members on a worker thread; the main
|
/* fetch the members on a worker thread; the main
|
||||||
* thread only animates the throbber meanwhile */
|
* thread only animates the throbber meanwhile */
|
||||||
albums_release_covers();
|
albums_release_covers();
|
||||||
@@ -2003,7 +2126,7 @@ pf_skip:
|
|||||||
g_search_active = 0;
|
g_search_active = 0;
|
||||||
if (g_gallery_active)
|
if (g_gallery_active)
|
||||||
gallery_close();
|
gallery_close();
|
||||||
g_album_open = g_alb_sel;
|
g_album_open = g_alb_view[g_alb_sel];
|
||||||
g_album_assets_done = 0;
|
g_album_assets_done = 0;
|
||||||
SceUID ot = sceKernelCreateThread("album_open",
|
SceUID ot = sceKernelCreateThread("album_open",
|
||||||
album_open_thread, 0x10000100,
|
album_open_thread, 0x10000100,
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ static int load_config(void)
|
|||||||
}
|
}
|
||||||
} else if (!strncmp(s, "syncmaxmb=", 10))
|
} else if (!strncmp(s, "syncmaxmb=", 10))
|
||||||
g_syncmax_mb = atol(clean_line(s + 10));
|
g_syncmax_mb = atol(clean_line(s + 10));
|
||||||
|
else if (!strncmp(s, "covershape=", 11)) {
|
||||||
|
const char *v = clean_line(s + 11);
|
||||||
|
for (int k = 0; k < COVER_COUNT; k++)
|
||||||
|
if (!strcmp(v, g_cover_shape_key[k]))
|
||||||
|
g_cover_shape = k;
|
||||||
|
}
|
||||||
if (!eolsave)
|
if (!eolsave)
|
||||||
break;
|
break;
|
||||||
lp = eol + 1;
|
lp = eol + 1;
|
||||||
|
|||||||
+94
-16
@@ -65,6 +65,74 @@ static int disp_cmp(const void *a, const void *b)
|
|||||||
return strcmp(dy, dx); /* newest first */
|
return strcmp(dy, dx); /* newest first */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the open album, sorted by name: one flat grid without month headers */
|
||||||
|
static int layout_flat(void)
|
||||||
|
{
|
||||||
|
return g_album_active && g_album_sort >= ASORT_NAME_AZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int disp_cmp_old(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return -disp_cmp(a, b); /* oldest first */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* case-insensitive "natural" order: digit runs compare by value, so
|
||||||
|
* IMG_2 sorts before IMG_10 */
|
||||||
|
static int name_cmp(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
while (*a && *b) {
|
||||||
|
if (isdigit((unsigned char)*a) && isdigit((unsigned char)*b)) {
|
||||||
|
while (*a == '0')
|
||||||
|
a++;
|
||||||
|
while (*b == '0')
|
||||||
|
b++;
|
||||||
|
const char *ea = a, *eb = b;
|
||||||
|
while (isdigit((unsigned char)*ea))
|
||||||
|
ea++;
|
||||||
|
while (isdigit((unsigned char)*eb))
|
||||||
|
eb++;
|
||||||
|
if (ea - a != eb - b)
|
||||||
|
return (int)(ea - a) - (int)(eb - b);
|
||||||
|
int r = strncmp(a, b, ea - a);
|
||||||
|
if (r)
|
||||||
|
return r;
|
||||||
|
a = ea;
|
||||||
|
b = eb;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int ca = tolower((unsigned char)*a), cb = tolower((unsigned char)*b);
|
||||||
|
if (ca != cb)
|
||||||
|
return ca - cb;
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
return (unsigned char)*a - (unsigned char)*b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int disp_cmp_name(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
const struct disp_item *x = a, *y = b;
|
||||||
|
int r = name_cmp(g_asset_names[x->idx], g_asset_names[y->idx]);
|
||||||
|
return r ? r : disp_cmp(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int disp_cmp_name_desc(const void *a, const void *b)
|
||||||
|
{
|
||||||
|
return -disp_cmp_name(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* group of a slot: its "YYYY-MM" month, or in a name-sorted album the
|
||||||
|
* file name's first letter (what L/R jumps between) */
|
||||||
|
static void disp_group(int d, char *out, size_t len)
|
||||||
|
{
|
||||||
|
if (layout_flat()) {
|
||||||
|
const char *n = g_asset_names[g_disp[d].idx];
|
||||||
|
snprintf(out, len, "%c", toupper((unsigned char)n[0]));
|
||||||
|
} else {
|
||||||
|
snprintf(out, len, "%.7s", disp_date(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* turn a sortable "YYYY-MM-..." date into a "Month YYYY" section title */
|
/* turn a sortable "YYYY-MM-..." date into a "Month YYYY" section title */
|
||||||
static void month_label(const char *date, char *out, size_t len)
|
static void month_label(const char *date, char *out, size_t len)
|
||||||
{
|
{
|
||||||
@@ -133,12 +201,13 @@ static void layout_grid(void)
|
|||||||
int row_start = 0;
|
int row_start = 0;
|
||||||
char curkey[8] = "";
|
char curkey[8] = "";
|
||||||
|
|
||||||
|
int flat = layout_flat();
|
||||||
for (int d = 0; d < g_disp_count; d++) {
|
for (int d = 0; d < g_disp_count; d++) {
|
||||||
const char *date = disp_date(d);
|
const char *date = disp_date(d);
|
||||||
char key[8];
|
char key[8];
|
||||||
snprintf(key, sizeof(key), "%.7s", date); /* YYYY-MM */
|
snprintf(key, sizeof(key), "%.7s", flat ? "" : date); /* YYYY-MM */
|
||||||
|
|
||||||
if (strcmp(key, curkey) != 0) {
|
if (!flat && strcmp(key, curkey) != 0) {
|
||||||
if (d > row_start) /* month's partial last row */
|
if (d > row_start) /* month's partial last row */
|
||||||
layout_finish_row(row_start, d, &y, natw, 0);
|
layout_finish_row(row_start, d, &y, natw, 0);
|
||||||
row_start = d;
|
row_start = d;
|
||||||
@@ -200,7 +269,8 @@ static void rebuild_display(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* album mode: the open album's members (server assets), date-grouped
|
/* album mode: the open album's members (server assets), date-grouped
|
||||||
* like search results; placeholders without a date are skipped */
|
* like search results or in a flat name order (g_album_sort);
|
||||||
|
* placeholders without a date are skipped */
|
||||||
if (g_album_active) {
|
if (g_album_active) {
|
||||||
if (!grow_disp(g_album_idx_count > 0 ? g_album_idx_count : 1))
|
if (!grow_disp(g_album_idx_count > 0 ? g_album_idx_count : 1))
|
||||||
return;
|
return;
|
||||||
@@ -214,7 +284,12 @@ static void rebuild_display(void)
|
|||||||
n++;
|
n++;
|
||||||
}
|
}
|
||||||
g_disp_count = n;
|
g_disp_count = n;
|
||||||
qsort(g_disp, n, sizeof(g_disp[0]), disp_cmp);
|
int (*cmp)(const void *, const void *) =
|
||||||
|
g_album_sort == ASORT_DATE_OLD ? disp_cmp_old :
|
||||||
|
g_album_sort == ASORT_NAME_AZ ? disp_cmp_name :
|
||||||
|
g_album_sort == ASORT_NAME_ZA ? disp_cmp_name_desc :
|
||||||
|
disp_cmp;
|
||||||
|
qsort(g_disp, n, sizeof(g_disp[0]), cmp);
|
||||||
layout_grid();
|
layout_grid();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -297,9 +372,9 @@ static void rebuild_keep_view(int *sel, float *scroll, float *target)
|
|||||||
*sel = 0;
|
*sel = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* first item of the month-run after (dir>0, older) or before (dir<0, newer)
|
/* first item of the group-run after (dir>0) or before (dir<0) the one
|
||||||
* the one containing display slot d; the timeline is "YYYY-MM"-grouped and
|
* containing display slot d; the grid is sorted, so a month (or, in a
|
||||||
* date-descending, so a month is a contiguous run of equal 7-char prefixes */
|
* name-sorted album, an initial letter) is a contiguous run (disp_group) */
|
||||||
static int month_jump(int d, int dir)
|
static int month_jump(int d, int dir)
|
||||||
{
|
{
|
||||||
if (g_disp_count == 0)
|
if (g_disp_count == 0)
|
||||||
@@ -308,25 +383,28 @@ static int month_jump(int d, int dir)
|
|||||||
d = 0;
|
d = 0;
|
||||||
if (d >= g_disp_count)
|
if (d >= g_disp_count)
|
||||||
d = g_disp_count - 1;
|
d = g_disp_count - 1;
|
||||||
const char *cur = disp_date(d);
|
char cur[8], k[8];
|
||||||
|
disp_group(d, cur, sizeof(cur));
|
||||||
int i = d;
|
int i = d;
|
||||||
if (dir > 0) {
|
if (dir > 0) {
|
||||||
while (i < g_disp_count - 1) {
|
while (i < g_disp_count - 1) {
|
||||||
i++;
|
i++;
|
||||||
if (strncmp(disp_date(i), cur, 7))
|
disp_group(i, k, sizeof(k));
|
||||||
return i; /* first item of the next month */
|
if (strcmp(k, cur))
|
||||||
|
return i; /* first item of the next group */
|
||||||
}
|
}
|
||||||
return g_disp_count - 1; /* already in the last month */
|
return g_disp_count - 1; /* already in the last group */
|
||||||
}
|
}
|
||||||
/* back to the start of the current month-run */
|
/* back to the start of the current run */
|
||||||
while (i > 0 && !strncmp(disp_date(i - 1), cur, 7))
|
while (i > 0 && (disp_group(i - 1, k, sizeof(k)), !strcmp(k, cur)))
|
||||||
i--;
|
i--;
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
return 0;
|
return 0;
|
||||||
/* then to the start of the previous (newer) month-run */
|
/* then to the start of the previous run */
|
||||||
const char *prev = disp_date(i - 1);
|
char prev[8];
|
||||||
|
disp_group(i - 1, prev, sizeof(prev));
|
||||||
i--;
|
i--;
|
||||||
while (i > 0 && !strncmp(disp_date(i - 1), prev, 7))
|
while (i > 0 && (disp_group(i - 1, k, sizeof(k)), !strcmp(k, prev)))
|
||||||
i--;
|
i--;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|||||||
+189
@@ -0,0 +1,189 @@
|
|||||||
|
/* vitaImmich unity build — part of one translation unit.
|
||||||
|
* This file is #included by src/main.c (in order) and shares all
|
||||||
|
* file-scope state with the other parts; it is NOT compiled on its own.
|
||||||
|
* Only src/main.c is listed in CMakeLists.txt. Editors/clangd may flag
|
||||||
|
* undefined symbols here because of that — those are expected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* save a server photo to the Vita's Photos app */
|
||||||
|
/* */
|
||||||
|
/* Triangle in the full-screen viewer. The original is downloaded and */
|
||||||
|
/* handed to ScePhotoExport, which copies it into ux0:picture and */
|
||||||
|
/* registers it with the Photos app. The Vita only takes JPEG/PNG, so */
|
||||||
|
/* an original in another format (HEIC, RAW, WebP...) — or one the */
|
||||||
|
/* exporter rejects — falls back to Immich's full-size JPEG, then to */
|
||||||
|
/* the preview. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
#define EXPORT_TMP_PATH CONFIG_DIR "/export.tmp"
|
||||||
|
#define EXPORT_WORK_SIZE (4 * 1024 * 1024)
|
||||||
|
|
||||||
|
/* a short status line drawn over the viewer for a few seconds */
|
||||||
|
static char g_toast[200];
|
||||||
|
static uint64_t g_toast_until;
|
||||||
|
|
||||||
|
static void show_toast(const char *text)
|
||||||
|
{
|
||||||
|
snprintf(g_toast, sizeof(g_toast), "%s", text);
|
||||||
|
g_toast_until = sceKernelGetProcessTimeWide() + 3 * 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void draw_toast(void)
|
||||||
|
{
|
||||||
|
if (!g_toast[0] || sceKernelGetProcessTimeWide() > g_toast_until)
|
||||||
|
return;
|
||||||
|
int w = text_width(0.9f, g_toast) + 40;
|
||||||
|
float x = (SCREEN_W - w) / 2.0f, y = SCREEN_H - 78;
|
||||||
|
vita2d_texture *pill = rounded_mask_tex(w, 40, 12.0f);
|
||||||
|
if (pill)
|
||||||
|
vita2d_draw_texture_tint(pill, x, y, RGBA8(20, 20, 24, 225));
|
||||||
|
else
|
||||||
|
vita2d_draw_rectangle(x, y, w, 40, RGBA8(20, 20, 24, 225));
|
||||||
|
draw_text(x + 20, y + 27, RGBA8(240, 240, 245, 255), 0.9f, g_toast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* extension of a JPEG/PNG file by its magic bytes, NULL for anything else */
|
||||||
|
static const char *image_ext(const char *path)
|
||||||
|
{
|
||||||
|
unsigned char m[4] = { 0 };
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f)
|
||||||
|
return NULL;
|
||||||
|
size_t n = fread(m, 1, sizeof(m), f);
|
||||||
|
fclose(f);
|
||||||
|
if (n >= 3 && m[0] == 0xFF && m[1] == 0xD8 && m[2] == 0xFF)
|
||||||
|
return "jpg";
|
||||||
|
if (n >= 4 && !memcmp(m, "\x89PNG", 4))
|
||||||
|
return "png";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* UTF-8 file name (extension dropped) as the UTF-32 photo title */
|
||||||
|
static void export_title(const char *name, SceWChar32 *out, int cap)
|
||||||
|
{
|
||||||
|
const char *dot = strrchr(name, '.');
|
||||||
|
const char *end = dot && dot != name ? dot : name + strlen(name);
|
||||||
|
int n = 0;
|
||||||
|
while (name < end && n < cap - 1) {
|
||||||
|
unsigned char c = (unsigned char)*name;
|
||||||
|
unsigned int cp = '?';
|
||||||
|
int len = 1;
|
||||||
|
if (c < 0x80)
|
||||||
|
cp = c;
|
||||||
|
else if ((c >> 5) == 0x6 && name + 1 < end)
|
||||||
|
cp = ((c & 0x1F) << 6) | (name[1] & 0x3F), len = 2;
|
||||||
|
else if ((c >> 4) == 0xE && name + 2 < end)
|
||||||
|
cp = ((c & 0x0F) << 12) | ((name[1] & 0x3F) << 6) |
|
||||||
|
(name[2] & 0x3F), len = 3;
|
||||||
|
else if ((c >> 3) == 0x1E && name + 3 < end)
|
||||||
|
cp = ((c & 0x07) << 18) | ((name[1] & 0x3F) << 12) |
|
||||||
|
((name[2] & 0x3F) << 6) | (name[3] & 0x3F), len = 4;
|
||||||
|
out[n++] = cp;
|
||||||
|
name += len;
|
||||||
|
}
|
||||||
|
out[n] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* save server asset idx to the Photos app; reports through the toast */
|
||||||
|
static void save_photo_to_vita(int idx)
|
||||||
|
{
|
||||||
|
if (g_asset_is_video[idx]) {
|
||||||
|
show_toast("Only photos can be saved to the Vita");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static void *work;
|
||||||
|
if (!work)
|
||||||
|
work = memalign(64, EXPORT_WORK_SIZE);
|
||||||
|
if (!work) {
|
||||||
|
show_toast("Not enough memory to save the photo");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *const sources[] = {
|
||||||
|
"original", "thumbnail?size=fullsize", "thumbnail?size=preview",
|
||||||
|
};
|
||||||
|
static SceWChar32 title[64];
|
||||||
|
export_title(g_asset_names[idx][0] ? g_asset_names[idx] : "Immich",
|
||||||
|
title, 64);
|
||||||
|
g_dl_what = "photo";
|
||||||
|
char err[160] = "", file[96], out[256];
|
||||||
|
int saved = 0;
|
||||||
|
for (unsigned s = 0; s < sizeof(sources) / sizeof(sources[0]) && !saved; s++) {
|
||||||
|
char url[700];
|
||||||
|
snprintf(url, sizeof(url), "%s/api/assets/%s/%s", g_server,
|
||||||
|
g_asset_ids[idx], sources[s]);
|
||||||
|
int r = download_to_file(url, EXPORT_TMP_PATH, err, sizeof(err));
|
||||||
|
if (r == 1) {
|
||||||
|
show_toast("Save cancelled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
continue;
|
||||||
|
const char *ext = image_ext(EXPORT_TMP_PATH);
|
||||||
|
if (!ext) {
|
||||||
|
log_line("export %s: %s is not JPEG/PNG", g_asset_ids[idx],
|
||||||
|
sources[s]);
|
||||||
|
snprintf(err, sizeof(err), "unsupported image format");
|
||||||
|
sceIoRemove(EXPORT_TMP_PATH);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* the exporter goes by the extension */
|
||||||
|
snprintf(file, sizeof(file), CONFIG_DIR "/export.%s", ext);
|
||||||
|
sceIoRemove(file);
|
||||||
|
sceIoRename(EXPORT_TMP_PATH, file);
|
||||||
|
|
||||||
|
/* the SDK doesn't document `version`; try the firmware's 3.15
|
||||||
|
* value, then 0, and log each result for diagnosis */
|
||||||
|
static const int versions[] = { 0x03150021, 0 };
|
||||||
|
int ret = -1;
|
||||||
|
for (unsigned v = 0; v < 2 && ret < 0; v++) {
|
||||||
|
PhotoExportParam param;
|
||||||
|
memset(¶m, 0, sizeof(param));
|
||||||
|
param.version = versions[v];
|
||||||
|
param.photoTitle = title;
|
||||||
|
out[0] = '\0';
|
||||||
|
ret = scePhotoExportFromFile(file, ¶m, work, NULL, NULL,
|
||||||
|
out, sizeof(out));
|
||||||
|
log_line("export %s (%s, v%08X): 0x%08X -> %s",
|
||||||
|
g_asset_ids[idx], sources[s], versions[v], ret,
|
||||||
|
out);
|
||||||
|
}
|
||||||
|
sceIoRemove(file);
|
||||||
|
if (ret >= 0)
|
||||||
|
saved = 1;
|
||||||
|
else
|
||||||
|
snprintf(err, sizeof(err), "export error 0x%08X", ret);
|
||||||
|
}
|
||||||
|
if (saved) {
|
||||||
|
show_toast("Saved to the Photos app");
|
||||||
|
} else {
|
||||||
|
char msg[200];
|
||||||
|
snprintf(msg, sizeof(msg), "Could not save the photo (%s)", err);
|
||||||
|
show_toast(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the viewer's "Save to Vita" button (bottom right; Triangle or a tap) */
|
||||||
|
#define SAVE_BTN_W 176.0f
|
||||||
|
#define SAVE_BTN_H 40.0f
|
||||||
|
#define SAVE_BTN_X (SCREEN_W - SAVE_BTN_W - 16.0f)
|
||||||
|
#define SAVE_BTN_Y (SCREEN_H - SAVE_BTN_H - 16.0f)
|
||||||
|
|
||||||
|
static void draw_save_button(void)
|
||||||
|
{
|
||||||
|
vita2d_texture *bg = rounded_mask_tex((int)SAVE_BTN_W, (int)SAVE_BTN_H, 12.0f);
|
||||||
|
if (bg)
|
||||||
|
vita2d_draw_texture_tint(bg, SAVE_BTN_X, SAVE_BTN_Y,
|
||||||
|
RGBA8(20, 20, 24, 200));
|
||||||
|
draw_ps_button(SAVE_BTN_X + 22, SAVE_BTN_Y + SAVE_BTN_H / 2, 28, ICON_TRI,
|
||||||
|
RGBA8(90, 200, 170, 255), 18);
|
||||||
|
draw_text(SAVE_BTN_X + 44, SAVE_BTN_Y + 27, RGBA8(235, 235, 240, 255),
|
||||||
|
0.85f, "Save to Vita");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int save_button_hit(float x, float y)
|
||||||
|
{
|
||||||
|
return x >= SAVE_BTN_X && x < SAVE_BTN_X + SAVE_BTN_W &&
|
||||||
|
y >= SAVE_BTN_Y && y < SAVE_BTN_Y + SAVE_BTN_H;
|
||||||
|
}
|
||||||
+11
-2
@@ -17,6 +17,8 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
@@ -40,6 +42,7 @@
|
|||||||
#include <psp2/net/netctl.h>
|
#include <psp2/net/netctl.h>
|
||||||
#include <psp2/sysmodule.h>
|
#include <psp2/sysmodule.h>
|
||||||
#include <psp2/ime_dialog.h>
|
#include <psp2/ime_dialog.h>
|
||||||
|
#include <psp2/photoexport.h>
|
||||||
#include <psp2/common_dialog.h>
|
#include <psp2/common_dialog.h>
|
||||||
|
|
||||||
#include <vita2d.h>
|
#include <vita2d.h>
|
||||||
@@ -52,8 +55,11 @@
|
|||||||
#define JSMN_PARENT_LINKS
|
#define JSMN_PARENT_LINKS
|
||||||
#include "jsmn.h"
|
#include "jsmn.h"
|
||||||
|
|
||||||
#define SCREEN_W 960
|
/* the logical screen the UI lays itself out in: 960x544, except while the
|
||||||
#define SCREEN_H 544
|
* rotated photo viewer draws into its portrait render target (544x960) */
|
||||||
|
static int g_screen_w = 960, g_screen_h = 544;
|
||||||
|
#define SCREEN_W g_screen_w
|
||||||
|
#define SCREEN_H g_screen_h
|
||||||
|
|
||||||
#define COLS 5 /* rough items per row, for prefetch distances only */
|
#define COLS 5 /* rough items per row, for prefetch distances only */
|
||||||
#define ROW_H 200 /* justified-grid row height; widths follow aspect */
|
#define ROW_H 200 /* justified-grid row height; widths follow aspect */
|
||||||
@@ -122,6 +128,7 @@ static vita2d_texture *g_ic_both;
|
|||||||
* buffers), so a realloc here can't dangle a pointer under it. */
|
* buffers), so a realloc here can't dangle a pointer under it. */
|
||||||
static char (*g_asset_ids)[40];
|
static char (*g_asset_ids)[40];
|
||||||
static char (*g_asset_dates)[DATELEN]; /* YYYY-MM-DDTHH:MM:SS */
|
static char (*g_asset_dates)[DATELEN]; /* YYYY-MM-DDTHH:MM:SS */
|
||||||
|
static char (*g_asset_names)[64]; /* originalFileName, "" = unknown */
|
||||||
static float *g_asset_ratio; /* display w/h from exif, 0 = unknown */
|
static float *g_asset_ratio; /* display w/h from exif, 0 = unknown */
|
||||||
static unsigned char *g_asset_rot; /* raw exif orientation (0 = none) */
|
static unsigned char *g_asset_rot; /* raw exif orientation (0 = none) */
|
||||||
static unsigned char *g_asset_is_video;
|
static unsigned char *g_asset_is_video;
|
||||||
@@ -160,5 +167,7 @@ static char (*g_tex_err)[160];
|
|||||||
#include "map.c" /* geotag map: markers, clustering, projection */
|
#include "map.c" /* geotag map: markers, clustering, projection */
|
||||||
#include "albums.c" /* album list page + album open/fetch threads */
|
#include "albums.c" /* album list page + album open/fetch threads */
|
||||||
#include "video.c" /* video download + SceAvPlayer playback */
|
#include "video.c" /* video download + SceAvPlayer playback */
|
||||||
|
#include "export.c" /* save a photo to the Vita's Photos app */
|
||||||
|
#include "viewrot.c" /* L/R-turned photo viewer (render target) */
|
||||||
#include "sync.c" /* local scan/hash/check/upload + sync overview */
|
#include "sync.c" /* local scan/hash/check/upload + sync overview */
|
||||||
#include "app.c" /* main(): init, event loop, threads */
|
#include "app.c" /* main(): init, event loop, threads */
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ static int map_open_marker(int marker_idx, int *out_sel)
|
|||||||
idx = g_asset_count++;
|
idx = g_asset_count++;
|
||||||
snprintf(g_asset_ids[idx], sizeof(g_asset_ids[0]), "%s", id);
|
snprintf(g_asset_ids[idx], sizeof(g_asset_ids[0]), "%s", id);
|
||||||
snprintf(g_asset_dates[idx], sizeof(g_asset_dates[0]), "%s", dval);
|
snprintf(g_asset_dates[idx], sizeof(g_asset_dates[0]), "%s", dval);
|
||||||
|
g_asset_names[idx][0] = '\0';
|
||||||
g_asset_ratio[idx] = 0.0f; /* unknown: detail view fits anyway */
|
g_asset_ratio[idx] = 0.0f; /* unknown: detail view fits anyway */
|
||||||
g_asset_rot[idx] = 0;
|
g_asset_rot[idx] = 0;
|
||||||
g_asset_is_video[idx] = !strncmp(tval, "VIDEO", 5);
|
g_asset_is_video[idx] = !strncmp(tval, "VIDEO", 5);
|
||||||
@@ -722,6 +723,7 @@ static int asset_for_id(const char *id)
|
|||||||
int idx = g_asset_count++;
|
int idx = g_asset_count++;
|
||||||
snprintf(g_asset_ids[idx], sizeof(g_asset_ids[0]), "%s", id);
|
snprintf(g_asset_ids[idx], sizeof(g_asset_ids[0]), "%s", id);
|
||||||
g_asset_dates[idx][0] = '\0'; /* "" = unresolved; meta_worker fills it */
|
g_asset_dates[idx][0] = '\0'; /* "" = unresolved; meta_worker fills it */
|
||||||
|
g_asset_names[idx][0] = '\0';
|
||||||
g_asset_ratio[idx] = 0.0f;
|
g_asset_ratio[idx] = 0.0f;
|
||||||
g_asset_rot[idx] = 0;
|
g_asset_rot[idx] = 0;
|
||||||
g_asset_is_video[idx] = 0;
|
g_asset_is_video[idx] = 0;
|
||||||
|
|||||||
+21
@@ -140,13 +140,32 @@ static char (*g_album_thumb)[40]; /* cover asset id, "" when none */
|
|||||||
static int *g_album_nassets; /* assetCount reported by the server */
|
static int *g_album_nassets; /* assetCount reported by the server */
|
||||||
static unsigned char *g_album_shared; /* shared with other users */
|
static unsigned char *g_album_shared; /* shared with other users */
|
||||||
static unsigned char *g_album_has_link; /* has a public share link */
|
static unsigned char *g_album_has_link; /* has a public share link */
|
||||||
|
static char (*g_album_updated)[32]; /* updatedAt (ISO 8601, sorts as text) */
|
||||||
static int g_album_len; /* number of albums fetched */
|
static int g_album_len; /* number of albums fetched */
|
||||||
static int g_album_cap;
|
static int g_album_cap;
|
||||||
|
|
||||||
static int g_album_state;
|
static int g_album_state;
|
||||||
static volatile int g_album_fetching;
|
static volatile int g_album_fetching;
|
||||||
|
static volatile int g_album_list_dirty; /* list (re)fetched: rebuild the view */
|
||||||
|
|
||||||
static int g_album_active; /* grid is showing one album's assets */
|
static int g_album_active; /* grid is showing one album's assets */
|
||||||
|
/* order of the open album's grid, cycled by SELECT: date newest / oldest
|
||||||
|
* (month headers) or file name A-Z / Z-A (one flat grid, no headers) */
|
||||||
|
enum { ASORT_DATE_NEW, ASORT_DATE_OLD, ASORT_NAME_AZ, ASORT_NAME_ZA, ASORT_COUNT };
|
||||||
|
static int g_album_sort;
|
||||||
|
static const char *const g_album_sort_label[ASORT_COUNT] = {
|
||||||
|
"Newest first", "Oldest first", "Name A-Z", "Name Z-A",
|
||||||
|
};
|
||||||
|
/* shape of the album page's cover thumbnails (setting on the Server &
|
||||||
|
* Backup page, saved as covershape= in config.txt) */
|
||||||
|
enum { COVER_VERTICAL, COVER_SQUARE, COVER_WIDE, COVER_COUNT };
|
||||||
|
static int g_cover_shape = COVER_SQUARE;
|
||||||
|
static const char *const g_cover_shape_key[COVER_COUNT] = {
|
||||||
|
"vertical", "square", "wide",
|
||||||
|
};
|
||||||
|
static const char *const g_cover_shape_label[COVER_COUNT] = {
|
||||||
|
"Vertical", "Square", "Panoramic",
|
||||||
|
};
|
||||||
static int g_album_open = -1; /* index into g_album_* being shown */
|
static int g_album_open = -1; /* index into g_album_* being shown */
|
||||||
static int *g_album_idx; /* g_asset indices of its members */
|
static int *g_album_idx; /* g_asset indices of its members */
|
||||||
static int g_album_idx_count;
|
static int g_album_idx_count;
|
||||||
@@ -218,6 +237,7 @@ static int grow_assets(int need)
|
|||||||
return 1;
|
return 1;
|
||||||
if (!GROW(g_asset_ids, c, need) ||
|
if (!GROW(g_asset_ids, c, need) ||
|
||||||
!GROW(g_asset_dates, c, need) ||
|
!GROW(g_asset_dates, c, need) ||
|
||||||
|
!GROW(g_asset_names, c, need) ||
|
||||||
!GROW(g_asset_ratio, c, need) ||
|
!GROW(g_asset_ratio, c, need) ||
|
||||||
!GROW(g_asset_rot, c, need) ||
|
!GROW(g_asset_rot, c, need) ||
|
||||||
!GROW(g_asset_is_video, c, need) ||
|
!GROW(g_asset_is_video, c, need) ||
|
||||||
@@ -301,6 +321,7 @@ static int grow_albums(int need)
|
|||||||
!GROW(g_album_nassets, c, need) ||
|
!GROW(g_album_nassets, c, need) ||
|
||||||
!GROW(g_album_shared, c, need) ||
|
!GROW(g_album_shared, c, need) ||
|
||||||
!GROW(g_album_has_link, c, need) ||
|
!GROW(g_album_has_link, c, need) ||
|
||||||
|
!GROW(g_album_updated, c, need) ||
|
||||||
!(g_album_cap = GROW(g_album_cover, c, need)))
|
!(g_album_cap = GROW(g_album_cover, c, need)))
|
||||||
return 0;
|
return 0;
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ static void draw_sel_outline(float x, float y, float w, float h)
|
|||||||
* texture's bilinear sampling are what soften the corners. */
|
* texture's bilinear sampling are what soften the corners. */
|
||||||
static vita2d_texture *rounded_mask_tex(int w, int h, float r)
|
static vita2d_texture *rounded_mask_tex(int w, int h, float r)
|
||||||
{
|
{
|
||||||
enum { NSLOT = 14 }; /* a small fixed set of shapes (pill, buttons, ...) */
|
enum { NSLOT = 32 }; /* a small fixed set of shapes (pill, buttons, ...) */
|
||||||
static vita2d_texture *cache[NSLOT];
|
static vita2d_texture *cache[NSLOT];
|
||||||
static int cw[NSLOT], ch[NSLOT];
|
static int cw[NSLOT], ch[NSLOT];
|
||||||
static float cr[NSLOT];
|
static float cr[NSLOT];
|
||||||
@@ -473,11 +473,17 @@ static void draw_search_bar(float yoff, int focus)
|
|||||||
|
|
||||||
float tx = BAR_PX + 44, ty = by + BAR_H - 13;
|
float tx = BAR_PX + 44, ty = by + BAR_H - 13;
|
||||||
if (g_album_active && g_album_open >= 0 && g_album_open < g_album_len) {
|
if (g_album_active && g_album_open >= 0 && g_album_open < g_album_len) {
|
||||||
/* inside an album: its name, and the O chip to go back */
|
/* inside an album: its name, the order (SELECT cycles it) and
|
||||||
|
* the O chip to go back */
|
||||||
char q[80];
|
char q[80];
|
||||||
snprintf(q, sizeof(q), "%.48s", g_album_name[g_album_open][0] ?
|
snprintf(q, sizeof(q), "%.40s", g_album_name[g_album_open][0] ?
|
||||||
g_album_name[g_album_open] : "(untitled album)");
|
g_album_name[g_album_open] : "(untitled album)");
|
||||||
draw_text(tx, ty, RGBA8(235, 235, 240, 255), 0.95f, q);
|
draw_text(tx, ty, RGBA8(235, 235, 240, 255), 0.95f, q);
|
||||||
|
char srt[40];
|
||||||
|
snprintf(srt, sizeof(srt), "SELECT %s",
|
||||||
|
g_album_sort_label[g_album_sort]);
|
||||||
|
draw_text(BAR_CLEAR_CX - 24 - text_width(0.8f, srt), ty,
|
||||||
|
RGBA8(140, 140, 148, 255), 0.8f, srt);
|
||||||
int gs = 26;
|
int gs = 26;
|
||||||
vita2d_texture *og = icon_tex(ICON_CIRCLE, gs);
|
vita2d_texture *og = icon_tex(ICON_CIRCLE, gs);
|
||||||
if (og)
|
if (og)
|
||||||
@@ -622,6 +628,8 @@ static void save_config(void)
|
|||||||
fprintf(f, "syncdir=%s\n", g_syncdirs[i]);
|
fprintf(f, "syncdir=%s\n", g_syncdirs[i]);
|
||||||
if (g_syncmax_mb != 512)
|
if (g_syncmax_mb != 512)
|
||||||
fprintf(f, "syncmaxmb=%ld\n", g_syncmax_mb);
|
fprintf(f, "syncmaxmb=%ld\n", g_syncmax_mb);
|
||||||
|
if (g_cover_shape != COVER_SQUARE)
|
||||||
|
fprintf(f, "covershape=%s\n", g_cover_shape_key[g_cover_shape]);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-19
@@ -52,6 +52,8 @@ static size_t file_write_cb(void *ptr, size_t size, size_t nmemb, void *ud)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t g_dl_last_draw;
|
static uint64_t g_dl_last_draw;
|
||||||
|
/* what the progress screen says is downloading ("video", "photo") */
|
||||||
|
static const char *g_dl_what = "video";
|
||||||
/* poster/thumb drawn behind the download progress and the player's startup
|
/* poster/thumb drawn behind the download progress and the player's startup
|
||||||
* (the seconds AVPlayer spends parsing before the first frame); set by the
|
* (the seconds AVPlayer spends parsing before the first frame); set by the
|
||||||
* play call sites, valid for the whole modal download + playback */
|
* play call sites, valid for the whole modal download + playback */
|
||||||
@@ -121,11 +123,12 @@ static int dl_progress_cb(void *ud, curl_off_t dltotal, curl_off_t dlnow,
|
|||||||
|
|
||||||
char buf[128];
|
char buf[128];
|
||||||
if (dltotal > 0)
|
if (dltotal > 0)
|
||||||
snprintf(buf, sizeof(buf), "Downloading video... %.1f / %.1f MB",
|
snprintf(buf, sizeof(buf), "Downloading %s... %.1f / %.1f MB",
|
||||||
dlnow / (1024.0 * 1024.0), dltotal / (1024.0 * 1024.0));
|
g_dl_what, dlnow / (1024.0 * 1024.0),
|
||||||
|
dltotal / (1024.0 * 1024.0));
|
||||||
else
|
else
|
||||||
snprintf(buf, sizeof(buf), "Downloading video... %.1f MB",
|
snprintf(buf, sizeof(buf), "Downloading %s... %.1f MB",
|
||||||
dlnow / (1024.0 * 1024.0));
|
g_dl_what, dlnow / (1024.0 * 1024.0));
|
||||||
|
|
||||||
vita2d_start_drawing();
|
vita2d_start_drawing();
|
||||||
vita2d_clear_screen();
|
vita2d_clear_screen();
|
||||||
@@ -149,17 +152,15 @@ static int dl_progress_cb(void *ud, curl_off_t dltotal, curl_off_t dlnow,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* download the playback stream of asset idx to VIDEO_TMP_PATH.
|
/* download `url` (an authenticated server path) to the file `path`, with
|
||||||
* returns 0 on success, 1 if the user cancelled, -1 on error. */
|
* the progress screen (O cancels). returns 0 on success, 1 if the user
|
||||||
static int download_video(int idx, char *err, size_t errlen)
|
* cancelled, -1 on error (`err` filled); the file is removed unless 0. */
|
||||||
|
static int download_to_file(const char *url, const char *path, char *err,
|
||||||
|
size_t errlen)
|
||||||
{
|
{
|
||||||
char url[700];
|
FILE *f = fopen(path, "wb");
|
||||||
snprintf(url, sizeof(url), "%s/api/assets/%s/video/playback",
|
|
||||||
g_server, g_asset_ids[idx]);
|
|
||||||
|
|
||||||
FILE *f = fopen(VIDEO_TMP_PATH, "wb");
|
|
||||||
if (!f) {
|
if (!f) {
|
||||||
snprintf(err, errlen, "cannot create %s", VIDEO_TMP_PATH);
|
snprintf(err, errlen, "cannot create %s", path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +181,7 @@ static int download_video(int idx, char *err, size_t errlen)
|
|||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
|
||||||
/* no overall timeout (videos can be big); abort on a 30 s stall */
|
/* no overall timeout (files can be big); abort on a 30 s stall */
|
||||||
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L);
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1024L);
|
||||||
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L);
|
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 30L);
|
||||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
||||||
@@ -200,30 +201,41 @@ static int download_video(int idx, char *err, size_t errlen)
|
|||||||
if (res == CURLE_OK)
|
if (res == CURLE_OK)
|
||||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
||||||
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &dl);
|
||||||
log_line("video dl %s: res=%d code=%ld bytes=%lld",
|
log_line("%s dl %s: res=%d code=%ld bytes=%lld", g_dl_what, url, res,
|
||||||
g_asset_ids[idx], res, code, (long long)dl);
|
code, (long long)dl);
|
||||||
curl_slist_free_all(hdrs);
|
curl_slist_free_all(hdrs);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
|
|
||||||
if (res == CURLE_ABORTED_BY_CALLBACK) {
|
if (res == CURLE_ABORTED_BY_CALLBACK) {
|
||||||
sceIoRemove(VIDEO_TMP_PATH);
|
sceIoRemove(path);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
snprintf(err, errlen, "curl error %d: %s", res,
|
snprintf(err, errlen, "curl error %d: %s", res,
|
||||||
curl_easy_strerror(res));
|
curl_easy_strerror(res));
|
||||||
sceIoRemove(VIDEO_TMP_PATH);
|
sceIoRemove(path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (code < 200 || code >= 300) {
|
if (code < 200 || code >= 300) {
|
||||||
snprintf(err, errlen, "HTTP %ld", code);
|
snprintf(err, errlen, "HTTP %ld", code);
|
||||||
sceIoRemove(VIDEO_TMP_PATH);
|
sceIoRemove(path);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* download the playback stream of asset idx to VIDEO_TMP_PATH.
|
||||||
|
* returns 0 on success, 1 if the user cancelled, -1 on error. */
|
||||||
|
static int download_video(int idx, char *err, size_t errlen)
|
||||||
|
{
|
||||||
|
char url[700];
|
||||||
|
snprintf(url, sizeof(url), "%s/api/assets/%s/video/playback",
|
||||||
|
g_server, g_asset_ids[idx]);
|
||||||
|
g_dl_what = "video";
|
||||||
|
return download_to_file(url, VIDEO_TMP_PATH, err, errlen);
|
||||||
|
}
|
||||||
|
|
||||||
static void *av_alloc(void *p, uint32_t alignment, uint32_t size)
|
static void *av_alloc(void *p, uint32_t alignment, uint32_t size)
|
||||||
{
|
{
|
||||||
void *r = memalign(alignment, size);
|
void *r = memalign(alignment, size);
|
||||||
|
|||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
/* vitaImmich unity build — part of one translation unit.
|
||||||
|
* This file is #included by src/main.c (in order) and shares all
|
||||||
|
* file-scope state with the other parts; it is NOT compiled on its own.
|
||||||
|
* Only src/main.c is listed in CMakeLists.txt. Editors/clangd may flag
|
||||||
|
* undefined symbols here because of that — those are expected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* turned photo viewer */
|
||||||
|
/* */
|
||||||
|
/* L / R in the full-screen viewer turn the whole viewer (not the */
|
||||||
|
/* photo) 90 degrees, so the Vita can be held upright and portrait */
|
||||||
|
/* photos fill the screen. While turned, the viewer lays itself out in */
|
||||||
|
/* its logical screen (SCREEN_W x SCREEN_H = 544x960 when upright), */
|
||||||
|
/* draws into an off-screen render target of that size, and the target */
|
||||||
|
/* is drawn rotated onto the real 960x544 screen. Buttons, sticks and */
|
||||||
|
/* touch are mapped into the logical screen so "up" stays up for the */
|
||||||
|
/* person holding the Vita. */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/* vita2d keeps its projection in this global and uploads it with every
|
||||||
|
* draw; it is set once for 960x544, so render targets of another size need
|
||||||
|
* it swapped while they're drawn into */
|
||||||
|
extern float _vita2d_ortho_matrix[4 * 4];
|
||||||
|
void matrix_init_orthographic(float *m, float left, float right, float bottom,
|
||||||
|
float top, float near, float far);
|
||||||
|
|
||||||
|
static int g_view_rot; /* 0 / 90 / 180 / 270, degrees clockwise */
|
||||||
|
static float g_view_rot_anim; /* degrees still to turn (eases to 0) */
|
||||||
|
static vita2d_texture *g_view_rt[2]; /* [0] 960x544, [1] 544x960 */
|
||||||
|
static int g_view_rt_on; /* this frame draws into a render target */
|
||||||
|
|
||||||
|
static void view_ortho(int w, int h)
|
||||||
|
{
|
||||||
|
matrix_init_orthographic(_vita2d_ortho_matrix, 0.0f, w, h, 0.0f,
|
||||||
|
0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* turn the viewer by +-90 degrees (R: clockwise, L: counter-clockwise) */
|
||||||
|
static void view_turn(int deg)
|
||||||
|
{
|
||||||
|
g_view_rot = (g_view_rot + deg + 360) % 360;
|
||||||
|
g_view_rot_anim -= deg; /* start from the old angle, ease in */
|
||||||
|
log_line("viewer turned to %d", g_view_rot);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the viewer's logical screen for this frame */
|
||||||
|
static void view_enter(void)
|
||||||
|
{
|
||||||
|
int upright = (g_view_rot % 180) != 0;
|
||||||
|
g_screen_w = upright ? 544 : 960;
|
||||||
|
g_screen_h = upright ? 960 : 544;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* back to the real screen (before anything that draws on its own, like
|
||||||
|
* the video player or a download progress screen) */
|
||||||
|
static void view_leave(void)
|
||||||
|
{
|
||||||
|
g_screen_w = 960;
|
||||||
|
g_screen_h = 544;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a screen-space direction as seen in the turned viewer */
|
||||||
|
static void view_vec(float sx, float sy, float *lx, float *ly)
|
||||||
|
{
|
||||||
|
switch (g_view_rot) {
|
||||||
|
case 90: *lx = sy; *ly = -sx; break;
|
||||||
|
case 180: *lx = -sx; *ly = -sy; break;
|
||||||
|
case 270: *lx = -sy; *ly = sx; break;
|
||||||
|
default: *lx = sx; *ly = sy; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* d-pad bits as seen in the turned viewer */
|
||||||
|
static unsigned int view_buttons(unsigned int b)
|
||||||
|
{
|
||||||
|
static const struct { unsigned int bit; int x, y; } d[4] = {
|
||||||
|
{ SCE_CTRL_UP, 0, -1 }, { SCE_CTRL_DOWN, 0, 1 },
|
||||||
|
{ SCE_CTRL_LEFT, -1, 0 }, { SCE_CTRL_RIGHT, 1, 0 },
|
||||||
|
};
|
||||||
|
unsigned int out = b & ~(SCE_CTRL_UP | SCE_CTRL_DOWN |
|
||||||
|
SCE_CTRL_LEFT | SCE_CTRL_RIGHT);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (!(b & d[i].bit))
|
||||||
|
continue;
|
||||||
|
float lx, ly;
|
||||||
|
view_vec(d[i].x, d[i].y, &lx, &ly);
|
||||||
|
for (int j = 0; j < 4; j++)
|
||||||
|
if (d[j].x == (int)lx && d[j].y == (int)ly)
|
||||||
|
out |= d[j].bit;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a stick (0..255, centre 128) as seen in the turned viewer */
|
||||||
|
static void view_stick(unsigned char *x, unsigned char *y)
|
||||||
|
{
|
||||||
|
float lx, ly;
|
||||||
|
view_vec(*x - 128.0f, *y - 128.0f, &lx, &ly);
|
||||||
|
lx += 128.0f;
|
||||||
|
ly += 128.0f;
|
||||||
|
*x = lx < 0 ? 0 : lx > 255 ? 255 : (unsigned char)lx;
|
||||||
|
*y = ly < 0 ? 0 : ly > 255 ? 255 : (unsigned char)ly;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a touch point on the real screen -> the turned viewer's screen */
|
||||||
|
static void view_touch(float *x, float *y)
|
||||||
|
{
|
||||||
|
float sx = *x, sy = *y;
|
||||||
|
switch (g_view_rot) {
|
||||||
|
case 90: *x = sy; *y = 960 - sx; break;
|
||||||
|
case 180: *x = 960 - sx; *y = 544 - sy; break;
|
||||||
|
case 270: *x = 544 - sy; *y = sx; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* start the viewer's frame: straight to the screen when not turned,
|
||||||
|
* else into the render target of the logical screen's size */
|
||||||
|
static void view_begin_drawing(void)
|
||||||
|
{
|
||||||
|
g_view_rt_on = 0;
|
||||||
|
if (g_view_rot != 0 || g_view_rot_anim != 0.0f) {
|
||||||
|
int k = (g_view_rot % 180) != 0;
|
||||||
|
if (!g_view_rt[k])
|
||||||
|
g_view_rt[k] = vita2d_create_empty_texture_rendertarget(
|
||||||
|
SCREEN_W, SCREEN_H, SCE_GXM_TEXTURE_FORMAT_A8B8G8R8);
|
||||||
|
g_view_rt_on = g_view_rt[k] != NULL;
|
||||||
|
if (g_view_rt_on) {
|
||||||
|
/* vita2d_start_drawing() is what empties the per-frame
|
||||||
|
* vertex pool; the _advanced variant doesn't, so without
|
||||||
|
* this every turned frame leaks pool space until a draw
|
||||||
|
* gets NULL and crashes */
|
||||||
|
vita2d_pool_reset();
|
||||||
|
vita2d_start_drawing_advanced(g_view_rt[k],
|
||||||
|
SCE_GXM_SCENE_FRAGMENT_SET_DEPENDENCY);
|
||||||
|
vita2d_clear_screen();
|
||||||
|
view_ortho(SCREEN_W, SCREEN_H);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vita2d_start_drawing();
|
||||||
|
vita2d_clear_screen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* finish the viewer's frame: draw the render target turned (and scaled
|
||||||
|
* to fit while the turn animates) onto the screen, then swap */
|
||||||
|
static void view_end_drawing(void)
|
||||||
|
{
|
||||||
|
vita2d_end_drawing();
|
||||||
|
if (g_view_rt_on) {
|
||||||
|
vita2d_texture *rt = g_view_rt[(g_view_rot % 180) != 0];
|
||||||
|
float lw = SCREEN_W, lh = SCREEN_H;
|
||||||
|
view_leave();
|
||||||
|
view_ortho(SCREEN_W, SCREEN_H);
|
||||||
|
vita2d_start_drawing_advanced(NULL,
|
||||||
|
SCE_GXM_SCENE_VERTEX_WAIT_FOR_DEPENDENCY);
|
||||||
|
vita2d_clear_screen();
|
||||||
|
float rad = (g_view_rot + g_view_rot_anim) * 3.14159265f / 180.0f;
|
||||||
|
float c = fabsf(cosf(rad)), s = fabsf(sinf(rad));
|
||||||
|
float bw = lw * c + lh * s, bh = lw * s + lh * c;
|
||||||
|
float k = fminf(1.0f, fminf(SCREEN_W / bw, SCREEN_H / bh));
|
||||||
|
vita2d_draw_texture_scale_rotate(rt, SCREEN_W / 2.0f,
|
||||||
|
SCREEN_H / 2.0f, k, k, rad);
|
||||||
|
vita2d_end_drawing();
|
||||||
|
}
|
||||||
|
#ifdef VIMM_TEST_INPUT
|
||||||
|
static unsigned int n;
|
||||||
|
if (++n % 120 == 0)
|
||||||
|
log_line("viewer pool free: %u", vita2d_pool_free_space());
|
||||||
|
#endif
|
||||||
|
vita2d_swap_buffers();
|
||||||
|
g_view_rot_anim *= 0.7f;
|
||||||
|
if (fabsf(g_view_rot_anim) < 0.5f)
|
||||||
|
g_view_rot_anim = 0.0f;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user