From eb18fd7e21b8680b58c9f9720f4a92f0a28a318b Mon Sep 17 00:00:00 2001 From: nakst <> Date: Fri, 10 Sep 2021 12:07:08 +0100 Subject: [PATCH] drag off tabs --- desktop/desktop.cpp | 55 +++++++++++++++++-- desktop/gui.cpp | 127 ++++++++++++++++++++++--------------------- desktop/os.header | 1 + kernel/syscall.cpp | 19 +++++++ res/Theme Source.dat | Bin 52257 -> 52282 bytes res/Themes/Theme.dat | Bin 53608 -> 53636 bytes 6 files changed, 137 insertions(+), 65 deletions(-) diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp index 2b05875..91b99a1 100644 --- a/desktop/desktop.cpp +++ b/desktop/desktop.cpp @@ -464,9 +464,10 @@ void WindowTabDestroy(WindowTab *tab) { } } -void WindowTabMoveToNewContainer(WindowTab *tab) { +WindowTab *WindowTabMoveToNewContainer(WindowTab *tab) { // Create the new tab and container window. WindowTab *newTab = WindowTabCreate(ContainerWindowCreate()); + if (!newTab) return nullptr; // Move ownership of the instance to the new tab. newTab->applicationInstance = tab->applicationInstance; @@ -479,6 +480,7 @@ void WindowTabMoveToNewContainer(WindowTab *tab) { // Destroy the old tab, and activate the new one. WindowTabDestroy(tab); // Deplaces the embedded window from the old container. WindowTabActivate(newTab); + return newTab; } int ContainerWindowMessage(EsElement *element, EsMessage *message) { @@ -582,14 +584,59 @@ int WindowTabMessage(EsElement *element, EsMessage *message) { message->animate.complete = ReorderItemAnimate(tab, message->animate.deltaMs, "windowTabEntranceDuration"); } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { } else if (message->type == ES_MSG_MOUSE_LEFT_DRAG) { + EsElementSetDisabled(band->GetChild(0), true); + if (band->items.Length() == 1) { + // TODO Dragging into other containers. EsPoint screenPosition = EsMouseGetPosition(); WindowChangeBounds(RESIZE_MOVE, screenPosition.x, screenPosition.y, &gui.lastClickX, &gui.lastClickY, band->window); } else { - ReorderItemDragged(tab, message->mouseDragged.newPositionX); - } + EsRectangle tabBarBounds = tab->parent->GetWindowBounds(); + EsRectangle tabBounds = tab->GetWindowBounds(); + int32_t mouseX = message->mouseDragged.newPositionX + tabBounds.l; + int32_t mouseY = message->mouseDragged.newPositionY + tabBounds.t; + int32_t dragOffThreshold = GetConstantNumber("tabDragOffThreshold"); - EsElementSetDisabled(band->GetChild(0), true); + if (EsRectangleContains(EsRectangleAdd(tabBarBounds, ES_RECT_1I(-dragOffThreshold)), mouseX, mouseY)) { + ReorderItemDragged(tab, message->mouseDragged.newPositionX); + } else { + // Save information about the old container. + int32_t oldTabDragX = mouseX - tab->dragOffset; + int32_t oldTabDragY = mouseY - (gui.lastClickY - tab->offsetY); + EsRectangle oldContainerBounds = tab->window->GetScreenBounds(); + EsRectangle oldTabBarScreenBounds = tab->parent->GetScreenBounds(); + EsWindow *oldContainer = tab->window; + EsPoint mousePosition = EsMouseGetPosition(oldContainer); + + // End the drag on this container. + EsMessage m = { .type = ES_MSG_MOUSE_LEFT_UP }; + UIMouseUp(oldContainer, &m, false); + + // Move the tab to a new container. + WindowTab *newTab = WindowTabMoveToNewContainer(tab); + + if (newTab) { + // Work out the position of the new container, so that the mouse position within the tab is preserved. + newTab->window->width = Width(oldContainerBounds); + newTab->window->height = Height(oldContainerBounds); + UIWindowLayoutNow(newTab->window, nullptr); + EsRectangle newTabWindowBounds = newTab->GetWindowBounds(); + EsRectangle bounds = ES_RECT_4PD(oldTabBarScreenBounds.l + oldTabDragX - newTabWindowBounds.l, + oldTabBarScreenBounds.t + oldTabDragY - newTabWindowBounds.t, + Width(oldContainerBounds), Height(oldContainerBounds)); + EsSyscall(ES_SYSCALL_WINDOW_MOVE, newTab->window->handle, (uintptr_t) &bounds, 0, ES_WINDOW_MOVE_DYNAMIC); + + // Start the drag on the new container. + EsSyscall(ES_SYSCALL_WINDOW_TRANSFER_PRESS, oldContainer->handle, newTab->window->handle, 0, 0); + newTab->window->pressed = newTab; + newTab->window->dragged = newTab; + gui.mouseButtonDown = true; + gui.draggingStarted = true; + gui.lastClickX = mousePosition.x + oldContainerBounds.l - bounds.l; + gui.lastClickY = mousePosition.y + oldContainerBounds.t - bounds.t; + } + } + } } else if (message->type == ES_MSG_MOUSE_LEFT_UP) { ReorderItemDragComplete(tab); EsElementSetDisabled(band->GetChild(0), false); diff --git a/desktop/gui.cpp b/desktop/gui.cpp index 40a6b6f..2544684 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -79,7 +79,7 @@ EsElement *WindowGetMainPanel(EsWindow *window); int AccessKeyLayerMessage(EsElement *element, EsMessage *message); void AccessKeyModeExit(); int ProcessButtonMessage(EsElement *element, EsMessage *message); -void UIMousePressReleased(EsWindow *window, EsMessage *message, bool sendClick); +void UIMouseUp(EsWindow *window, EsMessage *message, bool sendClick); void UIMaybeRemoveFocusedElement(EsWindow *window); EsTextStyle TextPlanGetPrimaryStyle(EsTextPlan *plan); EsElement *UIFindHoverElementRecursively(EsElement *element, int offsetX, int offsetY, EsPoint position); @@ -860,7 +860,7 @@ EsWindow *EsWindowCreate(EsInstance *instance, EsWindowStyle style) { EsMessageMutexCheck(); for (uintptr_t i = 0; i < gui.allWindows.Length(); i++) { - UIMousePressReleased(gui.allWindows[i], nullptr, false); + UIMouseUp(gui.allWindows[i], nullptr, false); } EsWindow *window = (EsWindow *) EsHeapAllocate(sizeof(EsWindow), true); @@ -6063,7 +6063,67 @@ int UIMessageSendPropagateToAncestors(EsElement *element, EsMessage *message, Es return 0; } -void UIMousePressReleased(EsWindow *window, EsMessage *message, bool sendClick) { +void UIMouseDown(EsWindow *window, EsMessage *message) { + window->mousePosition.x = message->mouseDown.positionX; + window->mousePosition.y = message->mouseDown.positionY; + + AccessKeyModeExit(); + + double timeStampMs = EsTimeStampMs(); + + if (gui.clickChainStartMs + api.global->clickChainTimeoutMs < timeStampMs + || window->hovered != gui.clickChainElement) { + // Start a new click chain. + gui.clickChainStartMs = timeStampMs; + gui.clickChainCount = 1; + gui.clickChainElement = window->hovered; + } else { + gui.clickChainStartMs = timeStampMs; + gui.clickChainCount++; + } + + message->mouseDown.clickChainCount = gui.clickChainCount; + + gui.lastClickX = message->mouseDown.positionX; + gui.lastClickY = message->mouseDown.positionY; + gui.lastClickButton = message->type; + gui.mouseButtonDown = true; + + if ((~window->hovered->flags & ES_ELEMENT_DISABLED) && (~window->hovered->state & UI_STATE_BLOCK_INTERACTION)) { + // If the hovered element is destroyed in response to one of these messages, + // window->hovered will be set to nullptr, so save the element here. + EsElement *element = window->hovered; + + if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { + element->state |= UI_STATE_LEFT_PRESSED; + } + + window->pressed = element; + EsMessage m = { ES_MSG_PRESSED_START }; + EsMessageSend(element, &m); + + EsRectangle bounds = element->GetWindowBounds(); + message->mouseDown.positionX -= bounds.l; + message->mouseDown.positionY -= bounds.t; + + if (ES_REJECTED != UIMessageSendPropagateToAncestors(element, message, &window->dragged)) { + if (window->dragged && (~window->dragged->flags & ES_ELEMENT_NO_FOCUS_ON_CLICK)) { + EsElementFocus(window->dragged, false); + } + } + } + + if (window->hovered != window->focused && window->focused && (~window->focused->state & UI_STATE_LOST_STRONG_FOCUS)) { + EsMessage m = { ES_MSG_STRONG_FOCUS_END }; + window->focused->state |= UI_STATE_LOST_STRONG_FOCUS; + EsMessageSend(window->focused, &m); + } +} + +void UIMouseUp(EsWindow *window, EsMessage *message, bool sendClick) { + gui.mouseButtonDown = false; + window->dragged = nullptr; + if (window->pressed) { EsElement *pressed = window->pressed; window->pressed = nullptr; @@ -6447,7 +6507,7 @@ void UIHandleKeyMessage(EsWindow *window, EsMessage *message) { if (window->pressed) { if (message->keyboard.scancode == ES_SCANCODE_ESCAPE) { - UIMousePressReleased(window, nullptr, false); + UIMouseUp(window, nullptr, false); return; } } @@ -6787,71 +6847,16 @@ void UIProcessWindowManagerMessage(EsWindow *window, EsMessage *message, Process } else if (message->type == ES_MSG_MOUSE_EXIT) { window->hovering = false; } else if (message->type == ES_MSG_MOUSE_LEFT_DOWN || message->type == ES_MSG_MOUSE_RIGHT_DOWN || message->type == ES_MSG_MOUSE_MIDDLE_DOWN) { - window->mousePosition.x = message->mouseDown.positionX; - window->mousePosition.y = message->mouseDown.positionY; - - AccessKeyModeExit(); - if (gui.mouseButtonDown || window->targetMenu) { goto skipInputMessage; } - double timeStampMs = EsTimeStampMs(); - - if (gui.clickChainStartMs + api.global->clickChainTimeoutMs < timeStampMs - || window->hovered != gui.clickChainElement) { - // Start a new click chain. - gui.clickChainStartMs = timeStampMs; - gui.clickChainCount = 1; - gui.clickChainElement = window->hovered; - } else { - gui.clickChainStartMs = timeStampMs; - gui.clickChainCount++; - } - - message->mouseDown.clickChainCount = gui.clickChainCount; - - gui.lastClickX = message->mouseDown.positionX; - gui.lastClickY = message->mouseDown.positionY; - gui.lastClickButton = message->type; - gui.mouseButtonDown = true; - - if ((~window->hovered->flags & ES_ELEMENT_DISABLED) && (~window->hovered->state & UI_STATE_BLOCK_INTERACTION)) { - // If the hovered element is destroyed in response to one of these messages, - // window->hovered will be set to nullptr, so save the element here. - EsElement *element = window->hovered; - - if (message->type == ES_MSG_MOUSE_LEFT_DOWN) { - element->state |= UI_STATE_LEFT_PRESSED; - } - - window->pressed = element; - EsMessage m = { ES_MSG_PRESSED_START }; - EsMessageSend(element, &m); - - EsRectangle bounds = element->GetWindowBounds(); - message->mouseDown.positionX -= bounds.l; - message->mouseDown.positionY -= bounds.t; - - if (ES_REJECTED != UIMessageSendPropagateToAncestors(element, message, &window->dragged)) { - if (window->dragged && (~window->dragged->flags & ES_ELEMENT_NO_FOCUS_ON_CLICK)) { - EsElementFocus(window->dragged, false); - } - } - } - - if (window->hovered != window->focused && window->focused && (~window->focused->state & UI_STATE_LOST_STRONG_FOCUS)) { - EsMessage m = { ES_MSG_STRONG_FOCUS_END }; - window->focused->state |= UI_STATE_LOST_STRONG_FOCUS; - EsMessageSend(window->focused, &m); - } + UIMouseDown(window, message); } else if (message->type == ES_MSG_MOUSE_LEFT_UP || message->type == ES_MSG_MOUSE_RIGHT_UP || message->type == ES_MSG_MOUSE_MIDDLE_UP) { AccessKeyModeExit(); if (gui.mouseButtonDown && gui.lastClickButton == message->type - 1) { - gui.mouseButtonDown = false; - window->dragged = nullptr; - UIMousePressReleased(window, message, true); + UIMouseUp(window, message, true); } } else if (message->type == ES_MSG_KEY_UP || message->type == ES_MSG_KEY_DOWN) { UIHandleKeyMessage(window, message); diff --git a/desktop/os.header b/desktop/os.header index 01d78fc..8076297 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -807,6 +807,7 @@ enum EsSyscallType { ES_SYSCALL_WINDOW_CLOSE ES_SYSCALL_WINDOW_REDRAW ES_SYSCALL_WINDOW_MOVE + ES_SYSCALL_WINDOW_TRANSFER_PRESS ES_SYSCALL_WINDOW_GET_ID ES_SYSCALL_WINDOW_GET_BOUNDS ES_SYSCALL_WINDOW_GET_EMBED_KEYBOARD diff --git a/kernel/syscall.cpp b/kernel/syscall.cpp index 28c14de..e2f0d5f 100644 --- a/kernel/syscall.cpp +++ b/kernel/syscall.cpp @@ -1132,6 +1132,25 @@ SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_MOVE) { SYSCALL_RETURN(success ? ES_SUCCESS : ES_ERROR_INVALID_DIMENSIONS, false); } +SYSCALL_IMPLEMENT(ES_SYSCALL_WINDOW_TRANSFER_PRESS) { + KObject _oldWindow(currentProcess, argument0, KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_oldWindow); + Window *oldWindow = (Window *) _oldWindow.object; + KObject _newWindow(currentProcess, argument1, KERNEL_OBJECT_WINDOW); + CHECK_OBJECT(_newWindow); + Window *newWindow = (Window *) _newWindow.object; + + KMutexAcquire(&windowManager.mutex); + + if (windowManager.pressedWindow == oldWindow) { + windowManager.pressedWindow = newWindow; + } + + KMutexRelease(&windowManager.mutex); + + SYSCALL_RETURN(ES_SUCCESS, false); +} + SYSCALL_IMPLEMENT(ES_SYSCALL_CURSOR_POSITION_GET) { EsPoint point = ES_POINT(windowManager.cursorX, windowManager.cursorY); SYSCALL_WRITE(argument0, &point, sizeof(EsPoint)); diff --git a/res/Theme Source.dat b/res/Theme Source.dat index 55aca0761d478a75403d261bef5b27b80be2a194..d000f4a8057004a0f3166467a044a15842203324 100644 GIT binary patch delta 45 zcmV+|0Mh@Vm;<_)1F-J80iLt(x;6n9lXPKXL~>zgPiAIRXmVw9Xm4y}f-yC-BfS0( D*wz!u delta 21 dcmdlrgL&Z$<_+(5G0xrmewP{JX1hIq_yKqV3ex}p diff --git a/res/Themes/Theme.dat b/res/Themes/Theme.dat index def7e345959418c261f0fa7d94f3ef0059600317..2e8c17eaf0ea5a5d0edb6a5967ac8a212eab40a8 100644 GIT binary patch delta 4301 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 delta 4254 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 TI5on?W+6LJyL$h~`I7$uvxJ`c