rockbox/apps/gui/quickscreen.c

479 lines
16 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2008 by Jonathan Gordon
*
* 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 <stdio.h>
#include "config.h"
#include "system.h"
#include "icons.h"
#include "font.h"
#include "kernel.h"
#include "misc.h"
#include "sound.h"
#include "action.h"
#include "settings_list.h"
#include "lang.h"
#include "playlist.h"
#include "viewport.h"
#include "audio.h"
#include "quickscreen.h"
#include "talk.h"
#include "list.h"
#include "option_select.h"
#include "debug.h"
#include "shortcuts.h"
/* 1 top, 1 bottom, 2 on either side, 1 for the icons
* if enough space, top and bottom have 2 lines */
#define MIN_LINES 5
#define MAX_NEEDED_LINES 10
/* pixels between the 2 center items minimum or between text and icons,
* and between text and parent boundaries */
#define MARGIN 10
#define CENTER_ICONAREA_SIZE (MARGIN+8*2)
static void quickscreen_fix_viewports(struct gui_quickscreen *qs,
struct screen *display,
struct viewport *parent,
struct viewport
vps[QUICKSCREEN_ITEM_COUNT],
struct viewport *vp_icons)
{
int char_height, width, pad = 0;
int left_width = 0, right_width = 0, vert_lines;
unsigned char *s;
int nb_lines = viewport_get_nb_lines(parent);
/* nb_lines only returns the number of fully visible lines, small screens
or really large fonts could cause problems with the calculation below.
*/
if (nb_lines == 0)
nb_lines++;
char_height = parent->height/nb_lines;
/* center the icons VP first */
*vp_icons = *parent;
vp_icons->width = CENTER_ICONAREA_SIZE; /* abosulte smallest allowed */
vp_icons->x = parent->x;
vp_icons->x += (parent->width-CENTER_ICONAREA_SIZE)/2;
vps[QUICKSCREEN_BOTTOM] = *parent;
vps[QUICKSCREEN_TOP] = *parent;
/* depending on the space the top/buttom items use 1 or 2 lines */
if (nb_lines < MIN_LINES)
vert_lines = 1;
else
vert_lines = 2;
vps[QUICKSCREEN_TOP].y = parent->y;
vps[QUICKSCREEN_TOP].height = vps[QUICKSCREEN_BOTTOM].height
= vert_lines*char_height;
vps[QUICKSCREEN_BOTTOM].y
= parent->y + parent->height - vps[QUICKSCREEN_BOTTOM].height;
/* enough space vertically, so put a nice margin */
if (nb_lines >= MAX_NEEDED_LINES)
{
vps[QUICKSCREEN_TOP].y += MARGIN;
vps[QUICKSCREEN_BOTTOM].y -= MARGIN;
}
vp_icons->y = vps[QUICKSCREEN_TOP].y
+ vps[QUICKSCREEN_TOP].height;
vp_icons->height = vps[QUICKSCREEN_BOTTOM].y - vp_icons->y;
/* adjust the left/right items widths to fit the screen nicely */
if (qs->items[QUICKSCREEN_LEFT])
{
s = P2STR(ID2P(qs->items[QUICKSCREEN_LEFT]->lang_id));
left_width = display->getstringsize(s, NULL, NULL);
}
if (qs->items[QUICKSCREEN_RIGHT])
{
s = P2STR(ID2P(qs->items[QUICKSCREEN_RIGHT]->lang_id));
right_width = display->getstringsize(s, NULL, NULL);
}
width = MAX(left_width, right_width);
if (width*2 + vp_icons->width > parent->width)
{ /* crop text viewports */
width = (parent->width - vp_icons->width)/2;
}
else
{ /* add more gap in icons vp */
int excess = parent->width - vp_icons->width - width*2;
if (excess > MARGIN*4)
{
pad = MARGIN;
excess -= MARGIN*2;
}
vp_icons->x -= excess/2;
vp_icons->width += excess;
}
vps[QUICKSCREEN_LEFT] = *parent;
vps[QUICKSCREEN_LEFT].x = parent->x + pad;
vps[QUICKSCREEN_LEFT].width = width;
vps[QUICKSCREEN_RIGHT] = *parent;
vps[QUICKSCREEN_RIGHT].x = parent->x + parent->width - width - pad;
vps[QUICKSCREEN_RIGHT].width = width;
vps[QUICKSCREEN_LEFT].height = vps[QUICKSCREEN_RIGHT].height
= 2*char_height;
vps[QUICKSCREEN_LEFT].y = vps[QUICKSCREEN_RIGHT].y
= parent->y + (parent->height/2) - char_height;
/* shrink the icons vp by a few pixels if there is room so the arrows
aren't drawn right next to the text */
if (vp_icons->width > CENTER_ICONAREA_SIZE*2)
{
vp_icons->width -= CENTER_ICONAREA_SIZE*2/3;
vp_icons->x += CENTER_ICONAREA_SIZE*2/6;
}
if (vp_icons->height > CENTER_ICONAREA_SIZE*2)
{
vp_icons->height -= CENTER_ICONAREA_SIZE*2/3;
vp_icons->y += CENTER_ICONAREA_SIZE*2/6;
}
/* text alignment */
vps[QUICKSCREEN_LEFT].flags &= ~VP_FLAG_ALIGNMENT_MASK; /* left-aligned */
vps[QUICKSCREEN_TOP].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
vps[QUICKSCREEN_BOTTOM].flags |= VP_FLAG_ALIGN_CENTER; /* centered */
vps[QUICKSCREEN_RIGHT].flags &= ~VP_FLAG_ALIGNMENT_MASK;/* right aligned*/
vps[QUICKSCREEN_RIGHT].flags |= VP_FLAG_ALIGN_RIGHT;
}
static void gui_quickscreen_draw(const struct gui_quickscreen *qs,
struct screen *display,
struct viewport *parent,
struct viewport vps[QUICKSCREEN_ITEM_COUNT],
struct viewport *vp_icons)
{
int i;
char buf[MAX_PATH];
unsigned const char *title, *value;
int temp;
LCD core move buf ptr and address look up function viewport struct I'm currently running up against the limitations of the lcd_draw functions I want these functions to be able to be used on any size buffer not just buffers with a stride matching the underlying device [DONE] allow the framebuffer to be decoupled from the device framebuffer [DONE need examples] allow for some simple blit like transformations [DONE] remove the device framebuffer from the plugin api [DONE}ditto remote framebuffer [DONE] remove _viewport_get_framebuffer you can call struct *vp = lcd_set_viewport(NULL) and vp->buffer->fb_ptr while remote lcds may compile (and work in the sim) its not been tested on targets [FIXED] backdrops need work to be screen agnostic [FIXED] screen statusbar is not being combined into the main viewport correctly yet [FIXED] screen elements are displayed incorrectly after switch to void* [FIXED] core didn't restore proper viewport on splash etc. [NEEDS TESTING] remote lcd garbled data [FIXED] osd lib garbled screen on bmp_part [FIXED] grey_set_vp needs to return old viewport like lcd_set_viewport [FIXED] Viewport update now handles viewports with differing buffers/strides by copying to the main buffer [FIXED] splash on top of WPS leaves old framebuffer data (doesn't redraw) [UPDATE] refined this a bit more to have clear_viewport set the clean bit and have skin_render do its own screen clear scrolling viewports no longer trigger wps refresh also fixed a bug where guisyncyesno was displaying and then disappearing [ADDED!] New LCD macros that allow you to create properly size frame buffers in you desired size without wasting bytes (LCD_ and LCD_REMOTE_) LCD_STRIDE(w, h) same as STRIDE_MAIN LCD_FBSTRIDE(w, h) returns target specific stride for a buffer W x H LCD_NBELEMS(w, h) returns the number of fb_data sized elemenst needed for a buffer W x H LCD_NATIVE_STRIDE(s) conversion between rockbox native vertical and lcd native stride (2bitH) test_viewports.c has an example of usage [FIXED!!] 2bit targets don't respect non-native strides [FIXED] Few define snags Change-Id: I0d04c3834e464eca84a5a715743a297a0cefd0af
2020-10-07 06:01:35 +00:00
struct viewport *last_vp = display->set_viewport(parent);
display->clear_viewport();
for (i = 0; i < QUICKSCREEN_ITEM_COUNT; i++)
{
struct viewport *vp = &vps[i];
if (!qs->items[i])
continue;
display->set_viewport(vp);
title = P2STR(ID2P(qs->items[i]->lang_id));
temp = option_value_as_int(qs->items[i]);
value = option_get_valuestring(qs->items[i],
buf, MAX_PATH, temp);
if (viewport_get_nb_lines(vp) < 2)
{
char text[MAX_PATH];
snprintf(text, MAX_PATH, "%s: %s", title, value);
display->puts_scroll(0, 0, text);
}
else
{
display->puts_scroll(0, 0, title);
display->puts_scroll(0, 1, value);
}
}
/* draw the icons */
display->set_viewport(vp_icons);
if (qs->items[QUICKSCREEN_TOP] != NULL)
{
display->mono_bitmap(bitmap_icons_7x8[Icon_UpArrow],
(vp_icons->width/2) - 4, 0, 7, 8);
}
if (qs->items[QUICKSCREEN_RIGHT] != NULL)
{
display->mono_bitmap(bitmap_icons_7x8[Icon_FastForward],
vp_icons->width - 8, (vp_icons->height/2) - 4, 7, 8);
}
if (qs->items[QUICKSCREEN_LEFT] != NULL)
{
display->mono_bitmap(bitmap_icons_7x8[Icon_FastBackward],
0, (vp_icons->height/2) - 4, 7, 8);
}
if (qs->items[QUICKSCREEN_BOTTOM] != NULL)
{
display->mono_bitmap(bitmap_icons_7x8[Icon_DownArrow],
(vp_icons->width/2) - 4, vp_icons->height - 8, 7, 8);
}
display->set_viewport(parent);
display->update_viewport();
LCD core move buf ptr and address look up function viewport struct I'm currently running up against the limitations of the lcd_draw functions I want these functions to be able to be used on any size buffer not just buffers with a stride matching the underlying device [DONE] allow the framebuffer to be decoupled from the device framebuffer [DONE need examples] allow for some simple blit like transformations [DONE] remove the device framebuffer from the plugin api [DONE}ditto remote framebuffer [DONE] remove _viewport_get_framebuffer you can call struct *vp = lcd_set_viewport(NULL) and vp->buffer->fb_ptr while remote lcds may compile (and work in the sim) its not been tested on targets [FIXED] backdrops need work to be screen agnostic [FIXED] screen statusbar is not being combined into the main viewport correctly yet [FIXED] screen elements are displayed incorrectly after switch to void* [FIXED] core didn't restore proper viewport on splash etc. [NEEDS TESTING] remote lcd garbled data [FIXED] osd lib garbled screen on bmp_part [FIXED] grey_set_vp needs to return old viewport like lcd_set_viewport [FIXED] Viewport update now handles viewports with differing buffers/strides by copying to the main buffer [FIXED] splash on top of WPS leaves old framebuffer data (doesn't redraw) [UPDATE] refined this a bit more to have clear_viewport set the clean bit and have skin_render do its own screen clear scrolling viewports no longer trigger wps refresh also fixed a bug where guisyncyesno was displaying and then disappearing [ADDED!] New LCD macros that allow you to create properly size frame buffers in you desired size without wasting bytes (LCD_ and LCD_REMOTE_) LCD_STRIDE(w, h) same as STRIDE_MAIN LCD_FBSTRIDE(w, h) returns target specific stride for a buffer W x H LCD_NBELEMS(w, h) returns the number of fb_data sized elemenst needed for a buffer W x H LCD_NATIVE_STRIDE(s) conversion between rockbox native vertical and lcd native stride (2bitH) test_viewports.c has an example of usage [FIXED!!] 2bit targets don't respect non-native strides [FIXED] Few define snags Change-Id: I0d04c3834e464eca84a5a715743a297a0cefd0af
2020-10-07 06:01:35 +00:00
display->set_viewport(last_vp);
}
static void talk_qs_option(const struct settings_list *opt, bool enqueue)
{
if (!global_settings.talk_menu || !opt)
return;
if (enqueue)
talk_id(opt->lang_id, enqueue);
option_talk_value(opt, option_value_as_int(opt), enqueue);
}
/*
* Does the actions associated to the given button if any
* - qs : the quickscreen
* - button : the key we are going to analyse
* returns : true if the button corresponded to an action, false otherwise
*/
static bool gui_quickscreen_do_button(struct gui_quickscreen * qs, int button)
{
int item;
bool invert = false;
switch(button)
{
case ACTION_QS_TOP:
invert = true;
item = QUICKSCREEN_TOP;
break;
case ACTION_QS_LEFT:
invert = true;
item = QUICKSCREEN_LEFT;
break;
case ACTION_QS_DOWN:
item = QUICKSCREEN_BOTTOM;
break;
case ACTION_QS_RIGHT:
item = QUICKSCREEN_RIGHT;
break;
default:
return false;
}
if (qs->items[item] == NULL)
return false;
#ifdef ASCENDING_INT_SETTINGS
if (((qs->items[item]->flags & F_INT_SETTING) == F_INT_SETTING) &&
( button == ACTION_QS_DOWN || button == ACTION_QS_TOP))
{
invert = !invert;
}
#endif
option_select_next_val(qs->items[item], invert, true);
talk_qs_option(qs->items[item], false);
return true;
}
#ifdef HAVE_TOUCHSCREEN
static int quickscreen_touchscreen_button(const struct viewport
vps[QUICKSCREEN_ITEM_COUNT])
{
short x,y;
/* only hitting the text counts, everything else is exit */
if (action_get_touchscreen_press(&x, &y) != BUTTON_REL)
return ACTION_NONE;
else if (viewport_point_within_vp(&vps[QUICKSCREEN_TOP], x, y))
return ACTION_QS_TOP;
else if (viewport_point_within_vp(&vps[QUICKSCREEN_BOTTOM], x, y))
return ACTION_QS_DOWN;
else if (viewport_point_within_vp(&vps[QUICKSCREEN_LEFT], x, y))
return ACTION_QS_LEFT;
else if (viewport_point_within_vp(&vps[QUICKSCREEN_RIGHT], x, y))
return ACTION_QS_RIGHT;
return ACTION_STD_CANCEL;
}
#endif
static bool gui_syncquickscreen_run(struct gui_quickscreen * qs, int button_enter, bool *usb)
{
int button;
struct viewport parent[NB_SCREENS];
struct viewport vps[NB_SCREENS][QUICKSCREEN_ITEM_COUNT];
struct viewport vp_icons[NB_SCREENS];
bool changed = false;
/* To quit we need either :
* - a second press on the button that made us enter
* - an action taken while pressing the enter button,
* then release the enter button*/
bool can_quit = false;
push_current_activity(ACTIVITY_QUICKSCREEN);
FOR_NB_SCREENS(i)
{
screens[i].set_viewport(NULL);
screens[i].scroll_stop();
viewportmanager_theme_enable(i, true, &parent[i]);
quickscreen_fix_viewports(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
gui_quickscreen_draw(qs, &screens[i], &parent[i], vps[i], &vp_icons[i]);
}
*usb = false;
/* Announce current selection on entering this screen. This is all
queued up, but can be interrupted as soon as a setting is
changed. */
cond_talk_ids(VOICE_QUICKSCREEN);
talk_qs_option(qs->items[QUICKSCREEN_TOP], true);
if (qs->items[QUICKSCREEN_TOP] != qs->items[QUICKSCREEN_BOTTOM])
talk_qs_option(qs->items[QUICKSCREEN_BOTTOM], true);
talk_qs_option(qs->items[QUICKSCREEN_LEFT], true);
if (qs->items[QUICKSCREEN_LEFT] != qs->items[QUICKSCREEN_RIGHT])
talk_qs_option(qs->items[QUICKSCREEN_RIGHT], true);
while (true) {
button = get_action(CONTEXT_QUICKSCREEN, HZ/5);
#ifdef HAVE_TOUCHSCREEN
if (button == ACTION_TOUCHSCREEN)
button = quickscreen_touchscreen_button(vps[SCREEN_MAIN]);
#endif
if (default_event_handler(button) == SYS_USB_CONNECTED)
{
*usb = true;
break;
}
if (gui_quickscreen_do_button(qs, button))
{
changed = true;
can_quit = true;
FOR_NB_SCREENS(i)
gui_quickscreen_draw(qs, &screens[i], &parent[i],
vps[i], &vp_icons[i]);
if (qs->callback)
qs->callback(qs);
}
else if (button == button_enter)
can_quit = true;
else if (button == ACTION_QS_VOLUP) {
global_settings.volume += sound_steps(SOUND_VOLUME);
setvol();
FOR_NB_SCREENS(i)
skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
}
else if (button == ACTION_QS_VOLDOWN) {
global_settings.volume -= sound_steps(SOUND_VOLUME);
setvol();
FOR_NB_SCREENS(i)
skin_update(CUSTOM_STATUSBAR, i, SKIN_REFRESH_NON_STATIC);
}
if ((button == button_enter) && can_quit)
break;
if (button == ACTION_STD_CANCEL)
break;
}
/* Notify that we're exiting this screen */
cond_talk_ids_fq(VOICE_OK);
FOR_NB_SCREENS(i)
{ /* stop scrolling before exiting */
for (int j = 0; j < QUICKSCREEN_ITEM_COUNT; j++)
screens[i].scroll_stop_viewport(&vps[i][j]);
viewportmanager_theme_undo(i, true);
}
pop_current_activity();
return changed;
}
static const struct settings_list *get_setting(int gs_value,
const struct settings_list *defaultval)
{
if (gs_value != -1 && gs_value < nb_settings &&
is_setting_quickscreenable(&settings[gs_value]))
return &settings[gs_value];
return defaultval;
}
int quick_screen_quick(int button_enter)
{
struct gui_quickscreen qs;
bool oldshuffle = global_settings.playlist_shuffle;
int oldrepeat = global_settings.repeat_mode;
bool usb = false;
if (global_settings.shortcuts_replaces_qs)
return do_shortcut_menu(NULL);
qs.items[QUICKSCREEN_TOP] =
get_setting(global_settings.qs_items[QUICKSCREEN_TOP], NULL);
qs.items[QUICKSCREEN_LEFT] =
get_setting(global_settings.qs_items[QUICKSCREEN_LEFT],
find_setting(&global_settings.playlist_shuffle, NULL));
qs.items[QUICKSCREEN_RIGHT] =
get_setting(global_settings.qs_items[QUICKSCREEN_RIGHT],
find_setting(&global_settings.repeat_mode, NULL));
qs.items[QUICKSCREEN_BOTTOM] =
get_setting(global_settings.qs_items[QUICKSCREEN_BOTTOM], NULL);
qs.callback = NULL;
if (gui_syncquickscreen_run(&qs, button_enter, &usb))
{
settings_save();
/* make sure repeat/shuffle/any other nasty ones get updated */
if ( oldrepeat != global_settings.repeat_mode &&
(audio_status() & AUDIO_STATUS_PLAY) )
{
audio_flush_and_reload_tracks();
}
if (oldshuffle != global_settings.playlist_shuffle
&& audio_status() & AUDIO_STATUS_PLAY)
{
replaygain_update();
if (global_settings.playlist_shuffle)
playlist_randomise(NULL, current_tick, true);
else
playlist_sort(NULL, true);
}
}
return (usb ? 1:0);
}
/* stuff to make the quickscreen configurable */
bool is_setting_quickscreenable(const struct settings_list *setting)
{
/* to keep things simple, only settings which have a lang_id set are ok */
if (setting->lang_id < 0 || (setting->flags&F_BANFROMQS))
return false;
switch (setting->flags&F_T_MASK)
{
case F_T_BOOL:
return true;
case F_T_INT:
case F_T_UINT:
return (setting->RESERVED != NULL);
default:
return false;
}
}
void set_as_qs_item(const struct settings_list *setting,
enum quickscreen_item item)
{
int i;
for (i = 0; i < nb_settings; i++)
{
if (&settings[i] == setting)
break;
}
global_settings.qs_items[item] = i;
}