Introduce put_line().

This function is a fully-fletched, high-level pixel-based line printer, that
combines functionality of several firmware and list functions. It can
draw spacing, icons and text in a single call, in any order and each multiple
times. It can also apply line decorations at the same time.

It features printf-like semantics by accepting a format string that contain
format tags as well as inline text.

It's accessible directly, but also through the multi-screen api for plugins.

Change-Id: I70f5a77bbf4b0252521f2e47ead377b9d6d29b54
This commit is contained in:
Thomas Martitz 2013-12-20 23:34:28 +01:00
parent 5752d029fd
commit 5d6974641b
6 changed files with 490 additions and 0 deletions

View file

@ -77,6 +77,7 @@ gui/buttonbar.c
gui/icon.c
#endif
gui/list.c
gui/line.c
#ifdef HAVE_LCD_BITMAP
gui/bitmap/list.c
gui/bitmap/list-skinned.c

349
apps/gui/line.c Normal file
View file

@ -0,0 +1,349 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2013 Thomas Martitz
*
* 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 <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include "scroll_engine.h"
#include "system.h"
#include "line.h"
#include "gcc_extensions.h"
#include "icon.h"
#include "screens.h"
#include "settings.h"
#include "debug.h"
#ifdef HAVE_REMOTE_LCD
#define MAX_LINES (LCD_SCROLLABLE_LINES + LCD_REMOTE_SCROLLABLE_LINES)
#else
#define MAX_LINES LCD_SCROLLABLE_LINES
#endif
#ifdef HAVE_LCD_CHARCELLS
#define style_line(d, x, y, l)
#else
static void style_line(struct screen *display, int x, int y, struct line_desc *line);
#endif
static void put_text(struct screen *display, int x, int y, struct line_desc *line,
const char *text, bool prevent_scroll, int text_skip_pixels);
static struct line_desc *get_line_desc(void)
{
static struct line_desc lines[MAX_LINES];
static unsigned line_index;
struct line_desc *ret;
ret = &lines[line_index++];
if (line_index >= ARRAYLEN(lines))
line_index = 0;
return ret;
}
static void scroller(struct scrollinfo *s, struct screen *display)
{
/* style_line() expects the entire line rect, including padding, to
* draw selector properly across the text+padding. however struct scrollinfo
* has only the rect for the text itself, which is off depending on the
* line padding. this needs to be corrected for calling style_line().
* The alternative would be to really redraw only the text area,
* but that would complicate the code a lot */
struct line_desc *line = s->userdata;
style_line(display, s->x, s->y - (line->height/2 - display->getcharheight()/2), line);
put_text(display, s->x, s->y, line, s->line, true, s->offset);
}
static void scroller_main(struct scrollinfo *s)
{
scroller(s, &screens[SCREEN_MAIN]);
}
#ifdef HAVE_REMOTE_LCD
static void scroller_remote(struct scrollinfo *s)
{
scroller(s, &screens[SCREEN_REMOTE]);
}
#endif
static void (*scrollers[NB_SCREENS])(struct scrollinfo *s) = {
scroller_main,
#ifdef HAVE_REMOTE_LCD
scroller_remote,
#endif
};
static void put_icon(struct screen *display, int x, int y,
struct line_desc *line,
enum themable_icons icon)
{
unsigned drmode = DRMODE_FG;
/* Need to change the drawmode:
* mono icons should behave like text, inverted on the selector bar
* native (colored) icons should be drawn as-is */
if (!get_icon_format(display->screen_type) == FORMAT_MONO && (line->style & STYLE_INVERT))
drmode = DRMODE_SOLID | DRMODE_INVERSEVID;
display->set_drawmode(drmode);
screen_put_iconxy(display, x, y, icon);
}
static void put_text(struct screen *display,
int x, int y, struct line_desc *line,
const char *text, bool prevent_scroll,
int text_skip_pixels)
{
/* set drawmode because put_icon() might have changed it */
unsigned drmode = DRMODE_FG;
if (line->style & STYLE_INVERT)
drmode = DRMODE_SOLID | DRMODE_INVERSEVID;
display->set_drawmode(drmode);
if (line->scroll && !prevent_scroll)
{
struct line_desc *line_data = get_line_desc();
*line_data = *line;
/* precalculate to avoid doing it in the scroller, it's save to
* do this on the copy of the original line_desc*/
if (line_data->height == -1)
line_data->height = display->getcharheight();
display->putsxy_scroll_func(x, y, text,
scrollers[display->screen_type], line_data, text_skip_pixels);
}
else
display->putsxy_scroll_func(x, y, text, NULL, NULL, text_skip_pixels);
}
/* A line consists of:
* |[Ss]|[i]|[Ss]|[t]|, where s is empty space (pixels), S is empty space
* (n space characters), i is an icon and t is the text.
*
* All components are optional. However, even if none are specified the whole
* line will be cleared and redrawn.
*
* For empty space with the width of an icon use i and pass Icon_NOICON as
* corresponding argument.
*/
static void print_line(struct screen *display,
int x, int y, struct line_desc *line,
const char *fmt, va_list ap)
{
const char *str;
bool num_is_valid;
int ch, num, height;
int xpos = x;
int icon_y, icon_h, icon_w;
enum themable_icons icon;
char tempbuf[128];
int tempbuf_idx;
height = line->height == -1 ? display->getcharheight() : line->height;
icon_h = get_icon_height(display->screen_type);
icon_w = get_icon_width(display->screen_type);
tempbuf_idx = 0;
/* vertically center string on the line
* x/2 - y/2 rounds up compared to (x-y)/2 if one of x and y is odd */
icon_y = y + height/2 - icon_h/2;
y += height/2 - display->getcharheight()/2;
/* parse format string */
while (1)
{
ch = *fmt++;
/* need to check for escaped '$' */
if (ch == '$' && *fmt != '$')
{
/* extra flag as num == 0 can be valid */
num_is_valid = false;
num = 0;
if (tempbuf_idx)
{ /* flush pending inline text */
tempbuf_idx = tempbuf[tempbuf_idx] = 0;
put_text(display, xpos, y, line, tempbuf, false, 0);
xpos += display->getstringsize(tempbuf, NULL, NULL);
}
next:
ch = *fmt++;
switch(ch)
{
case '*': /* num from parameter list */
num = va_arg(ap, int);
num_is_valid = true;
goto next;
case 'i': /* icon (without pad) */
case 'I': /* icon with pad */
if (ch == 'i')
num = 0;
else /* 'I' */
if (!num_is_valid)
num = 1;
icon = va_arg(ap, int);
/* draw it, then skip over */
if (icon != Icon_NOICON)
put_icon(display, xpos + num, icon_y, line, icon);
xpos += icon_w + num*2;
break;
case 'S':
if (!num_is_valid)
num = 1;
xpos += num * display->getcharwidth();
break;
case 's':
if (!num_is_valid)
num = 1;
xpos += num;
break;
case 't':
str = va_arg(ap, const char *);
put_text(display, xpos, y, line, str, false, num);
xpos += display->getstringsize(str, NULL, NULL);
break;
default:
if (LIKELY(isdigit(ch)))
{
num_is_valid = true;
num = 10*num + ch - '0';
goto next;
}
else
{
/* any other character here is an erroneous format string */
snprintf(tempbuf, sizeof(tempbuf), "<E:%c>", ch);
display->putsxy(xpos, y, tempbuf);
/* Don't consider going forward, fix the caller */
return;
}
}
}
else
{ /* handle string constant in format string */
tempbuf[tempbuf_idx++] = ch;
if (!ch)
{ /* end of string. put it online */
put_text(display, xpos, y, line, tempbuf, false, 0);
return;
}
else if (ch == '$')
fmt++; /* escaped '$', display just once */
}
}
}
#ifdef HAVE_LCD_BITMAP
static void style_line(struct screen *display,
int x, int y, struct line_desc *line)
{
int style = line->style;
int width = display->getwidth();
int height = line->height == -1 ? display->getcharheight() : line->height;
unsigned mask = STYLE_MODE_MASK & ~STYLE_COLORED;
/* mask out gradient and colorbar styles for non-color displays */
if (display->depth < 16)
{
if (style & (STYLE_COLORBAR|STYLE_GRADIENT))
{
style &= ~(STYLE_COLORBAR|STYLE_GRADIENT);
style |= STYLE_INVERT;
}
style &= ~STYLE_COLORED;
}
switch (style & mask)
{
#if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1))
case STYLE_GRADIENT:
display->set_drawmode(DRMODE_FG);
display->gradient_fillrect_part(x, y, width, height,
line->line_color,
line->line_end_color,
height*line->nlines,
height*line->line);
break;
case STYLE_COLORBAR:
display->set_drawmode(DRMODE_FG);
display->set_foreground(line->line_color);
display->fillrect(x, y, width - x, height);
break;
#endif
case STYLE_INVERT:
display->set_drawmode(DRMODE_FG);
display->fillrect(x, y, width - x, height);
break;
case STYLE_DEFAULT: default:
display->set_drawmode(DRMODE_BG | DRMODE_INVERSEVID);
display->fillrect(x, y, width - x, height);
break;
case STYLE_NONE:
break;
}
#if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1))
/* fg color and bg color are left as-is for text drawing */
if (display->depth > 1)
{
if (style & STYLE_COLORED)
{
if (style & STYLE_INVERT)
display->set_background(line->text_color);
else
display->set_foreground(line->text_color);
}
else if (style & (STYLE_GRADIENT|STYLE_COLORBAR))
display->set_foreground(line->text_color);
else
display->set_foreground(global_settings.fg_color);
}
#endif
}
#endif /* HAVE_LCD_BITMAP */
void vput_line(struct screen *display,
int x, int y, struct line_desc *line,
const char *fmt, va_list ap)
{
style_line(display, x, y, line);
print_line(display, x, y, line, fmt, ap);
#if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1))
if (display->depth > 1)
display->set_foreground(global_settings.fg_color);
#endif
display->set_drawmode(DRMODE_SOLID);
}
void put_line(struct screen *display,
int x, int y, struct line_desc *line,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vput_line(display, x, y, line, fmt, ap);
va_end(ap);
}

117
apps/gui/line.h Normal file
View file

@ -0,0 +1,117 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2013 Thomas Martitz
*
* 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.
*
****************************************************************************/
#ifndef __LINE_H__
#define __LINE_H__
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "lcd.h"
#include "screens.h"
struct line_desc {
/* height of the line (in pixels). -1 to inherit the height
* from the font. The text will be centered if the height is larger,
* but the decorations will span the entire height */
int height;
/* multiline support: For some decorations (e.g. gradient) to work
* across multiple lines (e.g. to draw a line selector across 2 lines)
* the line index and line count must be known. For normal, single
* lines specify nlines=1 and line=0 */
/* line count of a group */
int16_t nlines;
/* index of the line in the group */
int16_t line;
/* line text color if STYLE_COLORED is specified, in native
* lcd format (convert with LCD_RGBPACK() if necessary) */
fb_data text_color;
/* line color if STYLE_COLORBAR or STYLE_GRADIENT is specified, in native
* lcd format (convert with LCD_RGBPACK() if necessary) */
fb_data line_color, line_end_color;
/* line decorations, see STYLE_DEFAULT etc. */
int style;
/* whether the line can scroll */
bool scroll;
};
/* default initializer, can be used for static initialitation also.
* This initializer will result in single lines without style that don't scroll */
#define LINE_DESC_DEFINIT { .style = STYLE_DEFAULT, .height = -1, .line = 0, .nlines = 1, .scroll = false }
/**
* Print a line at a given pixel postion, using decoration information from
* line and content information from the format specifier. The format specifier
* can include tags that depend on further parameters given to the function
* (similar to the well-known printf()).
*
* Tags start with the $ sign. Below is a list of the currently supported tags:
* $s - insert a column (1px wide) of empty space.
* $S - insert a column (1 char wide) of empty space.
* $i - insert an icon. put_line() expects a corresponding parameter of the
* type 'enum themable_icons'. If Icon_NOICON is passed, then empty
* space (icon-wide) will be inserted.
* $I - insert an icon with padding. Works like $i but additionally
* adds a column (1px wide) of empty space on either side of the icon.
* $t - insert text. put_line() expects a corresponding parameter of the type
* 'const char *'
* $$ - insert a '$' char, use it to escape $.
*
* $I, $s and $S support the following two forms:
* $n[IsS] - inserts n columns (pixels/chars respectively) of empty space
* $*[IsS] - inserts n columns (pixels/chars respectively) of empty space. put_line()
* expects a correspinding paramter of the type 'int' that specifies n.
*
* $t supports the following two forms:
* $nt - skips the first n pixels when displaying the string
* $*t - skips the first n pixels when displaying the string. put_line()
* expects a correspinding paramter of the type 'int' that specifies n.
*
* Inline text will be printed as is and can be freely intermixed with tags,
* except when the line can scroll. Due to limitations of the scroll engine
* only the last piece of text (whether inline or via $t) can scroll.
*
* x, y - pixel postion of the line.
* line - holds information for the line decorations
* fmt - holds the text and optionally format tags
* ... - additional paramters for the format tags
*
*/
void put_line(struct screen *display,
int x, int y, struct line_desc *line,
const char *fmt, ...);
/**
* Print a line at a given pixel postion, using decoration information from
* line and content information from the format specifier. The format specifier
* can include tags that depend on further parameters given to the function
* (similar to the well-known vprintf()).
*
* For details, see put_line(). This function is equivalent, except for
* accepting a va_list instead of a variable paramter list.
*/
void vput_line(struct screen *display,
int x, int y, struct line_desc *line,
const char *fmt, va_list ap);
#endif /* __LINE_H__*/

View file

@ -113,6 +113,7 @@ void* plugin_get_buffer(size_t *buffer_size);
#include "crc32.h"
#include "rbpaths.h"
#include "core_alloc.h"
#include "screen_access.h"
#ifdef HAVE_ALBUMART
#include "albumart.h"

View file

@ -100,6 +100,15 @@ static void screen_helper_set_drawmode(int mode)
#endif
}
static void screen_helper_put_line(int x, int y, struct line_desc *line,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vput_line(&screens[0], x, y, line, fmt, ap);
va_end(ap);
}
#if NB_SCREENS == 2
static int screen_helper_remote_getcharwidth(void)
{
@ -156,6 +165,15 @@ static void screen_helper_remote_setuifont(int font)
#endif
}
static void screen_helper_remote_put_line(int x, int y, struct line_desc *line,
const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vput_line(&screens[0], x, y, line, fmt, ap);
va_end(ap);
}
#endif
struct screen screens[NB_SCREENS] =
@ -280,6 +298,7 @@ struct screen screens[NB_SCREENS] =
.gradient_fillrect_part = lcd_gradient_fillrect_part,
#endif
#endif
.put_line = screen_helper_put_line,
},
#if NB_SCREENS == 2
{
@ -380,6 +399,7 @@ struct screen screens[NB_SCREENS] =
#if defined(HAVE_LCD_BITMAP)
.set_framebuffer = (void*)lcd_remote_set_framebuffer,
#endif
.put_line = screen_helper_remote_put_line,
}
#endif /* NB_SCREENS == 2 */
};

View file

@ -26,6 +26,7 @@
#include "buttonbar.h"
#include "scroll_engine.h"
#include "backdrop.h"
#include "line.h"
#if defined(HAVE_REMOTE_LCD) && !defined (ROCKBOX_HAS_LOGF)
#define NB_SCREENS 2
@ -177,6 +178,7 @@ struct screen
void (*nine_segment_bmp)(const struct bitmap* bm, int x, int y,
int width, int height);
#endif
void (*put_line)(int x, int y, struct line_desc *line, const char *fmt, ...);
};
#if defined(HAVE_LCD_BITMAP) || defined(HAVE_REMOTE_LCD)