From fc013e8485ec0a739c5c9d35c469cba517e46d98 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Mon, 23 Aug 2021 19:07:44 +0100 Subject: [PATCH] cursor locator --- desktop/api.cpp | 33 ++++++++++++++++++----- desktop/desktop.cpp | 37 ++++++++++++++++++++++++++ desktop/gui.cpp | 21 ++++++++++----- desktop/os.header | 1 + desktop/settings.cpp | 22 ++++++++------- desktop/styles.header | 2 ++ kernel/windows.cpp | 14 ++++++++-- res/System Configuration Template.ini | 1 - res/Theme Source.dat | Bin 49348 -> 50052 bytes res/Themes/Theme.dat | Bin 59492 -> 59876 bytes 10 files changed, 105 insertions(+), 26 deletions(-) diff --git a/desktop/api.cpp b/desktop/api.cpp index bdb0a80..5815ab3 100644 --- a/desktop/api.cpp +++ b/desktop/api.cpp @@ -260,7 +260,7 @@ EsError NodeOpen(const char *path, size_t pathBytes, uint32_t flags, _EsNodeInfo return EsSyscall(ES_SYSCALL_NODE_OPEN, (uintptr_t) path, pathBytes, flags, (uintptr_t) node); } -EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes) { +EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup *group, const char *key, ptrdiff_t keyBytes, bool createIfNeeded = false) { if (keyBytes == -1) keyBytes = EsCStringLength(key); for (uintptr_t i = 0; i < group->itemCount; i++) { @@ -269,10 +269,23 @@ EsSystemConfigurationItem *SystemConfigurationGetItem(EsSystemConfigurationGroup } } + if (createIfNeeded) { + EsSystemConfigurationItem item = {}; + item.key = (char *) EsHeapAllocate(keyBytes, false); + item.keyBytes = keyBytes; + EsMemoryCopy(item.key, key, keyBytes); + + Array<EsSystemConfigurationItem> items = { group->items }; + EsSystemConfigurationItem *_item = items.Add(item); + group->items = items.array; + group->itemCount++; + return _item; + } + return nullptr; } -EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptrdiff_t sectionBytes) { +EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptrdiff_t sectionBytes, bool createIfNeeded = false) { if (sectionBytes == -1) sectionBytes = EsCStringLength(section); for (uintptr_t i = 0; i < api.systemConfigurationGroups.Length(); i++) { @@ -281,6 +294,14 @@ EsSystemConfigurationGroup *SystemConfigurationGetGroup(const char *section, ptr } } + if (createIfNeeded) { + EsSystemConfigurationGroup group = {}; + group.section = (char *) EsHeapAllocate(sectionBytes, false); + group.sectionBytes = sectionBytes; + EsMemoryCopy(group.section, section, sectionBytes); + return api.systemConfigurationGroups.Add(group); + } + return nullptr; } @@ -791,7 +812,7 @@ EsMessage *EsMessageReceive() { EsMessageSend((EsElement *) message.object, &message.message); } else if (type == ES_MSG_TIMER) { ((EsTimerCallbackFunction) message.message.user.context1.p)(message.message.user.context2); - } else if (type >= ES_MSG_WM_START && type <= ES_MSG_WM_END) { + } else if (type >= ES_MSG_WM_START && type <= ES_MSG_WM_END && message.object) { #if 0 ProcessMessageTiming timing = {}; double start = EsTimeStampMs(); @@ -802,11 +823,9 @@ EsMessage *EsMessageReceive() { timing.endLayout - timing.startLayout, timing.endPaint - timing.startPaint, timing.endUpdate - timing.startUpdate); +#else + UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr); #endif - - if (message.object) { - UIProcessWindowManagerMessage((EsWindow *) message.object, &message.message, nullptr); - } } else if (type == ES_MSG_TAB_INSPECT_UI) { EsInstance *_instance = InstanceFromWindowID(message.message.tabOperation.id); diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp index 984a68d..35083dd 100644 --- a/desktop/desktop.cpp +++ b/desktop/desktop.cpp @@ -2019,6 +2019,30 @@ void EmbeddedWindowDestroyed(EsObjectID id) { EsHeapFree(instance); } +int CursorLocatorMessage(EsElement *element, EsMessage *message) { + EsWindow *window = element->window; + + if (message->type == ES_MSG_ANIMATE) { + window->announcementTimeMs += message->animate.deltaMs; + double progress = window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration"); + + if (progress > 1) { + EsElementDestroy(window); + } else { + EsElementRelayout(element); + message->animate.complete = false; + return ES_HANDLED; + } + } else if (message->type == ES_MSG_LAYOUT) { + EsElement *child = element->GetChild(0); + double progress = 1.0 - window->announcementTimeMs / GetConstantNumber("cursorLocatorDuration"); + int width = progress * child->GetWidth(0), height = progress * child->GetHeight(0); + child->InternalMove(width, height, (element->width - width) / 2, (element->height - height) / 2); + } + + return 0; +} + void DesktopMessage(EsMessage *message) { if (message->type == ES_MSG_POWER_BUTTON_PRESSED) { ShutdownModalCreate(); @@ -2089,6 +2113,19 @@ void DesktopMessage(EsMessage *message) { } else { // The screen resolution will be correctly queried in DesktopSetup. } + } else if (message->type == ES_MSG_SINGLE_CTRL_PRESS) { + if (EsSystemConfigurationReadInteger(EsLiteral("general"), EsLiteral("locate_cursor_on_ctrl"))) { + EsPoint position = EsMouseGetPosition(); + EsWindow *window = EsWindowCreate(nullptr, ES_WINDOW_TIP); + EsElement *wrapper = EsCustomElementCreate(window, ES_CELL_FILL, ES_STYLE_CLEAR_BACKGROUND); + wrapper->messageUser = CursorLocatorMessage; + window->announcementBase = position; + EsElement *element = EsCustomElementCreate(wrapper, ES_CELL_FILL, ES_STYLE_CURSOR_LOCATOR); + int width = element->GetWidth(0), height = element->GetHeight(0); + EsRectangle bounds = ES_RECT_4PD(position.x - width / 2, position.y - height / 2, width, height); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ALWAYS_ON_TOP); + wrapper->StartAnimating(); + } } else if (message->type == MSG_SETUP_DESKTOP_UI) { DesktopSetup(); } diff --git a/desktop/gui.cpp b/desktop/gui.cpp index 012f475..07f5d24 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -449,7 +449,7 @@ struct EsWindow : EsElement { EsElement *source; // Menu source. EsWindow *targetMenu; // The menu that keyboard events should be sent to. - int32_t announcementBaseY; + EsPoint announcementBase; double announcementTimeMs; }; @@ -3582,7 +3582,6 @@ int AnnouncementMessage(EsElement *element, EsMessage *message) { double progress = window->announcementTimeMs / GetConstantNumber("announcementDuration"); if (progress > 1) { - progress = 1; EsElementDestroy(window); return 0; } @@ -3595,7 +3594,7 @@ int AnnouncementMessage(EsElement *element, EsMessage *message) { EsRectangle bounds = EsWindowGetBounds(window); int32_t height = Height(bounds); - bounds.t = window->announcementBaseY - inOnly * GetConstantNumber("announcementMovement"); + bounds.t = window->announcementBase.y - inOnly * GetConstantNumber("announcementMovement"); bounds.b = bounds.t + height; EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0xFF * inOut, 0, ES_WINDOW_PROPERTY_ALPHA); @@ -3619,11 +3618,12 @@ void EsAnnouncementShow(EsWindow *parent, uint64_t flags, int32_t x, int32_t y, int32_t width = display->GetWidth(0); int32_t height = display->GetHeight(width); - EsRectangle parentBounds = EsWindowGetBounds(parent); + EsRectangle parentBounds = {}; + if (parent) parentBounds = EsWindowGetBounds(parent); EsRectangle bounds = ES_RECT_4PD(x - width / 2 + parentBounds.l, y - height + parentBounds.t, width, height); EsSyscall(ES_SYSCALL_WINDOW_SET_PROPERTY, window->handle, 0x00, 0, ES_WINDOW_PROPERTY_ALPHA); EsSyscall(ES_SYSCALL_WINDOW_MOVE, window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN | ES_WINDOW_MOVE_ALWAYS_ON_TOP); - window->announcementBaseY = EsWindowGetBounds(window).t; + window->announcementBase.y = EsWindowGetBounds(window).t; window->StartAnimating(); } @@ -6003,11 +6003,14 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) { return; } + EsWindow *window = gui.accessKeys.window; + int ic, isc; ConvertScancodeToCharacter(message->keyboard.scancode, &ic, &isc, false, false); ic = EsCRTtoupper(ic); bool keepAccessKeyModeActive = false; + bool regatherKeys = false; if (ic >= 'A' && ic <= 'Z' && !gui.accessKeys.typedCharacter) { if (gui.accessKeys.numbers[ic - 'A'] > 1) { @@ -6023,6 +6026,7 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) { EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD); keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY; + regatherKeys = true; } } } @@ -6036,14 +6040,19 @@ void AccessKeyModeHandleKeyPress(EsMessage *message) { EsElementFocus(entry->element, ES_ELEMENT_FOCUS_ENSURE_VISIBLE | ES_ELEMENT_FOCUS_FROM_KEYBOARD); keepAccessKeyModeActive = entry->element->flags & ES_ELEMENT_STICKY_ACCESS_KEY; + regatherKeys = true; } } } if (!keepAccessKeyModeActive) { AccessKeyModeExit(); + } else if (regatherKeys) { + AccessKeyModeExit(); + window->InternalMove(window->width, window->height, 0, 0); + AccessKeyModeEnter(window); } else { - gui.accessKeys.window->Repaint(true); + window->Repaint(true); } } diff --git a/desktop/os.header b/desktop/os.header index 99dd605..91ee8d6 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -954,6 +954,7 @@ enum EsMessageType { ES_MSG_DESKTOP = 0x4806 ES_MSG_DEVICE_CONNECTED = 0x4807 ES_MSG_DEVICE_DISCONNECTED = 0x4808 + ES_MSG_SINGLE_CTRL_PRESS = 0x4809 // Messages sent from Desktop to application instances: ES_MSG_TAB_INSPECT_UI = 0x4A01 diff --git a/desktop/settings.cpp b/desktop/settings.cpp index 2bd635d..443d954 100644 --- a/desktop/settings.cpp +++ b/desktop/settings.cpp @@ -121,8 +121,8 @@ void SettingsNumberBoxSetValue(EsElement *element, int32_t newValue) { EsTextboxInsert(textbox, buffer, bytes); EsMutexAcquire(&api.systemConfigurationMutex); - EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1); // TODO Create if needed. - EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1); // TODO Create if needed. + EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1, true); + EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1, true); int32_t oldValue = EsIntegerParse(item->value, item->valueBytes); EsHeapFree(item->value); item->value = (char *) EsHeapAllocate(65, true); @@ -162,7 +162,7 @@ void SettingsAddTitle(EsElement *container, SettingsPage *page) { void SettingsAddUndoButton(EsElement *stack) { EsPanel *overlay = EsPanelCreate(stack, ES_CELL_H_RIGHT | ES_CELL_V_TOP, &styleSettingsOverlayPanel); - EsButton *undoButton = EsButtonCreate(overlay, ES_BUTTON_TOOLBAR, 0, INTERFACE_STRING(DesktopSettingsUndoButton)); + EsButton *undoButton = EsButtonCreate(overlay, ES_BUTTON_TOOLBAR | ES_ELEMENT_STICKY_ACCESS_KEY, 0, INTERFACE_STRING(DesktopSettingsUndoButton)); undoButton->accessKey = 'U'; ((SettingsInstance *) stack->instance)->undoButton = undoButton; EsButtonSetIcon(undoButton, ES_ICON_EDIT_UNDO_SYMBOLIC); @@ -184,8 +184,8 @@ void SettingsCheckboxCommand(EsInstance *_instance, EsElement *element, EsComman bool newValue = EsButtonGetCheck(button) == ES_CHECK_CHECKED; EsMutexAcquire(&api.systemConfigurationMutex); - EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1); // TODO Create if needed. - EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1); // TODO Create if needed. + EsSystemConfigurationGroup *group = SystemConfigurationGetGroup(control->cConfigurationSection, -1, true); + EsSystemConfigurationItem *item = SystemConfigurationGetItem(group, control->cConfigurationKey, -1, true); bool oldValue = EsIntegerParse(item->value, item->valueBytes); EsHeapFree(item->value); item->value = (char *) EsHeapAllocate(2, true); @@ -209,7 +209,7 @@ void SettingsAddCheckbox(EsElement *table, const char *string, ptrdiff_t stringB control->globalPointerBool = globalPointerBool; control->originalValueBool = EsSystemConfigurationReadInteger(control->cConfigurationSection, -1, control->cConfigurationKey, -1); - EsButton *button = EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX | ES_ELEMENT_FREE_USER_DATA, 0, string, stringBytes); + EsButton *button = EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX | ES_ELEMENT_FREE_USER_DATA | ES_ELEMENT_STICKY_ACCESS_KEY, 0, string, stringBytes); button->userData = control; button->accessKey = accessKey; if (control->originalValueBool) EsButtonSetCheck(button, ES_CHECK_CHECKED, false); @@ -327,8 +327,10 @@ void SettingsPageMouse(EsElement *element, SettingsPage *page) { table = EsPanelCreate(container, ES_CELL_H_FILL, &styleSettingsCheckboxGroup); SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseSwapLeftAndRightButtons), 'B', "general", "swap_left_and_right_buttons", &api.global->swapLeftAndRightButtons); - EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX, 0, INTERFACE_STRING(DesktopSettingsMouseShowShadow))->accessKey = 'W'; // TODO. - EsButtonCreate(table, ES_CELL_H_FILL | ES_BUTTON_CHECKBOX, 0, INTERFACE_STRING(DesktopSettingsMouseLocateCursorOnCtrl))->accessKey = 'L'; // TODO. + SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseShowShadow), 'W', + "general", "show_cursor_shadow", nullptr); // TODO. + SettingsAddCheckbox(table, INTERFACE_STRING(DesktopSettingsMouseLocateCursorOnCtrl), 'L', + "general", "locate_cursor_on_ctrl", nullptr); EsSpacerCreate(container, ES_CELL_H_FILL, ES_STYLE_BUTTON_GROUP_SEPARATOR); @@ -407,7 +409,7 @@ void SettingsButtonPressed(EsInstance *_instance, EsElement *element, EsCommand { EsPanel *overlay = EsPanelCreate(stack, ES_CELL_H_LEFT | ES_CELL_V_TOP, &styleSettingsOverlayPanel); - EsButton *backButton = EsButtonCreate(overlay, ES_CELL_H_LEFT | ES_BUTTON_TOOLBAR, 0, INTERFACE_STRING(DesktopSettingsBackButton)); + EsButton *backButton = EsButtonCreate(overlay, ES_CELL_H_LEFT | ES_BUTTON_TOOLBAR | ES_ELEMENT_STICKY_ACCESS_KEY, 0, INTERFACE_STRING(DesktopSettingsBackButton)); backButton->accessKey = 'A'; EsButtonSetIcon(backButton, ES_ICON_GO_HOME_SYMBOLIC); EsButtonOnCommand(backButton, SettingsBackButton); @@ -443,7 +445,7 @@ void InstanceSettingsCreate(EsMessage *message) { }, nullptr); for (uintptr_t i = 0; i < sizeof(settingsPages) / sizeof(settingsPages[0]); i++) { - EsButton *button = EsButtonCreate(container, ES_ELEMENT_NO_FOCUS_ON_CLICK | ES_CELL_H_FILL, + EsButton *button = EsButtonCreate(container, ES_ELEMENT_NO_FOCUS_ON_CLICK | ES_CELL_H_FILL | ES_ELEMENT_STICKY_ACCESS_KEY, &styleSettingsButton, settingsPages[i].string, settingsPages[i].stringBytes); button->userData = &settingsPages[i]; button->accessKey = settingsPages[i].accessKey; diff --git a/desktop/styles.header b/desktop/styles.header index 7445a6f..98d21d2 100644 --- a/desktop/styles.header +++ b/desktop/styles.header @@ -10,6 +10,7 @@ define ES_STYLE_BUTTON_GROUP_SEPARATOR (ES_STYLE_CAST(1233)) define_private ES_STYLE_CANVAS_SHADOW (ES_STYLE_CAST(1451)) define_private ES_STYLE_CHECKBOX_NORMAL (ES_STYLE_CAST(1559)) define_private ES_STYLE_CHECKBOX_RADIOBOX (ES_STYLE_CAST(1567)) +define ES_STYLE_CLEAR_BACKGROUND (ES_STYLE_CAST(1597)) define_private ES_STYLE_COLOR_CHOSEN_POINT (ES_STYLE_CAST(1241)) define_private ES_STYLE_COLOR_CIRCLE (ES_STYLE_CAST(1243)) define_private ES_STYLE_COLOR_HEX_TEXTBOX (ES_STYLE_CAST(1245)) @@ -17,6 +18,7 @@ define_private ES_STYLE_COLOR_PICKER_MAIN_PANEL (ES_STYLE_CAST(1247)) define_private ES_STYLE_COLOR_SLIDER (ES_STYLE_CAST(1249)) define_private ES_STYLE_CONTAINER_WINDOW_ACTIVE (ES_STYLE_CAST(1251)) define_private ES_STYLE_CONTAINER_WINDOW_INACTIVE (ES_STYLE_CAST(1255)) +define_private ES_STYLE_CURSOR_LOCATOR (ES_STYLE_CAST(1591)) define ES_STYLE_DIALOG_BUTTON_AREA (ES_STYLE_CAST(1259)) define ES_STYLE_DIALOG_CONTENT (ES_STYLE_CAST(1261)) define ES_STYLE_DIALOG_HEADING (ES_STYLE_CAST(1263)) diff --git a/kernel/windows.cpp b/kernel/windows.cpp index 2c944be..14db25a 100644 --- a/kernel/windows.cpp +++ b/kernel/windows.cpp @@ -123,6 +123,7 @@ struct WindowManager { // Miscellaneous: EsRectangle workArea; + bool inSingleCtrlPress; // Game controllers: @@ -181,8 +182,6 @@ void SendMessageToWindow(Window *window, EsMessage *message) { return; } - window->lastEmbedKeyboardMessage.type = ES_MSG_INVALID; - if (message->type == ES_MSG_WINDOW_RESIZED) { message->windowResized.content = ES_RECT_4(0, window->width, 0, window->height); window->owner->messageQueue.SendMessage(window->apiWindow, message); @@ -309,6 +308,17 @@ void WindowManager::PressKey(unsigned scancode) { KernelPanic("WindowManager::PressKey - Panic key pressed.\n"); } + if (scancode == ES_SCANCODE_LEFT_CTRL) { + inSingleCtrlPress = true; + } else if (scancode == (ES_SCANCODE_LEFT_CTRL | K_SCANCODE_KEY_RELEASED) && inSingleCtrlPress) { + EsMessage m; + EsMemoryZero(&m, sizeof(EsMessage)); + m.type = ES_MSG_SINGLE_CTRL_PRESS; + desktopProcess->messageQueue.SendMessage(nullptr, &m); + } else { + inSingleCtrlPress = false; + } + if (eyedropping) { if (scancode == (ES_SCANCODE_ESCAPE | K_SCANCODE_KEY_RELEASED)) { EndEyedrop(true); diff --git a/res/System Configuration Template.ini b/res/System Configuration Template.ini index c037a3c..5569e13 100644 --- a/res/System Configuration Template.ini +++ b/res/System Configuration Template.ini @@ -8,7 +8,6 @@ settings_path=0:/Settings default_user_documents_path=0:/ installation_state=0 click_chain_timeout_ms=500 -swap_left_and_right_buttons=0 [ui] ; User interface settings that are accessible by all applications. diff --git a/res/Theme Source.dat b/res/Theme Source.dat index 1e845e5caa2ad61b2d7f96b07251f9bb8bdb81ed..8a5759d03cba2e3bc5bb019509c5ac611fbea3ab 100644 GIT binary patch delta 250 zcmX@o$lTJ-%r45nz#!jNwvj!QQKAXRXzh2-Nlh$rN=(j9FUl{?OOb11oE*(~UPPu% zwyoE>w5T}0$R|HJu_V7px^1&MQ#Th2ZxdtVWJew8$p;)*H(%qiSj@~Q(*_h!n4A$T zGdX&yu&!)dLz{DEQF2Zy&^91QVMu9U1k!9kkOBlv)*w*`sW5q?iOA&ix$=`4L&YZ_ z;ANdOTVeA2nRA%r+a?#rnM@9-WCNKA6q?OAYcl@^Kk2E-$gXlJElMoO%+G5!GB9A+ a9J66AFJs>3Uwiu*nJaQ%Z|>P|!3Y387gRI= delta 72 zcmV-O0Jr~yhXcfc0|ylV000_>bg>6j0h4tB&a+wqhzbM<f&qb(aV#5?@IVE#1`Rl) e0hyDtvQD!dvz!e9aI?C=j{yUAZQHX<!Z-m^BN@g3 diff --git a/res/Themes/Theme.dat b/res/Themes/Theme.dat index 42b250fcc749437cf1ef63bf5f97a7cece1be43c..09dcfb62735a54fda620fa1ef512073e14c3959f 100644 GIT binary patch delta 4713 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 delta 4561 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 KtDTk6ul^sag2aIU