mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-06 22:04:10 +08:00
381 lines
13 KiB
C
381 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 );
|
|
}
|
|
|
|
// 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:
|
|
draw_one_option( item_sel, item_sel, SELECTED_ROW(idx_top, title_len, item_sel), 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;
|
|
}
|