diff options
author | Thomas Martitz <kugel@rockbox.org> | 2013-12-20 23:34:28 +0100 |
---|---|---|
committer | Thomas Martitz <kugel@rockbox.org> | 2014-01-07 14:13:17 +0100 |
commit | 5d6974641b14ef81396e8deebcc65a87c07334e5 (patch) | |
tree | a3c12feecc5ae2007d71be2fb383ea7047c87f11 /apps/gui/line.c | |
parent | 5752d029fd80e87fe522d7d5e952a56dc371d65e (diff) | |
download | rockbox-5d6974641b14ef81396e8deebcc65a87c07334e5.tar.gz rockbox-5d6974641b14ef81396e8deebcc65a87c07334e5.zip |
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
Diffstat (limited to 'apps/gui/line.c')
-rw-r--r-- | apps/gui/line.c | 349 |
1 files changed, 349 insertions, 0 deletions
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 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2013 Thomas Martitz | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include <ctype.h> | ||
23 | #include <stdarg.h> | ||
24 | #include <stdio.h> | ||
25 | |||
26 | #include "scroll_engine.h" | ||
27 | #include "system.h" | ||
28 | #include "line.h" | ||
29 | #include "gcc_extensions.h" | ||
30 | #include "icon.h" | ||
31 | #include "screens.h" | ||
32 | #include "settings.h" | ||
33 | #include "debug.h" | ||
34 | |||
35 | #ifdef HAVE_REMOTE_LCD | ||
36 | #define MAX_LINES (LCD_SCROLLABLE_LINES + LCD_REMOTE_SCROLLABLE_LINES) | ||
37 | #else | ||
38 | #define MAX_LINES LCD_SCROLLABLE_LINES | ||
39 | #endif | ||
40 | |||
41 | |||
42 | #ifdef HAVE_LCD_CHARCELLS | ||
43 | #define style_line(d, x, y, l) | ||
44 | #else | ||
45 | static void style_line(struct screen *display, int x, int y, struct line_desc *line); | ||
46 | #endif | ||
47 | |||
48 | static void put_text(struct screen *display, int x, int y, struct line_desc *line, | ||
49 | const char *text, bool prevent_scroll, int text_skip_pixels); | ||
50 | |||
51 | |||
52 | static struct line_desc *get_line_desc(void) | ||
53 | { | ||
54 | static struct line_desc lines[MAX_LINES]; | ||
55 | static unsigned line_index; | ||
56 | struct line_desc *ret; | ||
57 | |||
58 | ret = &lines[line_index++]; | ||
59 | if (line_index >= ARRAYLEN(lines)) | ||
60 | line_index = 0; | ||
61 | |||
62 | return ret; | ||
63 | } | ||
64 | |||
65 | static void scroller(struct scrollinfo *s, struct screen *display) | ||
66 | { | ||
67 | /* style_line() expects the entire line rect, including padding, to | ||
68 | * draw selector properly across the text+padding. however struct scrollinfo | ||
69 | * has only the rect for the text itself, which is off depending on the | ||
70 | * line padding. this needs to be corrected for calling style_line(). | ||
71 | * The alternative would be to really redraw only the text area, | ||
72 | * but that would complicate the code a lot */ | ||
73 | struct line_desc *line = s->userdata; | ||
74 | style_line(display, s->x, s->y - (line->height/2 - display->getcharheight()/2), line); | ||
75 | put_text(display, s->x, s->y, line, s->line, true, s->offset); | ||
76 | } | ||
77 | |||
78 | static void scroller_main(struct scrollinfo *s) | ||
79 | { | ||
80 | scroller(s, &screens[SCREEN_MAIN]); | ||
81 | } | ||
82 | |||
83 | #ifdef HAVE_REMOTE_LCD | ||
84 | static void scroller_remote(struct scrollinfo *s) | ||
85 | { | ||
86 | scroller(s, &screens[SCREEN_REMOTE]); | ||
87 | } | ||
88 | #endif | ||
89 | |||
90 | static void (*scrollers[NB_SCREENS])(struct scrollinfo *s) = { | ||
91 | scroller_main, | ||
92 | #ifdef HAVE_REMOTE_LCD | ||
93 | scroller_remote, | ||
94 | #endif | ||
95 | }; | ||
96 | |||
97 | static void put_icon(struct screen *display, int x, int y, | ||
98 | struct line_desc *line, | ||
99 | enum themable_icons icon) | ||
100 | { | ||
101 | unsigned drmode = DRMODE_FG; | ||
102 | /* Need to change the drawmode: | ||
103 | * mono icons should behave like text, inverted on the selector bar | ||
104 | * native (colored) icons should be drawn as-is */ | ||
105 | if (!get_icon_format(display->screen_type) == FORMAT_MONO && (line->style & STYLE_INVERT)) | ||
106 | drmode = DRMODE_SOLID | DRMODE_INVERSEVID; | ||
107 | |||
108 | display->set_drawmode(drmode); | ||
109 | screen_put_iconxy(display, x, y, icon); | ||
110 | } | ||
111 | |||
112 | |||
113 | static void put_text(struct screen *display, | ||
114 | int x, int y, struct line_desc *line, | ||
115 | const char *text, bool prevent_scroll, | ||
116 | int text_skip_pixels) | ||
117 | { | ||
118 | /* set drawmode because put_icon() might have changed it */ | ||
119 | unsigned drmode = DRMODE_FG; | ||
120 | if (line->style & STYLE_INVERT) | ||
121 | drmode = DRMODE_SOLID | DRMODE_INVERSEVID; | ||
122 | |||
123 | display->set_drawmode(drmode); | ||
124 | |||
125 | if (line->scroll && !prevent_scroll) | ||
126 | { | ||
127 | struct line_desc *line_data = get_line_desc(); | ||
128 | *line_data = *line; | ||
129 | /* precalculate to avoid doing it in the scroller, it's save to | ||
130 | * do this on the copy of the original line_desc*/ | ||
131 | if (line_data->height == -1) | ||
132 | line_data->height = display->getcharheight(); | ||
133 | display->putsxy_scroll_func(x, y, text, | ||
134 | scrollers[display->screen_type], line_data, text_skip_pixels); | ||
135 | } | ||
136 | else | ||
137 | display->putsxy_scroll_func(x, y, text, NULL, NULL, text_skip_pixels); | ||
138 | } | ||
139 | |||
140 | /* A line consists of: | ||
141 | * |[Ss]|[i]|[Ss]|[t]|, where s is empty space (pixels), S is empty space | ||
142 | * (n space characters), i is an icon and t is the text. | ||
143 | * | ||
144 | * All components are optional. However, even if none are specified the whole | ||
145 | * line will be cleared and redrawn. | ||
146 | * | ||
147 | * For empty space with the width of an icon use i and pass Icon_NOICON as | ||
148 | * corresponding argument. | ||
149 | */ | ||
150 | static void print_line(struct screen *display, | ||
151 | int x, int y, struct line_desc *line, | ||
152 | const char *fmt, va_list ap) | ||
153 | { | ||
154 | const char *str; | ||
155 | bool num_is_valid; | ||
156 | int ch, num, height; | ||
157 | int xpos = x; | ||
158 | int icon_y, icon_h, icon_w; | ||
159 | enum themable_icons icon; | ||
160 | char tempbuf[128]; | ||
161 | int tempbuf_idx; | ||
162 | |||
163 | height = line->height == -1 ? display->getcharheight() : line->height; | ||
164 | icon_h = get_icon_height(display->screen_type); | ||
165 | icon_w = get_icon_width(display->screen_type); | ||
166 | tempbuf_idx = 0; | ||
167 | /* vertically center string on the line | ||
168 | * x/2 - y/2 rounds up compared to (x-y)/2 if one of x and y is odd */ | ||
169 | icon_y = y + height/2 - icon_h/2; | ||
170 | y += height/2 - display->getcharheight()/2; | ||
171 | |||
172 | /* parse format string */ | ||
173 | while (1) | ||
174 | { | ||
175 | ch = *fmt++; | ||
176 | /* need to check for escaped '$' */ | ||
177 | if (ch == '$' && *fmt != '$') | ||
178 | { | ||
179 | /* extra flag as num == 0 can be valid */ | ||
180 | num_is_valid = false; | ||
181 | num = 0; | ||
182 | if (tempbuf_idx) | ||
183 | { /* flush pending inline text */ | ||
184 | tempbuf_idx = tempbuf[tempbuf_idx] = 0; | ||
185 | put_text(display, xpos, y, line, tempbuf, false, 0); | ||
186 | xpos += display->getstringsize(tempbuf, NULL, NULL); | ||
187 | } | ||
188 | next: | ||
189 | ch = *fmt++; | ||
190 | switch(ch) | ||
191 | { | ||
192 | case '*': /* num from parameter list */ | ||
193 | num = va_arg(ap, int); | ||
194 | num_is_valid = true; | ||
195 | goto next; | ||
196 | |||
197 | case 'i': /* icon (without pad) */ | ||
198 | case 'I': /* icon with pad */ | ||
199 | if (ch == 'i') | ||
200 | num = 0; | ||
201 | else /* 'I' */ | ||
202 | if (!num_is_valid) | ||
203 | num = 1; | ||
204 | icon = va_arg(ap, int); | ||
205 | /* draw it, then skip over */ | ||
206 | if (icon != Icon_NOICON) | ||
207 | put_icon(display, xpos + num, icon_y, line, icon); | ||
208 | xpos += icon_w + num*2; | ||
209 | break; | ||
210 | |||
211 | case 'S': | ||
212 | if (!num_is_valid) | ||
213 | num = 1; | ||
214 | xpos += num * display->getcharwidth(); | ||
215 | break; | ||
216 | |||
217 | case 's': | ||
218 | if (!num_is_valid) | ||
219 | num = 1; | ||
220 | xpos += num; | ||
221 | break; | ||
222 | |||
223 | case 't': | ||
224 | str = va_arg(ap, const char *); | ||
225 | put_text(display, xpos, y, line, str, false, num); | ||
226 | xpos += display->getstringsize(str, NULL, NULL); | ||
227 | break; | ||
228 | |||
229 | default: | ||
230 | if (LIKELY(isdigit(ch))) | ||
231 | { | ||
232 | num_is_valid = true; | ||
233 | num = 10*num + ch - '0'; | ||
234 | goto next; | ||
235 | } | ||
236 | else | ||
237 | { | ||
238 | /* any other character here is an erroneous format string */ | ||
239 | snprintf(tempbuf, sizeof(tempbuf), "<E:%c>", ch); | ||
240 | display->putsxy(xpos, y, tempbuf); | ||
241 | /* Don't consider going forward, fix the caller */ | ||
242 | return; | ||
243 | } | ||
244 | } | ||
245 | } | ||
246 | else | ||
247 | { /* handle string constant in format string */ | ||
248 | tempbuf[tempbuf_idx++] = ch; | ||
249 | if (!ch) | ||
250 | { /* end of string. put it online */ | ||
251 | put_text(display, xpos, y, line, tempbuf, false, 0); | ||
252 | return; | ||
253 | } | ||
254 | else if (ch == '$') | ||
255 | fmt++; /* escaped '$', display just once */ | ||
256 | } | ||
257 | } | ||
258 | } | ||
259 | |||
260 | #ifdef HAVE_LCD_BITMAP | ||
261 | static void style_line(struct screen *display, | ||
262 | int x, int y, struct line_desc *line) | ||
263 | { | ||
264 | int style = line->style; | ||
265 | int width = display->getwidth(); | ||
266 | int height = line->height == -1 ? display->getcharheight() : line->height; | ||
267 | unsigned mask = STYLE_MODE_MASK & ~STYLE_COLORED; | ||
268 | |||
269 | /* mask out gradient and colorbar styles for non-color displays */ | ||
270 | if (display->depth < 16) | ||
271 | { | ||
272 | if (style & (STYLE_COLORBAR|STYLE_GRADIENT)) | ||
273 | { | ||
274 | style &= ~(STYLE_COLORBAR|STYLE_GRADIENT); | ||
275 | style |= STYLE_INVERT; | ||
276 | } | ||
277 | style &= ~STYLE_COLORED; | ||
278 | } | ||
279 | |||
280 | switch (style & mask) | ||
281 | { | ||
282 | #if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1)) | ||
283 | case STYLE_GRADIENT: | ||
284 | display->set_drawmode(DRMODE_FG); | ||
285 | display->gradient_fillrect_part(x, y, width, height, | ||
286 | line->line_color, | ||
287 | line->line_end_color, | ||
288 | height*line->nlines, | ||
289 | height*line->line); | ||
290 | break; | ||
291 | case STYLE_COLORBAR: | ||
292 | display->set_drawmode(DRMODE_FG); | ||
293 | display->set_foreground(line->line_color); | ||
294 | display->fillrect(x, y, width - x, height); | ||
295 | break; | ||
296 | #endif | ||
297 | case STYLE_INVERT: | ||
298 | display->set_drawmode(DRMODE_FG); | ||
299 | display->fillrect(x, y, width - x, height); | ||
300 | break; | ||
301 | case STYLE_DEFAULT: default: | ||
302 | display->set_drawmode(DRMODE_BG | DRMODE_INVERSEVID); | ||
303 | display->fillrect(x, y, width - x, height); | ||
304 | break; | ||
305 | case STYLE_NONE: | ||
306 | break; | ||
307 | } | ||
308 | #if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1)) | ||
309 | /* fg color and bg color are left as-is for text drawing */ | ||
310 | if (display->depth > 1) | ||
311 | { | ||
312 | if (style & STYLE_COLORED) | ||
313 | { | ||
314 | if (style & STYLE_INVERT) | ||
315 | display->set_background(line->text_color); | ||
316 | else | ||
317 | display->set_foreground(line->text_color); | ||
318 | } | ||
319 | else if (style & (STYLE_GRADIENT|STYLE_COLORBAR)) | ||
320 | display->set_foreground(line->text_color); | ||
321 | else | ||
322 | display->set_foreground(global_settings.fg_color); | ||
323 | } | ||
324 | #endif | ||
325 | } | ||
326 | #endif /* HAVE_LCD_BITMAP */ | ||
327 | |||
328 | void vput_line(struct screen *display, | ||
329 | int x, int y, struct line_desc *line, | ||
330 | const char *fmt, va_list ap) | ||
331 | { | ||
332 | style_line(display, x, y, line); | ||
333 | print_line(display, x, y, line, fmt, ap); | ||
334 | #if (LCD_DEPTH > 1 || (defined(LCD_REMOTE_DEPTH) && LCD_REMOTE_DEPTH > 1)) | ||
335 | if (display->depth > 1) | ||
336 | display->set_foreground(global_settings.fg_color); | ||
337 | #endif | ||
338 | display->set_drawmode(DRMODE_SOLID); | ||
339 | } | ||
340 | |||
341 | void put_line(struct screen *display, | ||
342 | int x, int y, struct line_desc *line, | ||
343 | const char *fmt, ...) | ||
344 | { | ||
345 | va_list ap; | ||
346 | va_start(ap, fmt); | ||
347 | vput_line(display, x, y, line, fmt, ap); | ||
348 | va_end(ap); | ||
349 | } | ||