rockbox/apps/plugins/lib/osd.c

471 lines
12 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Floating on-screen display
*
* Copyright (C) 2012 Michael Sevakis
*
* 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 "plugin.h"
#include "osd.h"
#if 1
#undef DEBUGF
#define DEBUGF(...)
#endif
/* At this time: assumes use of the default viewport for normal drawing */
/* If multiple OSD's are wanted, could convert to caller-allocated */
static struct osd
{
enum osd_status
{
OSD_DISABLED = 0, /* Disabled entirely */
OSD_HIDDEN, /* Hidden from view */
OSD_VISIBLE, /* Visible on screen */
OSD_ERASED, /* Erased in preparation for regular drawing */
} status; /* View status */
struct viewport vp; /* Clipping viewport */
struct bitmap lcd_bitmap; /* The main LCD fb bitmap */
struct bitmap back_bitmap; /* The OSD backbuffer fb bitmap */
int maxwidth; /* How wide may it be at most? */
int maxheight; /* How high may it be at most? */
long timeout; /* Current popup stay duration */
long hide_tick; /* Tick when it should be hidden */
osd_draw_cb_fn_t draw_cb; /* Draw update callback */
} osd;
/* Framebuffer allocation macros */
#if LCD_DEPTH == 1
# if LCD_PIXELFORMAT == HORIZONTAL_PACKING
# define LCD_WIDTH2BYTES(w) (((w)+7)/8)
# define LCD_BYTES2WIDTH(b) ((b)*8)
# elif LCD_PIXELFORMAT == VERTICAL_PACKING
# define LCD_HEIGHT2BYTES(h) (((h)+7)/8)
# define LCD_BYTES2HEIGHT(b) ((b)*8)
# else
# error Unknown 1-bit format; please define macros
# endif /* LCD_PIXELFORMAT */
#elif LCD_DEPTH == 2
# if LCD_PIXELFORMAT == HORIZONTAL_PACKING
# define LCD_WIDTH2BYTES(w) (((w)+3)/4)
# define LCD_BYTES2WIDTH(b) ((b)*4)
# elif LCD_PIXELFORMAT == VERTICAL_PACKING
# define LCD_HEIGHT2BYTES(h) (((h)+3)/4)
# define LCD_BYTES2HEIGHT(b) ((b)*4)
# elif LCD_PIXELFORMAT == VERTICAL_INTERLEAVED
# define LCD_WIDTH2BYTES(w) ((w)*2)
# define LCD_BYTES2WIDTH(b) ((b)/2)
# define LCD_HEIGHT2BYTES(h) (((h)+7)/8*2)
# define LCD_BYTES2HEIGHT(b) ((b)/2*8)
# else
# error Unknown 2-bit format; please define macros
# endif /* LCD_PIXELFORMAT */
#elif LCD_DEPTH == 16
# define LCD_WIDTH2BYTES(w) ((w)*2)
# define LCD_BYTES2WIDTH(b) ((b)/2)
#else
# error Unknown LCD depth; please define macros
#endif /* LCD_DEPTH */
/* Set defaults if not defined different yet. */
#ifndef LCD_WIDTH2BYTES
# define LCD_WIDTH2BYTES(w) (w)
#endif
#ifndef LCD_BYTES2WIDTH
# define LCD_BYTES2WIDTH(b) (b)
#endif
#ifndef LCD_HEIGHT2BYTES
# define LCD_HEIGHT2BYTES(h) (h)
#endif
#ifndef LCD_BYTES2HEIGHT
# define LCD_BYTES2HEIGHT(b) (b)
#endif
/* Create a bitmap framebuffer from a buffer */
static fb_data * buf_to_fb_bitmap(void *buf, size_t bufsize,
int *width, int *height)
{
/* Used as dest, the LCD functions cannot deal with alternate
strides as of now - the stride guides the calulations. If
that is no longer the case, then width or height can be
used instead (and less memory needed for a small surface!).
*/
DEBUGF("buf: %p bufsize: %lu\n", buf, (unsigned long)bufsize);
#if defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
int h = LCD_BYTES2HEIGHT(LCD_HEIGHT2BYTES(LCD_HEIGHT));
int w = bufsize / LCD_HEIGHT2BYTES(h);
if (w == 0)
{
DEBUGF("OSD: not enough buffer\n");
return NULL; /* not enough buffer */
}
#else
int w = LCD_BYTES2WIDTH(LCD_WIDTH2BYTES(LCD_WIDTH));
int h = bufsize / LCD_WIDTH2BYTES(w);
if (h == 0)
{
DEBUGF("OSD: not enough buffer\n");
return NULL; /* not enough buffer */
}
#endif
DEBUGF("fbw:%d fbh:%d\n", w, h);
*width = w;
*height = h;
return (fb_data *)buf;
}
static inline void osd_set_vp_pos(int x, int y, int width, int height)
{
osd.vp.x = x;
osd.vp.y = y;
osd.vp.width = width;
osd.vp.height = height;
}
/* Sync the backbuffer to the on-screen image */
static void osd_lcd_update_back_buffer(void)
{
rb->lcd_set_framebuffer((fb_data *)osd.back_bitmap.data);
rb->lcd_bmp_part(&osd.lcd_bitmap, osd.vp.x, osd.vp.y,
0, 0, osd.vp.width, osd.vp.height);
/* Assume it was on default framebuffer for now */
rb->lcd_set_framebuffer(NULL);
}
/* Erase the OSD to restore the framebuffer */
static void osd_lcd_erase(void)
{
rb->lcd_bmp_part(&osd.back_bitmap, 0, 0, osd.vp.x, osd.vp.y,
osd.vp.width, osd.vp.height);
}
/* Draw the OSD image portion using the callback */
static void osd_lcd_draw_rect(int x, int y, int width, int height)
{
rb->lcd_set_viewport(&osd.vp);
osd.draw_cb(x, y, width, height);
rb->lcd_set_viewport(NULL);
}
/* Draw the OSD image using the callback */
static void osd_lcd_draw(void)
{
osd_lcd_draw_rect(0, 0, osd.vp.width, osd.vp.height);
}
/** Public APIs **/
/* Initialized the OSD and set its backbuffer */
bool osd_init(void *backbuf, size_t backbuf_size,
osd_draw_cb_fn_t draw_cb)
{
osd_show(OSD_HIDE);
osd.status = OSD_DISABLED; /* Disabled unless all is okay */
osd_set_vp_pos(0, 0, 0, 0);
osd.maxwidth = osd.maxheight = 0;
osd.timeout = 0;
if (!draw_cb)
return false;
if (!backbuf)
return false;
ALIGN_BUFFER(backbuf, backbuf_size, FB_DATA_SZ);
if (!backbuf_size)
return false;
rb->viewport_set_fullscreen(&osd.vp, SCREEN_MAIN);
fb_data *backfb = buf_to_fb_bitmap(backbuf, backbuf_size,
&osd.maxwidth, &osd.maxheight);
if (!backfb)
return false;
osd.draw_cb = draw_cb;
/* LCD framebuffer bitmap */
osd.lcd_bitmap.width = LCD_BYTES2WIDTH(LCD_WIDTH2BYTES(LCD_WIDTH));
osd.lcd_bitmap.height = LCD_BYTES2HEIGHT(LCD_HEIGHT2BYTES(LCD_HEIGHT));
#if LCD_DEPTH > 1
osd.lcd_bitmap.format = FORMAT_NATIVE;
osd.lcd_bitmap.maskdata = NULL;
#endif
#ifdef HAVE_LCD_COLOR
osd.lcd_bitmap.alpha_offset = 0;
#endif
osd.lcd_bitmap.data = (void *)rb->lcd_framebuffer;
/* Backbuffer bitmap */
osd.back_bitmap.width = osd.maxwidth;
osd.back_bitmap.height = osd.maxheight;
#if LCD_DEPTH > 1
osd.back_bitmap.format = FORMAT_NATIVE;
osd.back_bitmap.maskdata = NULL;
#endif
#ifdef HAVE_LCD_COLOR
osd.back_bitmap.alpha_offset = 0;
#endif
osd.back_bitmap.data = (void *)backfb;
DEBUGF("FB:%p BB:%p\n", osd.lcd_bitmap.data, osd.back_bitmap.data);
/* Set the default position to the whole thing */
osd_set_vp_pos(0, 0, osd.maxwidth, osd.maxheight);
osd.status = OSD_HIDDEN; /* Ready when you are */
return true;
}
/* Show/Hide the OSD on screen */
bool osd_show(unsigned flags)
{
if (flags & OSD_SHOW)
{
switch (osd.status)
{
case OSD_DISABLED:
break; /* No change */
case OSD_HIDDEN:
osd_lcd_update_back_buffer();
osd.status = OSD_VISIBLE;
osd_update();
osd.hide_tick = *rb->current_tick + osd.timeout;
break;
case OSD_VISIBLE:
if (flags & OSD_UPDATENOW)
osd_update();
/* Fall-through */
case OSD_ERASED:
osd.hide_tick = *rb->current_tick + osd.timeout;
return true;
}
}
else
{
switch (osd.status)
{
case OSD_DISABLED:
case OSD_HIDDEN:
break;
case OSD_VISIBLE:
osd_lcd_erase();
rb->lcd_update_rect(osd.vp.x, osd.vp.y, osd.vp.width,
osd.vp.height);
/* Fall-through */
case OSD_ERASED:
osd.status = OSD_HIDDEN;
return true;
}
}
return false;
}
/* Redraw the entire OSD */
bool osd_update(void)
{
if (osd.status != OSD_VISIBLE)
return false;
osd_lcd_draw();
rb->lcd_update_rect(osd.vp.x, osd.vp.y, osd.vp.width,
osd.vp.height);
return true;
}
/* Redraw part of the OSD (viewport-relative coordinates) */
bool osd_update_rect(int x, int y, int width, int height)
{
if (osd.status != OSD_VISIBLE)
return false;
osd_lcd_draw_rect(x, y, width, height);
if (x + width > osd.vp.width)
width = osd.vp.width - x;
if (x < 0)
{
width += x;
x = 0;
}
if (width <= 0)
return false;
if (y + height > osd.vp.height)
height = osd.vp.height - y;
if (y < 0)
{
height += y;
y = 0;
}
if (height <= 0)
return false;
rb->lcd_update_rect(osd.vp.x + x, osd.vp.y + y, width, height);
return true;
}
/* Set a new screen location and size (screen coordinates) */
bool osd_update_pos(int x, int y, int width, int height)
{
if (osd.status == OSD_DISABLED)
return false;
if (width < 0)
width = 0;
else if (width > osd.maxwidth)
width = osd.maxwidth;
if (height < 0)
height = 0;
else if (height > osd.maxheight)
height = osd.maxheight;
if (x == osd.vp.x && y == osd.vp.y &&
width == osd.vp.width && height == osd.vp.height)
return false; /* No change */
if (osd.status != OSD_VISIBLE)
{
/* Not visible - just update pos */
osd_set_vp_pos(x, y, width, height);
return false;
}
/* Visible area has changed */
osd_lcd_erase();
/* Update the smallest rectangle that encloses both the old and new
regions to make the change free of flicker (they may overlap) */
int xu = MIN(osd.vp.x, x);
int yu = MIN(osd.vp.y, y);
int wu = MAX(osd.vp.x + osd.vp.width, x + width) - xu;
int hu = MAX(osd.vp.y + osd.vp.height, y + height) - yu;
osd_set_vp_pos(x, y, width, height);
osd_lcd_update_back_buffer();
osd_lcd_draw();
rb->lcd_update_rect(xu, yu, wu, hu);
return true;
}
/* Call periodically to have the OSD timeout and hide itself */
void osd_monitor_timeout(void)
{
if (osd.status <= OSD_HIDDEN)
return; /* Already hidden/disabled */
if (osd.timeout > 0 && TIME_AFTER(*rb->current_tick, osd.hide_tick))
osd_show(OSD_HIDE);
}
/* Set the OSD timeout value. <= 0 = never timeout */
void osd_set_timeout(long timeout)
{
if (osd.status == OSD_DISABLED)
return;
osd.timeout = timeout;
osd_monitor_timeout();
}
/* Use the OSD viewport context */
struct viewport * osd_get_viewport(void)
{
return &osd.vp;
}
/* Get the maximum dimensions calculated by osd_init() */
void osd_get_max_dims(int *maxwidth, int *maxheight)
{
if (maxwidth)
*maxwidth = osd.maxwidth;
if (maxheight)
*maxheight = osd.maxheight;
}
/* Is the OSD enabled? */
bool osd_enabled(void)
{
return osd.status != OSD_DISABLED;
}
/** LCD update substitutes **/
/* Prepare LCD framebuffer for regular drawing */
void osd_lcd_update_prepare(void)
{
if (osd.status == OSD_VISIBLE)
{
osd.status = OSD_ERASED;
osd_lcd_erase();
}
}
/* Update the whole screen */
void osd_lcd_update(void)
{
if (osd.status == OSD_ERASED)
{
/* Save the screen image underneath and restore the OSD image */
osd.status = OSD_VISIBLE;
osd_lcd_update_back_buffer();
osd_lcd_draw();
}
rb->lcd_update();
}
/* Update a part of the screen */
void osd_lcd_update_rect(int x, int y, int width, int height)
{
if (osd.status == OSD_ERASED)
{
/* Save the screen image underneath and restore the OSD image */
osd.status = OSD_VISIBLE;
osd_lcd_update_back_buffer();
osd_lcd_draw();
}
rb->lcd_update_rect(x, y, width, height);
}