#include "arm11/atp.h" #include "arm11/acf.h" #include "arm11/fmt.h" #include "arm11/drivers/hid.h" #include "arm11/console.h" #include "drivers/gfx.h" #define TITLE_MAX 8 #define TIPS_MAX 64 static char dymt[TIPS_MAX] = {'\0'}; static atp_text_t stct = NULL; #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( dymt[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_LEFT, dymt ); if( stct[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_RIGHT, stct ); } 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*sizeof(u16) ); } // 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; } static atp_error_t dynamic_tips( atp_text_t tips ) { if( strlen(tips) > TIPS_MAX-1 ) return ATP_INDEX_OUTOFRANGE; strcpy( dymt, tips ); return ATP_SUCCESS; } #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; dynamic_tips(" "); 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 ); if( total > 1 ) dynamic_tips( buf ); else dynamic_tips(" "); 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 HIT( hit ) ( (hit & key) != 0 ) #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 ); \ } #define HANDLE_DOSELECT { \ if( res == NULL ) return ATP_SUCCESS; \ atp_error_t result = provider(data, item_sel, &config); \ if( ATP_SUCCESS == result ) \ { \ *res = config.value; \ return ATP_SUCCESS; \ } \ else \ { \ *res = result; \ return ATP_INVALID_VALUE; \ } \ } 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 ) { HANDLE_DOSELECT; } 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, HIT(KEY_X), HIT(KEY_Y), HIT(KEY_L), HIT(KEY_R), HIT(KEY_START), HIT(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; case ATP_POWER_OFF: return ATP_POWER_OFF; break; case ATP_PAGE_DOSELECT: HANDLE_DOSELECT; 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 ? total%CONTAINER_MAX_LINES : 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 tips, atp_text_t *old ) { if( old != NULL ) *old = stct; if( tips != NULL ) { if( strlen(tips) > TIPS_MAX-1 ) return ATP_INDEX_OUTOFRANGE; stct = tips; } return ATP_SUCCESS; }