2022-10-17 14:09:23 +08:00

391 lines
13 KiB
C

#include "arm11/atp.h"
#include "arm11/acf.h"
#include "arm11/fmt.h"
#include "arm11/console.h"
#include "arm11/drivers/hid.h"
#include "drivers/gfx.h"
#define TITLE_MAX 8
#define TIPS_MAX 64
static char ta[TIPS_MAX] = {'\0'};
static char tb[TIPS_MAX] = {'\0'};
#define ATP_COLOR_SIZE (ATP_COLOR_WHITE+1)
static uint16_t color_tbl[] = {
RGB8_to_565(0xc2, 0xcc, 0xd0), // 亮色
RGB8_to_565(0xff, 0x33, 0), // 红色
RGB8_to_565(0xaf, 0xdd, 0x22), // 绿色
RGB8_to_565(0xfa, 0xff, 0x72), // 黄色
RGB8_to_565(0x4c, 0x8d, 0xb4), // 蓝色
RGB8_to_565(0xe0, 0x00, 0x97), // 品红
RGB8_to_565(0x21, 0xa6, 0x75), // 青色
RGB8_to_565(0xf0, 0xfc, 0xff) // 白色
};
//---------------------------------------------------
// basic print helper
static void set_screen_color( acf_callerdata_t data, acf_position_t tx, acf_position_t ty, acf_color_t b )
{
if( b == 0 ) return;
int *draw_data = data;
u16 *frame = consoleGet()->frameBuffer;
int x = tx + draw_data[0];
int y = ty + draw_data[1];
// see: https://www.zhihu.com/question/264505093/answer/281849883
if( ( x | (draw_data[2]-x) | y | (draw_data[3]-y) ) > 0 ) // 0 <= x && x < draw_data[2] && 0 <= y && y < draw_data[3]
{
frame[ x*draw_data[3] + (draw_data[3]-1-y) ] = (u16)draw_data[4];
}
}
const char *acf_put_text(int x, int y, int width, int height, int maxwidth, u8 color, u8 placement, const char* text)
{
uint8_t draw_data[sizeof(int)*5];
int *draw_data_int = (int*)&draw_data;
draw_data_int[0] = x;
draw_data_int[1] = y;
draw_data_int[2] = width;
draw_data_int[3] = height;
draw_data_int[4] = (int)color_tbl[color % ATP_COLOR_SIZE];
const char *retval = text;
acf_rectedge_t realwid;
acf_canvas_t canvas = acf_get_canvas(maxwidth, text, &realwid, NULL, &retval);
if( !canvas ) return retval;
if( placement == ATP_PLACEMENT_RIGHT ) draw_data_int[0] += maxwidth - realwid;
else if( placement == ATP_PLACEMENT_CENTER ) draw_data_int[0] += (maxwidth-realwid) >> 1;
acf_recycle( acf_use_canvas(canvas, 1, set_screen_color, draw_data) );
return retval;
}
//---------------------------------------------------
#define CONTAINER_LEFTTOP_X 20
#define CONTAINER_LEFTTOP_Y 10
#define CONTAINER_RECT_WIDTH 282
#define CONTAINER_MAX_LINES 13
#define WINDOW_WIDTH 320
#define WINDOW_HEIGHT 240
#define FONT_HEIGHT 15
static void screen_clean()
{
memset(consoleGet()->frameBuffer, 0, WINDOW_WIDTH*WINDOW_HEIGHT*sizeof(uint16_t));
if( ta[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_LEFT, ta );
if( tb[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_RIGHT, tb );
}
static void screen_clean_zone( int x, int y, int w, int h )
{
if( x < 0 || y < 0 || x+w >= WINDOW_WIDTH || y+h >=WINDOW_HEIGHT )
return;
for( int i=0; i < w; i++ ) memset( consoleGet()->frameBuffer + WINDOW_HEIGHT*(i+x) + (WINDOW_HEIGHT-1-y-h), 0, h );
}
// wait key pressed, if power key is pressed, return 0
static uint32_t waitKey()
{
uint32_t ek, down;
do{
GFX_waitForVBlank0();
hidScanInput();
ek = hidGetExtraKeys(0);
if( ek & (KEY_POWER_HELD | KEY_POWER) )
return 0;
down = hidKeysDown();
}while ( down == 0 );
return down;
}
#define easy_put(text, align, color, row) acf_put_text( \
CONTAINER_LEFTTOP_X, \
CONTAINER_LEFTTOP_Y+FONT_HEIGHT*(row), \
WINDOW_WIDTH, \
WINDOW_HEIGHT, \
CONTAINER_RECT_WIDTH, \
(color) >= ATP_COLOR_SIZE ? ATP_COLOR_WHITE : (color), \
(align) > ATP_PLACEMENT_CENTER ? ATP_PLACEMENT_CENTER : (align), \
(text) \
)
static void paint_one_line( atp_lineprovider_t provider, acf_callerdata_t data, int idx, int row )
{
atp_linecfg_t config;
config.text_color = ATP_COLOR_WHITE;
config.text_align = ATP_PLACEMENT_LEFT;
atp_error_t err = provider( data, idx, &config );
if( !err )
{
easy_put( config.text, config.text_align, config.text_color, row );
}
}
static void container_paint( atp_lineprovider_t provider, atp_callerdata_t data, atp_counter_t nlines, int top )
{
int end = top + CONTAINER_MAX_LINES;
if( end > (int)nlines ) end = nlines;
for( int n = top; n < end; ++n )
{
paint_one_line( provider, data, n, n-top );
}
}
atp_error_t atp_show( atp_counter_t cnt, atp_lineprovider_t provider, atp_callerdata_t data )
{
int idx_top = 0;
screen_clean();
container_paint( provider, data, cnt, idx_top );
while( 1 )
{
int top = idx_top;
u32 kDown = waitKey();
if( kDown == 0 ) return ATP_POWER_OFF;
if( kDown == KEY_B || kDown == KEY_A ) return ATP_SUCCESS;
else if( cnt > CONTAINER_MAX_LINES )
{ // may scroll
if( kDown == KEY_UP || kDown == KEY_DUP )
{
top = idx_top - 1;
}
else if( kDown == KEY_DOWN || kDown == KEY_DDOWN )
{
top = idx_top + 1;
}
if( top+CONTAINER_MAX_LINES > (int)cnt ) top = (int)cnt - CONTAINER_MAX_LINES;
else if( top < 0 ) top = 0;
}
if( top != idx_top )
{
idx_top = top;
screen_clean();
container_paint(provider, data, cnt, idx_top);
}
}
}
static atp_error_t title_paint( atp_callerdata_t datain, atp_counter_t idx, atp_linecfg_t *config )
{
uint8_t **data = datain;
atp_text_t title = (atp_text_t)data[0];
uint8_t *title_offset = data[1];
for( atp_counter_t i=0; i < idx; ++i )
title += title_offset[i];
config->text = title;
config->text_color = ATP_COLOR_LIGHT;
return ATP_SUCCESS;
}
static void set_paging( int top, int len )
{
unsigned total = (len+CONTAINER_MAX_LINES-1) / CONTAINER_MAX_LINES;
unsigned current = top / CONTAINER_MAX_LINES;
char buf[32];
ee_snprintf( buf, sizeof(buf), "页码:%d/%d", current+1, total );
atp_tips( buf, NULL );
screen_clean();
}
static void draw_one_option( int index, int selected, int row, atp_itemprovider_t provider, atp_callerdata_t data )
{
atp_itemcfg_t item;
item.value = -1;
item.text = item.extra_text = NULL;
item.extra_text_color = ATP_COLOR_RED;
item.text_color = ATP_COLOR_WHITE;
atp_error_t e = provider( data, index, &item );
if( e == ATP_SUCCESS )
{
if( item.extra_text ) easy_put( item.extra_text, ATP_PLACEMENT_RIGHT, item.extra_text_color, row );
easy_put( item.text, ATP_PLACEMENT_LEFT, index == selected ? ATP_COLOR_YELLOW : item.text_color, row );
}
}
static void draw_options( int start_row, int start_idx, int option_cnt, int selected_idx, atp_itemprovider_t provider, atp_callerdata_t data )
{
int max_draw = CONTAINER_MAX_LINES - start_row;
int end_idx = start_idx + max_draw;
if( end_idx > option_cnt ) end_idx = option_cnt;
// draw
for( int i=start_idx; i < end_idx; ++i )
{
draw_one_option( i, selected_idx, start_row+i-start_idx, provider, data );
}
}
#define SELECTED_ROW(top, len, sel) (((top) < (len)) ? ((len)-(top)+(sel)) : ((sel)+(len)-(top)))
#define REFRESH_PAGE { \
set_paging( idx_top, title_len+cnt ); \
if( idx_top < title_len ) \
{ \
uint8_t* title_data[2] = {(uint8_t*)title, title_offset}; \
container_paint( title_paint, (atp_callerdata_t)title_data, title_len, idx_top ); \
draw_options( title_len - idx_top, 0, cnt, item_sel, provider, data ); \
} \
else draw_options( 0, idx_top - title_len, cnt, item_sel, provider, data ); \
}
atp_error_t atp_select( atp_text_t title, atp_counter_t cnt, atp_itemprovider_t provider, atp_keyhandler_t handler, atp_callerdata_t data, atp_counter_t index, atp_boolean_t action, atp_itemval_t *res )
{
uint8_t title_offset[TITLE_MAX];
int item_sel = index < cnt ? index : 0;
int title_len = 1;
atp_itemcfg_t config;
// draw title
const char *cursor = title;
for( int i=0; i < TITLE_MAX; ++i )
{
const char *next;
if( 0 == acf_calculate( CONTAINER_RECT_WIDTH, cursor, NULL, NULL, &next ) )
{
if( *next == '\0' ) break;
title_offset[i] = next - cursor;
++title_len;
cursor = next;
}
}
for( int i=title_len; i < TITLE_MAX; ++i ) title_offset[i] = 0;
// draw item
int idx_top = item_sel + title_len;
idx_top = idx_top - idx_top%CONTAINER_MAX_LINES;
REFRESH_PAGE;
while( 1 )
{
uint32_t key = waitKey();
if( key == 0 ) return ATP_POWER_OFF;
if( key & (KEY_A | KEY_B) )
{
if( key & KEY_A )
{
atp_error_t result = provider(data, item_sel, &config);
if( ATP_SUCCESS == result )
{
if( res != NULL ) *res = config.value;
return ATP_SUCCESS;
}
else
{
if( res != NULL ) *res = result;
return ATP_INVALID_VALUE;
}
}
else
{
if( action == 0 ) return ATP_NO_ACTION;
}
}
else if( key & (KEY_L | KEY_R | KEY_X | KEY_Y | KEY_SELECT | KEY_START) )
{
if( handler == NULL ) continue;
atp_pageopt_t opt = handler( data, item_sel, key&KEY_X, key&KEY_Y, key&KEY_L, key&KEY_R, key&KEY_START, key&KEY_SELECT );
switch( opt )
{
case ATP_PAGE_REFRESH:
REFRESH_PAGE;
break;
case ATP_PAGE_UPDATE:
int row = SELECTED_ROW(idx_top, title_len, item_sel);
screen_clean_zone( CONTAINER_LEFTTOP_X, CONTAINER_LEFTTOP_Y+FONT_HEIGHT*(row), CONTAINER_RECT_WIDTH, FONT_HEIGHT );
//draw_one_option( item_sel, item_sel, row, provider, data );
break;
default:
;
}
}
else if( key & (KEY_DOWN | KEY_UP) )
{
int sel = item_sel + (key&KEY_DOWN ? 1 : -1);
// 更新item_sel
if( sel >= (int)cnt ) sel = 0;
else if( sel < 0 ) sel = (int)cnt-1;
// 更新idx_top
int sel_row = title_len + sel;
int top = sel_row - sel_row % CONTAINER_MAX_LINES;
atp_boolean_t full_refresh = 0;
if( top != idx_top )
{
idx_top = top;
full_refresh = 1;
}
// 更新view
if( full_refresh )
{// 重新绘制全部
item_sel = sel;
REFRESH_PAGE;
}
else
{// 重新绘制item_sel和sel
draw_one_option( item_sel, sel, SELECTED_ROW(idx_top, title_len, item_sel), provider, data );
draw_one_option( sel, sel, SELECTED_ROW(idx_top, title_len, sel), provider, data);
item_sel = sel;
}
}
else if( key & (KEY_LEFT | KEY_RIGHT) )
{
const int total = title_len + cnt;
if( total <= CONTAINER_MAX_LINES )
continue;
// 更新idx_top
if( key & KEY_LEFT ) idx_top += CONTAINER_MAX_LINES;
else idx_top -= CONTAINER_MAX_LINES;
if( idx_top >= total ) idx_top = 0;
else if( idx_top < 0 ) idx_top = total - total%CONTAINER_MAX_LINES;
// 更新item_sel
item_sel = idx_top + item_sel % CONTAINER_MAX_LINES;
if( idx_top == 0 && item_sel >= CONTAINER_MAX_LINES-title_len )
item_sel = 0;
else if( item_sel >= (int)cnt ) item_sel = cnt-1;
// 更新view
REFRESH_PAGE;
}
}
}
atp_error_t atp_tips( atp_text_t tipsA, atp_text_t tipsB )
{
if( tipsA != NULL )
{
strncpy( ta, tipsA, TIPS_MAX-1 );
ta[TIPS_MAX-1] = 0;
}
if( tipsB != NULL )
{
strncpy( tb, tipsB, TIPS_MAX-1 );
tb[TIPS_MAX-1] = 0;
}
return ATP_SUCCESS;
}