diff --git a/src/dusk/display/text/font.c b/src/dusk/display/text/font.c index 0c824e1a..26ec286e 100644 --- a/src/dusk/display/text/font.c +++ b/src/dusk/display/text/font.c @@ -76,11 +76,23 @@ const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][ { 0x00, 0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00 }, // Y { 0x00, 0x3E, 0x02, 0x04, 0x08, 0x10, 0x20, 0x3E, 0x00, 0x00 }, // Z { 0x00, 0x0C, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0C, 0x00, 0x00 }, // [ - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // backslash (not drawn in source font) + // Custom icon glyph, not a real backslash - a backspace symbol: Caps + // Lock's up arrow (see FONT_ICON_CAPSLOCK) rotated 270 degrees to + // point left instead, adapted to this font's fixed 6x10 tile (each of + // Caps Lock's row widths becomes a column height here, centered + // vertically). Backslash was never drawn anyway, and isn't a key this + // virtual keyboard can type - see FONT_ICON_BACKSPACE. + { 0x00, 0x00, 0x08, 0x1F, 0x3F, 0x3F, 0x1F, 0x08, 0x00, 0x00 }, // FONT_ICON_BACKSPACE { 0x00, 0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x18, 0x00, 0x00 }, // ] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ^ (not drawn in source font) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // _ (not drawn in source font) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ` (not drawn in source font) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E }, // _ + // Custom icon glyph, not a real backtick - an up arrow for Shift, same + // head as Caps Lock's (see FONT_ICON_CAPSLOCK) but with a shaft 2px + // thinner, plus one empty row near the bottom of it, so it reads as a + // lighter/broken version of Caps Lock's thicker uninterrupted one. + // Backtick was never drawn anyway, and isn't a key this virtual + // keyboard can type - see FONT_ICON_SHIFT. + { 0x0C, 0x1E, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x00, 0x0C, 0x00 }, // FONT_ICON_SHIFT { 0x00, 0x00, 0x00, 0x1E, 0x22, 0x22, 0x22, 0x1E, 0x00, 0x00 }, // a { 0x00, 0x20, 0x20, 0x3C, 0x22, 0x22, 0x22, 0x3C, 0x00, 0x00 }, // b { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x20, 0x22, 0x1C, 0x00, 0x00 }, // c @@ -108,10 +120,25 @@ const uint8_t FONT_DEFAULT_GLYPHS[FONT_DEFAULT_TILE_COUNT][ { 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x1E, 0x02, 0x1C }, // y { 0x00, 0x00, 0x00, 0x3E, 0x04, 0x08, 0x10, 0x3E, 0x00, 0x00 }, // z { 0x00, 0x04, 0x08, 0x08, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00 }, // { - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // | (not drawn in source font) + // Custom icon glyph, not a real pipe - a "return" arrow for the + // keyboard's newline key: a vertical riser down the right side that + // hooks left into a leftward-pointing arrowhead, i.e. "<-|" rotated + // into an L. Pipe was never drawn anyway, and isn't a key this + // virtual keyboard can type - see FONT_ICON_NEWLINE. + { 0x02, 0x02, 0x02, 0x02, 0x0E, 0x1E, 0x08, 0x00, 0x00, 0x00 }, // FONT_ICON_NEWLINE { 0x00, 0x10, 0x08, 0x08, 0x04, 0x08, 0x08, 0x10, 0x00, 0x00 }, // } - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ~ (not drawn in source font) - { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile + // Custom icon glyph, not a real tilde - a spacebar symbol for the + // keyboard's space key: an underscore with a tick at each end, like + // "|___|". Tilde was never drawn anyway, and was explicitly dropped + // from this virtual keyboard's own key set - see FONT_ICON_SPACE. + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x3F }, // FONT_ICON_SPACE + // Custom icon glyph, not a real ASCII character - a thick, solid, + // uninterrupted up arrow for Caps Lock (contrast FONT_ICON_SHIFT's + // same head but a thinner, gapped shaft). Assigned to char code 127 + // (DEL) since that codepoint is never legitimately typed text and + // (unlike 128+) is still a positive value regardless of whether this + // platform's plain `char` is signed - see FONT_ICON_CAPSLOCK. + { 0x0C, 0x1E, 0x3F, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x00 }, // FONT_ICON_CAPSLOCK { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // unused trailing tile }; diff --git a/src/dusk/display/text/font.h b/src/dusk/display/text/font.h index 8efce342..35913b36 100644 --- a/src/dusk/display/text/font.h +++ b/src/dusk/display/text/font.h @@ -28,10 +28,45 @@ typedef struct { /** * Number of glyphs defined in FONT_DEFAULT_GLYPHS (FONT_DEFAULT_COLUMNS * * FONT_DEFAULT_ROWS), covering the printable ASCII range starting at - * TEXT_CHAR_START ('!') plus a couple of unused trailing tiles. + * TEXT_CHAR_START ('!'), one custom icon glyph in a trailing unused tile + * (see FONT_ICON_CAPSLOCK), plus one more genuinely unused trailing tile. + * FONT_ICON_SHIFT/FONT_ICON_NEWLINE reuse existing-but-blank slots within + * the printable range instead of more trailing tiles - see their own + * comments. */ #define FONT_DEFAULT_TILE_COUNT (FONT_DEFAULT_COLUMNS * FONT_DEFAULT_ROWS) +/** + * Custom (non-ASCII-meaning) icon glyphs baked into FONT_DEFAULT_GLYPHS + * at otherwise-unused codepoints within the range this font covers - + * safe to use anywhere a char_t string is expected, e.g. a uibutton_t + * label. Kept below 128: char_t is a plain `char`, whose signedness + * varies by platform, so a codepoint of 128 or above isn't safely + * representable everywhere this engine targets. + */ +// Thick, solid, uninterrupted up arrow - Caps Lock. A trailing tile +// (char code 127/DEL) that was never a real character to begin with. +#define FONT_ICON_CAPSLOCK "\x7F" +// Same up arrow, but with a thinner and gapped shaft - Shift. Reuses +// the backtick's glyph slot: backtick was never drawn by this font +// anyway, and isn't a key this engine's virtual keyboard can type. +#define FONT_ICON_SHIFT "`" +// A "return" arrow (down then left, with a leftward arrowhead) - the +// keyboard's newline key. Reuses the pipe's glyph slot, for the same +// reason as FONT_ICON_SHIFT. +#define FONT_ICON_NEWLINE "|" +// An underscore with a tick at each end ("|___|") - the keyboard's +// space key. Reuses the tilde's glyph slot: tilde was never drawn by +// this font, and was explicitly dropped from this engine's virtual +// keyboard's own key set. +#define FONT_ICON_SPACE "~" +// Caps Lock's arrow rotated to point left - the keyboard's backspace +// key. Reuses the backslash's glyph slot, for the same reason as +// FONT_ICON_SHIFT/FONT_ICON_NEWLINE/FONT_ICON_SPACE. Last free reused +// slot below 128 - one more custom icon after this needs +// FONT_DEFAULT_COLUMNS/ROWS grown to make room. +#define FONT_ICON_BACKSPACE "\\" + extern font_t FONT_DEFAULT; /** diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.c b/src/dusk/ui/dialog/keyboard/uikeyboard.c index f778af71..97f5fe8c 100644 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.c +++ b/src/dusk/ui/dialog/keyboard/uikeyboard.c @@ -17,12 +17,13 @@ #include "display/color.h" #include "display/text/font.h" #include "display/spritebatch/spritebatch.h" +#include "time/time.h" // Key cell size, expressed in font tiles so it scales if the font ever // does. Row height is derived from this ratio in uiKeyboardDraw rather // than being its own fixed constant, so a key cell keeps its proportions // even when contentWidth is stretched wider by a long title/text label. -#define UI_KEYBOARD_KEY_WIDTH ((float_t)(FONT_DEFAULT_TILE_WIDTH * 5)) +#define UI_KEYBOARD_KEY_WIDTH ((float_t)(FONT_DEFAULT_TILE_WIDTH * 4)) #define UI_KEYBOARD_KEY_HEIGHT ((float_t)(FONT_DEFAULT_TILE_HEIGHT * 2)) uikeyboard_t UI_KEYBOARD; @@ -47,11 +48,12 @@ static const char_t *const UI_KEYBOARD_KEYS_NONE[UI_KEYBOARD_KEY_COUNT] = { UI_KEYBOARD_KEY_CAPS, "a", "s", "d", "f", "g", "h", "j", "k", "l", NULL, - // SHIFT z x c v b n m SHIFT + // SHIFT z x c v b n m SHIFT UI_KEYBOARD_KEY_SHIFT, + NULL, "z", "x", "c", "v", "b", "n", "m", + NULL, UI_KEYBOARD_KEY_SHIFT, - NULL, NULL, // (CANCEL) SPACE (CONFIRM) NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, @@ -70,9 +72,10 @@ static const char_t *const UI_KEYBOARD_KEYS_CAPS[UI_KEYBOARD_KEY_COUNT] = { "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, UI_KEYBOARD_KEY_SHIFT, + NULL, "Z", "X", "C", "V", "B", "N", "M", + NULL, UI_KEYBOARD_KEY_SHIFT, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, NULL @@ -91,9 +94,10 @@ static const char_t *const UI_KEYBOARD_KEYS_SHIFT[UI_KEYBOARD_KEY_COUNT] = { "A", "S", "D", "F", "G", "H", "J", "K", "L", NULL, UI_KEYBOARD_KEY_SHIFT, + NULL, "Z", "X", "C", "V", "B", "N", "M", + NULL, UI_KEYBOARD_KEY_SHIFT, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, UI_KEYBOARD_KEY_SPACE, NULL, NULL, NULL, NULL, NULL @@ -141,8 +145,24 @@ void uiKeyboardUpdateDisplay( const char_t *const *table = uiKeyboardSelectTable(shift, caps); for(uint8_t i = 0; i < UI_KEYBOARD_KEY_COUNT; i++) { + // These reserved slots are blank in every shift/caps table - their + // real labels (NEWLINE/CANCEL/CONFIRM) are patched in once at open + // time and aren't affected by shift/caps, so leave them alone here. + if( + i == UI_KEYBOARD_NEWLINE_INDEX || + i == UI_KEYBOARD_CANCEL_INDEX || + i == UI_KEYBOARD_CONFIRM_INDEX + ) continue; + items[i].button.label.text = uiKeyboardNormalizeKey(table[i]); items[i].button.label.dirty = true; + + const char_t *canonical = uiKeyboardGetCanonicalKey(i); + if(stringCompare(canonical, UI_KEYBOARD_KEY_CAPS) == 0) { + uiButtonSetActive(&items[i].button, caps); + } else if(stringCompare(canonical, UI_KEYBOARD_KEY_SHIFT) == 0) { + uiButtonSetActive(&items[i].button, shift); + } } } @@ -244,8 +264,7 @@ bool_t uiKeyboardMenuCancel(const uimenu_t *menu) { // and the next uiKeyboardOpen() tripping its already-open assert. if(!UI_KEYBOARD.cancel) return true; - UI_KEYBOARD.confirmed = false; - uiKeyboardClose(); + uiKeyboardDoCancel(); return true; } @@ -256,6 +275,26 @@ void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user) { uiKeyboardClose(); } +void uiKeyboardDoCancel(void) { + if(UI_KEYBOARD.cancelConfirm) { + uiConfirmOpen( + UI_KEYBOARD.cancelConfirmQuestion, + uiKeyboardCancelConfirmSecondaryResult, NULL + ); + return; + } + + UI_KEYBOARD.confirmed = false; + uiKeyboardClose(); +} + +void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user) { + if(!result) return; + + UI_KEYBOARD.confirmed = false; + uiKeyboardClose(); +} + void uiKeyboardMenuSelected( const uimenu_t *menu, const uint8_t index, @@ -282,8 +321,7 @@ void uiKeyboardMenuSelected( } if(UI_KEYBOARD.cancel && index == UI_KEYBOARD_CANCEL_INDEX) { - UI_KEYBOARD.confirmed = false; - uiKeyboardClose(); + uiKeyboardDoCancel(); return; } @@ -360,6 +398,11 @@ errorret_t uiKeyboardInit(void) { &UI_KEYBOARD.textLabel, UI_KEYBOARD.text, UI_KEYBOARD.textSprites, UI_KEYBOARD_TEXT_SPRITES_MAX ); + uiLabelInit( + &UI_KEYBOARD.cursorLabel, UI_KEYBOARD_CURSOR_TEXT, + UI_KEYBOARD.cursorSprites, UI_KEYBOARD_CURSOR_SPRITES_MAX + ); + UI_KEYBOARD.cursorLabel.dirty = true; errorOk(); } @@ -507,6 +550,35 @@ errorret_t uiKeyboardDraw(void) { uiLabelSetY(&UI_KEYBOARD.textLabel, textY); errorChain(uiLabelRender(&UI_KEYBOARD.textLabel, COLOR_WHITE)); + // Insertion point is always the end of text - there's no mid-string + // cursor movement - so find it by counting characters since the last + // newline (column) and newlines overall (line). Once maxLength is + // reached, a new key overrides the last character (see + // uiKeyboardAppendChar) rather than appending one, so stop one + // character short here too - the cursor should sit on that last + // character, not past it, to reflect what typing will actually do. + size_t textLength = strlen(UI_KEYBOARD.text); + size_t cursorLimit = textLength >= UI_KEYBOARD.maxLength ? + textLength - 1 : textLength; + + uint8_t cursorLine = 0; + uint8_t cursorCol = 0; + for(size_t i = 0; i < cursorLimit; i++) { + if(UI_KEYBOARD.text[i] == '\n') { cursorLine++; cursorCol = 0; } + else cursorCol++; + } + + if(mathModFloat(TIME.time, UI_KEYBOARD_CURSOR_BLINK_RATE * 2.0f) < + UI_KEYBOARD_CURSOR_BLINK_RATE + ) { + uiLabelSetX( + &UI_KEYBOARD.cursorLabel, + contentX + (float_t)cursorCol * (float_t)FONT_DEFAULT_TILE_WIDTH + ); + uiLabelSetY(&UI_KEYBOARD.cursorLabel, textY + (float_t)cursorLine * rowHeight); + errorChain(uiLabelRender(&UI_KEYBOARD.cursorLabel, COLOR_WHITE)); + } + // Drawn directly rather than via uiMenuDraw - that helper hardcodes row // spacing to the font's native tile height, which would keep the grid // vertically compact no matter how much contentWidth grows. Focus diff --git a/src/dusk/ui/dialog/keyboard/uikeyboard.h b/src/dusk/ui/dialog/keyboard/uikeyboard.h index 7509318e..ce1d00c7 100644 --- a/src/dusk/ui/dialog/keyboard/uikeyboard.h +++ b/src/dusk/ui/dialog/keyboard/uikeyboard.h @@ -9,6 +9,7 @@ #include "error/error.h" #include "ui/widget/uilabel.h" #include "ui/widget/uimenu.h" +#include "display/text/font.h" // A fixed 11x5 QWERTY grid - the only layout that exists (numbers/ // symbols layouts were removed as separate concepts; digits already @@ -16,7 +17,7 @@ // 1 2 3 4 5 6 7 8 9 0 DEL // q w e r t y u i o p // CAPS a s d f g h j k l (NEWLINE) -// SHIFT z x c v b n m SHIFT +// SHIFT z x c v b n m SHIFT // (CANCEL) SPACE (CONFIRM) // Rows shorter than 11 columns are padded with blank filler buttons // (empty label) so every key lands in its intended position - the menu/ @@ -46,13 +47,13 @@ // tell special keys apart from an ordinary single-character key. A blank // ("") label is a filler cell - see above. Identical across every // UI_KEYBOARD_KEYS_* table, since these don't change with shift/caps. -#define UI_KEYBOARD_KEY_SPACE "SPACE" -#define UI_KEYBOARD_KEY_BACKSPACE "DEL" -#define UI_KEYBOARD_KEY_SHIFT "SHIFT" -#define UI_KEYBOARD_KEY_CAPS "CAPS" +#define UI_KEYBOARD_KEY_SPACE FONT_ICON_SPACE +#define UI_KEYBOARD_KEY_BACKSPACE FONT_ICON_BACKSPACE +#define UI_KEYBOARD_KEY_SHIFT FONT_ICON_SHIFT +#define UI_KEYBOARD_KEY_CAPS FONT_ICON_CAPSLOCK // Only patched into UI_KEYBOARD_NEWLINE_INDEX when // uikeyboardopen_t.lineCount > 1 - otherwise that slot stays blank. -#define UI_KEYBOARD_KEY_NEWLINE "NEWLINE" +#define UI_KEYBOARD_KEY_NEWLINE FONT_ICON_NEWLINE #define UI_KEYBOARD_TITLE_TEXT_MAX 64 #define UI_KEYBOARD_TITLE_SPRITES_MAX UI_KEYBOARD_TITLE_TEXT_MAX @@ -74,9 +75,20 @@ // Used when uikeyboardopen_t.confirmLabel is NULL. #define UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT "Is this correct?" +#define UI_KEYBOARD_CANCEL_QUESTION_MAX 64 +// Used when uikeyboardopen_t.cancelConfirmLabel is NULL. +#define UI_KEYBOARD_CANCEL_QUESTION_DEFAULT "Discard your changes?" + // Maximum number of lines uikeyboardopen_t.lineCount may request. #define UI_KEYBOARD_LINE_COUNT_MAX 4 +// Marks where the next typed character will be inserted (always the end +// of the current text - there's no mid-string cursor movement). +#define UI_KEYBOARD_CURSOR_TEXT "_" +#define UI_KEYBOARD_CURSOR_SPRITES_MAX 1 +// Seconds per on/off half-cycle of the cursor blink. +#define UI_KEYBOARD_CURSOR_BLINK_RATE 0.5f + /** * Callback invoked once the keyboard dialog is dismissed. * @@ -138,6 +150,20 @@ typedef struct { // UI_KEYBOARD_CONFIRM_QUESTION_DEFAULT. Ignored when confirm is false. const char_t *confirmLabel; + // When true, actually cancelling (the CANCEL button, or back with + // nothing left to delete - see uiKeyboardMenuCancel) opens a second + // uiConfirm "are you sure" dialog (see cancelConfirmLabel) before the + // keyboard closes - picking Cancel there returns to the keyboard with + // the text untouched. Ignored when cancel is false, since there's + // nothing to confirm. + bool_t cancelConfirm; + + // Question text/locale message ID for the cancel confirm dialog when + // cancelConfirm is true; copied internally, safe to be transient. NULL + // uses UI_KEYBOARD_CANCEL_QUESTION_DEFAULT. Ignored when cancelConfirm + // is false. + const char_t *cancelConfirmLabel; + // If true, leading/trailing whitespace is trimmed from the entered // text when confirm is pressed, before it's checked against // allowBlank or passed to onInput. @@ -167,10 +193,18 @@ typedef struct { char_t text[UI_KEYBOARD_TEXT_MAX]; spritebatchsprite_t textSprites[UI_KEYBOARD_TEXT_SPRITES_MAX]; + // Blinking insertion-point marker, positioned each draw at the end of + // text - a separate label rather than appending to text/textSprites, + // since neither buffer has spare room for it (see + // UI_KEYBOARD_TEXT_MAX/UI_KEYBOARD_TEXT_SPRITES_MAX). + uilabel_t cursorLabel; + spritebatchsprite_t cursorSprites[UI_KEYBOARD_CURSOR_SPRITES_MAX]; + uimenu_t menu; uimenuitem_t items[UI_KEYBOARD_ITEMS_MAX]; char_t confirmQuestion[UI_KEYBOARD_CONFIRM_QUESTION_MAX]; + char_t cancelConfirmQuestion[UI_KEYBOARD_CANCEL_QUESTION_MAX]; uikeyboardinputcallback_t onInput; uikeyboardkeypresscallback_t onKeyPress; @@ -179,6 +213,7 @@ typedef struct { uint8_t lineCount; bool_t cancel; bool_t confirm; + bool_t cancelConfirm; bool_t trimmed; bool_t allowBlank; bool_t open; @@ -300,6 +335,25 @@ void uiKeyboardMenuSelected( */ void uiKeyboardConfirmSecondaryResult(const bool_t result, void *user); +/** + * Actually cancels the keyboard - sets confirmed to false and closes the + * dialog. Shared by uiKeyboardMenuCancel (back with nothing to delete) + * and uiKeyboardMenuSelected (the CANCEL button); called directly when + * UI_KEYBOARD.cancelConfirm is false, or as the result callback (see + * uiKeyboardCancelConfirmSecondaryResult) once the "are you sure" dialog + * confirms it when cancelConfirm is true. + */ +void uiKeyboardDoCancel(void); + +/** + * Result callback for the "are you sure" uiConfirm dialog opened when + * UI_KEYBOARD.cancelConfirm is set - see uiKeyboardDoCancel. + * + * @param result True if Confirm was picked there, false if Cancel. + * @param user Unused. + */ +void uiKeyboardCancelConfirmSecondaryResult(const bool_t result, void *user); + /** * Internal menu callback - fires the result callback set by * uiKeyboardOpen once the menu finishes closing. diff --git a/src/dusk/ui/screen/mainmenu/uimainmenu.c b/src/dusk/ui/screen/mainmenu/uimainmenu.c index d6e37bd9..57508b85 100644 --- a/src/dusk/ui/screen/mainmenu/uimainmenu.c +++ b/src/dusk/ui/screen/mainmenu/uimainmenu.c @@ -72,11 +72,13 @@ void uiMainMenuSelected( uikeyboardopen_t open = { .onInput = uiMainMenuKeyboardTestInput, .onKeyPress = uiMainMenuKeyboardTestKeyPress, - .cancel = false, + .cancel = true, .confirm = true, + .cancelConfirm = true, .user = NULL, .maxLength = 6, - .lineCount = 2 + .lineCount = 2, + .allowBlank = true, }; uiKeyboardOpen(&open); break; diff --git a/src/dusk/ui/widget/uibutton.c b/src/dusk/ui/widget/uibutton.c index 5d0f8a0f..789676af 100644 --- a/src/dusk/ui/widget/uibutton.c +++ b/src/dusk/ui/widget/uibutton.c @@ -29,6 +29,16 @@ void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted) { button->highlighted = highlighted; } +bool_t uiButtonIsActive(const uibutton_t *button) { + assertNotNull(button, "Button cannot be NULL"); + return button->active; +} + +void uiButtonSetActive(uibutton_t *button, const bool_t active) { + assertNotNull(button, "Button cannot be NULL"); + button->active = active; +} + errorret_t uiButtonDraw( uibutton_t *button, const float_t x, @@ -40,6 +50,9 @@ errorret_t uiButtonDraw( uiLabelSetY(&button->label, y); return uiLabelRender( - &button->label, button->highlighted ? COLOR_RED : COLOR_WHITE + &button->label, + button->active ? COLOR_GREEN : + button->highlighted ? COLOR_RED : + COLOR_WHITE ); } diff --git a/src/dusk/ui/widget/uibutton.h b/src/dusk/ui/widget/uibutton.h index 956938cf..87cd1a21 100644 --- a/src/dusk/ui/widget/uibutton.h +++ b/src/dusk/ui/widget/uibutton.h @@ -15,6 +15,11 @@ typedef struct { uilabel_t label; spritebatchsprite_t sprites[UI_BUTTON_SPRITES_MAX]; bool_t highlighted; + // Independent of highlighted (which the focus system drives based on + // what's currently focused) - for a button that also needs to show a + // toggled-on state regardless of focus, e.g. the keyboard's CAPS/SHIFT + // keys. + bool_t active; } uibutton_t; /** @@ -42,6 +47,23 @@ bool_t uiButtonIsHighlighted(const uibutton_t *button); */ void uiButtonSetHighlighted(uibutton_t *button, const bool_t highlighted); +/** + * Returns whether the button is active. + * + * @param button The button to query. + * @returns True if active. + */ +bool_t uiButtonIsActive(const uibutton_t *button); + +/** + * Sets the active state of the button - a toggled-on indicator drawn + * the same way as highlighted, but independent of focus. + * + * @param button The button to update. + * @param active The new active state. + */ +void uiButtonSetActive(uibutton_t *button, const bool_t active); + /** * Draws the button at the given screen position. *