show keyboard shortcuts in menus

This commit is contained in:
nakst 2021-10-04 19:35:55 +01:00
parent 2e4f92b619
commit aabd7dbf36
8 changed files with 148 additions and 44 deletions

View File

@ -293,7 +293,6 @@ struct MeasurementCache {
struct EsButton : EsElement {
char *label;
size_t labelBytes;
EsGeneric menuItemContext;
uint32_t iconID;
MeasurementCache measurementCache;
EsCommand *command;
@ -302,6 +301,11 @@ struct EsButton : EsElement {
EsImageDisplay *imageDisplay;
};
struct MenuItem : EsButton {
// This shares the EsButton structure so that it can be used with EsButtonOnCommand.
EsGeneric menuItemContext;
};
struct EsImageDisplay : EsElement {
void *source;
size_t sourceBytes;
@ -3688,7 +3692,7 @@ EsDialog *EsDialogShow(EsWindow *window, const char *title, ptrdiff_t titleBytes
return dialog;
}
EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags, EsStyle *style, const char *label, ptrdiff_t labelBytes, EsCommandCallback callback) {
EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags, const EsStyle *style, const char *label, ptrdiff_t labelBytes, EsCommandCallback callback) {
EsButton *button = EsButtonCreate(dialog->buttonArea, flags, style, label, labelBytes);
if (button) {
@ -4154,31 +4158,125 @@ void EsButtonSetCheck(EsButton *button, EsCheckState checkState, bool sendUpdate
button->MaybeRefreshStyle();
}
void EsMenuAddItem(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes, EsMenuCallback callback, EsGeneric context) {
EsButton *button = (EsButton *) EsButtonCreate(menu,
ES_BUTTON_NOT_FOCUSABLE | ES_BUTTON_MENU_ITEM | ES_CELL_H_FILL | flags, 0,
label, labelBytes != -1 ? labelBytes : EsCStringLength(label));
if (!button) return;
button->userData = (void *) callback;
void MenuItemGetKeyboardShortcutString(EsCommand *command, EsBuffer *buffer) {
if (!command) {
return;
}
button->messageUser = [] (EsElement *element, EsMessage *message) {
if (message->type == ES_MSG_MOUSE_LEFT_CLICK) {
EsMenuCallback callback = (EsMenuCallback) element->userData.p;
if (callback) callback((EsMenu *) element->window, ((EsButton *) element)->menuItemContext);
const char *input = command->cKeyboardShortcut;
if (!input) {
return;
}
while (true) {
if (input[0] == 'C' && input[1] == 't' && input[2] == 'r' && input[3] == 'l' && input[4] == '+') {
EsBufferFormat(buffer, "%c", 0x2303);
input += 5;
} else if (input[0] == 'S' && input[1] == 'h' && input[2] == 'i' && input[3] == 'f' && input[4] == 't' && input[5] == '+') {
EsBufferFormat(buffer, "%c", 0x21E7);
input += 6;
} else if (input[0] == 'A' && input[1] == 'l' && input[2] == 't' && input[3] == '+') {
EsBufferFormat(buffer, "%c", 0x2325);
input += 4;
} else {
break;
}
}
while (*input != 0 && *input != '|') {
EsBufferWrite(buffer, input++, 1);
}
}
int ProcessMenuItemMessage(EsElement *element, EsMessage *message) {
MenuItem *button = (MenuItem *) element;
if (message->type == ES_MSG_PAINT) {
// Draw the label.
EsDrawContent(message->painter, element,
ES_RECT_2S(message->painter->width, message->painter->height),
button->label, button->labelBytes, 0, ES_FLAGS_DEFAULT);
// Draw the keyboard shortcut.
// TODO If activated by the keyboard, show access keys instead.
uint8_t _buffer[64];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
MenuItemGetKeyboardShortcutString(button->command, &buffer);
EsDrawContent(message->painter, element,
ES_RECT_2S(message->painter->width, message->painter->height),
(const char *) _buffer, buffer.position, 0, ES_DRAW_CONTENT_CHANGE_ALIGNMENT | ES_TEXT_H_RIGHT | ES_TEXT_V_CENTER);
} else if (message->type == ES_MSG_GET_WIDTH) {
uint8_t _buffer[64];
EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) };
MenuItemGetKeyboardShortcutString(button->command, &buffer);
EsTextStyle textStyle;
button->currentStyle->GetTextStyle(&textStyle);
int stringWidth = TextGetStringWidth(button, &textStyle, button->label, button->labelBytes);
int keyboardShortcutWidth = TextGetStringWidth(button, &textStyle, (const char *) _buffer, buffer.position);
int contentWidth = stringWidth + button->currentStyle->insets.l + button->currentStyle->insets.r
+ (keyboardShortcutWidth ? (keyboardShortcutWidth + button->currentStyle->gapMinor) : 0);
message->measure.width = MaximumInteger(GetConstantNumber("menuItemMinimumReportedWidth"), contentWidth);
} else if (message->type == ES_MSG_DESTROY) {
EsHeapFree(button->label);
if (button->command) {
Array<EsElement *> elements = { button->command->elements };
elements.FindAndDeleteSwap(button, true);
button->command->elements = elements.array;
}
} else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) {
} else if (message->type == ES_MSG_MOUSE_LEFT_CLICK) {
if (~element->flags & ES_ELEMENT_DISABLED) {
EsMenuCallback callback = (EsMenuCallback) element->userData.p;
if (button->onCommand) {
button->onCommand(button->instance, button, button->command);
} else if (callback) {
callback((EsMenu *) element->window, button->menuItemContext);
}
EsAssert(button->window->windowStyle == ES_WINDOW_MENU);
EsMenuClose((EsMenu *) button->window);
}
} else if (message->type == ES_MSG_GET_INSPECTOR_INFORMATION) {
EsBufferFormat(message->getContent.buffer, "'%s'", button->labelBytes, button->label);
} else {
return 0;
};
}
return ES_HANDLED;
}
MenuItem *MenuItemCreate(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes) {
MenuItem *button = (MenuItem *) EsHeapAllocate(sizeof(MenuItem), true);
if (!button) return nullptr;
const EsStyle *style = (flags & ES_MENU_ITEM_HEADER) ? ES_STYLE_MENU_ITEM_HEADER : ES_STYLE_MENU_ITEM_NORMAL;
if (flags & ES_MENU_ITEM_HEADER) flags |= ES_ELEMENT_DISABLED;
button->Initialise(menu, flags | ES_CELL_H_FILL, ProcessMenuItemMessage, style);
button->cName = "menu item";
if (labelBytes == -1) labelBytes = EsCStringLength(label);
HeapDuplicate((void **) &button->label, &button->labelBytes, label, labelBytes);
return button;
}
void EsMenuAddItem(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes, EsMenuCallback callback, EsGeneric context) {
MenuItem *button = MenuItemCreate(menu, flags, label, labelBytes);
if (!button) return;
EsButtonSetCheck(button, (EsCheckState) (flags & 3), false);
button->MaybeRefreshStyle();
button->userData = (void *) callback;
button->menuItemContext = context;
}
void EsMenuAddCommand(EsMenu *menu, uint64_t flags, const char *label, ptrdiff_t labelBytes, EsCommand *command) {
EsButton *button = (EsButton *) EsButtonCreate(menu,
ES_BUTTON_NOT_FOCUSABLE | ES_BUTTON_MENU_ITEM | ES_CELL_H_FILL | flags,
0, label, labelBytes);
if (!button) return;
EsCommandAddButton(command, button);
MenuItem *button = MenuItemCreate(menu, flags, label, labelBytes);
if (button) EsCommandAddButton(command, button);
}
// --------------------------------- Color wells and pickers.

View File

@ -1778,15 +1778,8 @@ struct EsListView : EsElement {
message->zOrder.child = nullptr;
} else if (message->type == ES_MSG_PAINT && !totalItemCount && emptyMessageBytes) {
UIStyle *style = GetStyle(MakeStyleKey(ES_STYLE_TEXT_LABEL_SECONDARY, 0), true);
EsTextPlanProperties properties = {};
properties.flags = ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_WRAP | ES_TEXT_PLAN_SINGLE_USE;
EsTextRun textRun[2] = {};
style->GetTextStyle(&textRun[0].style);
textRun[1].offset = emptyMessageBytes;
EsRectangle bounds = EsPainterBoundsInset(message->painter);
EsTextPlan *plan = EsTextPlanCreate(this, &properties, bounds, emptyMessage, textRun, 1);
EsDrawText(message->painter, plan, bounds);
EsDrawTextThemed(message->painter, this, EsPainterBoundsInset(message->painter), emptyMessage, emptyMessageBytes,
ES_STYLE_TEXT_LABEL_SECONDARY, ES_TEXT_H_CENTER | ES_TEXT_V_CENTER | ES_TEXT_WRAP);
} else if (message->type == ES_MSG_ANIMATE) {
if (scroll.dragScrolling && (flags & ES_LIST_VIEW_CHOICE_SELECT)) {
DragSelect();

View File

@ -362,19 +362,28 @@ define ES_LIST_VIEW_COLUMN_SORT_DESCENDING (2)
define ES_SHARED_MEMORY_NAME_MAX_LENGTH (32)
define ES_MAP_OBJECT_ALL (0)
define ES_TEXT_H_LEFT (1 << 0) // Keep in sync with designer.c.
define ES_TEXT_H_CENTER (1 << 1)
define ES_TEXT_H_RIGHT (1 << 2)
define ES_TEXT_V_TOP (1 << 3)
define ES_TEXT_V_CENTER (1 << 4)
define ES_TEXT_V_BOTTOM (1 << 5)
define ES_TEXT_ELLIPSIS (1 << 6)
define ES_TEXT_WRAP (1 << 7)
define ES_TEXT_PLAN_SINGLE_USE (1 << 8)
define ES_TEXT_PLAN_TRIM_SPACES (1 << 9)
define ES_TEXT_H_LEFT (1 << 0)
define ES_TEXT_H_CENTER (1 << 1)
define ES_TEXT_H_RIGHT (1 << 2)
define ES_TEXT_V_TOP (1 << 3)
define ES_TEXT_V_CENTER (1 << 4)
define ES_TEXT_V_BOTTOM (1 << 5)
define ES_TEXT_ELLIPSIS (1 << 6)
define ES_TEXT_WRAP (1 << 7)
define ES_TEXT_PLAN_SINGLE_USE (1 << 8)
define ES_TEXT_PLAN_TRIM_SPACES (1 << 9)
define ES_TEXT_PLAN_RTL (1 << 10)
define ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES (1 << 11)
define ES_TEXT_PLAN_NO_FONT_SUBSTITUTION (1 << 12)
// ...plus alignment flags.
define ES_DRAW_CONTENT_CHANGE_ALIGNMENT (1 << 8)
define ES_DRAW_CONTENT_MARKER_DOWN_ARROW (1 << 9)
define ES_DRAW_CONTENT_MARKER_UP_ARROW (1 << 10)
define ES_DRAW_CONTENT_TABULAR (1 << 11)
define ES_DRAW_CONTENT_RICH_TEXT (1 << 12)
// ...plus alignment flags, if CHANGE_ALIGNMENT set.
define ES_FILE_READ_SHARED (0x1) // Read-only. The file can still be opened for writing.
define ES_FILE_READ (0x2) // Read-only. The file will not openable for writing. This will fail if the file is already opened for writing.
@ -693,11 +702,6 @@ define ES_APPLICATION_STARTUP_BACKGROUND_SERVICE (1 << 1)
define ES_LIST_VIEW_INLINE_TEXTBOX_COPY_EXISTING_TEXT (1 << 0)
define ES_LIST_VIEW_INLINE_TEXTBOX_REJECT_EDIT_IF_FOCUS_LOST (1 << 1)
define ES_DRAW_CONTENT_MARKER_DOWN_ARROW (1 << 0)
define ES_DRAW_CONTENT_MARKER_UP_ARROW (1 << 1)
define ES_DRAW_CONTENT_TABULAR (1 << 2)
define ES_DRAW_CONTENT_RICH_TEXT (1 << 3)
define ES_MODIFIER_CTRL (1 << 0)
define ES_MODIFIER_SHIFT (1 << 1)
define ES_MODIFIER_ALT (1 << 2)
@ -2112,6 +2116,7 @@ function bool EsDrawStandardIcon(EsPainter *painter, uint32_t id, int size, EsRe
function void EsDrawPaintTarget(EsPainter *painter, EsPaintTarget *source, EsRectangle destinationRegion, EsRectangle sourceRegion, uint8_t alpha);
function void EsDrawText(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsRectangle *clip = ES_NULL, EsTextSelection *selectionProperties = ES_NULL);
function void EsDrawTextSimple(EsPainter *painter, EsElement *element, EsRectangle bounds, const char *string, ptrdiff_t stringBytes, EsTextStyle style, uint32_t flags = ES_FLAGS_DEFAULT);
function void EsDrawTextThemed(EsPainter *painter, EsElement *element, EsRectangle bounds, const char *string, ptrdiff_t stringBytes, const EsStyle *style, uint32_t flags = ES_FLAGS_DEFAULT); // The style must be one of the ES_STYLE_TEXT_... styles.
function void EsDrawTextLayers(EsPainter *painter, EsTextPlan *plan, EsRectangle bounds, EsTextSelection *selectionProperties = ES_NULL);
function void EsDrawVectorFile(EsPainter *painter, EsRectangle bounds, const void *data, size_t dataBytes);
@ -2398,7 +2403,7 @@ function void EsMenuAddCommandsFromToolbar(EsMenu *menu, EsElement *element);
function void EsDialogClose(EsDialog *dialog);
function EsDialog *EsDialogShow(EsWindow *window, STRING title, STRING content, uint32_t iconID, uint32_t flags = ES_FLAGS_DEFAULT);
function EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags = ES_FLAGS_DEFAULT, EsStyle *style = ES_NULL,
function EsButton *EsDialogAddButton(EsDialog *dialog, uint64_t flags = ES_FLAGS_DEFAULT, const EsStyle *style = ES_NULL,
STRING label = BLANK_STRING, EsCommandCallback callback = ES_NULL);
function void EsFileMenuAddToToolbar(EsElement *toolbar, const EsFileMenuSettings *settings = ES_NULL);

View File

@ -2594,9 +2594,16 @@ void EsDrawTextSimple(EsPainter *painter, EsElement *element, EsRectangle bounds
EsTextRun textRuns[2] = {};
textRuns[0].style = style;
textRuns[1].offset = stringBytes == -1 ? EsCStringLength(string) : stringBytes;
if (!textRuns[1].offset) return;
EsDrawText(painter, EsTextPlanCreate(element, &properties, bounds, string, textRuns, 1), bounds);
}
void EsDrawTextThemed(EsPainter *painter, EsElement *element, EsRectangle bounds, const char *string, ptrdiff_t stringBytes, const EsStyle *style, uint32_t flags) {
EsTextStyle textStyle;
GetStyle(MakeStyleKey(style, 0), true)->GetTextStyle(&textStyle);
EsDrawTextSimple(painter, element, bounds, string, stringBytes, textStyle, flags);
}
#elif defined(TEXT_ELEMENTS)
// --------------------------------- Markup parsing.

View File

@ -1900,7 +1900,7 @@ void UIStyle::PaintText(EsPainter *painter, EsElement *element, EsRectangle rect
if (textBytes) {
EsTextPlanProperties properties = {};
properties.flags = textAlign;
properties.flags = (flags & ES_DRAW_CONTENT_CHANGE_ALIGNMENT) ? (flags & 0xFF) : textAlign;
EsTextRun textRun[2] = {};
textRun[1].offset = textBytes;

Binary file not shown.

Binary file not shown.

View File

@ -476,3 +476,4 @@ EsCRTatof=474
EsCRTstrtod=475
EsCRTstrtof=476
EsOpenDocumentQueryInformation=477
EsDrawTextThemed=478