diff --git a/src/hci/jumpscroll.c b/src/hci/jumpscroll.c new file mode 100644 index 00000000..dd6bcac2 --- /dev/null +++ b/src/hci/jumpscroll.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2015 Michael Brown . + * + * 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 program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * Jump scrolling + * + */ + +#include +#include +#include + +/** + * Handle keypress + * + * @v scroll Jump scroller + * @v key Key pressed by user + * @ret move Scroller movement, or zero + */ +int jump_scroll_key ( struct jump_scroller *scroll, int key ) { + + /* Sanity checks */ + assert ( scroll->rows != 0 ); + assert ( scroll->count != 0 ); + assert ( scroll->current < scroll->count ); + assert ( scroll->first < scroll->count ); + assert ( scroll->first <= scroll->current ); + assert ( scroll->current < ( scroll->first + scroll->rows ) ); + + /* Handle key, if applicable */ + switch ( key ) { + case KEY_UP: + return -1; + case KEY_DOWN: + return +1; + case KEY_PPAGE: + return ( scroll->first - scroll->current - 1 ); + case KEY_NPAGE: + return ( scroll->first - scroll->current + scroll->rows ); + case KEY_HOME: + return -( scroll->count ); + case KEY_END: + return +( scroll->count ); + default: + return 0; + } +} + +/** + * Move scroller + * + * @v scroll Jump scroller + * @v move Scroller movement + * @ret move Continuing scroller movement (if applicable) + */ +int jump_scroll_move ( struct jump_scroller *scroll, int move ) { + int current = scroll->current; + int last = ( scroll->count - 1 ); + + /* Sanity checks */ + assert ( move != 0 ); + assert ( scroll->count != 0 ); + + /* Move to the new current item */ + current += move; + + /* Check for start/end of list */ + if ( current < 0 ) { + /* We have attempted to move before the start of the + * list. Move to the start of the list and continue + * moving forwards (if applicable). + */ + scroll->current = 0; + return +1; + } else if ( current > last ) { + /* We have attempted to move after the end of the + * list. Move to the end of the list and continue + * moving backwards (if applicable). + */ + scroll->current = last; + return -1; + } else { + /* Update the current item and continue moving in the + * same direction (if applicable). + */ + scroll->current = current; + return ( ( move > 0 ) ? +1 : -1 ); + } +} + +/** + * Jump scroll to new page (if applicable) + * + * @v scroll Jump scroller + * @ret jumped Jumped to a new page + */ +int jump_scroll ( struct jump_scroller *scroll ) { + unsigned int index; + + /* Sanity checks */ + assert ( scroll->rows != 0 ); + assert ( scroll->count != 0 ); + assert ( scroll->current < scroll->count ); + assert ( scroll->first < scroll->count ); + + /* Do nothing if we are already on the correct page */ + index = ( scroll->current - scroll->first ); + if ( index < scroll->rows ) + return 0; + + /* Move to required page */ + while ( scroll->first < scroll->current ) + scroll->first += scroll->rows; + while ( scroll->first > scroll->current ) + scroll->first -= scroll->rows; + + return 1; +} diff --git a/src/hci/tui/menu_ui.c b/src/hci/tui/menu_ui.c index 13bdf65e..f9dd9d10 100644 --- a/src/hci/tui/menu_ui.c +++ b/src/hci/tui/menu_ui.c @@ -36,6 +36,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include /* Screen layout */ @@ -50,12 +51,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); struct menu_ui { /** Menu */ struct menu *menu; - /** Number of menu items */ - unsigned int count; - /** Currently selected item */ - int selected; - /** First visible item */ - int first_visible; + /** Jump scroller */ + struct jump_scroller scroll; /** Timeout (0=indefinite) */ unsigned long timeout; }; @@ -84,7 +81,7 @@ static struct menu_item * menu_item ( struct menu *menu, unsigned int index ) { * @v ui Menu user interface * @v index Index */ -static void draw_menu_item ( struct menu_ui *ui, int index ) { +static void draw_menu_item ( struct menu_ui *ui, unsigned int index ) { struct menu_item *item; unsigned int row_offset; char buf[ MENU_COLS + 1 /* NUL */ ]; @@ -94,7 +91,7 @@ static void draw_menu_item ( struct menu_ui *ui, int index ) { size_t len; /* Move to start of row */ - row_offset = ( index - ui->first_visible ); + row_offset = ( index - ui->scroll.first ); move ( ( MENU_ROW + row_offset ), MENU_COL ); /* Get menu item */ @@ -106,7 +103,7 @@ static void draw_menu_item ( struct menu_ui *ui, int index ) { color_set ( CPAIR_SEPARATOR, NULL ); /* Highlight if this is the selected item */ - if ( index == ui->selected ) { + if ( index == ui->scroll.current ) { color_set ( CPAIR_SELECT, NULL ); attron ( A_BOLD ); } @@ -125,7 +122,7 @@ static void draw_menu_item ( struct menu_ui *ui, int index ) { snprintf ( timeout_buf, sizeof ( timeout_buf ), "(%ld)", ( ( ui->timeout + TICKS_PER_SEC - 1 ) / TICKS_PER_SEC ) ); - if ( ( index == ui->selected ) && ( ui->timeout != 0 ) ) { + if ( ( index == ui->scroll.current ) && ( ui->timeout != 0 ) ) { memcpy ( ( buf + MENU_COLS - MENU_PAD - timeout_len ), timeout_buf, timeout_len ); } @@ -154,24 +151,17 @@ static void draw_menu_item ( struct menu_ui *ui, int index ) { static void draw_menu_items ( struct menu_ui *ui ) { unsigned int i; - /* Jump scroll to correct point in list */ - while ( ui->first_visible < ui->selected ) - ui->first_visible += MENU_ROWS; - while ( ui->first_visible > ui->selected ) - ui->first_visible -= MENU_ROWS; - /* Draw ellipses before and/or after the list as necessary */ color_set ( CPAIR_SEPARATOR, NULL ); mvaddstr ( ( MENU_ROW - 1 ), ( MENU_COL + MENU_PAD ), - ( ( ui->first_visible > 0 ) ? "..." : " " ) ); + ( jump_scroll_is_first ( &ui->scroll ) ? " " : "..." ) ); mvaddstr ( ( MENU_ROW + MENU_ROWS ), ( MENU_COL + MENU_PAD ), - ( ( ( ui->first_visible + MENU_ROWS ) < ui->count ) ? - "..." : " " ) ); + ( jump_scroll_is_last ( &ui->scroll ) ? " " : "..." ) ); color_set ( CPAIR_NORMAL, NULL ); /* Draw visible items */ for ( i = 0 ; i < MENU_ROWS ; i++ ) - draw_menu_item ( ui, ( ui->first_visible + i ) ); + draw_menu_item ( ui, ( ui->scroll.first + i ) ); } /** @@ -184,8 +174,7 @@ static void draw_menu_items ( struct menu_ui *ui ) { static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { struct menu_item *item; unsigned long timeout; - unsigned int delta; - int current; + unsigned int previous; int key; int i; int move; @@ -194,7 +183,7 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { do { /* Record current selection */ - current = ui->selected; + previous = ui->scroll.current; /* Calculate timeout as remainder of current second */ timeout = ( ui->timeout % TICKS_PER_SEC ); @@ -213,27 +202,11 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { /* Cancel any timeout */ ui->timeout = 0; - /* Handle key */ + /* Handle scroll keys */ + move = jump_scroll_key ( &ui->scroll, key ); + + /* Handle other keys */ switch ( key ) { - case KEY_UP: - move = -1; - break; - case KEY_DOWN: - move = +1; - break; - case KEY_PPAGE: - move = ( ui->first_visible - ui->selected - 1 ); - break; - case KEY_NPAGE: - move = ( ui->first_visible - ui->selected - + MENU_ROWS ); - break; - case KEY_HOME: - move = -ui->count; - break; - case KEY_END: - move = +ui->count; - break; case ESC: case CTRL_C: rc = -ECANCELED; @@ -251,7 +224,7 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { i++; continue; } - ui->selected = i; + ui->scroll.current = i; if ( item->label ) { chosen = 1; } else { @@ -264,31 +237,22 @@ static int menu_loop ( struct menu_ui *ui, struct menu_item **selected ) { /* Move selection, if applicable */ while ( move ) { - ui->selected += move; - if ( ui->selected < 0 ) { - ui->selected = 0; - move = +1; - } else if ( ui->selected >= ( int ) ui->count ) { - ui->selected = ( ui->count - 1 ); - move = -1; - } - item = menu_item ( ui->menu, ui->selected ); + move = jump_scroll_move ( &ui->scroll, move ); + item = menu_item ( ui->menu, ui->scroll.current ); if ( item->label ) break; - move = ( ( move > 0 ) ? +1 : -1 ); } /* Redraw selection if necessary */ - if ( ( ui->selected != current ) || ( timeout != 0 ) ) { - draw_menu_item ( ui, current ); - delta = ( ui->selected - ui->first_visible ); - if ( delta >= MENU_ROWS ) + if ( ( ui->scroll.current != previous ) || ( timeout != 0 ) ) { + draw_menu_item ( ui, previous ); + if ( jump_scroll ( &ui->scroll ) ) draw_menu_items ( ui ); - draw_menu_item ( ui, ui->selected ); + draw_menu_item ( ui, ui->scroll.current ); } /* Record selection */ - item = menu_item ( ui->menu, ui->selected ); + item = menu_item ( ui->menu, ui->scroll.current ); assert ( item != NULL ); assert ( item->label != NULL ); *selected = item; @@ -317,21 +281,22 @@ int show_menu ( struct menu *menu, unsigned long timeout, /* Initialise UI */ memset ( &ui, 0, sizeof ( ui ) ); ui.menu = menu; + ui.scroll.rows = MENU_ROWS; ui.timeout = timeout; list_for_each_entry ( item, &menu->items, list ) { if ( item->label ) { if ( ! labelled_count ) - ui.selected = ui.count; + ui.scroll.current = ui.scroll.count; labelled_count++; if ( select ) { if ( strcmp ( select, item->label ) == 0 ) - ui.selected = ui.count; + ui.scroll.current = ui.scroll.count; } else { if ( item->is_default ) - ui.selected = ui.count; + ui.scroll.current = ui.scroll.count; } } - ui.count++; + ui.scroll.count++; } if ( ! labelled_count ) { /* Menus with no labelled items cannot be selected @@ -353,8 +318,9 @@ int show_menu ( struct menu *menu, unsigned long timeout, snprintf ( buf, sizeof ( buf ), "%s", ui.menu->title ); mvprintw ( TITLE_ROW, ( ( COLS - strlen ( buf ) ) / 2 ), "%s", buf ); attroff ( A_BOLD ); + jump_scroll ( &ui.scroll ); draw_menu_items ( &ui ); - draw_menu_item ( &ui, ui.selected ); + draw_menu_item ( &ui, ui.scroll.current ); /* Enter main loop */ rc = menu_loop ( &ui, selected ); diff --git a/src/include/ipxe/jumpscroll.h b/src/include/ipxe/jumpscroll.h new file mode 100644 index 00000000..7a5b111c --- /dev/null +++ b/src/include/ipxe/jumpscroll.h @@ -0,0 +1,50 @@ +#ifndef _IPXE_JUMPSCROLL_H +#define _IPXE_JUMPSCROLL_H + +/** @file + * + * Jump scrolling + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** A jump scroller */ +struct jump_scroller { + /** Maximum number of visible rows */ + unsigned int rows; + /** Total number of items */ + unsigned int count; + /** Currently selected item */ + unsigned int current; + /** First visible item */ + unsigned int first; +}; + +/** + * Check if jump scroller is currently on first page + * + * @v scroll Jump scroller + * @ret is_first Scroller is currently on first page + */ +static inline int jump_scroll_is_first ( struct jump_scroller *scroll ) { + + return ( scroll->first == 0 ); +} + +/** + * Check if jump scroller is currently on last page + * + * @v scroll Jump scroller + * @ret is_last Scroller is currently on last page + */ +static inline int jump_scroll_is_last ( struct jump_scroller *scroll ) { + + return ( ( scroll->first + scroll->rows ) >= scroll->count ); +} + +extern int jump_scroll_key ( struct jump_scroller *scroll, int key ); +extern int jump_scroll_move ( struct jump_scroller *scroll, int move ); +extern int jump_scroll ( struct jump_scroller *scroll ); + +#endif /* _IPXE_JUMPSCROLL_H */