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