1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
10 * Copyright (C) 2007 by Jonathan Gordon
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
22 /* This file contains the code to draw the list widget on BITMAP LCDs. */
35 #include "screen_access.h"
37 #include "scrollbar.h"
42 #include "statusbar-skinned.h"
45 #define ICON_PADDING 1
47 /* these are static to make scrolling work */
48 static struct viewport list_text
[NB_SCREENS
], title_text
[NB_SCREENS
];
50 #ifdef HAVE_TOUCHSCREEN
51 /* difference in pixels between draws, above it means enough to start scrolling */
52 #define SCROLL_BEGIN_THRESHOLD 3
55 SCROLL_NONE
, /* no scrolling */
56 SCROLL_BAR
, /* scroll by using the scrollbar */
57 SCROLL_SWIPE
, /* scroll by wiping over the screen */
58 SCROLL_KINETIC
, /* state after releasing swipe */
59 SCROLL_KINETIC_CANCEL
, /* state when cancelling kinetic via touch */
65 int gui_list_get_item_offset(struct gui_synclist
* gui_list
, int item_width
,
66 int text_pos
, struct screen
* display
,
68 bool list_display_title(struct gui_synclist
*list
, enum screen_type screen
);
70 void gui_synclist_scroll_stop(struct gui_synclist
*lists
)
75 screens
[i
].scroll_stop(&list_text
[i
]);
76 screens
[i
].scroll_stop(&title_text
[i
]);
77 screens
[i
].scroll_stop(lists
->parent
[i
]);
82 internal screen layout:
84 |TI| title | TI is title icon
88 | | | items | I - icons
92 Note: This image is flipped horizontally when the language is a
93 right-to-left one (Hebrew, Arabic)
95 static bool draw_title(struct screen
*display
, struct gui_synclist
*list
)
97 const int screen
= display
->screen_type
;
98 int style
= STYLE_DEFAULT
;
99 struct viewport
*title_text_vp
= &title_text
[screen
];
101 if (sb_set_title_text(list
->title
, list
->title_icon
, screen
))
102 return false; /* the sbs is handling the title */
103 display
->scroll_stop(title_text_vp
);
104 if (!list_display_title(list
, screen
))
106 *title_text_vp
= *(list
->parent
[screen
]);
107 title_text_vp
->height
= font_get(title_text_vp
->font
)->height
;
109 if (list
->title_icon
!= Icon_NOICON
&& global_settings
.show_icons
)
111 struct viewport title_icon
= *title_text_vp
;
113 title_icon
.width
= get_icon_width(screen
) + ICON_PADDING
* 2;
114 if (VP_IS_RTL(&title_icon
))
116 title_icon
.x
+= title_text_vp
->width
- title_icon
.width
;
120 title_text_vp
->x
+= title_icon
.width
;
122 title_text_vp
->width
-= title_icon
.width
;
124 display
->set_viewport(&title_icon
);
125 screen_put_icon(display
, 0, 0, list
->title_icon
);
127 #ifdef HAVE_LCD_COLOR
128 if (list
->title_color
>= 0)
130 style
|= (STYLE_COLORED
|list
->title_color
);
133 display
->set_viewport(title_text_vp
);
134 display
->puts_scroll_style(0, 0, list
->title
, style
);
138 void list_draw(struct screen
*display
, struct gui_synclist
*list
)
140 struct viewport list_icons
;
141 int start
, end
, line_height
, style
, i
;
142 const int screen
= display
->screen_type
;
143 const int list_start_item
= list
->start_item
[screen
];
144 const int icon_width
= get_icon_width(screen
) + ICON_PADDING
;
145 const bool scrollbar_in_left
= (global_settings
.scrollbar
== SCROLLBAR_LEFT
);
146 const bool show_cursor
= !global_settings
.cursor_style
&&
147 list
->show_selection_marker
;
148 struct viewport
*parent
= (list
->parent
[screen
]);
149 #ifdef HAVE_LCD_COLOR
150 unsigned char cur_line
= 0;
154 struct viewport
*list_text_vp
= &list_text
[screen
];
156 line_height
= font_get(parent
->font
)->height
;
157 display
->set_viewport(parent
);
158 display
->clear_viewport();
159 display
->scroll_stop(list_text_vp
);
160 *list_text_vp
= *parent
;
161 if ((show_title
= draw_title(display
, list
)))
163 list_text_vp
->y
+= line_height
;
164 list_text_vp
->height
-= line_height
;
168 start
= list_start_item
;
169 end
= start
+ viewport_get_nb_lines(list_text_vp
);
171 #ifdef HAVE_TOUCHSCREEN
172 if (list
->selected_item
== 0)
173 y_offset
= 0; /* reset in case it's a new list */
175 int draw_offset
= y_offset
;
176 /* draw some extra items to not have empty lines at the top and bottom */
179 /* make it negative for more consistent apparence when switching
181 draw_offset
-= line_height
;
185 else if (y_offset
< 0)
188 #define draw_offset 0
191 /* draw the scrollbar if its needed */
192 if (global_settings
.scrollbar
&&
193 viewport_get_nb_lines(list_text_vp
) < list
->nb_items
)
195 struct viewport vp
= *list_text_vp
;
196 vp
.width
= SCROLLBAR_WIDTH
;
197 vp
.height
= line_height
* viewport_get_nb_lines(list_text_vp
);
199 list_text_vp
->width
-= SCROLLBAR_WIDTH
;
200 if (scrollbar_in_left
)
201 list_text_vp
->x
+= SCROLLBAR_WIDTH
;
203 vp
.x
+= list_text_vp
->width
;
204 display
->set_viewport(&vp
);
205 gui_scrollbar_draw(display
,
206 (scrollbar_in_left
? 0: 1), 0, SCROLLBAR_WIDTH
-1, vp
.height
,
207 list
->nb_items
, list_start_item
, list_start_item
+ end
-start
,
212 /* shift everything a bit in relation to the title... */
213 if (!VP_IS_RTL(list_text_vp
) && scrollbar_in_left
)
215 list_text_vp
->width
-= SCROLLBAR_WIDTH
;
216 list_text_vp
->x
+= SCROLLBAR_WIDTH
;
218 else if (VP_IS_RTL(list_text_vp
) && !scrollbar_in_left
)
220 list_text_vp
->width
-= SCROLLBAR_WIDTH
;
224 /* setup icon placement */
225 list_icons
= *list_text_vp
;
226 int icon_count
= (list
->callback_get_item_icon
!= NULL
) ? 1 : 0;
231 list_icons
.width
= icon_width
* icon_count
;
232 list_text_vp
->width
-= list_icons
.width
+ ICON_PADDING
;
233 if (VP_IS_RTL(&list_icons
))
234 list_icons
.x
+= list_text_vp
->width
+ ICON_PADDING
;
236 list_text_vp
->x
+= list_icons
.width
+ ICON_PADDING
;
239 for (i
=start
; i
<end
&& i
<list
->nb_items
; i
++)
242 unsigned const char *s
;
243 char entry_buffer
[MAX_PATH
];
244 unsigned char *entry_name
;
246 int line
= i
- start
;
247 s
= list
->callback_get_item_name(i
, list
->data
, entry_buffer
,
248 sizeof(entry_buffer
));
249 entry_name
= P2STR(s
);
250 display
->set_viewport(list_text_vp
);
251 style
= STYLE_DEFAULT
;
252 /* position the string at the correct offset place */
254 display
->getstringsize(entry_name
, &item_width
, &h
);
255 item_offset
= gui_list_get_item_offset(list
, item_width
, text_pos
,
256 display
, list_text_vp
);
258 #ifdef HAVE_LCD_COLOR
259 /* if the list has a color callback */
260 if (list
->callback_get_item_color
)
262 int color
= list
->callback_get_item_color(i
, list
->data
);
263 /* if color selected */
266 style
|= STYLE_COLORED
|color
;
270 /* draw the selected line */
272 #ifdef HAVE_TOUCHSCREEN
273 /* don't draw it during scrolling */
274 scroll_mode
== SCROLL_NONE
&&
276 i
>= list
->selected_item
277 && i
< list
->selected_item
+ list
->selected_size
278 && list
->show_selection_marker
)
279 {/* The selected item must be displayed scrolling */
280 if (global_settings
.cursor_style
== 1
281 #ifdef HAVE_REMOTE_LCD
282 /* the global_settings.cursor_style check is here to make
283 * sure if they want the cursor instead of bar it will work
285 || (display
->depth
< 16 && global_settings
.cursor_style
)
289 /* Display inverted-line-style */
290 style
= STYLE_INVERT
;
292 #ifdef HAVE_LCD_COLOR
293 else if (global_settings
.cursor_style
== 2)
295 /* Display colour line selector */
296 style
= STYLE_COLORBAR
;
298 else if (global_settings
.cursor_style
== 3)
300 /* Display gradient line selector */
301 style
= STYLE_GRADIENT
;
303 /* Make the lcd driver know how many lines the gradient should
304 cover and current line number */
305 /* number of selected lines */
306 style
|= NUMLN_PACK(list
->selected_size
);
307 /* current line number, zero based */
308 style
|= CURLN_PACK(cur_line
);
312 /* if the text is smaller than the viewport size */
313 if (item_offset
> item_width
- (list_text_vp
->width
- text_pos
))
316 display
->puts_style_xyoffset(0, line
, entry_name
,
317 style
, item_offset
, draw_offset
);
321 display
->puts_scroll_style_xyoffset(0, line
, entry_name
,
322 style
, item_offset
, draw_offset
);
327 if (list
->scroll_all
)
328 display
->puts_scroll_style_xyoffset(0, line
, entry_name
,
329 style
, item_offset
, draw_offset
);
331 display
->puts_style_xyoffset(0, line
, entry_name
,
332 style
, item_offset
, draw_offset
);
335 display
->set_viewport(&list_icons
);
336 if (list
->callback_get_item_icon
&& global_settings
.show_icons
)
338 screen_put_icon_with_offset(display
, show_cursor
?1:0,
339 (line
),show_cursor
?ICON_PADDING
:0,draw_offset
,
340 list
->callback_get_item_icon(i
, list
->data
));
342 if (show_cursor
&& i
>= list
->selected_item
&&
343 i
< list
->selected_item
+ list
->selected_size
)
345 screen_put_icon_with_offset(display
, 0, line
, 0, draw_offset
, Icon_Cursor
);
348 display
->set_viewport(parent
);
349 display
->update_viewport();
350 display
->set_viewport(NULL
);
353 #if defined(HAVE_TOUCHSCREEN)
354 /* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
356 static bool released
= false;
358 /* Used for kinetic scrolling as we need to know the last position to
359 * recognize the scroll direction.
360 * This gets reset to 0 at the end of scrolling
362 static int last_position
=0;
364 static int scrollbar_scroll(struct gui_synclist
* gui_list
,
367 const int screen
= screens
[SCREEN_MAIN
].screen_type
;
368 const int nb_lines
= viewport_get_nb_lines(&list_text
[screen
]);
370 if (nb_lines
< gui_list
->nb_items
)
372 /* scrollbar scrolling is still line based */
374 int scrollbar_size
= nb_lines
*
375 font_get(gui_list
->parent
[screen
]->font
)->height
;
376 int actual_y
= y
- list_text
[screen
].y
;
378 int new_selection
= (actual_y
* gui_list
->nb_items
)
381 int start_item
= new_selection
- nb_lines
/2;
384 else if(start_item
> gui_list
->nb_items
- nb_lines
)
385 start_item
= gui_list
->nb_items
- nb_lines
;
387 gui_list
->start_item
[screen
] = start_item
;
389 return ACTION_REDRAW
;
395 /* kinetic scrolling, based on
397 * v = a*t + v0 and ds = v*dt
399 * In each (fixed interval) timeout, the list is advanced by ds, then
400 * the v is reduced by a.
401 * This way we get a linear and smooth deceleration of the scrolling
403 * As v is the difference of distance per time unit, v is passed (as
404 * pixels moved since the last call) to the scrolling function which takes
405 * care of the pixel accurate drawing
407 * v0 is dertermined by averaging the last 4 movements of the list
408 * (the pixel and time difference is used to compute each v)
410 * influenced by http://stechz.com/tag/kinetic/
411 * We take the easy and smooth first approach (until section "Drawbacks"),
412 * since its drawbacks don't apply for us since our timers seem to be
413 * relatively accurate
417 #define SIGN(a) ((a) < 0 ? -1 : 1)
418 /* these could possibly be configurable */
419 #define RELOAD_INTERVAL (HZ/20)
420 #define DECELERATION (1000*RELOAD_INTERVAL/HZ)
422 /* this array holds data to compute the initial velocity v0 */
423 static struct kinetic_info
{
427 static size_t cur_idx
;
429 static struct cb_data
{
430 struct gui_synclist
*list
; /* current list */
431 int velocity
; /* in pixel/s */
434 /* data member points to the above struct */
435 static struct timeout kinetic_tmo
;
438 * collect data about how fast the list is moved in order to compute
439 * the initial velocity from it later */
440 static void kinetic_stats_collect(const int difference
)
442 static long last_tick
;
443 /* collect velocity statistics */
444 kinetic_data
[cur_idx
].difference
= difference
;
445 kinetic_data
[cur_idx
].ticks
= current_tick
- last_tick
;
447 last_tick
= current_tick
;
449 if (cur_idx
>= ARRAYLEN(kinetic_data
))
450 cur_idx
= 0; /* rewind the index */
454 * resets the statistic */
455 static void kinetic_stats_reset(void)
457 memset(kinetic_data
, 0, sizeof(kinetic_data
));
461 /* cancels all currently active kinetic scrolling */
462 static void kinetic_force_stop(void)
464 timeout_cancel(&kinetic_tmo
);
465 kinetic_stats_reset();
469 * returns false if scrolling should be stopped entirely
471 * otherwise it returns true even if it didn't actually scroll,
472 * but scrolling mode shouldn't be changed
474 static bool swipe_scroll(struct gui_synclist
* gui_list
, int line_height
, int difference
)
477 const enum screen_type screen
= screens
[SCREEN_MAIN
].screen_type
;
478 const int nb_lines
= viewport_get_nb_lines(&list_text
[screen
]);
480 /* make selecting items easier */
481 if (abs(difference
) < SCROLL_BEGIN_THRESHOLD
&& scroll_mode
== SCROLL_NONE
)
484 /* does the list even scroll? if no, return but still show
485 * the caller that we would scroll */
486 if (nb_lines
>= gui_list
->nb_items
)
489 const int old_start
= gui_list
->start_item
[screen
];
490 int new_start_item
= -1;
493 /* don't scroll at the edges of the list */
494 if ((old_start
== 0 && difference
> 0)
495 || (old_start
== (gui_list
->nb_items
- nb_lines
) && difference
< 0))
498 return scroll_mode
!= SCROLL_KINETIC
; /* stop kinetic at the edges */
501 /* add up y_offset over time and translate to lines
502 * if scrolled enough */
503 y_offset
+= difference
;
504 if (abs(y_offset
) > line_height
)
506 line_diff
= y_offset
/line_height
;
507 y_offset
-= line_diff
* line_height
;
512 new_start_item
= old_start
- line_diff
;
513 /* check if new_start_item is bigger than list item count */
514 if(new_start_item
> gui_list
->nb_items
- nb_lines
)
515 new_start_item
= gui_list
->nb_items
- nb_lines
;
516 /* set new_start_item to 0 if it's negative */
517 if(new_start_item
< 0)
520 gui_list
->start_item
[screen
] = new_start_item
;
526 static int kinetic_callback(struct timeout
*tmo
)
528 /* cancel if screen was pressed */
529 if (scroll_mode
!= SCROLL_KINETIC
)
532 struct cb_data
*data
= (struct cb_data
*)tmo
->data
;
533 int line_height
= font_get(data
->list
->parent
[0]->font
)->height
;
535 int pixel_diff
= data
->velocity
* RELOAD_INTERVAL
/ HZ
;
536 /* remember signedness to detect stopping */
537 int old_sign
= SIGN(data
->velocity
);
538 /* advance the list */
539 if (!swipe_scroll(data
->list
, line_height
, pixel_diff
))
541 /* nothing to scroll? */
546 /* decelerate by a fixed amount
547 * decrementing v0 over time by the deceleration is
548 * equivalent to computing v = a*t + v0 */
549 data
->velocity
-= SIGN(data
->velocity
)*DECELERATION
;
552 /* stop if the velocity hit or crossed zero */
553 if (!data
->velocity
|| (SIGN(data
->velocity
) != old_sign
))
555 kinetic_stats_reset();
556 scroll_mode
= SCROLL_NONE
;
557 return 0; /* cancel timer */
561 /* let get_action() timeout, which loads to a
562 * gui_synclist_draw() call from the main thread */
563 queue_post(&button_queue
, BUTTON_TOUCHSCREEN
, 0);
564 return RELOAD_INTERVAL
; /* reload */
569 * computes the initial velocity v0 and sets up the timer */
570 static bool kinetic_setup_scroll(struct gui_synclist
*list
)
572 /* compute initial velocity */
573 int i
, _i
, v0
, len
= ARRAYLEN(kinetic_data
);
574 for(i
= 0, _i
= 0, v0
= 0; i
< len
; i
++)
576 if (kinetic_data
[i
].ticks
> 0)
578 v0
+= kinetic_data
[i
].difference
*HZ
/kinetic_data
[i
].ticks
;
590 cb_data
.velocity
= v0
;
591 timeout_register(&kinetic_tmo
, kinetic_callback
, RELOAD_INTERVAL
, (intptr_t)&cb_data
);
597 unsigned gui_synclist_do_touchscreen(struct gui_synclist
* gui_list
)
600 const enum screen_type screen
= SCREEN_MAIN
;
601 struct viewport
*info_vp
= sb_skin_get_info_vp(screen
);
602 const int button
= action_get_touchscreen_press_in_vp(&x
, &y
, info_vp
);
603 const int list_start_item
= gui_list
->start_item
[screen
];
604 const int line_height
= font_get(gui_list
->parent
[screen
]->font
)->height
;
605 const struct viewport
*list_text_vp
= &list_text
[screen
];
606 const bool old_released
= released
;
607 const bool show_title
= list_display_title(gui_list
, screen
);
608 const bool show_cursor
= !global_settings
.cursor_style
&&
609 gui_list
->show_selection_marker
;
610 const bool on_title_clicked
= show_title
&& y
< line_height
;
612 int line
, list_width
= list_text_vp
->width
;
614 released
= (button
&BUTTON_REL
) != 0;
616 if (scroll_mode
== SCROLL_KINETIC
)
618 if (button
!= ACTION_NONE
&& button
!= ACTION_UNKNOWN
)
620 kinetic_force_stop();
621 scroll_mode
= SCROLL_KINETIC_CANCEL
;
623 return ACTION_REDRAW
;
626 if (button
== ACTION_NONE
|| button
== ACTION_UNKNOWN
)
629 /* x and y are relative to info_vp */
630 if (global_settings
.show_icons
)
631 icon_width
+= get_icon_width(screen
);
633 icon_width
+= get_icon_width(screen
);
635 released
= (button
&BUTTON_REL
) != 0;
637 if (button
== BUTTON_NONE
)
640 if (on_title_clicked
)
644 /* Top left corner is GO_TO_ROOT */
645 if (button
== BUTTON_REL
)
646 return ACTION_STD_MENU
;
647 else if (button
== (BUTTON_REPEAT
|BUTTON_REL
))
648 return ACTION_STD_CONTEXT
;
651 else /* click on title text is cancel */
652 if (button
== BUTTON_REL
&& scroll_mode
== SCROLL_NONE
)
653 return ACTION_STD_CANCEL
;
655 else /* list area clicked */
657 const int actual_y
= y
- (show_title
? line_height
: 0);
658 bool on_scrollbar_clicked
;
659 switch (global_settings
.scrollbar
)
662 on_scrollbar_clicked
= x
<= SCROLLBAR_WIDTH
; break;
663 case SCROLLBAR_RIGHT
:
664 on_scrollbar_clicked
= x
> (icon_width
+ list_width
); break;
666 on_scrollbar_clicked
= false; break;
668 /* conditions for scrollbar scrolling:
669 * * pen is on the scrollbar
670 * AND scrollbar is on the right (left case is handled above)
671 * OR * pen is in the somewhere else but we did scrollbar scrolling before
673 * scrollbar scrolling must end if the pen is released
674 * scrollbar scrolling must not happen if we're currently scrolling
675 * via swiping the screen
678 if (!released
&& scroll_mode
< SCROLL_SWIPE
&&
679 (on_scrollbar_clicked
|| scroll_mode
== SCROLL_BAR
))
681 scroll_mode
= SCROLL_BAR
;
682 return scrollbar_scroll(gui_list
, y
);
685 /* |--------------------------------------------------------|
686 * | Description of the touchscreen list interface: |
687 * |--------------------------------------------------------|
688 * | Pressing an item will select it and "enter" it. |
690 * | Pressing and holding your pen down will scroll through |
691 * | the list of items. |
693 * | Pressing and holding your pen down on a single item |
694 * | will bring up the context menu of it. |
695 * |--------------------------------------------------------|
697 if (actual_y
> 0 || button
& BUTTON_REPEAT
)
699 /* selection needs to be corrected if an items are only
700 * partially visible */
701 line
= (actual_y
- y_offset
) / line_height
;
703 /* Pressed below the list*/
704 if (list_start_item
+ line
>= gui_list
->nb_items
)
706 /* don't collect last_position outside of the list area
707 * it'd break selecting after such a situation */
714 /* Pen was released anywhere on the screen */
716 if (scroll_mode
== SCROLL_KINETIC_CANCEL
)
718 scroll_mode
= SCROLL_NONE
;
721 if (scroll_mode
== SCROLL_NONE
)
723 gui_synclist_select_item(gui_list
, list_start_item
+ line
);
724 /* If BUTTON_REPEAT is set, then the pen was hold on
725 * the same line for some time
730 if (button
& BUTTON_REPEAT
)
731 return ACTION_STD_CONTEXT
;
732 return ACTION_STD_OK
;
737 * -> reset scrolling but do nothing else */
738 if (scroll_mode
== SCROLL_SWIPE
)
740 if (kinetic_setup_scroll(gui_list
))
741 scroll_mode
= SCROLL_KINETIC
;
743 scroll_mode
= SCROLL_NONE
;
745 return ACTION_REDRAW
;
749 { /* pen is on the screen */
750 bool redraw
= false, result
= false;
751 if (scroll_mode
== SCROLL_KINETIC_CANCEL
)
753 scroll_mode
= SCROLL_SWIPE
;
756 /* beginning of list interaction denoted by release in
757 * the previous call */
760 scroll_mode
= SCROLL_NONE
;
764 /* select current item */
765 gui_synclist_select_item(gui_list
, list_start_item
+line
);
766 if (last_position
== 0)
767 last_position
= actual_y
;
770 /* record speed data in case we do kinetic scrolling */
771 int diff
= actual_y
- last_position
;
772 kinetic_stats_collect(diff
);
773 result
= swipe_scroll(gui_list
, line_height
, diff
);
776 /* Start scrolling once the pen is moved without
777 * releasing it inbetween */
781 scroll_mode
= SCROLL_SWIPE
;
784 last_position
= actual_y
;
786 return redraw
? ACTION_REDRAW
:ACTION_NONE
;