mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-07-10 13:29:52 -04:00
450 lines
14 KiB
C
450 lines
14 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) Daniel Stenberg (2002)
|
|
* Copyright (C) William Wilgus (2026)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "stdarg.h"
|
|
#include "string.h"
|
|
#include "rbunicode.h"
|
|
#include "stdio.h"
|
|
#include "kernel.h"
|
|
#include "screen_access.h"
|
|
#include "lang.h"
|
|
#include "settings.h"
|
|
#include "talk.h"
|
|
#include "splash.h"
|
|
#include "viewport.h"
|
|
#include "strptokspn_r.h"
|
|
#include "scrollbar.h"
|
|
#include "font.h"
|
|
#ifndef BOOTLOADER
|
|
#include "misc.h" /* get_current_activity */
|
|
#endif
|
|
|
|
static long progress_next_tick, talked_tick;
|
|
|
|
#define MAXLINES (LCD_HEIGHT/6)
|
|
|
|
|
|
#ifndef BOOTLOADER
|
|
#if MEMORYSIZE > 8
|
|
#define MAXBUFFER MAX(1024, (LCD_WIDTH/SYSFONT_WIDTH) * MAXLINES + 1)
|
|
#else
|
|
#define MAXBUFFER MAX(512, (LCD_WIDTH/SYSFONT_WIDTH) * MAXLINES + 1)
|
|
#endif
|
|
#else
|
|
#define MAXBUFFER (512)
|
|
#endif
|
|
|
|
#define RECT_SPACING 3
|
|
#define SPLASH_MEMORY_INTERVAL (HZ)
|
|
|
|
/* splash_internal does the actual drawing of the splash box and text
|
|
* each call to splash remembers the max size and will not shrink the splash box
|
|
* until it is reset by an activity change or \f at the beginning of a string
|
|
*
|
|
* splash supports several escape sequences ("\n\t\f\r\v\b")
|
|
* '\n' (new line) indicates the end of a line of text and starts a new one
|
|
* ("\n\n" will end a line and create a blank line)
|
|
* '\n' as the first character of the text will create a blank line
|
|
*
|
|
* '\t' (tab) starts a new line and adds several spaces to the beginning of the
|
|
* new line it also left/right justifies the splash text box
|
|
* (RTL languages will right justify)
|
|
* '\t' as the last character of the text will left/right justify
|
|
* without adding any tabs to the text output
|
|
*
|
|
* '\f' (form feed) as the first character of the text will reset the text box
|
|
* width and height
|
|
*
|
|
* Others:
|
|
* '\f', \r', '\v' '\b' will be treated as a breaking space character
|
|
* they will not be displayed as output and multiple sequences will be
|
|
* treated as a single character
|
|
* eg. "\b\v\f\r" will break the line but only once and will not show as a space
|
|
* where as "\n\n" will break a line and add a blank line
|
|
* "\t\t" would break a line and add a total of 4 spaces to the next line
|
|
*/
|
|
static bool splash_internal(struct screen * screen, const char *fmt, va_list ap,
|
|
struct viewport *vp, int addl_lines)
|
|
{
|
|
static const char matchstr[] = "\r\n\f\v\t\b";
|
|
static int max_width[NB_SCREENS] = {0};
|
|
static int max_height[NB_SCREENS] = {0};
|
|
#ifndef BOOTLOADER
|
|
static char splash_buf[MAXBUFFER] = {0};
|
|
static enum current_activity last_act = ACTIVITY_UNKNOWN;
|
|
enum current_activity act = get_current_activity();
|
|
|
|
if (last_act != act) /* changed activities reset width / height */
|
|
{
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
max_width[i] = 0;
|
|
max_height[i] = 0;
|
|
}
|
|
last_act = act;
|
|
}
|
|
#else /*def BOOTLOADER*/
|
|
char splash_buf[MAXBUFFER] = {0}; /* on the stack for the bootloader */
|
|
#endif
|
|
|
|
struct splash_lines {
|
|
const char *str;
|
|
uint16_t x;
|
|
uint16_t len;
|
|
} lines[MAXLINES];
|
|
memset(lines, 0, sizeof(lines));
|
|
|
|
char *buf = splash_buf;
|
|
const char *next, *store;
|
|
|
|
bool has_tabs = false;
|
|
int line = 0;
|
|
int x = 0, w = 0;
|
|
int y;
|
|
int space_w, chr_h;
|
|
int width = vp->width - RECT_SPACING*2;
|
|
int height = vp->height;
|
|
|
|
/* prevent screen artifacts by keeping the max width / height seen */
|
|
int maxw = max_width[screen->screen_type];
|
|
int maxh = max_height[screen->screen_type];
|
|
int fontnum = vp->font;
|
|
|
|
size_t next_len;
|
|
|
|
font_getstringsize(" ", &space_w, &chr_h, fontnum);
|
|
y = (addl_lines * chr_h);
|
|
|
|
int res = vsnprintf(splash_buf, sizeof(splash_buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (res <= 0 || width < space_w || height < chr_h)
|
|
{
|
|
#ifdef SIMULATOR
|
|
if (res <= 0)
|
|
printf("ERROR: %s %d\n", __func__, res);
|
|
else
|
|
printf("WARNING: %s vp too small\n", __func__);
|
|
#endif
|
|
return false; /* nothing to display */
|
|
}
|
|
|
|
const char *lastbreak = splash_buf;
|
|
if (*lastbreak == '\f') /* only as first character - Reset splash box size */
|
|
{
|
|
maxw = 0;
|
|
maxh = 0;
|
|
}
|
|
|
|
while(true) /* break splash string into display lines, doing proper word wrap */
|
|
{
|
|
#ifndef BOOTLOADER
|
|
while (*lastbreak != '\0') /* handle escape chars*/
|
|
{
|
|
switch (*lastbreak) /* all chars in matchstr */
|
|
{
|
|
case '\t': /*fallthrough*/
|
|
has_tabs = true; /* left justify (right justify for RTL)*/
|
|
case '\n':
|
|
{
|
|
if (lines[line].len > 0 || *lastbreak != '\t')
|
|
{
|
|
x += w;
|
|
if (x > maxw)
|
|
maxw = x;
|
|
|
|
x = 0;
|
|
y += chr_h;
|
|
if (y >= height || line >= MAXLINES-1)
|
|
{
|
|
break; /* out of lines */
|
|
}
|
|
line++;
|
|
}
|
|
/* /t as last char left justifies only */
|
|
if (lastbreak[0] == '\t' && lastbreak[1] != '\0')
|
|
{
|
|
/* add offset for tab */
|
|
lines[line].x += space_w * 2;
|
|
x += space_w * 2;
|
|
}
|
|
break;
|
|
}
|
|
case '\b': /*fallthrough*/
|
|
case '\f': /*fallthrough*/
|
|
case '\v': /*fallthrough*/
|
|
case '\r':
|
|
break; /* acts the same as a space character */
|
|
default: /* No valid break chars */
|
|
lastbreak = " "; /* exit */
|
|
break;
|
|
}
|
|
lastbreak++;
|
|
}
|
|
#endif
|
|
if (lines[line].len > 0)
|
|
{
|
|
x += w;
|
|
if (x > maxw)
|
|
maxw = x;
|
|
x = 0;
|
|
y += chr_h;
|
|
if (y >= height || line >= MAXLINES-1)
|
|
{
|
|
if (y > maxh)
|
|
maxh = y;
|
|
break; /* out of lines */
|
|
}
|
|
line++;
|
|
}
|
|
|
|
next = strptokspn_r(buf, matchstr, &next_len, &store);
|
|
|
|
if (!next)
|
|
{
|
|
x += w;
|
|
if (x > maxw)
|
|
maxw = x;
|
|
if (y > maxh)
|
|
maxh = y;
|
|
break;
|
|
}
|
|
buf = NULL; /* uses store for continuation */
|
|
|
|
lines[line].str = next;
|
|
lines[line].len = next_len;
|
|
|
|
w = font_getstringnsize(next, next_len, NULL, NULL, fontnum);
|
|
if (w > width)
|
|
{
|
|
const char *nxp, *nx = next;
|
|
int nw, newlen;
|
|
int oldlen = next_len;
|
|
|
|
while (nx - next < oldlen) /* try to split at a space char */
|
|
{
|
|
nxp = nx;
|
|
nx++;
|
|
if (*nxp != ' ' && *nx != '\0') /* split on space or EOL */
|
|
continue;
|
|
newlen = nxp - next;
|
|
nw = font_getstringnsize(next, newlen, NULL, NULL, fontnum);
|
|
|
|
if (nw > width)
|
|
{
|
|
/* is next word larger than max width & room left on this line? */
|
|
if (nw - w > width && w + space_w * 8 < width)
|
|
w = nw;
|
|
break;
|
|
}
|
|
w = nw;
|
|
next_len = newlen;
|
|
store = nx; /* we want to skip the space char */
|
|
}
|
|
if (w > width) /* split when it fits */
|
|
{
|
|
w = width;
|
|
next_len = font_measurestring(next, oldlen, w, &w, NULL, fontnum);
|
|
store = next + next_len;
|
|
}
|
|
|
|
lines[line].len = next_len;
|
|
}
|
|
lastbreak = next + next_len;
|
|
}
|
|
|
|
/* prepare viewport
|
|
* First boundaries, then the background filling, then the border and finally
|
|
* the text*/
|
|
|
|
screen->scroll_stop();
|
|
|
|
width = maxw + 2*RECT_SPACING;
|
|
height = maxh + 2*RECT_SPACING;
|
|
|
|
if (width > vp->width)
|
|
width = vp->width;
|
|
if (height > vp->height)
|
|
height = vp->height;
|
|
|
|
/* center the vp in the screen area */
|
|
vp->x += (vp->width - width) / 2;
|
|
vp->y += (vp->height - height) / 2;
|
|
vp->width = width;
|
|
vp->height = height;
|
|
if (!has_tabs)
|
|
vp->flags |= VP_FLAG_ALIGN_CENTER;
|
|
|
|
/* prevent artifacts by locking to max width & height observed on repeated calls */
|
|
max_width[screen->screen_type] = width - 2*RECT_SPACING;
|
|
max_height[screen->screen_type] = height - 2*RECT_SPACING;
|
|
|
|
#if LCD_DEPTH > 1
|
|
unsigned fg = 0, bg = 0;
|
|
bool broken = false;
|
|
|
|
if (screen->depth > 1)
|
|
{
|
|
fg = screen->get_foreground();
|
|
bg = screen->get_background();
|
|
|
|
broken = (fg == bg) ||
|
|
(bg == 63422 && fg == 65535); /* -> iPod reFresh themes from '22 */
|
|
|
|
vp->drawmode = DRMODE_FG;
|
|
/* can't do vp->fg_pattern here, since set_foreground does a bit more on
|
|
* greyscale */
|
|
screen->set_foreground(broken ? SCREEN_COLOR_TO_NATIVE(screen, LCD_LIGHTGRAY) :
|
|
bg); /* gray as fallback for broken themes */
|
|
}
|
|
else
|
|
#endif
|
|
vp->drawmode = (DRMODE_SOLID|DRMODE_INVERSEVID);
|
|
|
|
screen->fill_viewport();
|
|
|
|
#if LCD_DEPTH > 1
|
|
if (screen->depth > 1)
|
|
/* can't do vp->fg_pattern here, since set_foreground does a bit more on
|
|
* greyscale */
|
|
screen->set_foreground(broken ? SCREEN_COLOR_TO_NATIVE(screen, LCD_BLACK) :
|
|
fg); /* black as fallback for broken themes */
|
|
else
|
|
#endif
|
|
vp->drawmode = DRMODE_SOLID;
|
|
|
|
screen->draw_border_viewport();
|
|
|
|
/* center y within the available splash box */
|
|
if (y < maxh)
|
|
y = (maxh - y) / 2;
|
|
else
|
|
y = RECT_SPACING;
|
|
|
|
/* print the message to screen */
|
|
for(int i = 0; i < line && y < height; i++, y+= chr_h)
|
|
{
|
|
if (lines[i].len > 0)
|
|
{
|
|
x = lines[i].x;
|
|
screen->putsxyf(x, y, "%.*s", lines[i].len, lines[i].str);
|
|
}
|
|
}
|
|
|
|
return true; /* needs update */
|
|
}
|
|
|
|
void splashf(int ticks, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
/* fmt may be a so called virtual pointer. See settings.h. */
|
|
long id;
|
|
if((id = P2ID((const unsigned char*)fmt)) >= 0)
|
|
/* If fmt specifies a voicefont ID, and voice menus are
|
|
enabled, then speak it. */
|
|
cond_talk_ids_fq(id);
|
|
|
|
/* If fmt is a lang ID then get the corresponding string (which
|
|
still might contain % place holders). */
|
|
fmt = P2STR((unsigned char *)fmt);
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
struct screen * screen = &(screens[i]);
|
|
struct viewport vp;
|
|
viewport_set_defaults(&vp, screen->screen_type);
|
|
struct viewport *last_vp = screen->set_viewport(&vp);
|
|
|
|
va_start(ap, fmt);
|
|
if (splash_internal(screen, fmt, ap, &vp, 0))
|
|
screen->update_viewport();
|
|
va_end(ap);
|
|
|
|
screen->set_viewport(last_vp);
|
|
}
|
|
if (ticks)
|
|
sleep(ticks);
|
|
}
|
|
|
|
/* set delay before progress meter is shown */
|
|
void splash_progress_set_delay(long delay_ticks)
|
|
{
|
|
progress_next_tick = current_tick + delay_ticks;
|
|
talked_tick = 0;
|
|
}
|
|
|
|
/* splash a progress meter */
|
|
void splash_progress(int current, int total, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int vp_flag = VP_FLAG_VP_DIRTY;
|
|
/* progress update tick */
|
|
long now = current_tick;
|
|
|
|
if (current < total)
|
|
{
|
|
if(TIME_BEFORE(now, progress_next_tick))
|
|
return;
|
|
/* limit to 20fps */
|
|
progress_next_tick = now + HZ/20;
|
|
vp_flag = 0; /* don't mark vp dirty to prevent flashing */
|
|
}
|
|
|
|
if (global_settings.talk_menu &&
|
|
total > 0 &&
|
|
TIME_AFTER(current_tick, talked_tick + HZ*5))
|
|
{
|
|
talked_tick = current_tick;
|
|
talk_ids(false, LANG_LOADING_PERCENT,
|
|
TALK_ID(current * 100 / total, UNIT_PERCENT));
|
|
}
|
|
|
|
/* If fmt is a lang ID then get the corresponding string (which
|
|
still might contain % place holders). */
|
|
fmt = P2STR((unsigned char *)fmt);
|
|
FOR_NB_SCREENS(i)
|
|
{
|
|
struct screen * screen = &(screens[i]);
|
|
struct viewport vp;
|
|
viewport_set_defaults(&vp, screen->screen_type);
|
|
struct viewport *last_vp = screen->set_viewport_ex(&vp, vp_flag);
|
|
|
|
va_start(ap, fmt);
|
|
if (splash_internal(screen, fmt, ap, &vp, 1))
|
|
{
|
|
int size = screen->getcharheight();
|
|
int x = RECT_SPACING;
|
|
int y = vp.height - size - RECT_SPACING;
|
|
int w = vp.width - RECT_SPACING * 2;
|
|
int h = size;
|
|
#ifdef HAVE_LCD_COLOR
|
|
const int sb_flags = HORIZONTAL | FOREGROUND;
|
|
#else
|
|
const int sb_flags = HORIZONTAL;
|
|
#endif
|
|
gui_scrollbar_draw(screen, x, y, w, h, total, 0, current, sb_flags);
|
|
|
|
screen->update_viewport();
|
|
}
|
|
va_end(ap);
|
|
|
|
screen->set_viewport(last_vp);
|
|
}
|
|
}
|