Startup logo spinner, search-bar focus nav, resume rescan, logo + AA play

Startup: bundle the Immich logo (app0:logo.png, transparent + AA) and show a
spinning-logo loading screen while the first page loads on a thread.

Search bar: logo to the left of the field; d-pad up from the top row focuses
the bar, left/right move across field/map/cloud (indigo ring), X activates,
down or [] returns to the grid. The selection square stays hidden while focused.

Cloud: "Made by SadsArches with love" credit; the O/Back button lives on the
See-details menu (top-right, tappable). Reverted the back circle from the
image/detail view.

Resume from sleep: an RTC wall-clock jump triggers an append-only camera
re-scan (startup reserves array headroom so it can't realloc under the worker
/sync threads).

Play overlay redrawn with the AA disc + a baked play-triangle glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sads
2026-06-13 17:39:01 +02:00
co-authored by Claude Opus 4.8
parent a8129d5262
commit e54179f5c3
4 changed files with 256 additions and 45 deletions
+1
View File
@@ -56,4 +56,5 @@ vita_create_vpk(${PROJECT_NAME}.vpk ${VITA_TITLEID} ${PROJECT_NAME}.self
FILE ${CMAKE_SOURCE_DIR}/assets/sce_sys/icon0.png sce_sys/icon0.png FILE ${CMAKE_SOURCE_DIR}/assets/sce_sys/icon0.png sce_sys/icon0.png
FILE ${CMAKE_SOURCE_DIR}/assets/Overpass-SemiBold.ttf font.ttf FILE ${CMAKE_SOURCE_DIR}/assets/Overpass-SemiBold.ttf font.ttf
FILE ${CMAKE_SOURCE_DIR}/assets/OFL-Overpass.txt OFL-Overpass.txt FILE ${CMAKE_SOURCE_DIR}/assets/OFL-Overpass.txt OFL-Overpass.txt
FILE ${CMAKE_SOURCE_DIR}/assets/immich_logo.png logo.png
) )
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

+15
View File
@@ -2,6 +2,21 @@
Task list for the current round of work. Checked items are done. Task list for the current round of work. Checked items are done.
## -1. UI polish round 2 (current)
- [x] Reload images taken by the camera while the app was asleep: detect
resume via an RTC wall-clock jump (>3 s) and append-only re-scan the
camera folders (headroom reserved at startup so no realloc races the
worker/sync threads).
- [x] Pressing up on the d-pad (from the top row) focuses the search bar
(indigo ring); down returns to the grid, X opens the keyboard.
- [x] Immich logo (bundled app0:logo.png, white bg made transparent) to the
left of the search bar.
- [x] "Made by SadsArches with love" credit on the cloud menu.
- [x] Spinning Immich logo on the startup loading screen (first page fetched
on a thread so the logo actually spins), loading text below.
- [x] Anti-alias the video play overlay (AA disc + baked play-triangle glyph).
- [x] O (back) circle button top-right in the detail view (tappable + O).
## 0. UI polish (current round) ## 0. UI polish (current round)
- [x] Hide the white selection square while touch-scrolling; show it again - [x] Hide the white selection square while touch-scrolling; show it again
when the selection moves with the d-pad. when the selection moves with the d-pad.
+240 -45
View File
@@ -104,6 +104,7 @@ int BZ2_bzDecompressEnd(void *strm) { return -1; }
static vita2d_pgf *g_font; /* system font, fallback */ static vita2d_pgf *g_font; /* system font, fallback */
static vita2d_font *g_ttf; /* bundled Overpass (Immich's face) */ static vita2d_font *g_ttf; /* bundled Overpass (Immich's face) */
static vita2d_texture *g_logo; /* bundled Immich logo (app0:logo.png) */
/* server-asset arrays. dynamically grown (doubling) as pages are fetched, so /* server-asset arrays. dynamically grown (doubling) as pages are fetched, so
* the library is bounded only by memory, not a fixed cap. grown on the main * the library is bounded only by memory, not a fixed cap. grown on the main
@@ -391,6 +392,26 @@ static void draw_centered(int y, uint32_t color, const char *text)
draw_text((SCREEN_W - w) / 2, y, color, 1.0f, text); draw_text((SCREEN_W - w) / 2, y, color, 1.0f, text);
} }
/* startup loading screen: the Immich logo spinning, with text below it */
static void draw_loading(const char *text, unsigned int frame)
{
vita2d_start_drawing();
vita2d_clear_screen();
float cx = SCREEN_W / 2.0f, cy = SCREEN_H / 2.0f - 30.0f;
if (g_logo) {
float lw = vita2d_texture_get_width(g_logo);
float lh = vita2d_texture_get_height(g_logo);
float scale = 110.0f / lw;
vita2d_draw_texture_scale_rotate_hotspot(g_logo, cx, cy,
scale, scale, frame * 0.10f, lw / 2.0f, lh / 2.0f);
}
int w = text_width(1.0f, text);
draw_text((SCREEN_W - w) / 2, cy + 90.0f,
RGBA8(225, 225, 230, 255), 1.0f, text);
vita2d_end_drawing();
vita2d_swap_buffers();
}
static void show_status(const char *fmt, ...) static void show_status(const char *fmt, ...)
{ {
char buf[512]; char buf[512];
@@ -1957,7 +1978,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 = 6 }; /* a small fixed set of shapes (pill, buttons, ...) */ enum { NSLOT = 8 }; /* 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];
@@ -2141,12 +2162,19 @@ static int cov_circle(float px, float py, int s)
return d <= R && d >= R - W; return d <= R && d >= R - W;
} }
/* a filled right-pointing play triangle */
static int cov_play(float px, float py, int s)
{
return in_tri(px, py, s * 0.36f, s * 0.27f,
s * 0.36f, s * 0.73f, s * 0.76f, s * 0.5f);
}
/* cache one texture per glyph; all are drawn at a fixed size each frame */ /* cache one texture per glyph; all are drawn at a fixed size each frame */
static vita2d_texture *icon_tex(int which, int s) static vita2d_texture *icon_tex(int which, int s)
{ {
static vita2d_texture *cache[7]; static vita2d_texture *cache[8];
static int cs[7]; static int cs[8];
if (which < 0 || which > 6) if (which < 0 || which > 7)
return NULL; return NULL;
if (cache[which] && cs[which] == s) if (cache[which] && cs[which] == s)
return cache[which]; return cache[which];
@@ -2160,7 +2188,8 @@ static vita2d_texture *icon_tex(int which, int s)
which == 2 ? cov_cloud : which == 2 ? cov_cloud :
which == 3 ? cov_tri : which == 3 ? cov_tri :
which == 4 ? cov_sq : which == 4 ? cov_sq :
which == 5 ? cov_cross : cov_circle; which == 5 ? cov_cross :
which == 6 ? cov_circle : cov_play;
cache[which] = bake_icon(s, fn); cache[which] = bake_icon(s, fn);
cs[which] = s; cs[which] = s;
return cache[which]; return cache[which];
@@ -2172,6 +2201,7 @@ static vita2d_texture *icon_tex(int which, int s)
#define ICON_SQUARE 4 #define ICON_SQUARE 4
#define ICON_CROSS 5 #define ICON_CROSS 5
#define ICON_CIRCLE 6 #define ICON_CIRCLE 6
#define ICON_PLAY 7
/* a DualShock-style face button: a dark disc (subtle lighter rim) centred at /* a DualShock-style face button: a dark disc (subtle lighter rim) centred at
* (cx,cy) with the coloured glyph on top. each glyph keeps a per-call size. */ * (cx,cy) with the coloured glyph on top. each glyph keeps a per-call size. */
@@ -2244,8 +2274,9 @@ static void draw_ps_button(float cx, float cy, float d, int icon,
#define BTN2_CX ((float)SCREEN_W - BAR_M - BTN_SZ / 2) /* cloud (rightmost) */ #define BTN2_CX ((float)SCREEN_W - BAR_M - BTN_SZ / 2) /* cloud (rightmost) */
#define BTN1_CX (BTN2_CX - BTN_SZ - BTN_GAP) /* map */ #define BTN1_CX (BTN2_CX - BTN_SZ - BTN_GAP) /* map */
#define BTN_CY (BAR_Y + BAR_H / 2) #define BTN_CY (BAR_Y + BAR_H / 2)
#define BAR_W (BTN1_CX - BTN_SZ / 2 - BTN_GAP - BAR_M) /* pill width */ #define BAR_PX (BAR_M + 42.0f) /* pill left x (logo to its left) */
#define BAR_CLEAR_CX (BAR_M + BAR_W - 22.0f) /* centre of the clear chip */ #define BAR_W (BTN1_CX - BTN_SZ / 2 - BTN_GAP - BAR_PX) /* pill width */
#define BAR_CLEAR_CX (BAR_PX + BAR_W - 22.0f) /* centre of the clear chip */
/* one future-feature button: rounded-square background + a centred AA glyph */ /* one future-feature button: rounded-square background + a centred AA glyph */
static void draw_bar_button(float cx, float cy, int icon) static void draw_bar_button(float cx, float cy, int icon)
@@ -2261,32 +2292,55 @@ static void draw_bar_button(float cx, float cy, int icon)
RGBA8(190, 190, 196, 255)); RGBA8(190, 190, 196, 255));
} }
/* draw the bar shifted vertically by `yoff` (Square slides it out of view) */ /* draw the bar shifted vertically by `yoff` (Square slides it out of view);
static void draw_search_bar(float yoff) * `focus` highlights the focused element (1=field, 2=map, 3=cloud) */
static void draw_search_bar(float yoff, int focus)
{ {
unsigned int field = RGBA8(38, 38, 42, 255); unsigned int field = RGBA8(38, 38, 42, 255);
unsigned int hi = RGBA8(94, 110, 215, 255); /* focus ring (indigo) */
float by = BAR_Y + yoff, cy = BTN_CY + yoff; float by = BAR_Y + yoff, cy = BTN_CY + yoff;
/* opaque strip so grid items scroll cleanly underneath the bar */ /* opaque strip so grid items scroll cleanly underneath the bar */
vita2d_draw_rectangle(0, yoff, SCREEN_W, SEARCH_H, RGBA8(16, 16, 16, 255)); vita2d_draw_rectangle(0, yoff, SCREEN_W, SEARCH_H, RGBA8(16, 16, 16, 255));
/* Immich logo to the left of the search field */
if (g_logo) {
float ls = (BAR_H - 6.0f) / vita2d_texture_get_width(g_logo);
vita2d_draw_texture_scale(g_logo, BAR_M, by + 3.0f, ls, ls);
}
/* focus ring behind the field */
if (focus == 1) {
vita2d_texture *ring = rounded_mask_tex((int)BAR_W + 6,
(int)BAR_H + 6, BAR_R + 3);
if (ring)
vita2d_draw_texture_tint(ring, BAR_PX - 3, by - 3, hi);
}
/* borderless field: an AA rounded-rect mask tinted to the field colour */ /* borderless field: an AA rounded-rect mask tinted to the field colour */
vita2d_texture *mask = rounded_mask_tex((int)BAR_W, (int)BAR_H, BAR_R); vita2d_texture *mask = rounded_mask_tex((int)BAR_W, (int)BAR_H, BAR_R);
if (mask) if (mask)
vita2d_draw_texture_tint(mask, BAR_M, by, field); vita2d_draw_texture_tint(mask, BAR_PX, by, field);
else else
vita2d_draw_rectangle(BAR_M, by, BAR_W, BAR_H, field); vita2d_draw_rectangle(BAR_PX, by, BAR_W, BAR_H, field);
int isz = 24; int isz = 24;
vita2d_texture *icon = icon_tex(ICON_SEARCH, isz); vita2d_texture *icon = icon_tex(ICON_SEARCH, isz);
if (icon) if (icon)
vita2d_draw_texture_tint(icon, BAR_M + 22 - isz / 2.0f, vita2d_draw_texture_tint(icon, BAR_PX + 22 - isz / 2.0f,
cy - isz / 2.0f, cy - isz / 2.0f,
RGBA8(190, 190, 196, 255)); RGBA8(190, 190, 196, 255));
/* focus rings behind the placeholder buttons */
if (focus == 2 || focus == 3) {
vita2d_texture *r = rounded_mask_tex((int)BTN_SZ + 6,
(int)BTN_SZ + 6, 14.0f);
float fcx = (focus == 2) ? BTN1_CX : BTN2_CX;
if (r)
vita2d_draw_texture_tint(r, fcx - (BTN_SZ + 6) / 2,
cy - (BTN_SZ + 6) / 2, hi);
}
/* placeholder buttons (map, cloud) for future features */ /* placeholder buttons (map, cloud) for future features */
draw_bar_button(BTN1_CX, cy, ICON_MAP); draw_bar_button(BTN1_CX, cy, ICON_MAP);
draw_bar_button(BTN2_CX, cy, ICON_CLOUD); draw_bar_button(BTN2_CX, cy, ICON_CLOUD);
float tx = BAR_M + 44, ty = by + BAR_H - 13; float tx = BAR_PX + 44, ty = by + BAR_H - 13;
if (g_search_active && g_search_query[0]) { if (g_search_active && g_search_query[0]) {
char q[64]; char q[64];
snprintf(q, sizeof(q), "%.40s", g_search_query); snprintf(q, sizeof(q), "%.40s", g_search_query);
@@ -2521,20 +2575,16 @@ static void draw_throbber(float cx, float cy, float r, unsigned int frame)
} }
} }
/* translucent play button over a video's poster in the detail view */ /* translucent play button over a video's poster in the detail view (AA) */
static void draw_play_overlay(void) static void draw_play_overlay(void)
{ {
float cx = SCREEN_W / 2.0f, cy = SCREEN_H / 2.0f; float cx = SCREEN_W / 2.0f, cy = SCREEN_H / 2.0f;
vita2d_draw_fill_circle(cx, cy, 46.0f, RGBA8(20, 20, 20, 150)); draw_disc(cx, cy, 92.0f, RGBA8(20, 20, 20, 150));
vita2d_color_vertex *v = int gs = 56;
vita2d_pool_memalign(3 * sizeof(*v), sizeof(*v)); vita2d_texture *p = icon_tex(ICON_PLAY, gs);
if (!v) if (p)
return; vita2d_draw_texture_tint(p, cx - gs / 2.0f, cy - gs / 2.0f,
unsigned int col = RGBA8(255, 255, 255, 220); RGBA8(255, 255, 255, 235));
v[0] = (vita2d_color_vertex){ cx - 14.0f, cy - 24.0f, 0.5f, col };
v[1] = (vita2d_color_vertex){ cx - 14.0f, cy + 24.0f, 0.5f, col };
v[2] = (vita2d_color_vertex){ cx + 28.0f, cy, 0.5f, col };
vita2d_draw_array(SCE_GXM_PRIMITIVE_TRIANGLES, v, 3);
} }
static void draw_error_detail(int idx) static void draw_error_detail(int idx)
@@ -3281,6 +3331,11 @@ static int is_media_name(const char *name, int *is_video)
return 0; return 0;
} }
/* set while re-scanning after a resume from sleep: scan_dir then only appends
* genuinely-new files into the headroom (no realloc; the worker/sync threads
* are reading the arrays) */
static int g_rescan;
/* recurse a directory (up to depth levels) collecting camera media */ /* recurse a directory (up to depth levels) collecting camera media */
static void scan_dir(const char *path, int depth) static void scan_dir(const char *path, int depth)
{ {
@@ -3328,12 +3383,26 @@ static void scan_dir(const char *path, int depth)
continue; continue;
} }
if (!grow_locals(g_local_count + 1)) { /* rescan (resume from sleep): skip files we already know, and
* never realloc while the sync/worker threads are reading the
* arrays — only fill into the headroom reserved at startup */
if (g_rescan) {
int known = 0;
for (int k = 0; k < g_local_count && !known; k++)
known = !strcmp(g_local_path[k], full);
if (known)
continue;
if (g_local_count >= g_local_cap) {
log_line("rescan: no headroom at %d files",
g_local_count);
break;
}
} else if (!grow_locals(g_local_count + 1)) {
log_line("scan: out of memory at %d local files", log_line("scan: out of memory at %d local files",
g_local_count); g_local_count);
break; break;
} }
int j = g_local_count++; int j = g_local_count; /* fill, then publish (count++) */
memcpy(g_local_path[j], full, pl + 1 + nl + 1); memcpy(g_local_path[j], full, pl + 1 + nl + 1);
g_local_is_video[j] = is_video; g_local_is_video[j] = is_video;
g_local_size[j] = (long long)ent.d_stat.st_size; g_local_size[j] = (long long)ent.d_stat.st_size;
@@ -3345,6 +3414,8 @@ static void scan_dir(const char *path, int depth)
g_local_server_id[j][0] = '\0'; g_local_server_id[j][0] = '\0';
g_local_err[j][0] = '\0'; g_local_err[j][0] = '\0';
g_local_state[j] = SYNC_UNSCANNED; g_local_state[j] = SYNC_UNSCANNED;
__sync_synchronize();
g_local_count = j + 1;
} }
sceIoDclose(dfd); sceIoDclose(dfd);
log_line("scan: %s: %d media so far", path, g_local_count); log_line("scan: %s: %d media so far", path, g_local_count);
@@ -3374,6 +3445,27 @@ static void scan_local_media(void)
log_line("local scan: %d files", g_local_count); log_line("local scan: %d files", g_local_count);
} }
/* append-only re-scan of the camera folders (resume from sleep). same dirs as
* scan_local_media, but g_rescan keeps it from touching known entries or
* reallocating. returns how many new files were appended. */
static int rescan_local_new(void)
{
int before = g_local_count;
g_rescan = 1;
if (g_syncdir_count > 0) {
for (int i = 0; i < g_syncdir_count; i++)
scan_dir(g_syncdirs[i], 3);
} else {
scan_dir(g_photo0_mounted ? "photo0:" : "ux0:picture", 3);
scan_dir("ux0:video/CAMERA", 3);
}
g_rescan = 0;
int added = g_local_count - before;
if (added > 0)
log_line("rescan: %d new local file(s) after resume", added);
return added;
}
static int sha1_file_hex(const char *path, char out[41]) static int sha1_file_hex(const char *path, char out[41])
{ {
FILE *f = fopen(path, "rb"); FILE *f = fopen(path, "rb");
@@ -3830,12 +3922,25 @@ static int count_state(int st)
enum { MODE_GRID, MODE_DETAIL, MODE_CLOUD, MODE_CLOUD_DETAILS }; enum { MODE_GRID, MODE_DETAIL, MODE_CLOUD, MODE_CLOUD_DETAILS };
/* the first library page is fetched on this thread so the main thread can
* spin the loading logo while it waits */
static volatile int g_init_load_done;
static int init_load_thread(SceSize args, void *argp)
{
(void)args; (void)argp;
fetch_page(0);
__sync_synchronize();
g_init_load_done = 1;
return 0;
}
int main(void) int main(void)
{ {
vita2d_init(); vita2d_init();
vita2d_set_clear_color(RGBA8(16, 16, 16, 255)); vita2d_set_clear_color(RGBA8(16, 16, 16, 255));
g_font = vita2d_load_default_pgf(); g_font = vita2d_load_default_pgf();
g_ttf = vita2d_load_font_file("app0:font.ttf"); g_ttf = vita2d_load_font_file("app0:font.ttf");
g_logo = vita2d_load_PNG_file("app0:logo.png");
/* 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 */
@@ -3915,10 +4020,22 @@ int main(void)
log_line("server='%s' len=%d hex=%s", g_server, n, hex); log_line("server='%s' len=%d hex=%s", g_server, n, hex);
} }
show_status("Loading library from %s ...", g_server); /* spinning-logo loading screen while the first page loads on a thread */
fetch_page(1); g_init_load_done = 0;
SceUID lt = sceKernelCreateThread("init_load", init_load_thread,
0x10000100, 256 * 1024, 0, 0, NULL);
if (lt >= 0) {
sceKernelStartThread(lt, 0, NULL);
unsigned int lf = 0;
while (!g_init_load_done) {
draw_loading("Loading your library...", lf++);
sceDisplayWaitVblankStart();
}
} else {
fetch_page(1); /* fallback: blocking, no spinner */
}
if (g_asset_count == 0) if (g_asset_count == 0)
fatal_error(NULL, "Server returned no assets"); fatal_error(NULL, "Server returned no assets (see log.txt)");
SceUID worker = sceKernelCreateThread("thumb_loader", worker_thread, SceUID worker = sceKernelCreateThread("thumb_loader", worker_thread,
0x10000100, 256 * 1024, 0, 0, NULL); 0x10000100, 256 * 1024, 0, 0, NULL);
@@ -3927,8 +4044,11 @@ int main(void)
/* scan the Vita's own camera media (fast: paths + stat only), build /* scan the Vita's own camera media (fast: paths + stat only), build
* the merged timeline, then let the sync thread hash + dup-check it */ * the merged timeline, then let the sync thread hash + dup-check it */
show_status("Scanning local media..."); draw_loading("Scanning local media...", 0);
scan_local_media(); scan_local_media();
/* reserve headroom so a resume-from-sleep rescan can append new camera
* files without reallocating while the worker/sync threads read */
grow_locals(g_local_count + 512);
rebuild_display(); rebuild_display();
SceUID syncw = sceKernelCreateThread("sync_worker", sync_thread, SceUID syncw = sceKernelCreateThread("sync_worker", sync_thread,
@@ -3944,6 +4064,8 @@ int main(void)
int show_sel = 1; /* selection square: shown for d-pad, hidden for touch */ int show_sel = 1; /* selection square: shown for d-pad, hidden for touch */
int bar_shown = 1; /* top search bar visible */ int bar_shown = 1; /* top search bar visible */
float bar_hidden = 0.0f;/* animated pixels the bar is slid up (0..SEARCH_H) */ float bar_hidden = 0.0f;/* animated pixels the bar is slid up (0..SEARCH_H) */
int bar_focus = 0; /* search bar focused (d-pad up from the top row) */
uint64_t last_tick = 0; /* wall-clock (incl. sleep) to detect resume */
/* periodic check for photos added to the server while we run */ /* periodic check for photos added to the server while we run */
uint64_t last_poll = sceKernelGetProcessTimeWide(); uint64_t last_poll = sceKernelGetProcessTimeWide();
int grid_last_sel = -1; /* sel at the previous frame */ int grid_last_sel = -1; /* sel at the previous frame */
@@ -3992,6 +4114,22 @@ int main(void)
frame++; frame++;
/* resume from sleep: wall-clock (RTC) keeps running while the
* Vita is suspended, so a big jump between frames means we just
* woke — re-scan the camera folders for photos taken meanwhile */
{
SceRtcTick rt;
if (sceRtcGetCurrentTick(&rt) == 0) {
if (last_tick &&
rt.tick - last_tick > 3000000ULL) {
if (rescan_local_new() > 0)
rebuild_keep_view(&sel, &scroll,
&target);
}
last_tick = rt.tick;
}
}
/* auto-backup: while enabled, queue any newly-found local-only /* auto-backup: while enabled, queue any newly-found local-only
* photos for upload (~1/s; the sync thread drains the queue). * photos for upload (~1/s; the sync thread drains the queue).
* this covers both launch and photos found mid-session. */ * this covers both launch and photos found mid-session. */
@@ -4042,18 +4180,42 @@ int main(void)
(pressed & SCE_CTRL_CIRCLE) != 0; (pressed & SCE_CTRL_CIRCLE) != 0;
int do_open_cloud = 0; /* tap on the cloud button */ int do_open_cloud = 0; /* tap on the cloud button */
/* SQUARE slides the top bar in/out; animate the offset */ /* SQUARE slides the top bar in/out (when not focused);
if (pressed & SCE_CTRL_SQUARE) * while focused it drops focus back to the grid */
if ((pressed & SCE_CTRL_SQUARE) && !bar_focus)
bar_shown = !bar_shown; bar_shown = !bar_shown;
float bar_t = bar_shown ? 0.0f : (float)SEARCH_H; float bar_t = bar_shown ? 0.0f : (float)SEARCH_H;
bar_hidden += (bar_t - bar_hidden) * 0.3f; bar_hidden += (bar_t - bar_hidden) * 0.3f;
if (fabsf(bar_t - bar_hidden) < 0.5f) if (fabsf(bar_t - bar_hidden) < 0.5f)
bar_hidden = bar_t; bar_hidden = bar_t;
/* the d-pad (and month jumps) bring the selection square /* the d-pad brings the selection square back — but not
* back; a touch drag hides it again (set below) */ * while the bar is focused; a touch drag hides it again */
if (nav & dirs) if ((nav & dirs) && !bar_focus)
show_sel = 1; show_sel = 1;
if (bar_focus) {
/* bar focused (1=field, 2=map, 3=cloud): left/right
* move across the elements, down or [] returns to
* the grid, X activates the focused element */
show_sel = 0;
if ((pressed & SCE_CTRL_SQUARE) ||
(nav & SCE_CTRL_DOWN)) {
bar_focus = 0;
show_sel = 1;
} else {
if ((nav & SCE_CTRL_LEFT) && bar_focus > 1)
bar_focus--;
if ((nav & SCE_CTRL_RIGHT) && bar_focus < 3)
bar_focus++;
if (pressed & SCE_CTRL_CROSS) {
if (bar_focus == 1)
do_search_open = 1;
else if (bar_focus == 3)
do_open_cloud = 1;
/* map (2): placeholder, no-op */
}
}
} else {
if (nav & SCE_CTRL_RIGHT) if (nav & SCE_CTRL_RIGHT)
sel++; sel++;
if (nav & SCE_CTRL_LEFT) if (nav & SCE_CTRL_LEFT)
@@ -4064,8 +4226,16 @@ int main(void)
sel = g_disp_count - 1; sel = g_disp_count - 1;
if (nav & SCE_CTRL_DOWN) if (nav & SCE_CTRL_DOWN)
sel = nav_row(sel, +1); sel = nav_row(sel, +1);
if (nav & SCE_CTRL_UP) if (nav & SCE_CTRL_UP) {
sel = nav_row(sel, -1); int nr = nav_row(sel, -1);
if (nr == sel) { /* already on the top row */
bar_focus = 1;
bar_shown = 1; /* reveal the bar if hidden */
show_sel = 0;
} else {
sel = nr;
}
}
if ((pressed & (SCE_CTRL_RTRIGGER | if ((pressed & (SCE_CTRL_RTRIGGER |
SCE_CTRL_LTRIGGER)) && SCE_CTRL_LTRIGGER)) &&
g_disp_count > 0) { g_disp_count > 0) {
@@ -4092,6 +4262,7 @@ int main(void)
if (sel >= 0 && sel < g_disp_count) if (sel >= 0 && sel < g_disp_count)
target = g_item_y[sel] - HEADER_H - SEARCH_H; target = g_item_y[sel] - HEADER_H - SEARCH_H;
} }
} /* end !bar_focus */
/* Track how long the selection has held still. A held /* Track how long the selection has held still. A held
* d-pad auto-repeats one row every few frames; while it's * d-pad auto-repeats one row every few frames; while it's
@@ -4112,7 +4283,8 @@ int main(void)
} }
int scrolling_fast = (grid_settle < 12); int scrolling_fast = (grid_settle < 12);
if ((pressed & SCE_CTRL_CROSS) && g_disp_count > 0) { if ((pressed & SCE_CTRL_CROSS) && g_disp_count > 0 &&
!bar_focus) {
/* photos and videos both open the detail /* photos and videos both open the detail
* view; a video shows its poster with the * view; a video shows its poster with the
* play button there */ * play button there */
@@ -4167,6 +4339,7 @@ int main(void)
touch_start_y = ty; touch_start_y = ty;
touch_start_scroll = scroll; touch_start_scroll = scroll;
touch_vel = 0; touch_vel = 0;
bar_focus = 0; /* touching cancels bar focus */
/* a contact on the (visible) bar opens /* a contact on the (visible) bar opens
* the keyboard instead of scrolling */ * the keyboard instead of scrolling */
touch_on_bar = touch_on_bar =
@@ -4209,7 +4382,7 @@ int main(void)
/* cloud button: open the /* cloud button: open the
* server/backup page */ * server/backup page */
do_open_cloud = 1; do_open_cloud = 1;
} else if (touch_x > BAR_M + BAR_W) { } else if (touch_x > BAR_PX + BAR_W) {
/* map button (placeholder) */ /* map button (placeholder) */
} else if (g_search_active && } else if (g_search_active &&
touch_x > BAR_CLEAR_CX - 16) touch_x > BAR_CLEAR_CX - 16)
@@ -4460,7 +4633,7 @@ int main(void)
/* pinned Immich-style search bar, slid up by bar_hidden /* pinned Immich-style search bar, slid up by bar_hidden
* (Square toggles it); covers items up to its lower edge */ * (Square toggles it); covers items up to its lower edge */
draw_search_bar(-bar_hidden); draw_search_bar(-bar_hidden, bar_focus);
char hud[200]; char hud[200];
if (g_search_active) if (g_search_active)
@@ -4910,10 +5083,8 @@ pf_skip:
g_disp_count > 0 ? disp_date(sel) : "", g_disp_count > 0 ? disp_date(sel) : "",
xhint); xhint);
draw_hud(hud); draw_hud(hud);
/* ...and a discreet corner throbber while the /* discreet corner throbber while the full-res image loads,
* full-res image is still on its way — but only when * only when a preview is showing */
* a preview is showing; with no preview at all the
* big centered throbber is already up */
if (detail_loading && disp_thumb(sel)) if (detail_loading && disp_thumb(sel))
draw_throbber(SCREEN_W - 36.0f, 36.0f, draw_throbber(SCREEN_W - 36.0f, 36.0f,
14.0f, frame); 14.0f, frame);
@@ -5119,12 +5290,32 @@ pf_skip:
draw_ps_button(ubx - 23, bty + bth / 2, 38, ICON_TRI, draw_ps_button(ubx - 23, bty + bth / 2, 38, ICON_TRI,
RGBA8(95, 205, 130, 255), 28); RGBA8(95, 205, 130, 255), 28);
draw_centered(SCREEN_H - 56, RGBA8(150, 150, 158, 255),
"Made by SadsArches with love");
draw_hud("X / tap details O back"); draw_hud("X / tap details O back");
vita2d_end_drawing(); vita2d_end_drawing();
vita2d_swap_buffers(); vita2d_swap_buffers();
} else if (mode == MODE_CLOUD_DETAILS) { } else if (mode == MODE_CLOUD_DETAILS) {
/* scrollable list of not-backed-up local files + status */ /* scrollable list of not-backed-up local files + status */
if (pressed & SCE_CTRL_CIRCLE) { float bkd = 36, bkcx = SCREEN_W - 30, bkcy = 36;
int do_back = (pressed & SCE_CTRL_CIRCLE) != 0;
/* touch: tap the top-right Back button */
{
SceTouchData td;
sceTouchPeek(SCE_TOUCH_PORT_FRONT, &td, 1);
if (td.reportNum > 0) {
float tx = td.report[0].x * 0.5f;
float ty = td.report[0].y * 0.5f;
if (!touch_active) {
touch_active = 1;
if (ty < bkcy + bkd && tx > bkcx - bkd)
do_back = 1;
}
} else {
touch_active = 0;
}
}
if (do_back) {
mode = MODE_CLOUD; mode = MODE_CLOUD;
continue; continue;
} }
@@ -5185,6 +5376,10 @@ pf_skip:
RGBA8(120, 200, 120, 255), RGBA8(120, 200, 120, 255),
"Everything is backed up"); "Everything is backed up");
/* top-right Back button (also press O) */
draw_ps_button(bkcx, bkcy, bkd, ICON_CIRCLE,
RGBA8(235, 90, 85, 255), 26);
draw_hud("Up/Down scroll O back"); draw_hud("Up/Down scroll O back");
vita2d_end_drawing(); vita2d_end_drawing();
vita2d_swap_buffers(); vita2d_swap_buffers();