New option for the bar tags: 'backdrop, <label>' will draw another image under the...
[kugel-rb.git] / apps / gui / skin_engine / skin_display.c
blob5d90e14a0ea7152d5b6d5501ab052e4de098035a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002-2007 Björn Stenberg
11 * Copyright (C) 2007-2008 Nicolas Pennequin
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ****************************************************************************/
22 #include "config.h"
23 #include <stdio.h>
24 #include "string-extra.h"
25 #include "misc.h"
26 #include "font.h"
27 #include "system.h"
28 #include "rbunicode.h"
29 #include "sound.h"
30 #include "powermgmt.h"
31 #ifdef DEBUG
32 #include "debug.h"
33 #endif
34 #include "action.h"
35 #include "abrepeat.h"
36 #include "lang.h"
37 #include "language.h"
38 #include "statusbar.h"
39 #include "settings.h"
40 #include "scrollbar.h"
41 #include "screen_access.h"
42 #include "playlist.h"
43 #include "audio.h"
44 #include "tagcache.h"
46 #ifdef HAVE_LCD_BITMAP
47 #include "peakmeter.h"
48 /* Image stuff */
49 #include "bmp.h"
50 #ifdef HAVE_ALBUMART
51 #include "albumart.h"
52 #endif
53 #endif
55 #include "cuesheet.h"
56 #if CONFIG_CODEC == SWCODEC
57 #include "playback.h"
58 #endif
59 #include "backdrop.h"
60 #include "viewport.h"
61 #if CONFIG_TUNER
62 #include "radio.h"
63 #include "tuner.h"
64 #endif
65 #include "root_menu.h"
68 #include "wps_internals.h"
69 #include "skin_engine.h"
70 #include "statusbar-skinned.h"
71 #include "skin_display.h"
73 void skin_render(struct gui_wps *gwps, unsigned refresh_mode);
75 /* update a skinned screen, update_type is WPS_REFRESH_* values.
76 * Usually it should only be WPS_REFRESH_NON_STATIC
77 * A full update will be done if required (skin_do_full_update() == true)
79 void skin_update(enum skinnable_screens skin, enum screen_type screen,
80 unsigned int update_type)
82 struct gui_wps *gwps = skin_get_gwps(skin, screen);
83 /* This maybe shouldnt be here,
84 * This is also safe for skined screen which dont use the id3 */
85 struct mp3entry *id3 = skin_get_global_state()->id3;
86 bool cuesheet_update = (id3 != NULL ? cuesheet_subtrack_changed(id3) : false);
87 if (cuesheet_update)
88 skin_request_full_update(skin);
90 skin_render(gwps, skin_do_full_update(skin, screen) ?
91 SKIN_REFRESH_ALL : update_type);
94 #ifdef HAVE_LCD_BITMAP
96 void skin_statusbar_changed(struct gui_wps *skin)
98 if (!skin)
99 return;
100 struct wps_data *data = skin->data;
101 const struct screen *display = skin->display;
102 const int screen = display->screen_type;
104 struct viewport *vp = &find_viewport(VP_DEFAULT_LABEL, false, data)->vp;
105 viewport_set_defaults(vp, screen);
107 if (data->wps_sb_tag)
108 { /* fix up the default viewport */
109 if (data->show_sb_on_wps)
111 if (statusbar_position(screen) != STATUSBAR_OFF)
112 return; /* vp is fixed already */
114 vp->y = STATUSBAR_HEIGHT;
115 vp->height = display->lcdheight - STATUSBAR_HEIGHT;
117 else
119 if (statusbar_position(screen) == STATUSBAR_OFF)
120 return; /* vp is fixed already */
121 vp->y = vp->x = 0;
122 vp->height = display->lcdheight;
123 vp->width = display->lcdwidth;
128 void draw_progressbar(struct gui_wps *gwps, int line, struct progressbar *pb)
130 struct screen *display = gwps->display;
131 struct viewport *vp = pb->vp;
132 struct wps_state *state = skin_get_global_state();
133 struct mp3entry *id3 = state->id3;
134 int x = pb->x, y = pb->y, width = pb->width, height = pb->height;
135 unsigned long length, end;
136 int flags = HORIZONTAL;
138 if (height < 0)
139 height = font_get(vp->font)->height;
141 if (y < 0)
143 int line_height = font_get(vp->font)->height;
144 /* center the pb in the line, but only if the line is higher than the pb */
145 int center = (line_height-height)/2;
146 /* if Y was not set calculate by font height,Y is -line_number-1 */
147 y = line*line_height + (0 > center ? 0 : center);
150 if (pb->type == SKIN_TOKEN_VOLUMEBAR)
152 int minvol = sound_min(SOUND_VOLUME);
153 int maxvol = sound_max(SOUND_VOLUME);
154 length = maxvol-minvol;
155 end = global_settings.volume-minvol;
157 else if (pb->type == SKIN_TOKEN_BATTERY_PERCENTBAR)
159 length = 100;
160 end = battery_level();
162 else if (pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ||
163 pb->type == SKIN_TOKEN_PEAKMETER_RIGHTBAR)
165 int left, right, val;
166 peak_meter_current_vals(&left, &right);
167 val = pb->type == SKIN_TOKEN_PEAKMETER_LEFTBAR ? left : right;
168 length = MAX_PEAK;
169 end = peak_meter_scale_value(val, length);
171 #if CONFIG_TUNER
172 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
174 #ifdef HAVE_RADIO_RSSI
175 if (pb->type == SKIN_TOKEN_TUNER_RSSI_BAR)
177 int val = tuner_get(RADIO_RSSI);
178 int min = tuner_get(RADIO_RSSI_MIN);
179 int max = tuner_get(RADIO_RSSI_MAX);
180 end = val - min;
181 length = max - min;
183 else
184 #endif
186 int min = fm_region_data[global_settings.fm_region].freq_min;
187 end = radio_current_frequency() - min;
188 length = fm_region_data[global_settings.fm_region].freq_max - min;
191 #endif
192 else if (id3 && id3->length)
194 length = id3->length;
195 end = id3->elapsed + state->ff_rewind_count;
197 else
199 length = 1;
200 end = 0;
203 if (!pb->horizontal)
205 /* we want to fill upwards which is technically inverted. */
206 flags = INVERTFILL;
209 if (pb->invert_fill_direction)
211 flags ^= INVERTFILL;
214 if (pb->nofill)
216 flags |= INNER_NOFILL;
219 if (pb->slider)
221 struct gui_img *img = pb->slider;
222 /* clear the slider */
223 screen_clear_area(display, x, y, width, height);
225 /* shrink the bar so the slider is inside bounds */
226 if (flags&HORIZONTAL)
228 width -= img->bm.width;
229 x += img->bm.width / 2;
231 else
233 height -= img->bm.height;
234 y += img->bm.height / 2;
238 if (pb->backdrop)
240 struct gui_img *img = pb->backdrop;
241 #if LCD_DEPTH > 1
242 if(img->bm.format == FORMAT_MONO) {
243 #endif
244 display->mono_bitmap_part(img->bm.data,
245 0, 0, img->bm.width,
246 x, y, width, height);
247 #if LCD_DEPTH > 1
248 } else {
249 display->transparent_bitmap_part((fb_data *)img->bm.data,
250 0, 0,
251 STRIDE(display->screen_type,
252 img->bm.width, img->bm.height),
253 x, y, width, height);
255 #endif
256 flags |= DONT_CLEAR_EXCESS;
259 if (!pb->nobar)
261 if (pb->image)
262 gui_bitmap_scrollbar_draw(display, &pb->image->bm,
263 x, y, width, height,
264 length, 0, end, flags);
265 else
266 gui_scrollbar_draw(display, x, y, width, height,
267 length, 0, end, flags);
270 if (pb->slider)
272 int xoff = 0, yoff = 0;
273 int w = width, h = height;
274 struct gui_img *img = pb->slider;
276 if (flags&HORIZONTAL)
278 w = img->bm.width;
279 xoff = width * end / length;
280 if (flags&INVERTFILL)
281 xoff = width - xoff;
282 xoff -= w / 2;
284 else
286 h = img->bm.height;
287 yoff = height * end / length;
288 if (flags&INVERTFILL)
289 yoff = height - yoff;
290 yoff -= h / 2;
292 #if LCD_DEPTH > 1
293 if(img->bm.format == FORMAT_MONO) {
294 #endif
295 display->mono_bitmap_part(img->bm.data,
296 0, 0, img->bm.width,
297 x + xoff, y + yoff, w, h);
298 #if LCD_DEPTH > 1
299 } else {
300 display->transparent_bitmap_part((fb_data *)img->bm.data,
301 0, 0,
302 STRIDE(display->screen_type,
303 img->bm.width, img->bm.height),
304 x + xoff, y + yoff, w, h);
306 #endif
309 if (pb->type == SKIN_TOKEN_PROGRESSBAR)
311 if (id3 && id3->length)
313 #ifdef AB_REPEAT_ENABLE
314 if (ab_repeat_mode_enabled())
315 ab_draw_markers(display, id3->length, x, y, width, height);
316 #endif
318 if (id3->cuesheet)
319 cue_draw_markers(display, id3->cuesheet, id3->length,
320 x, y+1, width, height-2);
322 #if 0 /* disable for now CONFIG_TUNER */
323 else if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF))
325 presets_draw_markers(display, x, y, width, height);
327 #endif
331 /* clears the area where the image was shown */
332 void clear_image_pos(struct gui_wps *gwps, struct gui_img *img)
334 if(!gwps)
335 return;
336 gwps->display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
337 gwps->display->fillrect(img->x, img->y, img->bm.width, img->subimage_height);
338 gwps->display->set_drawmode(DRMODE_SOLID);
341 void wps_draw_image(struct gui_wps *gwps, struct gui_img *img, int subimage)
343 struct screen *display = gwps->display;
344 if(img->always_display)
345 display->set_drawmode(DRMODE_FG);
346 else
347 display->set_drawmode(DRMODE_SOLID);
349 #if LCD_DEPTH > 1
350 if(img->bm.format == FORMAT_MONO) {
351 #endif
352 display->mono_bitmap_part(img->bm.data,
353 0, img->subimage_height * subimage,
354 img->bm.width, img->x,
355 img->y, img->bm.width,
356 img->subimage_height);
357 #if LCD_DEPTH > 1
358 } else {
359 display->transparent_bitmap_part((fb_data *)img->bm.data,
360 0, img->subimage_height * subimage,
361 STRIDE(display->screen_type,
362 img->bm.width, img->bm.height),
363 img->x, img->y, img->bm.width,
364 img->subimage_height);
366 #endif
370 void wps_display_images(struct gui_wps *gwps, struct viewport* vp)
372 if(!gwps || !gwps->data || !gwps->display)
373 return;
375 struct wps_data *data = gwps->data;
376 struct screen *display = gwps->display;
377 struct skin_token_list *list = data->images;
379 while (list)
381 struct gui_img *img = (struct gui_img*)list->token->value.data;
382 if (img->using_preloaded_icons && img->display >= 0)
384 screen_put_icon(display, img->x, img->y, img->display);
386 else if (img->loaded)
388 if (img->display >= 0)
390 wps_draw_image(gwps, img, img->display);
392 else if (img->always_display && img->vp == vp)
394 wps_draw_image(gwps, img, 0);
397 list = list->next;
399 #ifdef HAVE_ALBUMART
400 /* now draw the AA */
401 if (data->albumart && data->albumart->vp == vp
402 && data->albumart->draw_handle >= 0)
404 draw_album_art(gwps, data->albumart->draw_handle, false);
405 data->albumart->draw_handle = -1;
407 #endif
409 display->set_drawmode(DRMODE_SOLID);
412 #endif /* HAVE_LCD_BITMAP */
414 /* Evaluate the conditional that is at *token_index and return whether a skip
415 has ocurred. *token_index is updated with the new position.
417 int evaluate_conditional(struct gui_wps *gwps, int offset,
418 struct conditional *conditional, int num_options)
420 if (!gwps)
421 return false;
423 char result[128];
424 const char *value;
426 int intval = num_options < 2 ? 2 : num_options;
427 /* get_token_value needs to know the number of options in the enum */
428 value = get_token_value(gwps, conditional->token, offset,
429 result, sizeof(result), &intval);
431 /* intval is now the number of the enum option we want to read,
432 starting from 1. If intval is -1, we check if value is empty. */
433 if (intval == -1)
435 if (num_options == 1) /* so %?AA<true> */
436 intval = (value && *value) ? 1 : 0; /* returned as 0 for true, -1 for false */
437 else
438 intval = (value && *value) ? 1 : num_options;
440 else if (intval > num_options || intval < 1)
441 intval = num_options;
443 return intval -1;
447 /* Display a line appropriately according to its alignment format.
448 format_align contains the text, separated between left, center and right.
449 line is the index of the line on the screen.
450 scroll indicates whether the line is a scrolling one or not.
452 void write_line(struct screen *display,
453 struct align_pos *format_align,
454 int line,
455 bool scroll)
457 int left_width = 0, left_xpos;
458 int center_width = 0, center_xpos;
459 int right_width = 0, right_xpos;
460 int ypos;
461 int space_width;
462 int string_height;
463 int scroll_width;
465 /* calculate different string sizes and positions */
466 display->getstringsize((unsigned char *)" ", &space_width, &string_height);
467 if (format_align->left != 0) {
468 display->getstringsize((unsigned char *)format_align->left,
469 &left_width, &string_height);
472 if (format_align->right != 0) {
473 display->getstringsize((unsigned char *)format_align->right,
474 &right_width, &string_height);
477 if (format_align->center != 0) {
478 display->getstringsize((unsigned char *)format_align->center,
479 &center_width, &string_height);
482 left_xpos = 0;
483 right_xpos = (display->getwidth() - right_width);
484 center_xpos = (display->getwidth() + left_xpos - center_width) / 2;
486 scroll_width = display->getwidth() - left_xpos;
488 /* Checks for overlapping strings.
489 If needed the overlapping strings will be merged, separated by a
490 space */
492 /* CASE 1: left and centered string overlap */
493 /* there is a left string, need to merge left and center */
494 if ((left_width != 0 && center_width != 0) &&
495 (left_xpos + left_width + space_width > center_xpos)) {
496 /* replace the former separator '\0' of left and
497 center string with a space */
498 *(--format_align->center) = ' ';
499 /* calculate the new width and position of the merged string */
500 left_width = left_width + space_width + center_width;
501 /* there is no centered string anymore */
502 center_width = 0;
504 /* there is no left string, move center to left */
505 if ((left_width == 0 && center_width != 0) &&
506 (left_xpos + left_width > center_xpos)) {
507 /* move the center string to the left string */
508 format_align->left = format_align->center;
509 /* calculate the new width and position of the string */
510 left_width = center_width;
511 /* there is no centered string anymore */
512 center_width = 0;
515 /* CASE 2: centered and right string overlap */
516 /* there is a right string, need to merge center and right */
517 if ((center_width != 0 && right_width != 0) &&
518 (center_xpos + center_width + space_width > right_xpos)) {
519 /* replace the former separator '\0' of center and
520 right string with a space */
521 *(--format_align->right) = ' ';
522 /* move the center string to the right after merge */
523 format_align->right = format_align->center;
524 /* calculate the new width and position of the merged string */
525 right_width = center_width + space_width + right_width;
526 right_xpos = (display->getwidth() - right_width);
527 /* there is no centered string anymore */
528 center_width = 0;
530 /* there is no right string, move center to right */
531 if ((center_width != 0 && right_width == 0) &&
532 (center_xpos + center_width > right_xpos)) {
533 /* move the center string to the right string */
534 format_align->right = format_align->center;
535 /* calculate the new width and position of the string */
536 right_width = center_width;
537 right_xpos = (display->getwidth() - right_width);
538 /* there is no centered string anymore */
539 center_width = 0;
542 /* CASE 3: left and right overlap
543 There is no center string anymore, either there never
544 was one or it has been merged in case 1 or 2 */
545 /* there is a left string, need to merge left and right */
546 if ((left_width != 0 && center_width == 0 && right_width != 0) &&
547 (left_xpos + left_width + space_width > right_xpos)) {
548 /* replace the former separator '\0' of left and
549 right string with a space */
550 *(--format_align->right) = ' ';
551 /* calculate the new width and position of the string */
552 left_width = left_width + space_width + right_width;
553 /* there is no right string anymore */
554 right_width = 0;
556 /* there is no left string, move right to left */
557 if ((left_width == 0 && center_width == 0 && right_width != 0) &&
558 (left_width > right_xpos)) {
559 /* move the right string to the left string */
560 format_align->left = format_align->right;
561 /* calculate the new width and position of the string */
562 left_width = right_width;
563 /* there is no right string anymore */
564 right_width = 0;
567 ypos = (line * string_height);
570 if (scroll && ((left_width > scroll_width) ||
571 (center_width > scroll_width) ||
572 (right_width > scroll_width)))
574 display->puts_scroll(0, line,
575 (unsigned char *)format_align->left);
577 else
579 #ifdef HAVE_LCD_BITMAP
580 /* clear the line first */
581 display->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID);
582 display->fillrect(left_xpos, ypos, display->getwidth(), string_height);
583 display->set_drawmode(DRMODE_SOLID);
584 #endif
586 /* Nasty hack: we output an empty scrolling string,
587 which will reset the scroller for that line */
588 display->puts_scroll(0, line, (unsigned char *)"");
590 /* print aligned strings */
591 if (left_width != 0)
593 display->putsxy(left_xpos, ypos,
594 (unsigned char *)format_align->left);
596 if (center_width != 0)
598 display->putsxy(center_xpos, ypos,
599 (unsigned char *)format_align->center);
601 if (right_width != 0)
603 display->putsxy(right_xpos, ypos,
604 (unsigned char *)format_align->right);
609 #ifdef HAVE_LCD_BITMAP
610 void draw_peakmeters(struct gui_wps *gwps, int line_number,
611 struct viewport *viewport)
613 struct wps_data *data = gwps->data;
614 if (!data->peak_meter_enabled)
616 peak_meter_enable(false);
618 else
620 int h = font_get(viewport->font)->height;
621 int peak_meter_y = line_number * h;
623 /* The user might decide to have the peak meter in the last
624 line so that it is only displayed if no status bar is
625 visible. If so we neither want do draw nor enable the
626 peak meter. */
627 if (peak_meter_y + h <= viewport->y+viewport->height) {
628 peak_meter_enable(true);
629 peak_meter_screen(gwps->display, 0, peak_meter_y,
630 MIN(h, viewport->y+viewport->height - peak_meter_y));
635 bool skin_has_sbs(enum screen_type screen, struct wps_data *data)
637 (void)screen;
638 (void)data;
639 bool draw = false;
640 #ifdef HAVE_LCD_BITMAP
641 if (data->wps_sb_tag)
642 draw = data->show_sb_on_wps;
643 else if (statusbar_position(screen) != STATUSBAR_OFF)
644 draw = true;
645 #endif
646 return draw;
648 #endif
650 /* do the button loop as often as required for the peak meters to update
651 * with a good refresh rate.
653 int skin_wait_for_action(enum skinnable_screens skin, int context, int timeout)
655 (void)skin; /* silence charcell warning */
656 int button = ACTION_NONE;
657 #ifdef HAVE_LCD_BITMAP
658 int i;
659 /* when the peak meter is enabled we want to have a
660 few extra updates to make it look smooth. On the
661 other hand we don't want to waste energy if it
662 isn't displayed */
663 bool pm=false;
664 FOR_NB_SCREENS(i)
666 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
667 pm = true;
670 if (pm) {
671 long next_refresh = current_tick;
672 long next_big_refresh = current_tick + timeout;
673 button = BUTTON_NONE;
674 while (TIME_BEFORE(current_tick, next_big_refresh)) {
675 button = get_action(context,TIMEOUT_NOBLOCK);
676 if (button != ACTION_NONE) {
677 break;
679 peak_meter_peek();
680 sleep(0); /* Sleep until end of current tick. */
682 if (TIME_AFTER(current_tick, next_refresh)) {
683 FOR_NB_SCREENS(i)
685 if(skin_get_gwps(skin, i)->data->peak_meter_enabled)
686 skin_update(skin, i, SKIN_REFRESH_PEAK_METER);
687 next_refresh += HZ / PEAK_METER_FPS;
694 /* The peak meter is disabled
695 -> no additional screen updates needed */
696 else
697 #endif
699 button = get_action(context, timeout);
701 return button;