471 lines
12 KiB
C
471 lines
12 KiB
C
|
/***************************************************************************
|
||
|
* __________ __ ___.
|
||
|
* 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);
|
||
|
}
|