mirror of https://gitlab.com/nakst/essence
1431 lines
45 KiB
C++
1431 lines
45 KiB
C++
// This file is part of the Essence operating system.
|
|
// It is released under the terms of the MIT license -- see LICENSE.md.
|
|
// Written by: nakst.
|
|
|
|
#ifndef IMPLEMENTATION
|
|
|
|
// TODO Don't send key released messages if the focused window has changed.
|
|
// TODO Blur clamping is incorrect with minimal repainting!
|
|
// TODO Cursor trails.
|
|
|
|
// Terminology:
|
|
// Dynamic resize - flicker-free resizing in container windows with an embedded window owned by a separate process.
|
|
// Direct update - paint first onto the video card's framebuffer, then onto the window manager's; used to reduce latency.
|
|
|
|
struct EmbeddedWindow {
|
|
void Destroy();
|
|
void Close();
|
|
void SetEmbedOwner(Process *process);
|
|
|
|
Process *volatile owner;
|
|
void *volatile apiWindow;
|
|
volatile uint32_t handles;
|
|
struct Window *container;
|
|
EsObjectID id;
|
|
bool closed;
|
|
};
|
|
|
|
struct Window {
|
|
void Update(EsRectangle *region, bool addToModifiedRegion);
|
|
bool UpdateDirect(K_USER_BUFFER void *bits, uintptr_t stride, EsRectangle region);
|
|
void Destroy();
|
|
void Close();
|
|
bool Move(EsRectangle newBounds, uint32_t flags);
|
|
void SetEmbed(EmbeddedWindow *window);
|
|
bool IsVisible();
|
|
void ResizeEmbed(); // Send a message to the embedded window telling it to resize.
|
|
|
|
// State:
|
|
EsWindowStyle style;
|
|
EsRectangle solidOffsets, embedInsets;
|
|
bool solid, noClickActivate, hidden, isMaximised, alwaysOnTop, hoveringOverEmbed, activationClick, noBringToFront;
|
|
volatile bool closed;
|
|
|
|
// Appearance:
|
|
Surface surface;
|
|
EsRectangle opaqueBounds, blurBounds;
|
|
uint8_t alpha, material;
|
|
|
|
// Owner and children:
|
|
Process *owner;
|
|
void *apiWindow;
|
|
EmbeddedWindow *embed;
|
|
volatile uint32_t handles;
|
|
EsObjectID id;
|
|
|
|
// Location:
|
|
EsPoint position;
|
|
size_t width, height;
|
|
};
|
|
|
|
struct WindowManager {
|
|
void *CreateWindow(Process *process, void *apiWindow, EsWindowStyle style);
|
|
void *CreateEmbeddedWindow(Process *process, void *apiWindow);
|
|
Window *FindWindowAtPosition(int cursorX, int cursorY, EsObjectID exclude = 0);
|
|
|
|
void Initialise();
|
|
|
|
void MoveCursor(int64_t xMovement, int64_t yMovement);
|
|
void ClickCursor(uint32_t buttons);
|
|
void ScrollWheel(int32_t dx, int32_t dy);
|
|
void PressKey(uint32_t scancode);
|
|
|
|
void Redraw(EsPoint position, int width, int height, Window *except = nullptr, int startingAt = 0, bool addToModifiedRegion = true);
|
|
|
|
bool ActivateWindow(Window *window); // Returns true if any menus were closed.
|
|
void HideWindow(Window *window);
|
|
Window *FindWindowToActivate(Window *excluding = nullptr);
|
|
uintptr_t GetActivationZIndex();
|
|
void ChangeWindowDepth(Window *window, bool alwaysRedraw, ptrdiff_t newZDepth);
|
|
intptr_t FindWindowDepth(Window *window);
|
|
bool CloseMenus(); // Returns true if any menus were closed.
|
|
|
|
void StartEyedrop(uintptr_t object, Window *avoid, uint32_t cancelColor);
|
|
void EndEyedrop(bool cancelled);
|
|
|
|
bool initialised;
|
|
|
|
// Windows:
|
|
|
|
Array<Window *, K_FIXED> windows; // Sorted by z.
|
|
Array<EmbeddedWindow *, K_FIXED> embeddedWindows;
|
|
Window *pressedWindow, *activeWindow, *hoverWindow;
|
|
KMutex mutex;
|
|
KEvent windowsToCloseEvent;
|
|
EsObjectID currentWindowID;
|
|
size_t inspectorWindowCount;
|
|
EsMessageType pressedWindowButton;
|
|
|
|
// Cursor:
|
|
|
|
int32_t cursorX, cursorY;
|
|
int32_t cursorXPrecise, cursorYPrecise; // Scaled up by a factor of K_CURSOR_MOVEMENT_SCALE.
|
|
uint32_t lastButtons;
|
|
|
|
Surface cursorSurface, cursorSwap, cursorTemporary;
|
|
int cursorImageOffsetX, cursorImageOffsetY;
|
|
uintptr_t cursorID;
|
|
bool cursorShadow;
|
|
bool changedCursorImage;
|
|
|
|
uint32_t cursorProperties;
|
|
|
|
// Keyboard:
|
|
|
|
bool numlock;
|
|
uint8_t leftModifiers, rightModifiers;
|
|
uint16_t keysHeld, maximumKeysHeld /* cleared when all released */;
|
|
uint8_t keysHeldBitSet[512 / 8];
|
|
|
|
// Eyedropper:
|
|
|
|
uintptr_t eyedropObject;
|
|
bool eyedropping;
|
|
Process *eyedropProcess;
|
|
uint64_t eyedropAvoidID;
|
|
uint32_t eyedropCancelColor;
|
|
|
|
// Miscellaneous:
|
|
|
|
EsRectangle workArea;
|
|
|
|
// Devices:
|
|
|
|
KMutex deviceMutex;
|
|
|
|
Array<KDevice *, K_FIXED> hiDevices;
|
|
|
|
EsGameControllerState gameControllers[ES_GAME_CONTROLLER_MAX_COUNT];
|
|
size_t gameControllerCount;
|
|
EsObjectID gameControllerID;
|
|
|
|
// Flicker-free resizing:
|
|
|
|
#define RESIZE_FLICKER_TIMEOUT_MS (40)
|
|
#define RESIZE_SLOW_THRESHOLD (RESIZE_FLICKER_TIMEOUT_MS * 3 / 4)
|
|
Window *resizeWindow;
|
|
bool resizeReceivedBitsFromContainer;
|
|
bool resizeReceivedBitsFromEmbed;
|
|
uint64_t resizeStartTimeStampMs;
|
|
EsRectangle resizeQueuedRectangle;
|
|
bool resizeQueued;
|
|
bool resizeSlow; // Set if the previous resize went past RESIZE_FLICKER_SLOW_THRESHOLD;
|
|
// when set, the old surface bits are copied on resize, so that if the resize times out the result will be reasonable.
|
|
};
|
|
|
|
WindowManager windowManager;
|
|
|
|
#else
|
|
|
|
void HIDeviceUpdateIndicators() {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
|
|
// TODO Other indicators.
|
|
uint32_t indicators = windowManager.numlock ? K_KEYBOARD_INDICATOR_NUM_LOCK : 0;
|
|
|
|
for (uintptr_t i = 0; i < windowManager.hiDevices.Length(); i++) {
|
|
KHIDevice *device = (KHIDevice *) windowManager.hiDevices[i]->parent;
|
|
|
|
if (device->setKeyboardIndicators) {
|
|
device->setKeyboardIndicators(device, indicators);
|
|
}
|
|
}
|
|
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
}
|
|
|
|
void HIDeviceRemoved(KDevice *device) {
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
windowManager.hiDevices.FindAndDeleteSwap(device, true);
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
}
|
|
|
|
void KRegisterHIDevice(KHIDevice *device) {
|
|
KDevice *child = KDeviceCreate("HID child", device, sizeof(KDevice));
|
|
|
|
if (child) {
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
child->removed = HIDeviceRemoved;
|
|
windowManager.hiDevices.Add(child);
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
KDeviceCloseHandle(child);
|
|
}
|
|
|
|
KDeviceCloseHandle(device);
|
|
}
|
|
|
|
bool Window::IsVisible() {
|
|
return !hidden && !closed && (id != windowManager.eyedropAvoidID || !windowManager.eyedropping);
|
|
}
|
|
|
|
void SendMessageToWindow(Window *window, EsMessage *message) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
if (window->closed) {
|
|
return;
|
|
}
|
|
|
|
if (!window->owner->handles) {
|
|
KernelPanic("SendMessageToWindow - (%x:%d/%x:%d) No handles.\n", window, window->handles, window->owner, window->owner->handles);
|
|
}
|
|
|
|
if (window->style != ES_WINDOW_CONTAINER || !window->embed) {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
return;
|
|
}
|
|
|
|
if (message->type == ES_MSG_WINDOW_RESIZED) {
|
|
message->windowResized.content = ES_RECT_2S(window->width, window->height);
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
message->windowResized.content = ES_RECT_2S(window->width - window->embedInsets.l - window->embedInsets.r,
|
|
window->height - window->embedInsets.t - window->embedInsets.b);
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
} else if (message->type == ES_MSG_WINDOW_DEACTIVATED || message->type == ES_MSG_WINDOW_ACTIVATED) {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
} else if (message->type == ES_MSG_MOUSE_MOVED) {
|
|
EsRectangle embedRegion = ES_RECT_4(window->embedInsets.l, window->width - window->embedInsets.r,
|
|
window->embedInsets.t, window->height - window->embedInsets.b);
|
|
bool inEmbed = windowManager.pressedWindow ? window->hoveringOverEmbed
|
|
: EsRectangleContains(embedRegion, message->mouseMoved.newPositionX, message->mouseMoved.newPositionY);
|
|
|
|
if (inEmbed) {
|
|
message->mouseMoved.newPositionX -= window->embedInsets.l;
|
|
message->mouseMoved.newPositionY -= window->embedInsets.t;
|
|
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
|
|
if (!windowManager.pressedWindow && !window->hoveringOverEmbed) {
|
|
message->type = ES_MSG_MOUSE_EXIT;
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
}
|
|
} else {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
|
|
if (!windowManager.pressedWindow && window->hoveringOverEmbed) {
|
|
message->type = ES_MSG_MOUSE_EXIT;
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
}
|
|
}
|
|
|
|
if (!windowManager.pressedWindow) {
|
|
window->hoveringOverEmbed = inEmbed;
|
|
}
|
|
} else if (message->type >= ES_MSG_MOUSE_LEFT_DOWN && message->type <= ES_MSG_MOUSE_MIDDLE_UP) {
|
|
if (window->hoveringOverEmbed) {
|
|
message->mouseDown.positionX -= window->embedInsets.l;
|
|
message->mouseDown.positionY -= window->embedInsets.t;
|
|
|
|
if (!window->activationClick) {
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
}
|
|
} else {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
}
|
|
} else if (message->type == ES_MSG_SCROLL_WHEEL) {
|
|
if (window->hoveringOverEmbed) {
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
} else {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
}
|
|
} else if (message->type == ES_MSG_KEY_DOWN || message->type == ES_MSG_KEY_UP) {
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
|
|
if ((message->keyboard.modifiers & (ES_MODIFIER_CTRL | ES_MODIFIER_ALT | ES_MODIFIER_FLAG | ES_MODIFIER_ALT_GR))
|
|
|| message->keyboard.scancode == ES_SCANCODE_LEFT_CTRL || message->keyboard.scancode == ES_SCANCODE_RIGHT_CTRL
|
|
|| message->keyboard.scancode == ES_SCANCODE_LEFT_SHIFT || message->keyboard.scancode == ES_SCANCODE_RIGHT_SHIFT
|
|
|| message->keyboard.scancode == ES_SCANCODE_LEFT_ALT || message->keyboard.scancode == ES_SCANCODE_RIGHT_ALT
|
|
|| message->keyboard.scancode == ES_SCANCODE_LEFT_FLAG || message->keyboard.scancode == ES_SCANCODE_RIGHT_FLAG) {
|
|
// Additionally send the keyboard input message to the embed parent
|
|
// if Ctrl, Alt or Flag were held, or this is a modifier change message.
|
|
// (Otherwise don't, to avoid increasing typing latency.)
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
}
|
|
} else if (message->type == ES_MSG_MOUSE_EXIT) {
|
|
window->embed->owner->messageQueue.SendMessage(window->embed->apiWindow, message);
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
} else {
|
|
window->owner->messageQueue.SendMessage(window->apiWindow, message);
|
|
}
|
|
}
|
|
|
|
Window *WindowManager::FindWindowAtPosition(int cursorX, int cursorY, EsObjectID exclude) {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
for (intptr_t i = windows.Length() - 1; i >= 0; i--) {
|
|
Window *window = windows[i];
|
|
EsRectangle bounds = ES_RECT_4PD(window->position.x, window->position.y, window->width, window->height);
|
|
|
|
if (window->solid && !window->hidden && exclude != window->id
|
|
&& EsRectangleContains(EsRectangleAdd(bounds, window->solidOffsets), cursorX, cursorY)
|
|
&& (!window->isMaximised || EsRectangleContains(workArea, cursorX, cursorY))) {
|
|
return window;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void WindowManager::EndEyedrop(bool cancelled) {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
eyedropping = false;
|
|
|
|
EsMessage m = { ES_MSG_EYEDROP_REPORT };
|
|
uint32_t color = *(uint32_t *) ((uint8_t *) graphics.frameBuffer.bits + 4 * cursorX + graphics.frameBuffer.stride * cursorY) | 0xFF000000;
|
|
m.eyedrop.color = cancelled ? eyedropCancelColor : color;
|
|
m.eyedrop.cancelled = cancelled;
|
|
eyedropProcess->messageQueue.SendMessage((void *) eyedropObject, &m);
|
|
CloseHandleToObject(eyedropProcess, KERNEL_OBJECT_PROCESS);
|
|
eyedropProcess = nullptr;
|
|
|
|
Redraw(ES_POINT(0, 0), graphics.width, graphics.height);
|
|
}
|
|
|
|
void WindowManager::PressKey(uint32_t scancode) {
|
|
if (!initialised) {
|
|
return;
|
|
}
|
|
|
|
KMutexAcquire(&mutex);
|
|
|
|
if (scancode == ES_SCANCODE_NUM_DIVIDE && (leftModifiers & ES_MODIFIER_FLAG)) {
|
|
KernelPanic("WindowManager::PressKey - Panic key pressed.\n");
|
|
}
|
|
|
|
if (eyedropping) {
|
|
if (scancode == (ES_SCANCODE_ESCAPE | K_SCANCODE_KEY_RELEASED)) {
|
|
EndEyedrop(true);
|
|
}
|
|
|
|
MoveCursor(0, 0);
|
|
KMutexRelease(&mutex);
|
|
return;
|
|
}
|
|
|
|
// TODO Caps lock.
|
|
|
|
if (scancode == ES_SCANCODE_LEFT_CTRL) leftModifiers |= ES_MODIFIER_CTRL;
|
|
if (scancode == (ES_SCANCODE_LEFT_CTRL | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_CTRL;
|
|
if (scancode == ES_SCANCODE_LEFT_SHIFT) leftModifiers |= ES_MODIFIER_SHIFT;
|
|
if (scancode == (ES_SCANCODE_LEFT_SHIFT | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_SHIFT;
|
|
if (scancode == ES_SCANCODE_LEFT_ALT) leftModifiers |= ES_MODIFIER_ALT;
|
|
if (scancode == (ES_SCANCODE_LEFT_ALT | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_ALT;
|
|
if (scancode == ES_SCANCODE_LEFT_FLAG) leftModifiers |= ES_MODIFIER_FLAG;
|
|
if (scancode == (ES_SCANCODE_LEFT_FLAG | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_FLAG;
|
|
|
|
if (scancode == ES_SCANCODE_RIGHT_CTRL) leftModifiers |= ES_MODIFIER_CTRL;
|
|
if (scancode == (ES_SCANCODE_RIGHT_CTRL | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_CTRL;
|
|
if (scancode == ES_SCANCODE_RIGHT_SHIFT) leftModifiers |= ES_MODIFIER_SHIFT;
|
|
if (scancode == (ES_SCANCODE_RIGHT_SHIFT | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_SHIFT;
|
|
if (scancode == ES_SCANCODE_RIGHT_ALT) leftModifiers |= ES_MODIFIER_ALT_GR;
|
|
if (scancode == (ES_SCANCODE_RIGHT_ALT | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_ALT_GR;
|
|
if (scancode == ES_SCANCODE_RIGHT_FLAG) leftModifiers |= ES_MODIFIER_FLAG;
|
|
if (scancode == (ES_SCANCODE_RIGHT_FLAG | K_SCANCODE_KEY_RELEASED)) leftModifiers &= ~ES_MODIFIER_FLAG;
|
|
|
|
if (scancode == ES_SCANCODE_NUM_LOCK) {
|
|
numlock = !numlock;
|
|
HIDeviceUpdateIndicators();
|
|
}
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = (scancode & K_SCANCODE_KEY_RELEASED) ? ES_MSG_KEY_UP : ES_MSG_KEY_DOWN;
|
|
message.keyboard.modifiers = leftModifiers | rightModifiers;
|
|
message.keyboard.scancode = scancode & ~K_SCANCODE_KEY_RELEASED;
|
|
message.keyboard.numlock = numlock;
|
|
|
|
if (message.keyboard.scancode >= 512) {
|
|
KernelPanic("WindowManager::PressKey - Scancode outside valid range.\n");
|
|
}
|
|
|
|
if (message.type == ES_MSG_KEY_DOWN && (keysHeldBitSet[message.keyboard.scancode / 8] & (1 << (message.keyboard.scancode % 8)))) {
|
|
message.keyboard.repeat = true;
|
|
}
|
|
|
|
if (message.type == ES_MSG_KEY_DOWN) {
|
|
keysHeldBitSet[message.keyboard.scancode / 8] |= (1 << (message.keyboard.scancode % 8));
|
|
} else {
|
|
keysHeldBitSet[message.keyboard.scancode / 8] &= ~(1 << (message.keyboard.scancode % 8));
|
|
}
|
|
|
|
message.keyboard.single = (scancode & K_SCANCODE_KEY_RELEASED) && maximumKeysHeld == 1;
|
|
if (!message.keyboard.repeat) keysHeld += (scancode & K_SCANCODE_KEY_RELEASED) ? -1 : 1;
|
|
keysHeld = MaximumInteger(keysHeld, 0); // Prevent negative keys held count.
|
|
maximumKeysHeld = (!keysHeld || keysHeld > maximumKeysHeld) ? keysHeld : maximumKeysHeld;
|
|
|
|
KernelLog(LOG_VERBOSE, "WM", "press key", "WindowManager::PressKey - Received key press %x. Modifiers are %X. Keys held: %d/%d%z.\n",
|
|
scancode, leftModifiers | rightModifiers, keysHeld, maximumKeysHeld, message.keyboard.single ? " (single)" : "");
|
|
|
|
if ((((leftModifiers | rightModifiers) & ES_MODIFIER_CTRL) && ((leftModifiers | rightModifiers) & ES_MODIFIER_FLAG)) || !activeWindow) {
|
|
_EsMessageWithObject messageWithObject = { nullptr, message };
|
|
DesktopSendMessage(&messageWithObject);
|
|
} else {
|
|
SendMessageToWindow(activeWindow, &message);
|
|
}
|
|
|
|
KMutexRelease(&mutex);
|
|
}
|
|
|
|
Window *WindowManager::FindWindowToActivate(Window *excluding) {
|
|
for (uintptr_t i = windows.Length(); i > 0; i--) {
|
|
if (!windows[i - 1]->hidden
|
|
&& windows[i - 1]->style == ES_WINDOW_CONTAINER
|
|
&& windows[i - 1] != excluding) {
|
|
return windows[i - 1];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
uintptr_t WindowManager::GetActivationZIndex() {
|
|
for (uintptr_t i = windows.Length(); i > 0; i--) {
|
|
if (!windows[i - 1]->alwaysOnTop) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void WindowManager::HideWindow(Window *window) {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
if (window->hidden) return;
|
|
window->hidden = true;
|
|
|
|
if (window == activeWindow) {
|
|
ActivateWindow(FindWindowToActivate());
|
|
}
|
|
|
|
Redraw(ES_POINT(window->position.x, window->position.y), window->width, window->height);
|
|
}
|
|
|
|
intptr_t WindowManager::FindWindowDepth(Window *window) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
for (uintptr_t i = 0; i < windows.Length(); i++) {
|
|
if (windows[i] == window) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void WindowManager::ChangeWindowDepth(Window *window, bool redraw, ptrdiff_t _newZDepth) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
// Reorder the windows in the array, and update the depth buffer.
|
|
intptr_t oldZDepth = FindWindowDepth(window);
|
|
if (oldZDepth == -1) KernelPanic("WindowManager::ChangeWindowDepth - Window %x was not in array.\n", window);
|
|
windows.Delete(oldZDepth);
|
|
intptr_t newZDepth = _newZDepth != -1 ? _newZDepth : GetActivationZIndex();
|
|
windows.Insert(window, newZDepth);
|
|
|
|
if (oldZDepth != newZDepth || redraw) {
|
|
// Redraw the modified area of the screen.
|
|
windowManager.Redraw(ES_POINT(window->position.x, window->position.y), window->width, window->height);
|
|
}
|
|
}
|
|
|
|
bool WindowManager::CloseMenus() {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
|
|
bool result = false;
|
|
|
|
for (uintptr_t i = 0; i < windows.Length(); i++) {
|
|
// Close any open menus.
|
|
|
|
if (windows[i]->style == ES_WINDOW_MENU) {
|
|
message.type = ES_MSG_WINDOW_DEACTIVATED;
|
|
SendMessageToWindow(windows[i], &message);
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool WindowManager::ActivateWindow(Window *window) {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
if (window && (window->style == ES_WINDOW_TIP || window->style == ES_WINDOW_MENU || window->closed)) {
|
|
return false;
|
|
}
|
|
|
|
Window *oldWindow = activeWindow;
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
|
|
bool result = CloseMenus();
|
|
|
|
// Set the active window, unless it hasn't changed.
|
|
|
|
if (activeWindow == window && (!window || !window->hidden)) {
|
|
// Bring the window to the front anyway.
|
|
if (window && !window->noBringToFront) ChangeWindowDepth(window, false, -1);
|
|
return result;
|
|
}
|
|
|
|
activeWindow = window;
|
|
if (window) window->hidden = false;
|
|
|
|
if (window) {
|
|
// Bring the window to the front.
|
|
if (!window->noBringToFront) ChangeWindowDepth(window, true, -1);
|
|
|
|
// Activate the new window.
|
|
message.type = ES_MSG_WINDOW_ACTIVATED;
|
|
message.windowActivated.leftModifiers = leftModifiers;
|
|
message.windowActivated.rightModifiers = rightModifiers;
|
|
SendMessageToWindow(window, &message);
|
|
} else {
|
|
// No window is active.
|
|
}
|
|
|
|
if (oldWindow && oldWindow != window) {
|
|
// Deactivate the old window.
|
|
|
|
if (oldWindow != FindWindowAtPosition(cursorX, cursorY)) {
|
|
message.type = ES_MSG_MOUSE_EXIT;
|
|
SendMessageToWindow(oldWindow, &message);
|
|
}
|
|
|
|
message.type = ES_MSG_WINDOW_DEACTIVATED;
|
|
SendMessageToWindow(oldWindow, &message);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void WindowManager::ClickCursor(uint32_t buttons) {
|
|
KMutexAcquire(&mutex);
|
|
|
|
unsigned delta = lastButtons ^ buttons;
|
|
lastButtons = buttons;
|
|
|
|
bool moveCursorNone = false;
|
|
|
|
if (eyedropping && delta) {
|
|
if ((delta & K_LEFT_BUTTON) && (~buttons & K_LEFT_BUTTON)) {
|
|
EndEyedrop(false);
|
|
moveCursorNone = true;
|
|
}
|
|
} else if (delta) {
|
|
// Send a mouse pressed message to the window the cursor is over.
|
|
Window *window = FindWindowAtPosition(cursorX, cursorY);
|
|
|
|
bool activationClick = false, activationClickFromMenu = false;
|
|
|
|
if (buttons == delta) {
|
|
if (activeWindow != window) {
|
|
activationClick = true;
|
|
}
|
|
|
|
if (window && window->noClickActivate) {
|
|
if (!window->noBringToFront) {
|
|
ChangeWindowDepth(window, false, -1);
|
|
}
|
|
|
|
if (window->style != ES_WINDOW_MENU) {
|
|
activationClickFromMenu = CloseMenus();
|
|
}
|
|
} else {
|
|
activationClickFromMenu = ActivateWindow(window);
|
|
}
|
|
}
|
|
|
|
{
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
|
|
if (delta & K_LEFT_BUTTON) message.type = (buttons & K_LEFT_BUTTON) ? ES_MSG_MOUSE_LEFT_DOWN : ES_MSG_MOUSE_LEFT_UP;
|
|
if (delta & K_RIGHT_BUTTON) message.type = (buttons & K_RIGHT_BUTTON) ? ES_MSG_MOUSE_RIGHT_DOWN : ES_MSG_MOUSE_RIGHT_UP;
|
|
if (delta & K_MIDDLE_BUTTON) message.type = (buttons & K_MIDDLE_BUTTON) ? ES_MSG_MOUSE_MIDDLE_DOWN : ES_MSG_MOUSE_MIDDLE_UP;
|
|
|
|
// TODO Setting pressedWindow if holding with other mouse buttons.
|
|
|
|
if (message.type == ES_MSG_MOUSE_LEFT_DOWN || message.type == ES_MSG_MOUSE_MIDDLE_DOWN || message.type == ES_MSG_MOUSE_RIGHT_DOWN) {
|
|
if (!pressedWindow) {
|
|
pressedWindowButton = message.type;
|
|
pressedWindow = window;
|
|
}
|
|
}
|
|
|
|
if (message.type == ES_MSG_MOUSE_LEFT_UP || message.type == ES_MSG_MOUSE_MIDDLE_UP || message.type == ES_MSG_MOUSE_RIGHT_UP) {
|
|
if (pressedWindow) {
|
|
// Always send the messages to the pressed window, if there is one.
|
|
window = pressedWindow;
|
|
}
|
|
|
|
if (pressedWindowButton == message.type - 1) {
|
|
// Only end pressing if this is the same button as pressing started with.
|
|
pressedWindow = nullptr;
|
|
moveCursorNone = true; // We might have moved outside the window.
|
|
}
|
|
}
|
|
|
|
if (window) {
|
|
message.mouseDown.positionX = cursorX - window->position.x;
|
|
message.mouseDown.positionY = cursorY - window->position.y;
|
|
window->activationClick = activationClick || activationClickFromMenu;
|
|
SendMessageToWindow(window, &message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (moveCursorNone) {
|
|
MoveCursor(0, 0);
|
|
} else {
|
|
GraphicsUpdateScreen();
|
|
}
|
|
|
|
KMutexRelease(&mutex);
|
|
}
|
|
|
|
void WindowManager::MoveCursor(int64_t xMovement, int64_t yMovement) {
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
if ((xMovement * xMovement + yMovement * yMovement > 50 * K_CURSOR_MOVEMENT_SCALE * K_CURSOR_MOVEMENT_SCALE) && (cursorProperties & CURSOR_USE_ACCELERATION)) {
|
|
// Apply cursor acceleration.
|
|
xMovement *= 2;
|
|
yMovement *= 2;
|
|
}
|
|
|
|
// Apply cursor speed. Introduces another factor of K_CURSOR_MOVEMENT_SCALE.
|
|
xMovement *= CURSOR_SPEED(cursorProperties);
|
|
yMovement *= CURSOR_SPEED(cursorProperties);
|
|
|
|
if ((leftModifiers & ES_MODIFIER_ALT) && (cursorProperties & CURSOR_USE_ALT_SLOW)) {
|
|
// Apply cursor slowdown.
|
|
xMovement /= 5, yMovement /= 5;
|
|
}
|
|
|
|
// Update cursor position.
|
|
cursorXPrecise = ClampInteger(0, graphics.width * K_CURSOR_MOVEMENT_SCALE - 1, cursorXPrecise + xMovement / K_CURSOR_MOVEMENT_SCALE);
|
|
cursorYPrecise = ClampInteger(0, graphics.height * K_CURSOR_MOVEMENT_SCALE - 1, cursorYPrecise + yMovement / K_CURSOR_MOVEMENT_SCALE);
|
|
cursorX = cursorXPrecise / K_CURSOR_MOVEMENT_SCALE;
|
|
cursorY = cursorYPrecise / K_CURSOR_MOVEMENT_SCALE;
|
|
|
|
if (eyedropping) {
|
|
EsMessage m = { ES_MSG_EYEDROP_REPORT };
|
|
uint32_t color = *(uint32_t *) ((uint8_t *) graphics.frameBuffer.bits + 4 * cursorX + graphics.frameBuffer.stride * cursorY);
|
|
m.eyedrop.color = color;
|
|
m.eyedrop.cancelled = false;
|
|
eyedropProcess->messageQueue.SendMessage((void *) eyedropObject, &m);
|
|
} else {
|
|
Window *window = pressedWindow;
|
|
|
|
if (!window) {
|
|
// Work out which window the mouse is now over.
|
|
window = FindWindowAtPosition(cursorX, cursorY);
|
|
|
|
if (hoverWindow != window && hoverWindow) {
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_MOUSE_EXIT;
|
|
SendMessageToWindow(hoverWindow, &message);
|
|
}
|
|
|
|
hoverWindow = window;
|
|
}
|
|
|
|
if (window) {
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_MOUSE_MOVED;
|
|
message.mouseMoved.newPositionX = cursorX - window->position.x;
|
|
message.mouseMoved.newPositionY = cursorY - window->position.y;
|
|
SendMessageToWindow(window, &message);
|
|
}
|
|
}
|
|
|
|
GraphicsUpdateScreen();
|
|
}
|
|
|
|
void WindowManager::ScrollWheel(int32_t dx, int32_t dy) {
|
|
KMutexAssertLocked(&mutex);
|
|
Window *window = pressedWindow ?: FindWindowAtPosition(cursorX, cursorY);
|
|
if (!window) return;
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_SCROLL_WHEEL;
|
|
message.scrollWheel.dx = dx;
|
|
message.scrollWheel.dy = dy;
|
|
SendMessageToWindow(window, &message);
|
|
}
|
|
|
|
void _CloseWindows(uintptr_t) {
|
|
while (true) {
|
|
KEventWait(&windowManager.windowsToCloseEvent);
|
|
KMutexAcquire(&windowManager.mutex);
|
|
|
|
for (uintptr_t i = 0; i < windowManager.windows.Length(); i++) {
|
|
Window *window = windowManager.windows[i];
|
|
|
|
if (window->handles > 1) {
|
|
continue;
|
|
}
|
|
|
|
// Remove the window from the array.
|
|
windowManager.windows.Delete(i);
|
|
|
|
if (!window->closed) {
|
|
// Only the window manager's handle to the window remains,
|
|
// but the window has not been closed.
|
|
// This probably means the process crashed before it could close its windows;
|
|
// we should close the window ourselves.
|
|
window->Close();
|
|
}
|
|
|
|
// Close the window manager's handle to the window.
|
|
CloseHandleToObject(window, KERNEL_OBJECT_WINDOW);
|
|
|
|
i--;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < windowManager.embeddedWindows.Length(); i++) {
|
|
// Apply a similar set of operations to embedded windows.
|
|
EmbeddedWindow *window = windowManager.embeddedWindows[i];
|
|
if (window->handles > 1) continue;
|
|
if (!window->closed) window->Close();
|
|
windowManager.embeddedWindows.Delete(i);
|
|
CloseHandleToObject(window, KERNEL_OBJECT_EMBEDDED_WINDOW);
|
|
i--;
|
|
}
|
|
|
|
KMutexRelease(&windowManager.mutex);
|
|
}
|
|
}
|
|
|
|
void WindowManager::Initialise() {
|
|
windowsToCloseEvent.autoReset = true;
|
|
cursorProperties = K_CURSOR_MOVEMENT_SCALE << 16;
|
|
KThreadCreate("CloseWindows", _CloseWindows);
|
|
KMutexAcquire(&mutex);
|
|
MoveCursor(graphics.width / 2 * K_CURSOR_MOVEMENT_SCALE, graphics.height / 2 * K_CURSOR_MOVEMENT_SCALE);
|
|
GraphicsUpdateScreen();
|
|
initialised = true;
|
|
KMutexRelease(&mutex);
|
|
}
|
|
|
|
void *WindowManager::CreateEmbeddedWindow(Process *process, void *apiWindow) {
|
|
EmbeddedWindow *window = (EmbeddedWindow *) EsHeapAllocate(sizeof(EmbeddedWindow), true, K_PAGED);
|
|
if (!window) return nullptr;
|
|
|
|
if (!embeddedWindows.Add(window)) {
|
|
EsHeapFree(window, sizeof(EmbeddedWindow), K_PAGED);
|
|
return nullptr;
|
|
}
|
|
|
|
window->apiWindow = apiWindow;
|
|
window->owner = process;
|
|
window->handles = 2; // One handle for the window manager, and one handle for the calling process.
|
|
|
|
KMutexAcquire(&mutex);
|
|
window->id = ++currentWindowID;
|
|
KMutexRelease(&mutex);
|
|
|
|
OpenHandleToObject(window->owner, KERNEL_OBJECT_PROCESS);
|
|
|
|
return window;
|
|
}
|
|
|
|
void *WindowManager::CreateWindow(Process *process, void *apiWindow, EsWindowStyle style) {
|
|
KMutexAcquire(&mutex);
|
|
EsDefer(KMutexRelease(&mutex));
|
|
|
|
// Allocate and initialise the window object.
|
|
|
|
Window *window = (Window *) EsHeapAllocate(sizeof(Window), true, K_PAGED);
|
|
if (!window) return nullptr;
|
|
|
|
window->style = style;
|
|
window->apiWindow = apiWindow;
|
|
window->alpha = 0xFF;
|
|
window->position = ES_POINT(-8, -8);
|
|
window->owner = process;
|
|
window->handles = 2; // One handle for the window manager, and one handle for the calling process.
|
|
window->hidden = true;
|
|
window->id = ++currentWindowID;
|
|
|
|
if (style == ES_WINDOW_INSPECTOR) windowManager.inspectorWindowCount++;
|
|
|
|
// Insert the window into the window array.
|
|
|
|
uintptr_t insertionPoint = style == ES_WINDOW_TIP ? windows.Length() : GetActivationZIndex();
|
|
|
|
if (!windows.Insert(window, insertionPoint)) {
|
|
EsHeapFree(window->surface.bits, 0, K_PAGED);
|
|
EsHeapFree(window, sizeof(Window), K_PAGED);
|
|
return nullptr;
|
|
}
|
|
|
|
// Get a handle to the owner process.
|
|
|
|
OpenHandleToObject(window->owner, KERNEL_OBJECT_PROCESS);
|
|
|
|
MoveCursor(0, 0);
|
|
return window;
|
|
}
|
|
|
|
bool Window::Move(EsRectangle rectangle, uint32_t flags) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
if (closed) {
|
|
return false;
|
|
}
|
|
|
|
if ((flags & ES_WINDOW_MOVE_DYNAMIC)
|
|
&& (~flags & ES_WINDOW_MOVE_MAXIMIZED) && !isMaximised
|
|
&& windowManager.resizeWindow == this
|
|
&& windowManager.resizeStartTimeStampMs + RESIZE_FLICKER_TIMEOUT_MS > KGetTimeInMs()) {
|
|
windowManager.resizeQueued = true;
|
|
windowManager.resizeQueuedRectangle = rectangle;
|
|
return false;
|
|
}
|
|
|
|
windowManager.resizeQueued = false;
|
|
|
|
bool result = true;
|
|
|
|
isMaximised = flags & ES_WINDOW_MOVE_MAXIMIZED;
|
|
alwaysOnTop = flags & ES_WINDOW_MOVE_ALWAYS_ON_TOP;
|
|
|
|
// TS("Move window\n");
|
|
|
|
if (flags & ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN) {
|
|
rectangle = EsRectangleAdd(rectangle, solidOffsets);
|
|
|
|
if (rectangle.r > (int32_t) graphics.frameBuffer.width) {
|
|
rectangle = Translate(rectangle, graphics.frameBuffer.width - rectangle.r, 0);
|
|
}
|
|
|
|
if (rectangle.b > (int32_t) graphics.frameBuffer.height) {
|
|
rectangle = Translate(rectangle, 0, graphics.frameBuffer.height - rectangle.b);
|
|
}
|
|
|
|
if (rectangle.l < 0) {
|
|
rectangle = Translate(rectangle, -rectangle.l, 0);
|
|
}
|
|
|
|
if (rectangle.t < 0) {
|
|
rectangle = Translate(rectangle, 0, -rectangle.t);
|
|
}
|
|
|
|
rectangle = EsRectangleSubtract(rectangle, solidOffsets);
|
|
}
|
|
|
|
size_t newWidth = rectangle.r - rectangle.l;
|
|
size_t newHeight = rectangle.b - rectangle.t;
|
|
|
|
if (newWidth < 4 || newHeight < 4
|
|
|| rectangle.l > rectangle.r
|
|
|| rectangle.t > rectangle.b
|
|
|| newWidth > graphics.width * 2
|
|
|| newHeight > graphics.height * 2) {
|
|
return false;
|
|
}
|
|
|
|
if (!hidden) {
|
|
// TS("Clear previous image\n");
|
|
windowManager.Redraw(ES_POINT(position.x, position.y), width, height, this);
|
|
}
|
|
|
|
hidden = false;
|
|
position = ES_POINT(rectangle.l, rectangle.t);
|
|
bool changedSize = width != newWidth || height != newHeight;
|
|
width = newWidth, height = newHeight;
|
|
|
|
if (changedSize) {
|
|
if (style == ES_WINDOW_CONTAINER && embed) {
|
|
surface.Resize(width, height, 0, windowManager.resizeSlow);
|
|
} else {
|
|
surface.Resize(width, height);
|
|
}
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_WINDOW_RESIZED;
|
|
message.windowResized.content = ES_RECT_4(0, width, 0, height);
|
|
SendMessageToWindow(this, &message);
|
|
|
|
if (flags & ES_WINDOW_MOVE_DYNAMIC) {
|
|
// Don't redraw the screen until both the container and embedded window have painted
|
|
// (or the timeout completes).
|
|
windowManager.resizeWindow = this;
|
|
windowManager.resizeReceivedBitsFromContainer = false;
|
|
windowManager.resizeReceivedBitsFromEmbed = embed == nullptr;
|
|
windowManager.resizeStartTimeStampMs = KGetTimeInMs();
|
|
}
|
|
}
|
|
|
|
if (flags & ES_WINDOW_MOVE_AT_BOTTOM) {
|
|
windowManager.ChangeWindowDepth(this, false, 0);
|
|
}
|
|
|
|
if ((flags & ES_WINDOW_MOVE_DYNAMIC) && changedSize && style == ES_WINDOW_CONTAINER && !windowManager.resizeSlow) {
|
|
// Don't redraw anything yet.
|
|
} else {
|
|
windowManager.Redraw(position, width, height, nullptr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void EmbeddedWindow::Destroy() {
|
|
KernelLog(LOG_INFO, "WM", "destroy embedded window", "EmbeddedWindow::Destroy - Destroying embedded window.\n");
|
|
EsHeapFree(this, sizeof(EmbeddedWindow), K_PAGED);
|
|
}
|
|
|
|
void EmbeddedWindow::Close() {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
_EsMessageWithObject m;
|
|
EsMemoryZero(&m, sizeof(m));
|
|
m.object = apiWindow;
|
|
m.message.type = ES_MSG_WINDOW_DESTROYED;
|
|
owner->messageQueue.SendMessage(&m);
|
|
SetEmbedOwner(nullptr);
|
|
m.object = nullptr;
|
|
m.message.type = ES_MSG_EMBEDDED_WINDOW_DESTROYED;
|
|
m.message.embeddedWindowDestroyedID = id;
|
|
DesktopSendMessage(&m);
|
|
|
|
if (container && container->embed == this) {
|
|
container->SetEmbed(nullptr);
|
|
}
|
|
|
|
closed = true;
|
|
}
|
|
|
|
void Window::Destroy() {
|
|
if (!closed) {
|
|
KernelPanic("Window::Destroy - Window %x has not been closed.\n", this);
|
|
}
|
|
|
|
EsHeapFree(this, sizeof(Window), K_PAGED);
|
|
}
|
|
|
|
void Window::Close() {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
SetEmbed(nullptr);
|
|
|
|
// Send the destroy message - the last message sent to the window.
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_WINDOW_DESTROYED;
|
|
owner->messageQueue.SendMessage(apiWindow, &message);
|
|
|
|
Process *_owner = owner;
|
|
owner = nullptr;
|
|
CloseHandleToObject(_owner, KERNEL_OBJECT_PROCESS);
|
|
|
|
hidden = true;
|
|
solid = false;
|
|
|
|
bool findActiveWindow = false;
|
|
|
|
if (windowManager.pressedWindow == this) windowManager.pressedWindow = nullptr;
|
|
if (windowManager.hoverWindow == this) windowManager.hoverWindow = nullptr;
|
|
if (windowManager.resizeWindow == this) windowManager.resizeWindow = nullptr;
|
|
if (windowManager.activeWindow == this) windowManager.activeWindow = nullptr, findActiveWindow = true;
|
|
|
|
if (style == ES_WINDOW_INSPECTOR) windowManager.inspectorWindowCount--;
|
|
|
|
windowManager.Redraw(ES_POINT(position.x, position.y), width, height);
|
|
|
|
if (findActiveWindow) {
|
|
windowManager.ActivateWindow(windowManager.FindWindowToActivate());
|
|
}
|
|
|
|
windowManager.MoveCursor(0, 0);
|
|
GraphicsUpdateScreen();
|
|
|
|
__sync_fetch_and_sub(&graphics.totalSurfaceBytes, surface.width * surface.height * 4);
|
|
EsHeapFree(surface.bits, 0, K_PAGED);
|
|
surface.bits = nullptr;
|
|
|
|
__sync_synchronize();
|
|
closed = true;
|
|
}
|
|
|
|
void WindowManager::Redraw(EsPoint position, int width, int height, Window *except, int startingAt, bool addToModifiedRegion) {
|
|
// TS("Window manager redraw at [%d, %d] with size [%d, %d]\n", position.x, position.y, width, height);
|
|
|
|
KMutexAssertLocked(&mutex);
|
|
|
|
if (!width || !height) return;
|
|
if (scheduler.shutdown) return;
|
|
|
|
for (int index = startingAt; index < (int) windows.Length(); index++) {
|
|
Window *window = windows[index];
|
|
|
|
if (!window->IsVisible() || window == except) {
|
|
continue;
|
|
}
|
|
|
|
if (position.x >= window->position.x + window->opaqueBounds.l
|
|
&& position.x + width <= window->position.x + window->opaqueBounds.r
|
|
&& position.y >= window->position.y + window->opaqueBounds.t
|
|
&& position.y + height <= window->position.y + window->opaqueBounds.b) {
|
|
startingAt = index;
|
|
}
|
|
}
|
|
|
|
for (int index = startingAt; index < (int) windows.Length(); index++) {
|
|
Window *window = windows[index];
|
|
|
|
if (!window->IsVisible() || window == except) continue;
|
|
|
|
EsRectangle rectangle = ES_RECT_4PD(window->position.x, window->position.y, window->width, window->height);
|
|
rectangle = EsRectangleIntersection(rectangle, ES_RECT_2S(graphics.frameBuffer.width, graphics.frameBuffer.height));
|
|
rectangle = EsRectangleIntersection(rectangle, ES_RECT_4PD(position.x, position.y, width, height));
|
|
if (window->isMaximised) rectangle = EsRectangleIntersection(rectangle, workArea);
|
|
rectangle = Translate(rectangle, -window->position.x, -window->position.y);
|
|
|
|
if (!ES_RECT_VALID(rectangle)) continue;
|
|
|
|
Surface *surface = &window->surface;
|
|
|
|
EsRectangle transparentRegions[] = {
|
|
EsRectangleIntersection(rectangle, ES_RECT_4(0, window->opaqueBounds.l, 0, window->height)),
|
|
EsRectangleIntersection(rectangle, ES_RECT_4(window->opaqueBounds.r, window->width, 0, window->height)),
|
|
EsRectangleIntersection(rectangle, ES_RECT_4(window->opaqueBounds.l, window->opaqueBounds.r, 0, window->opaqueBounds.t)),
|
|
EsRectangleIntersection(rectangle, ES_RECT_4(window->opaqueBounds.l, window->opaqueBounds.r, window->opaqueBounds.b, window->height)),
|
|
};
|
|
|
|
EsRectangle opaqueRegion = EsRectangleIntersection(rectangle, window->opaqueBounds);
|
|
EsRectangle blurRegion = Translate(EsRectangleIntersection(window->blurBounds, rectangle), window->position.x, window->position.y);
|
|
|
|
for (uintptr_t i = 0; i < 4; i++) {
|
|
if (ES_RECT_VALID(transparentRegions[i])) {
|
|
EsPoint point = ES_POINT(window->position.x + transparentRegions[i].l, window->position.y + transparentRegions[i].t);
|
|
graphics.frameBuffer.BlendWindow(surface, point, transparentRegions[i], window->material, window->alpha, blurRegion);
|
|
}
|
|
}
|
|
|
|
if (ES_RECT_VALID(opaqueRegion)) {
|
|
EsPoint point = ES_POINT(window->position.x + opaqueRegion.l, window->position.y + opaqueRegion.t);
|
|
graphics.frameBuffer.Copy(surface, point, opaqueRegion, addToModifiedRegion);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Window::UpdateDirect(K_USER_BUFFER void *bits, uintptr_t stride, EsRectangle region) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
intptr_t z = windowManager.FindWindowDepth(this);
|
|
|
|
if (z == -1) {
|
|
return false;
|
|
}
|
|
|
|
if (hidden) {
|
|
return false;
|
|
}
|
|
|
|
if (windowManager.changedCursorImage) {
|
|
// The entire cursor needs to be redrawn about its image changes,
|
|
// which might not happen with a direct update.
|
|
return false;
|
|
}
|
|
|
|
if (region.l + position.x < 0) {
|
|
bits = (K_USER_BUFFER uint8_t *) bits + 4 * (-position.x - region.l);
|
|
region.l = -position.x;
|
|
}
|
|
|
|
if (region.t + position.y < 0) {
|
|
bits = (K_USER_BUFFER uint8_t *) bits + stride * (-position.y - region.t);
|
|
region.t = -position.y;
|
|
}
|
|
|
|
if ((uint32_t) (region.r + position.x) > graphics.width) {
|
|
region.r = graphics.width - position.x;
|
|
}
|
|
|
|
if ((uint32_t) (region.b + position.y) > graphics.height) {
|
|
region.b = graphics.height - position.y;
|
|
}
|
|
|
|
// If the update region is completely obscured, then we don't need to update.
|
|
|
|
for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) {
|
|
Window *other = windowManager.windows[i];
|
|
EsRectangle otherBounds = Translate(other->opaqueBounds, other->position.x, other->position.y);
|
|
|
|
if (region.l + position.x > otherBounds.l && region.r + position.x < otherBounds.r
|
|
&& region.t + position.y > otherBounds.t && region.b + position.y < otherBounds.b
|
|
&& other->alpha == 0xFF && other->IsVisible()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// If the update region isn't opaque, we cannot do a direct update.
|
|
|
|
if (region.l < opaqueBounds.l || region.r > opaqueBounds.r || region.t < opaqueBounds.t || region.b > opaqueBounds.b
|
|
|| alpha != 0xFF) {
|
|
return false;
|
|
}
|
|
|
|
// If any window overlaps the update region, we cannot do a direct update.
|
|
|
|
EsRectangle thisBounds = ES_RECT_4(position.x, position.x + width, position.y, position.y + height);
|
|
|
|
for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) {
|
|
Window *other = windowManager.windows[i];
|
|
EsRectangle otherBounds = ES_RECT_4(other->position.x, other->position.x + other->width,
|
|
other->position.y, other->position.y + other->height);
|
|
|
|
if (EsRectangleClip(thisBounds, otherBounds, nullptr)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
region = Translate(region, position.x, position.y);
|
|
|
|
// Write the updated bits directly to the frame buffer!
|
|
GraphicsUpdateScreen(bits, ®ion, stride);
|
|
return true;
|
|
}
|
|
|
|
void Window::Update(EsRectangle *_region, bool addToModifiedRegion) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
intptr_t z = windowManager.FindWindowDepth(this);
|
|
|
|
if (z == -1) {
|
|
return;
|
|
}
|
|
|
|
// TS("Update window %x within %R\n", this, _region ? *_region : ES_RECT_1(0));
|
|
|
|
EsRectangle region = _region ? *_region : ES_RECT_4(0, width, 0, height);
|
|
|
|
// Is this updated region completely obscured?
|
|
for (uintptr_t i = z + 1; i < windowManager.windows.Length(); i++) {
|
|
Window *other = windowManager.windows[i];
|
|
EsRectangle otherBounds = Translate(other->opaqueBounds, other->position.x, other->position.y);
|
|
|
|
if (region.l + position.x > otherBounds.l && region.r + position.x < otherBounds.r
|
|
&& region.t + position.y > otherBounds.t && region.b + position.y < otherBounds.b
|
|
&& other->alpha == 0xFF && other->IsVisible()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
EsRectangle r;
|
|
// EsPrint("Update window width region %d/%d/%d/%d\n", region.left, region.right, region.top, region.bottom);
|
|
|
|
if (alpha == 0xFF) {
|
|
EsRectangle translucentBorder = opaqueBounds;
|
|
|
|
// Draw the opaque region, then everything above it.
|
|
EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, translucentBorder.t, translucentBorder.b), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, z, addToModifiedRegion);
|
|
|
|
// Draw the transparent regions after everything below them, then everything above them.
|
|
EsRectangleClip(region, ES_RECT_4(0, translucentBorder.l, 0, height), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion);
|
|
EsRectangleClip(region, ES_RECT_4(translucentBorder.r, width, 0, height), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion);
|
|
EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, 0, translucentBorder.t), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion);
|
|
EsRectangleClip(region, ES_RECT_4(translucentBorder.l, translucentBorder.r, translucentBorder.b, height), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion);
|
|
} else {
|
|
// The whole window is translucent; draw it.
|
|
EsRectangleClip(region, ES_RECT_4(0, width, 0, height), &r);
|
|
windowManager.Redraw(ES_POINT(position.x + r.l, position.y + r.t), r.r - r.l, r.b - r.t, nullptr, 0, addToModifiedRegion);
|
|
}
|
|
}
|
|
|
|
void EmbeddedWindow::SetEmbedOwner(Process *process) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
apiWindow = nullptr;
|
|
|
|
if (process) {
|
|
OpenHandleToObject(process, KERNEL_OBJECT_PROCESS);
|
|
}
|
|
|
|
if (owner) {
|
|
CloseHandleToObject(owner, KERNEL_OBJECT_PROCESS);
|
|
}
|
|
|
|
owner = process;
|
|
}
|
|
|
|
void Window::ResizeEmbed() {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
if (!embed || !embed->apiWindow) return;
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_WINDOW_RESIZED;
|
|
message.windowResized.content = ES_RECT_2S(width - embedInsets.l - embedInsets.r, height - embedInsets.t - embedInsets.b);
|
|
embed->owner->messageQueue.SendMessage(embed->apiWindow, &message);
|
|
}
|
|
|
|
void Window::SetEmbed(EmbeddedWindow *newEmbed) {
|
|
KMutexAssertLocked(&windowManager.mutex);
|
|
|
|
if (newEmbed && (newEmbed->container || newEmbed->closed)) {
|
|
return;
|
|
}
|
|
|
|
if (newEmbed == embed) {
|
|
return;
|
|
}
|
|
|
|
if (embed) {
|
|
if (embed->closed) {
|
|
KernelPanic("Window::SetEmbed - Previous embed %x was closed.\n");
|
|
}
|
|
|
|
embed->container = nullptr;
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_WINDOW_RESIZED;
|
|
message.windowResized.content = ES_RECT_4(0, 0, 0, 0);
|
|
message.windowResized.hidden = true;
|
|
|
|
if (embed->owner) {
|
|
embed->owner->messageQueue.SendMessage(embed->apiWindow, &message);
|
|
}
|
|
}
|
|
|
|
embed = newEmbed;
|
|
|
|
if (embed) {
|
|
embed->container = this;
|
|
ResizeEmbed();
|
|
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(message));
|
|
message.type = windowManager.activeWindow == this ? ES_MSG_WINDOW_ACTIVATED : ES_MSG_WINDOW_DEACTIVATED;
|
|
|
|
if (message.type == ES_MSG_WINDOW_ACTIVATED) {
|
|
message.windowActivated.leftModifiers = windowManager.leftModifiers;
|
|
message.windowActivated.rightModifiers = windowManager.rightModifiers;
|
|
}
|
|
|
|
embed->owner->messageQueue.SendMessage(embed->apiWindow, &message);
|
|
}
|
|
|
|
windowManager.CloseMenus();
|
|
}
|
|
|
|
void WindowManager::StartEyedrop(uintptr_t object, Window *avoid, uint32_t cancelColor) {
|
|
KMutexAcquire(&mutex);
|
|
|
|
if (!eyedropping) {
|
|
eyedropObject = object;
|
|
eyedropping = true;
|
|
eyedropAvoidID = avoid->id;
|
|
eyedropCancelColor = cancelColor;
|
|
Redraw(avoid->position, avoid->width, avoid->height);
|
|
|
|
if (hoverWindow) {
|
|
EsMessage message;
|
|
EsMemoryZero(&message, sizeof(EsMessage));
|
|
message.type = ES_MSG_MOUSE_EXIT;
|
|
SendMessageToWindow(hoverWindow, &message);
|
|
}
|
|
|
|
hoverWindow = pressedWindow = nullptr;
|
|
|
|
eyedropProcess = GetCurrentThread()->process;
|
|
OpenHandleToObject(eyedropProcess, KERNEL_OBJECT_PROCESS);
|
|
}
|
|
|
|
GraphicsUpdateScreen();
|
|
KMutexRelease(&mutex);
|
|
}
|
|
|
|
void KMouseUpdate(const KMouseUpdateData *data) {
|
|
if (!windowManager.initialised) {
|
|
return;
|
|
}
|
|
|
|
KMutexAcquire(&windowManager.mutex);
|
|
|
|
if (data->xMovement || data->yMovement) {
|
|
int32_t xMovement = data->xMovement;
|
|
int32_t yMovement = data->yMovement;
|
|
|
|
// TODO Handling xIsAbsolute and yIsAbsolute
|
|
|
|
if (xMovement * xMovement + yMovement * yMovement < 10 && data->buttons != windowManager.lastButtons) {
|
|
// This seems to be movement noise generated when the buttons were pressed/released.
|
|
} else {
|
|
windowManager.MoveCursor(xMovement, yMovement);
|
|
}
|
|
}
|
|
|
|
if (data->xScroll || data->yScroll) {
|
|
windowManager.ScrollWheel(data->xScroll, data->yScroll);
|
|
}
|
|
|
|
KMutexRelease(&windowManager.mutex);
|
|
|
|
windowManager.ClickCursor(data->buttons);
|
|
}
|
|
|
|
void KKeyPress(uint32_t scancode) {
|
|
windowManager.PressKey(scancode);
|
|
}
|
|
|
|
void KKeyboardUpdate(uint16_t *keysDown, size_t keysDownCount) {
|
|
// TODO Key repeat.
|
|
|
|
static uint16_t previousKeysDown[32] = {};
|
|
static size_t previousKeysDownCount = 0;
|
|
static KMutex mutex = {};
|
|
|
|
KMutexAcquire(&mutex);
|
|
EsDefer(KMutexRelease(&mutex));
|
|
|
|
if (keysDownCount > 32) {
|
|
keysDownCount = 32;
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < keysDownCount; i++) {
|
|
bool found = false;
|
|
|
|
for (uintptr_t j = 0; j < previousKeysDownCount; j++) {
|
|
if (keysDown[i] == previousKeysDown[j]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found && keysDown[i]) {
|
|
if (keysDown[i] == ES_SCANCODE_PAUSE) {
|
|
KDebugKeyPressed(); // TODO Doesn't work if scheduler not functioning correctly.
|
|
}
|
|
|
|
KKeyPress(keysDown[i] | K_SCANCODE_KEY_PRESSED);
|
|
}
|
|
}
|
|
|
|
for (uintptr_t i = 0; i < previousKeysDownCount; i++) {
|
|
bool found = false;
|
|
|
|
for (uintptr_t j = 0; j < keysDownCount; j++) {
|
|
if (keysDown[j] == previousKeysDown[i]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
KKeyPress(previousKeysDown[i] | K_SCANCODE_KEY_RELEASED);
|
|
}
|
|
}
|
|
|
|
previousKeysDownCount = keysDownCount;
|
|
EsMemoryCopy(previousKeysDown, keysDown, sizeof(uint16_t) * keysDownCount);
|
|
}
|
|
|
|
uint64_t KGameControllerConnect() {
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
|
|
EsObjectID id = ++windowManager.gameControllerID;
|
|
|
|
if (windowManager.gameControllerCount != ES_GAME_CONTROLLER_MAX_COUNT) {
|
|
windowManager.gameControllers[windowManager.gameControllerCount++].id = id;
|
|
} else {
|
|
id = 0;
|
|
}
|
|
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
|
|
return id;
|
|
}
|
|
|
|
void KGameControllerDisconnect(uint64_t id) {
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
|
|
for (uintptr_t i = 0; i < windowManager.gameControllerCount; i++) {
|
|
if (windowManager.gameControllers[i].id == id) {
|
|
EsMemoryMove(windowManager.gameControllers + i + 1,
|
|
windowManager.gameControllers + windowManager.gameControllerCount,
|
|
-sizeof(EsGameControllerState), false);
|
|
windowManager.gameControllerCount--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
}
|
|
|
|
void KGameControllerUpdate(EsGameControllerState *state) {
|
|
KMutexAcquire(&windowManager.deviceMutex);
|
|
|
|
for (uintptr_t i = 0; i < windowManager.gameControllerCount; i++) {
|
|
if (windowManager.gameControllers[i].id == state->id) {
|
|
windowManager.gameControllers[i] = *state;
|
|
#if 0
|
|
EsPrint("game controller %d: buttons %x analog %d %d %d %d %d %d dpad %d\n",
|
|
state->id, state->buttons, state->analog[0].x, state->analog[0].y, state->analog[0].z,
|
|
state->analog[1].x, state->analog[1].y, state->analog[1].z, state->directionalPad);
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
KMutexRelease(&windowManager.deviceMutex);
|
|
}
|
|
|
|
#endif
|