diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index 67f1f0f448..28483cbc49 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c @@ -779,7 +779,9 @@ void skin_render_viewport(struct skin_element* viewport, struct gui_wps *gwps, if (refresh_type && needs_update) { if (info.force_redraw) - display->scroll_stop_viewport_line(&skin_viewport->vp, info.line_number); + display->scroll_stop_viewport_rect(&skin_viewport->vp, + 0, info.line_number*display->getcharheight(), + skin_viewport->vp.width, display->getcharheight()); write_line(display, align, info.line_number, info.line_scrolls, info.text_style); } @@ -967,8 +969,11 @@ void skin_render_playlistviewer(struct playlistviewer* viewer, /* only update if the line needs to be, and there is something to write */ if (refresh_type && needs_update) { + struct viewport *vp = SKINOFFSETTOPTR(skin_buffer, viewer->vp); if (!info.force_redraw) - display->scroll_stop_viewport_line(&skin_viewport->vp, info.line_number); + display->scroll_stop_viewport_rect(vp, + 0, info.line_number*display->getcharheight(), + vp->width, display->getcharheight()); write_line(display, align, info.line_number, info.line_scrolls, info.text_style); } diff --git a/apps/screen_access.c b/apps/screen_access.c index 161e596aa2..f454e0adef 100644 --- a/apps/screen_access.c +++ b/apps/screen_access.c @@ -247,9 +247,9 @@ struct screen screens[NB_SCREENS] = .scroll_delay=&lcd_scroll_delay, .clear_display=&lcd_clear_display, .clear_viewport=&lcd_clear_viewport, + .scroll_stop_viewport_rect=&lcd_scroll_stop_viewport_rect, .scroll_stop=&lcd_scroll_stop, .scroll_stop_viewport=&lcd_scroll_stop_viewport, - .scroll_stop_viewport_line=&lcd_scroll_stop_viewport_line, .update=&lcd_update, .update_viewport=&lcd_update_viewport, .backlight_on=&backlight_on, @@ -348,9 +348,9 @@ struct screen screens[NB_SCREENS] = .scroll_delay=&lcd_remote_scroll_delay, .clear_display=&lcd_remote_clear_display, .clear_viewport=&lcd_remote_clear_viewport, + .scroll_stop_viewport_rect=&lcd_remote_scroll_stop_viewport_rect, .scroll_stop=&lcd_remote_scroll_stop, .scroll_stop_viewport=&lcd_remote_scroll_stop_viewport, - .scroll_stop_viewport_line=&lcd_remote_scroll_stop_viewport_line, .update=&lcd_remote_update, .update_viewport=&lcd_remote_update_viewport, .backlight_on=&remote_backlight_on, diff --git a/apps/screen_access.h b/apps/screen_access.h index 448437c637..90b63ea338 100644 --- a/apps/screen_access.h +++ b/apps/screen_access.h @@ -149,7 +149,7 @@ struct screen void (*clear_viewport)(void); void (*scroll_stop)(void); void (*scroll_stop_viewport)(const struct viewport *vp); - void (*scroll_stop_viewport_line)(const struct viewport *vp, int line); + void (*scroll_stop_viewport_rect)(const struct viewport* vp, int x, int y, int width, int height); void (*update)(void); void (*update_viewport)(void); void (*backlight_on)(void); diff --git a/firmware/drivers/lcd-bitmap-common.c b/firmware/drivers/lcd-bitmap-common.c index c04f57ef22..f3e700a4a1 100644 --- a/firmware/drivers/lcd-bitmap-common.c +++ b/firmware/drivers/lcd-bitmap-common.c @@ -412,12 +412,10 @@ static void LCDFN(putsxyofs_style)(int xpos, int ypos, /*** Line oriented text output ***/ -/* put a string at a given char position */ void LCDFN(puts_style_xyoffset)(int x, int y, const unsigned char *str, int style, int x_offset, int y_offset) { int xpos, ypos, h; - LCDFN(scroll_stop_viewport_line)(current_vp, y); if(!str) return; @@ -425,14 +423,15 @@ void LCDFN(puts_style_xyoffset)(int x, int y, const unsigned char *str, if ((style&STYLE_XY_PIXELS) == 0) { xpos = x * LCDFN(getstringsize)(" ", NULL, NULL); - ypos = y * h + y_offset; + ypos = y * h; } else { xpos = x; - ypos = y + y_offset; + ypos = y; } - LCDFN(putsxyofs_style)(xpos, ypos, str, style, h, x_offset); + LCDFN(scroll_stop_viewport_rect)(current_vp, x, y, current_vp->width - x, h); + LCDFN(putsxyofs_style)(xpos, ypos+y_offset, str, style, h, x_offset); } void LCDFN(puts_style_offset)(int x, int y, const unsigned char *str, @@ -469,7 +468,7 @@ void LCDFN(puts_offset)(int x, int y, const unsigned char *str, int offset) /*** scrolling ***/ -static struct scrollinfo* find_scrolling_line(int line) +static struct scrollinfo* find_scrolling_line(int x, int y) { struct scrollinfo* s = NULL; int i; @@ -477,93 +476,101 @@ static struct scrollinfo* find_scrolling_line(int line) for(i=0; iy == line && s->vp == current_vp) + if (s->x == x && s->y == y && s->vp == current_vp) return s; } return NULL; } -void LCDFN(puts_scroll_style_xyoffset)(int x, int y, const unsigned char *string, - int style, int x_offset, int y_offset) +void LCDFN(scroll_fn)(struct scrollinfo* s) +{ + LCDFN(putsxyofs_style)(s->x, s->y, s->line, s->style, s->height, s->offset); +} + +static void LCDFN(puts_scroll_worker)(int x, int y, const unsigned char *string, + int style, int x_offset, int y_offset, + bool linebased, + void (*scroll_func)(struct scrollinfo *), + void *data) { struct scrollinfo* s; - char *end; - int w, h; - int len; - bool restart = false; - int space_width; + int width, height; + int w, h, cwidth, margin; + bool restart; - if (!string || ((unsigned)y >= (unsigned)current_vp->height)) + if (!string) return; - s = find_scrolling_line(y); - if (!s) - restart = true; + /* prepare rectangle for scrolling. x and y must be calculated early + * for find_scrolling_line() to work */ + cwidth = font_get(current_vp->font)->maxwidth; + height = current_vp->line_height ?: (int)font_get(current_vp->font)->height; + y = y * (linebased ? height : 1) + y_offset; + x = x * (linebased ? cwidth : 1); + width = current_vp->width - x; - if (restart) - { + if (y >= current_vp->height) + return; + + s = find_scrolling_line(x, y); + restart = !s; + + if (restart) { /* remove any previously scrolling line at the same location */ - LCDFN(scroll_stop_viewport_line)(current_vp, y); + LCDFN(scroll_stop_viewport_rect)(current_vp, x, y, width, height); + LCDFN(putsxyofs_style)(x, y, string, style, height, x_offset); - if (LCDFN(scroll_info).lines >= LCDM(SCROLLABLE_LINES)) return; - LCDFN(puts_style_xyoffset)(x, y, string, style, x_offset, y_offset); + if (LCDFN(scroll_info).lines >= LCDM(SCROLLABLE_LINES)) + return; } + /* get width (pixeks) of the string */ LCDFN(getstringsize)(string, &w, &h); - if (current_vp->width - x * 8 >= w) + /* check if scrolling is actually necessary (consider the actual start + * of the line) */ + margin = x * linebased ? cwidth : 1; + if (current_vp->width >= margin+w) return; - if (restart) - { + if (restart) { /* prepare scroll line */ s = &LCDFN(scroll_info).scroll[LCDFN(scroll_info).lines]; s->start_tick = current_tick + LCDFN(scroll_info).delay; } - strlcpy(s->line, string, sizeof s->line); - space_width = LCDFN(getstringsize)(" ", NULL, NULL); - /* get width */ - LCDFN(getstringsize)(s->line, &w, &h); - if (!restart && s->width > w) - { - if (s->startx > w) - s->startx = w; - } - s->width = w; - - /* scroll bidirectional or forward only depending on the string - width */ + /* copy contents to the line buffer */ + strlcpy(s->linebuffer, string, sizeof(s->linebuffer)); + /* scroll bidirectional or forward only depending on the string width */ if ( LCDFN(scroll_info).bidir_limit ) { - s->bidir = s->width < (current_vp->width) * + s->bidir = w < (current_vp->width) * (100 + LCDFN(scroll_info).bidir_limit) / 100; } else s->bidir = false; - if (!s->bidir) { /* add spaces if scrolling in the round */ - strlcat(s->line, " ", sizeof s->line); - /* get new width incl. spaces */ - s->width += space_width * 3; - } + s->scroll_func = scroll_func; + s->userdata = data; - end = strchr(s->line, '\0'); - len = sizeof s->line - (end - s->line); - strlcpy(end, string, MIN(current_vp->width/2, len)); - - s->vp = current_vp; - s->y = y; - if (restart) - { + if (restart) { s->offset = x_offset; - s->startx = x * space_width; s->backward = false; s->style = style; - } - s->y_offset = y_offset; - - if (restart) + /* assign the rectangle. not necessary if continuing an earlier line */ + s->x = x; + s->y = y; + s->width = width; + s->height = height; + s->vp = current_vp; LCDFN(scroll_info).lines++; + } +} + +void LCDFN(puts_scroll_style_xyoffset)(int x, int y, const unsigned char *string, + int style, int x_offset, int y_offset) +{ + LCDFN(puts_scroll_worker)(x, y, string, style, x_offset, y_offset, + true, LCDFN(scroll_fn), NULL); } void LCDFN(puts_scroll)(int x, int y, const unsigned char *string) @@ -583,65 +590,6 @@ void LCDFN(puts_scroll_offset)(int x, int y, const unsigned char *string, LCDFN(puts_scroll_style_offset)(x, y, string, STYLE_DEFAULT, offset); } -void LCDFN(scroll_fn)(void) -{ - struct scrollinfo* s; - int index; - int xpos, ypos, height; - struct viewport* old_vp = current_vp; - bool makedelay; - - for ( index = 0; index < LCDFN(scroll_info).lines; index++ ) { - s = &LCDFN(scroll_info).scroll[index]; - - /* check pause */ - if (TIME_BEFORE(current_tick, s->start_tick)) - continue; - - LCDFN(set_viewport)(s->vp); - height = s->vp->line_height ?: (int)font_get(s->vp->font)->height; - - if (s->backward) - s->offset -= LCDFN(scroll_info).step; - else - s->offset += LCDFN(scroll_info).step; - - xpos = s->startx; - ypos = s->y * height + s->y_offset; - - makedelay = false; - if (s->bidir) { /* scroll bidirectional */ - if (s->offset <= 0) { - /* at beginning of line */ - s->offset = 0; - s->backward = false; - makedelay = true; - } - else if (s->offset >= s->width - (current_vp->width - xpos)) { - /* at end of line */ - s->offset = s->width - (current_vp->width - xpos); - s->backward = true; - makedelay = true; - } - } - else { - /* scroll forward the whole time */ - if (s->offset >= s->width) { - s->offset = 0; - makedelay = true; - } - } - - if (makedelay) - s->start_tick = current_tick + LCDFN(scroll_info).delay + - LCDFN(scroll_info).ticks; - - LCDFN(putsxyofs_style)(xpos, ypos, s->line, s->style, height, s->offset); - LCDFN(update_viewport_rect)(xpos, ypos, current_vp->width-xpos, height); - } - LCDFN(set_viewport)(old_vp); -} - void LCDFN(puts_scroll_style_offset)(int x, int y, const unsigned char *string, int style, int x_offset) { diff --git a/firmware/drivers/lcd-scroll.c b/firmware/drivers/lcd-scroll.c index ffd4663c79..31c2cf20b0 100644 --- a/firmware/drivers/lcd-scroll.c +++ b/firmware/drivers/lcd-scroll.c @@ -54,30 +54,27 @@ void LCDFN(scroll_stop)(void) LCDFN(scroll_info).lines = 0; } -/* Stop scrolling line y in the specified viewport, or all lines if y < 0 */ -void LCDFN(scroll_stop_viewport_line)(const struct viewport *current_vp, int line) +/* Clears scrolling lines that intersect with the area */ +void LCDFN(scroll_stop_viewport_rect)(const struct viewport *vp, int x, int y, int width, int height) { int i = 0; - while (i < LCDFN(scroll_info).lines) { - struct viewport *vp = LCDFN(scroll_info).scroll[i].vp; - if (((vp == current_vp)) && - ((line < 0) || (LCDFN(scroll_info).scroll[i].y == line))) + struct scrollinfo *s = &LCDFN(scroll_info).scroll[i]; + /* check if the specified area crosses the viewport in some way */ + if (s->vp == vp + && (x < (s->x+s->width) && (x+width) >= s->x) + && (y < (s->y+s->height) && (y+height) >= s->y)) { /* If i is not the last active line in the array, then move - the last item to position i */ + the last item to position i. This compacts + the scroll array at the same time of removing the line */ if ((i + 1) != LCDFN(scroll_info).lines) { LCDFN(scroll_info).scroll[i] = LCDFN(scroll_info).scroll[LCDFN(scroll_info).lines-1]; } LCDFN(scroll_info).lines--; - - /* A line can only appear once, so we're done, - * unless we are clearing the whole viewport */ - if (line >= 0) - return ; } else { @@ -87,9 +84,9 @@ void LCDFN(scroll_stop_viewport_line)(const struct viewport *current_vp, int lin } /* Stop all scrolling lines in the specified viewport */ -void LCDFN(scroll_stop_viewport)(const struct viewport *current_vp) +void LCDFN(scroll_stop_viewport)(const struct viewport *vp) { - LCDFN(scroll_stop_viewport_line)(current_vp, -1); + LCDFN(scroll_stop_viewport_rect)(vp, 0, 0, vp->width, vp->height); } void LCDFN(scroll_speed)(int speed) @@ -125,3 +122,89 @@ void LCDFN(jump_scroll_delay)(int ms) LCDFN(scroll_info).jump_scroll_delay = ms / (HZ / 10); } #endif + +static void LCDFN(scroll_worker)(void) +{ + int index, width; + bool makedelay; + static char line_buf[SCROLL_LINE_SIZE]; + bool is_default; + struct scroll_screen_info *si = &LCDFN(scroll_info); + struct scrollinfo *s; + struct viewport *vp; + + unsigned fg_pattern, bg_pattern, drawmode; + + for ( index = 0; index < si->lines; index++ ) { + s = &si->scroll[index]; + + /* check pause */ + if (TIME_BEFORE(current_tick, s->start_tick)) + continue; + + s->start_tick = current_tick; + + /* this runs out of the ui thread, thus we need to + * save and restore the current viewport since the ui thread + * is unaware of the swapped viewports. the vp must + * be switched early so that lcd_getstringsize() picks the + * correct font */ + vp = LCDFN(get_viewport)(&is_default); + LCDFN(set_viewport)(s->vp); + + width = LCDFN(getstringsize)(s->linebuffer, NULL, NULL); + makedelay = false; + + if (s->backward) + s->offset -= si->step; + else + s->offset += si->step; + + if (s->bidir) { /* scroll bidirectional */ + + s->line = s->linebuffer; + if (s->offset <= 0) { + /* at beginning of line */ + s->offset = 0; + s->backward = false; + makedelay = true; + } + else if (s->offset >= width - (s->width - s->x)) { + /* at end of line */ + s->offset = width - (s->width - s->x); + s->backward = true; + makedelay = true; + } + } + else { + + snprintf(line_buf, sizeof(line_buf)-1, "%s%s%s", + s->linebuffer, " ", s->linebuffer); + s->line = line_buf; + width += LCDFN(getstringsize)(" ", NULL, NULL); + /* scroll forward the whole time */ + if (s->offset >= width) { + s->offset = 0; + makedelay = true; + } + } + + /* Stash and restore these three, so that the scroll_func + * can do whatever it likes without destroying the state */ + fg_pattern = s->vp->fg_pattern; + bg_pattern = s->vp->bg_pattern; + drawmode = s->vp->drawmode; + + s->scroll_func(s); + LCDFN(update_viewport_rect)(s->x, s->y, s->width, s->height); + + s->vp->fg_pattern = fg_pattern; + s->vp->bg_pattern = bg_pattern; + s->vp->drawmode = drawmode; + + LCDFN(set_viewport)(vp); + + if (makedelay) + s->start_tick += si->delay + si->ticks; + } +} diff --git a/firmware/export/scroll_engine.h b/firmware/export/scroll_engine.h index 01a9a5e33d..c7eb97aecc 100644 --- a/firmware/export/scroll_engine.h +++ b/firmware/export/scroll_engine.h @@ -37,19 +37,19 @@ extern void lcd_scroll_delay(int ms); extern void lcd_scroll_stop(void); extern void lcd_scroll_stop_viewport(const struct viewport *vp); -extern void lcd_scroll_stop_viewport_line(const struct viewport *vp, int line); -extern void lcd_scroll_fn(void); +extern void lcd_scroll_stop_viewport_rect(const struct viewport *vp, int x, int y, int width, int height); #ifdef HAVE_REMOTE_LCD extern void lcd_remote_scroll_speed(int speed); extern void lcd_remote_scroll_delay(int ms); extern void lcd_remote_scroll_stop(void); extern void lcd_remote_scroll_stop_viewport(const struct viewport *vp); -extern void lcd_remote_scroll_stop_viewport_line(const struct viewport *vp, int line); -extern void lcd_remote_scroll_fn(void); +extern void lcd_remote_scroll_stop_viewport_rect(const struct viewport *vp, int x, int y, int width, int height); #endif -/* internal usage, but in multiple drivers */ +/* internal usage, but in multiple drivers + * larger than the normal linebuffer since it holds the line a second + * time (+3 spaces) for non-bidir scrolling */ #define SCROLL_SPACING 3 #ifdef HAVE_LCD_BITMAP #define SCROLL_LINE_SIZE (MAX_PATH + SCROLL_SPACING + 3*LCD_WIDTH/2 + 2) @@ -60,21 +60,27 @@ extern void lcd_remote_scroll_fn(void); struct scrollinfo { struct viewport* vp; - char line[SCROLL_LINE_SIZE]; + char linebuffer[9*MAX_PATH/10]; + const char *line; #ifdef HAVE_LCD_CHARCELLS int len; /* length of line in chars */ #endif - int y; /* Position of the line on the screen (char co-ordinates) */ + /* rectangle for the line */ + int x, y; /* relative to the viewort */ + int width, height; + /* pixel to skip from the beginning of the string, increments as the text scrolls */ int offset; - int startx; - int y_offset; /* y offset of the line, used for pixel-accurate list scrolling */ #ifdef HAVE_LCD_BITMAP - int width; /* length of line in pixels */ int style; /* line style */ -#endif/* HAVE_LCD_BITMAP */ - bool backward; /* scroll presently forward or backward? */ +#endif /* HAVE_LCD_BITMAP */ + /* scroll presently forward or backward? */ + bool backward; bool bidir; long start_tick; + + /* support for custom scrolling functions */ + void (*scroll_func)(struct scrollinfo *s); + void *userdata; }; struct scroll_screen_info diff --git a/firmware/scroll_engine.c b/firmware/scroll_engine.c index d134f7b2ce..d1bc2976a1 100644 --- a/firmware/scroll_engine.c +++ b/firmware/scroll_engine.c @@ -23,6 +23,7 @@ * ****************************************************************************/ +#include #include "config.h" #include "gcc_extensions.h" #include "cpu.h" @@ -36,16 +37,20 @@ #endif #include "scroll_engine.h" + +/* private helper function for the scroll engine. Do not use in apps/. + * defined in lcd-bitmap-common.c */ +extern struct viewport *lcd_get_viewport(bool *is_defaut); +#ifdef HAVE_REMOTE_LCD +extern struct viewport *lcd_remote_get_viewport(bool *is_defaut); +#endif + static const char scroll_tick_table[18] = { /* Hz values [f(x)=100.8/(x+.048)]: 1, 1.25, 1.55, 2, 2.5, 3.12, 4, 5, 6.25, 8.33, 10, 12.5, 16.7, 20, 25, 33, 49.2, 96.2 */ 100, 80, 64, 50, 40, 32, 25, 20, 16, 12, 10, 8, 6, 5, 4, 3, 2, 1 }; -/* imported private functions from lcd-bitmap-common.c */ -extern struct viewport *lcd_get_viewport(void); -extern struct viewport *lcd_remote_get_viewport(void); - static void scroll_thread(void); static char scroll_stack[DEFAULT_STACK_SIZE*3]; static const char scroll_name[] = "scroll"; @@ -156,7 +161,7 @@ static void scroll_thread(void) #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) if (lcd_active()) #endif - lcd_scroll_fn(); + lcd_scroll_worker(); lcd_scroll_info.last_scroll = current_tick; } @@ -165,7 +170,7 @@ static void scroll_thread(void) if (scroll & SCROLL_LCD_REMOTE) { - lcd_remote_scroll_fn(); + lcd_remote_scroll_worker(); lcd_remote_scroll_info.last_scroll = current_tick; } } @@ -179,7 +184,7 @@ static void scroll_thread(void) #if defined(HAVE_LCD_ENABLE) || defined(HAVE_LCD_SLEEP) if (lcd_active()) #endif - lcd_scroll_fn(); + lcd_scroll_worker(); } } #endif /* HAVE_REMOTE_LCD */