From 5d6974641b14ef81396e8deebcc65a87c07334e5 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Fri, 20 Dec 2013 23:34:28 +0100 Subject: [PATCH] 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 --- apps/SOURCES | 1 + apps/gui/line.c | 349 +++++++++++++++++++++++++++++++++++++++++++ apps/gui/line.h | 117 +++++++++++++++ apps/plugin.h | 1 + apps/screen_access.c | 20 +++ apps/screen_access.h | 2 + 6 files changed, 490 insertions(+) create mode 100644 apps/gui/line.c create mode 100644 apps/gui/line.h diff --git a/apps/SOURCES b/apps/SOURCES index b6bb82fa31..918ee1e4fc 100644 --- a/apps/SOURCES +++ b/apps/SOURCES @@ -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 diff --git a/apps/gui/line.c b/apps/gui/line.c new file mode 100644 index 0000000000..9374279b60 --- /dev/null +++ b/apps/gui/line.c @@ -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 +#include +#include + +#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), "", 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); +} diff --git a/apps/gui/line.h b/apps/gui/line.h new file mode 100644 index 0000000000..d5350eb410 --- /dev/null +++ b/apps/gui/line.h @@ -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 +#include +#include + +#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__*/ diff --git a/apps/plugin.h b/apps/plugin.h index 38d8889d9e..4e6f4a8c4e 100644 --- a/apps/plugin.h +++ b/apps/plugin.h @@ -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" diff --git a/apps/screen_access.c b/apps/screen_access.c index 7f44cf5305..81bb6bae8f 100644 --- a/apps/screen_access.c +++ b/apps/screen_access.c @@ -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 */ }; diff --git a/apps/screen_access.h b/apps/screen_access.h index 26c9977bf2..7bc9c35237 100644 --- a/apps/screen_access.h +++ b/apps/screen_access.h @@ -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)