roo_e/roo-e/src/gui/wmwindow.c

914 lines
28 KiB
C

/*
* Roo/E, the Kangaroo Punch Portable GUI Toolkit
* Copyright (C) 2022 Scott Duensing
*
* http://kangaroopunch.com
*
*
* This file is part of Roo/E.
*
* Roo/E is free software: you can redistribute it and/or modify it under the
* terms of the GNU Affero General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Roo/E. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "wmwindow.h"
#include "array.h"
#include "font.h"
uint8_t __MAGIC_WINDOW = 0;
static WindowT **_windowList = NULL;
static WindowT *_windowTop = NULL;
static int16_t _iconCount = 0;
static void windowCache(WindowT *w, uint8_t redrawWindow);
static void windowDestroy(struct WidgetS *widget, ...);
static void windowPaint(struct WidgetS *widget, ...);
static WidgetT *wuWidgetUnderMouseGet(WindowT *win, EventT *event, int16_t *localX, int16_t *localY);
static void windowCache(WindowT *w, uint8_t redrawWindow) {
char c;
int16_t i;
int16_t x1;
int16_t y1;
int16_t x2;
int16_t y2;
int16_t tx1;
int16_t tx2;
int16_t ty2;
int16_t originalX;
int16_t originalY;
ColorT titleBackgroundColor;
ColorT gadgetColor;
SurfaceT *target = surfaceGet();
// Do we have a cached surface already?
if (w->cached) {
// Did the size change?
if ((surfaceWidthGet(w->cached) != w->base.r.w) || (surfaceHeightGet(w->cached) != w->base.r.h)) {
// Yeah. We will recreate it.
surfaceDestroy(&w->cached);
}
}
// Do we need to create a surface?
if (!w->cached) {
w->cached = surfaceCreate(w->base.r.w, w->base.r.h);
}
// Draw into cache.
surfaceSet(w->cached);
// Does the window itself need redrawn or just the contents?
if (redrawWindow) {
// Move the window to 0,0 to draw it into it's own surface.
originalX = w->base.r.x;
originalY = w->base.r.y;
w->base.r.x = 0;
w->base.r.y = 0;
// Determine some colors.
titleBackgroundColor = (w == _windowTop) ? GUI_DARKGRAY : GUI_LIGHTGRAY;
gadgetColor = (w == _windowTop) ? GUI_LIGHTGRAY : GUI_DARKGRAY;
// Get ready!
x1 = w->base.r.x;
y1 = w->base.r.y;
x2 = w->base.r.x + w->base.r.w - 1;
y2 = w->base.r.y + w->base.r.h - 1;
// Draw border.
surfaceBoxHighlight(x1, y1, x2, y2, GUI_WHITE, GUI_BLACK);
x1++; y1++; x2--; y2--;
for (i=0; i<3; i++) {
surfaceBox(x1, y1, x2, y2, GUI_LIGHTGRAY);
x1++; y1++; x2--; y2--;
}
surfaceBoxHighlight(x1, y1, x2, y2, GUI_BLACK, GUI_WHITE);
x1++; y1++; x2--; y2--;
// Do we need a titlebar?
if (w->title || w->flags & WIN_CLOSE || w->flags & WIN_MAXIMIZE || w->flags & WIN_MINIMIZE) {
tx1 = x1;
tx2 = x2;
ty2 = y1 + GADGET_SIZE;
// Close box?
if (w->flags & WIN_CLOSE) {
tx1 += GADGET_SIZE;
w->close.x = x1;
w->close.y = y1;
w->close.x2 = tx1 - 1;
w->close.y2 = ty2 - 1;
surfaceBoxFilled(w->close.x + 1, w->close.y + 1, w->close.x2 - 1, w->close.y2 - 1, titleBackgroundColor);
surfaceBoxHighlight(w->close.x + 3, w->close.y + 8, w->close.x2 - 3, w->close.y2 - 8, GUI_WHITE, gadgetColor);
surfaceBoxHighlight(w->close.x, w->close.y, w->close.x2, w->close.y2, GUI_WHITE, GUI_BLACK);
} else {
w->close.x = w->close.y = w->close.x2 = w->close.y2 = 0;
}
// Maximize box?
if (w->flags & WIN_MAXIMIZE) {
tx2 -= GADGET_SIZE;
w->maximize.x = tx2 + 1;
w->maximize.y = y1;
w->maximize.x2 = tx2 + GADGET_SIZE;
w->maximize.y2 = ty2 - 1;
surfaceBoxFilled(w->maximize.x + 1, w->maximize.y + 1, w->maximize.x2 - 1, w->maximize.y2 - 1, titleBackgroundColor);
surfaceLine(w->maximize.x + 4, w->maximize.y + 10, w->maximize.x + 10, w->maximize.y + 4, GUI_WHITE);
surfaceLine(w->maximize.x + 4, w->maximize.y + 10, w->maximize.x + 10, w->maximize.y2 - 3, GUI_WHITE);
surfaceLine(w->maximize.x2 - 3, w->maximize.y + 10, w->maximize.x2 - 9, w->maximize.y + 4, gadgetColor);
surfaceLine(w->maximize.x2 - 3, w->maximize.y + 10, w->maximize.x2 - 9, w->maximize.y2 - 3, gadgetColor);
surfaceBoxHighlight(w->maximize.x, w->maximize.y, w->maximize.x2, w->maximize.y2, GUI_WHITE, GUI_BLACK);
} else {
w->maximize.x = w->maximize.y = w->maximize.x2 = w->maximize.y2 = 0;
}
// Minimize box?
if (w->flags & WIN_MINIMIZE) {
tx2 -= GADGET_SIZE;
w->minimize.x = tx2 + 1;
w->minimize.y = y1;
w->minimize.x2 = tx2 + GADGET_SIZE;
w->minimize.y2 = ty2 - 1;
surfaceBoxFilled(w->minimize.x + 1, w->minimize.y + 1, w->minimize.x2 - 1, w->minimize.y2 - 1, titleBackgroundColor);
surfaceBoxHighlight(w->minimize.x + 7, w->minimize.y + 7, w->minimize.x2 - 7, w->minimize.y2 - 7, GUI_WHITE, gadgetColor);
surfaceBoxHighlight(w->minimize.x, w->minimize.y, w->minimize.x2, w->minimize.y2, GUI_WHITE, GUI_BLACK);
} else {
w->minimize.x = w->minimize.y = w->minimize.x2 = w->maximize.y2 = 0;
}
// Draw titlebar background.
w->titlebar.x = tx1;
w->titlebar.y = y1;
w->titlebar.x2 = tx2;
w->titlebar.y2 = ty2 - 1;
surfaceBoxFilled(w->titlebar.x + 1, w->titlebar.y + 1, w->titlebar.x2 - 1, w->titlebar.y2 - 1, titleBackgroundColor);
if (w->title) {
// Prepare font. Don't allow color changes in titles.
fontSet(__guiFontVGA8x16);
fontColorSet(GUI_WHITE, titleBackgroundColor);
fontModsEnabledSet(0);
// How many characters do we have room for?
i = ((w->titlebar.x2 - w->titlebar.x) >> 3) - 2;
// Does the title fit?
if ((int16_t)strlen(w->title) <= i) {
// Render entire title.
fontRender(w->title, w->titlebar.x + 12, w->titlebar.y + 2);
} else {
// Render partial title.
c = w->title[i];
w->title[i] = 0;
fontRender(w->title, w->titlebar.x + 12, w->titlebar.y + 2);
w->title[i] = c;
}
}
surfaceBoxHighlight(w->titlebar.x, w->titlebar.y, w->titlebar.x2, w->titlebar.y2, GUI_WHITE, GUI_BLACK);
y1 += GADGET_SIZE;
} else {
w->close.x = w->close.y = w->close.x2 = w->close.y2 = 0;
w->maximize.x = w->maximize.y = w->maximize.x2 = w->maximize.y2 = 0;
w->minimize.x = w->minimize.y = w->minimize.x2 = w->maximize.y2 = 0;
w->titlebar.x = w->titlebar.y = w->titlebar.x2 = w->titlebar.y2 = 0;
}
// Resize handle.
if (w->flags & WIN_RESIZE) {
w->resize1.x2 = w->base.r.x + w->base.r.w - 2;
w->resize1.x = w->resize1.x2 - 3;
w->resize1.y2 = w->base.r.y + w->base.r.h;
w->resize1.y = w->resize1.y2 - (GADGET_SIZE + 5);
surfaceLineH(w->resize1.x, w->resize1.x2, w->resize1.y, GUI_BLACK);
surfaceLineH(w->resize1.x, w->resize1.x2, w->resize1.y + 1, GUI_WHITE);
w->resize2.x2 = w->base.r.x + w->base.r.w;
w->resize2.x = w->resize2.x2 - (GADGET_SIZE + 5);
w->resize2.y2 = w->base.r.y + w->base.r.h - 2;
w->resize2.y = w->resize2.y2 - 3;
surfaceLineV(w->resize2.x, w->resize2.y, w->resize2.y2, GUI_BLACK);
surfaceLineV(w->resize2.x + 1, w->resize2.y, w->resize2.y2, GUI_WHITE);
} else {
w->resize1.x = w->resize1.y = w->resize1.x2 = w->resize1.y2 = 0;
w->resize2.x = w->resize2.y = w->resize2.x2 = w->resize2.y2 = 0;
}
// Inside window frame.
w->bounds.x = x1;
w->bounds.y = y1;
w->bounds.x2 = x2;
w->bounds.y2 = y2;
// Resize scrollable to fill window.
if (w->scroll) {
w->scroll->base.r.x = x1;
w->scroll->base.r.y = y1;
scrollableWidthSet(w->scroll, x2 - x1);
scrollableHeightSet(w->scroll, y2 - y1);
}
// Fixup all the widget coordinates.
windowMove(w, originalX, originalY);
} // Redraw window.
// Draw contents.
widgetChildrenPaint(W(w));
surfaceSet(target);
}
// Passing "flags" as a default int provides proper alignment for the following va_args list.
WindowT *windowCreate(uint16_t x, uint16_t y, uint16_t w, uint16_t h, char *title, int flags, ...) {
PointT visibleSize;
PointT totalSize;
va_list args;
WindowT *win = NULL;
int8_t sflags = SCROLLABLE_NONE;
NEW(WindowT, win);
memset(win, 0, sizeof(WindowT));
widgetBaseSet((WidgetT *)win, __MAGIC_WINDOW, w, h);
win->base.r.x = x;
win->base.r.y = y;
win->title = strdup(title);
win->flags = (uint8_t)flags;
logWrite("windowCreate: %dx%d\n", win->base.r.w, win->base.r.h);
// Cache the window so we get valid bounds values.
windowCache(win, 1);
visibleSize.x = win->bounds.x2 - win->bounds.x + 1;
visibleSize.y = win->bounds.y2 - win->bounds.y + 1;
// If the window is resizable, we need to get two more arguments for the content size.
if (win->flags & WIN_RESIZE) {
va_start(args, flags);
totalSize.x = va_arg(args, int);
totalSize.y = va_arg(args, int);
va_end(args);
sflags = SCROLLABLE_STANDARD;
} else {
// Use whatever the default content area is. This causes an extra draw on create. Oh well.
totalSize = visibleSize;
widgetDirtySet(W(win), 1);
if (flags & WIN_SCROLL_H) sflags |= SCROLLABLE_SCROLL_H;
if (flags & WIN_SCROLL_V) sflags |= SCROLLABLE_SCROLL_V;
}
win->scroll = scrollableCreate(visibleSize.x, visibleSize.y, totalSize.x, totalSize.y, sflags);
win->scroll->base.flags |= WIDGET_IS_WINDOW;
widgetAdd(W(win), 0, 0, W(win->scroll));
// Add to window list.
arrput(_windowList, win);
// Focus us.
windowFocusSet(win);
return win;
}
static void windowDestroy(struct WidgetS *widget, ...) {
uint16_t i;
uint16_t c;
WindowT *window = (WindowT *)widget;
// Remove it from the window list, if it exists there.
for (i=0; i<arrlen(_windowList); i++) {
if (window == _windowList[i]) {
_windowList[i] = NULL;
arrdel(_windowList, i);
break;
}
}
// Was it the focused window?
if (window == _windowTop) {
// Find new topmost window on next call to focus.
_windowTop = NULL;
}
// Free the title.
if (window->title) DEL(window->title);
// Free children.
for (c=0; c<arrlen(window->base.children); c++) widgetDestroy(window->base.children[c]);
arrfree(window->base.children);
// Free cached surface.
if (window->cached) surfaceDestroy(&window->cached);
// Delete the window.
DEL(window);
// Fixup focus.
windowFocusSet(NULL);
}
void windowFocusSet(WindowT *win) {
int16_t i;
// Do we have a focused window at the moment?
if (!_windowTop || !win) {
_windowTop = NULL;
// If there's a list of windows, use the topmost non-minimized.
if (arrlen(_windowList) > 0) {
// Work backwards through the window list.
i = arrlen(_windowList) - 1;
for (; i>=0; i--) {
if (_windowList[i]->flags & WIN_IS_ICON) {
// Minimized window. Skip it.
continue;
} else {
// Open window. Use as new top.
_windowTop = _windowList[i];
widgetDirtySet(W(_windowTop), 1);
break;
}
}
}
}
// Did they even pass in a window? If not, we're just intended to fixup _windowTop.
if (!win) return;
// Were we already the topmost window?
if (win == _windowTop) return;
// Is this window minimized?
if (win->flags & WIN_IS_ICON) {
// Can't receive focus - find new top.
windowFocusSet(NULL);
return;
}
// Mark old focus and new focus dirty to repaint title bar.
widgetDirtySet(W(win), 1);
widgetDirtySet(W(_windowTop), 1);
// Change who has focus.
_windowTop = win;
// Reorder window list.
i = arrlen(_windowList) - 2;
if (i >= 0) {
for (; i>=0; i--) {
if (_windowList[i] == win) {
arrdel(_windowList, i);
arrput(_windowList, win);
break;
}
}
}
}
void windowMaximizeRestore(WindowT *win) {
int16_t i;
// Are we maximized?
if (win->flags & WIN_IS_MAX) {
// Restore to previous size.
win->flags &= ~WIN_IS_MAX;
win->base.r = win->restore;
} else { // Maximized?
// Maximize window. Reposition if needed.
win->flags |= WIN_IS_MAX;
// Remember current size.
win->restore = win->base.r;
// Expand to full contents.
win->base.r.w = (win->base.r.w - (win->bounds.x2 - win->bounds.x) + win->scroll->original.x);
win->base.r.h = (win->base.r.h - (win->bounds.y2 - win->bounds.y) + win->scroll->original.y);
// Does this go off the screen to the right?
if (win->base.r.x + win->base.r.w >= videoDisplayWidthGet()) {
// Can we move it left to fit?
i = videoDisplayWidthGet() - (win->base.r.x + win->base.r.w);
if (i <= win->base.r.x) {
// Yes, go left!
win->base.r.x += i;
} else {
// Nope. Go all the way left and resize to fit on desktop.
win->base.r.x = 0;
win->base.r.w = videoDisplayWidthGet();
}
}
// Does this go off the screen to the bottom?
if (win->base.r.y + win->base.r.h >= videoDisplayHeightGet()) {
// Can we move it up to fit?
i = videoDisplayHeightGet() - (win->base.r.y + win->base.r.h);
if (i <= win->base.r.y) {
// Yes, go up!
win->base.r.y += i;
} else {
// Nope. Go all the way up and resize to fit on desktop.
win->base.r.y = 0;
win->base.r.h = videoDisplayHeightGet();
}
}
} // Maximized?
// Update.
windowCache(win, 1);
}
void windowMinimize(WindowT *win) {
// Minimize.
win->flags |= WIN_IS_ICON;
// Find topmost non-minimized window.
windowFocusSet(NULL);
}
void windowMove(WindowT *w, uint16_t x, uint16_t y) {
int16_t dx = x - w->base.r.x;
int16_t dy = y - w->base.r.y;
// This is all because we draw the window at 0,0 in order to cache it.
// To keep the coordinates correct, we have to adjust them all when
// the window is moved.
if (w->title || w->flags & WIN_CLOSE || w->flags & WIN_MAXIMIZE || w->flags & WIN_MINIMIZE) {
if (w->flags & WIN_CLOSE) {
w->close.x += dx;
w->close.y += dy;
w->close.x2 += dx;
w->close.y2 += dy;
}
if (w->flags & WIN_MAXIMIZE) {
w->maximize.x += dx;
w->maximize.y += dy;
w->maximize.x2 += dx;
w->maximize.y2 += dy;
}
if (w->flags & WIN_MINIMIZE) {
w->minimize.x += dx;
w->minimize.y += dy;
w->minimize.x2 += dx;
w->minimize.y2 += dy;
}
w->titlebar.x += dx;
w->titlebar.y += dy;
w->titlebar.x2 += dx;
w->titlebar.y2 += dy;
}
if (w->flags & WIN_RESIZE) {
w->resize1.x += dx;
w->resize1.y += dy;
w->resize1.x2 += dx;
w->resize1.y2 += dy;
w->resize2.x += dx;
w->resize2.y += dy;
w->resize2.x2 += dx;
w->resize2.y2 += dy;
}
w->bounds.x += dx;
w->bounds.y += dy;
w->bounds.x2 += dx;
w->bounds.y2 += dy;
w->base.r.x = x;
w->base.r.y = y;
}
static void windowPaint(struct WidgetS *widget, ...) {
int16_t x = 0;
int16_t y;
int16_t px;
int16_t py;
int16_t xi;
int16_t yi;
int16_t xc;
int16_t yc;
WindowT *w = (WindowT *)widget;
// Does the window itself need redrawn?
if (widgetDirtyGet(widget)) {
widgetDirtySet(widget, 0);
x = 1; // This is a flag for later that we need to update the cached surface.
}
// Did a widget or the window need redrawn?
if (x || widgetChildrenDirty(widget)) windowCache(w, x);
// Are we minimized?
if (w->flags & WIN_IS_ICON) {
// Draw iconized version of contents. There are too many counters here but it's the only way it worked reliably.
yi = (surfaceHeightGet(w->scroll->area) - 1) / ICON_SIZE;
xi = (surfaceWidthGet(w->scroll->area) - 1) / ICON_SIZE;
py = videoDisplayHeightGet() - ICON_SIZE;
y = 0;
x = 0;
for (yc=0; yc<ICON_SIZE; yc++) {
y += yi;
px = (ICON_SIZE + 1) * _iconCount;
for (xc=0; xc<ICON_SIZE; xc++) {
x += xi;
surfacePixelSet(px++, py, surfacePixelGet(w->scroll->area, x, y));
}
py++;
}
_iconCount++;
} else {
// By now we have a valid cached window. Blit it.
surfaceBlit(w->base.r.x, w->base.r.y, 0, 0, 0, 0, w->cached);
}
}
RegisterT *windowRegister(uint8_t magic) {
static RegisterT reg = {
"Window",
NULL, // Click event is special for windows.
windowDestroy, // Destroy.
windowPaint, // Paint.
NULL // Unregister.
};
// One-time widget startup code.
__MAGIC_WINDOW = magic;
return &reg;
}
void windowResize(WindowT *win, uint16_t width, uint16_t height) {
int16_t x = 0;
int16_t y = 0;
int16_t content = 0;
PointT chrome;
// Find size of window chrome.
chrome.x = win->base.r.w - (win->bounds.x2 - win->bounds.x) + 1;
chrome.y = win->base.r.h - (win->bounds.y2 - win->bounds.y) + 1;
// Too small?
if (width < (GADGET_SIZE * 4) + chrome.x) width = (GADGET_SIZE * 4) + chrome.x;
if (height < (GADGET_SIZE * 4) + chrome.y) height = (GADGET_SIZE * 4) + chrome.y;
// Too big? Horizontal.
// Get the current view offset.
x = scrollableValueHGet(win->scroll);
// Width of content area of window.
content = win->bounds.x2 - win->bounds.x + 1;
// Do we need to take the height of the vertical scroll bar into account?
if (win->scroll->scrollv) content -= win->scroll->scrollv->base.r.w;
// Clamp.
x = win->base.r.w - content + surfaceWidthGet(win->scroll->area);
if (width > x) width = x;
// Too big? Vertical.
// Get the current view offset.
y = scrollableValueVGet(win->scroll);
// Height of content area of window.
content = win->bounds.y2 - win->bounds.y + 1;
// Do we need to take the height of the horizontal scroll bar into account?
if (win->scroll->scrollh) content -= win->scroll->scrollh->base.r.h;
// Clamp.
y = win->base.r.h - content + surfaceHeightGet(win->scroll->area);
if (height > y) height = y;
// Did the size change?
if (win->base.r.w != width || win->base.r.h !=height) {
// Yes! If we were maximized, clear it - can't restore after resizing.
win->flags &= ~WIN_IS_MAX;
// Do resize.
win->base.r.w = width;
win->base.r.h =height;
widgetDirtySet(W(win), 1);
}
}
void wmShutdown(void) {
uint16_t i;
// Delete all windows.
for (i=0; i<arrlen(_windowList); i++) widgetDestroy(W(_windowList[i]));
arrfree(_windowList);
}
void wmStartup(void) {
// Nada
}
void wmUpdate(EventT *event) {
int16_t i;
int16_t windowLocalX;
int16_t windowLocalY;
int16_t x2;
int16_t y2;
uint8_t onResize = 0;
WidgetT *widget;
WidgetT *widgetOver = NULL;
WindowT *win = NULL;
ClickRawInputT rawEvent = { 0 };
static uint8_t resizing = 0;
static PointT resizeOffset = { 0 };
static uint8_t dragging = 0;
static PointT dragOffset = { 0 };
static WidgetT *widgetDown = NULL;
// Do we have windows?
if (arrlen(_windowList) > 0) {
// Mouse is always the default pointer unless something changes it for this frame.
guiMousePointerSet(MOUSE_POINTER);
// Paint all windows.
_iconCount = 0;
for (i=0; i<arrlen(_windowList); i++) {
surfaceSet(__guiBackBuffer);
widget = (WidgetT *)_windowList[i];
widget->reg->paint(widget);
}
// Get top window.
win = _windowTop;
// If we found a window, get right/bottom and widget.
if (win) {
// Find window outer bounds.
x2 = win->base.r.x + win->base.r.w - 1;
y2 = win->base.r.y + win->base.r.h - 1;
// Are we over a widget?
widgetOver = wuWidgetUnderMouseGet(win, event, &windowLocalX, &windowLocalY);
} else {
x2 = 0;
y2 = 0;
widgetOver = NULL;
}
// If we're over a widget that has raw input processing enabled and we haven't started a click on another widget yet, send to target for raw processing.
if (widgetOver && !widgetDown && !dragging && !resizing && (widgetOver->flags & WIDGET_RAW_INPUT) && (event->buttons != 0)) {
if (widgetOver->reg->click) {
rawEvent.event = event;
rawEvent.data = widget->data;
//logWrite("Firing raw click for %s\n", widgetOver->reg->widgetName);
widgetOver->reg->click(widgetOver, windowLocalX - widgetOver->r.x, windowLocalY - widgetOver->r.y, CLICK_RAW_INPUT, &rawEvent);
}
return;
}
// Wrap left button processing with a 'for' so we can 'break' out of it.
for (;;) {
// Does the topmost window have a resize?
if (win && win->flags & WIN_RESIZE) {
// Are we over the resize of the topmost window?
if ((event->x <= win->resize1.x2 && event->x >= win->resize1.x && event->y <= win->resize1.y2 && event->y >= win->resize1.y) ||
(event->x <= win->resize2.x2 && event->x >= win->resize2.x && event->y <= win->resize2.y2 && event->y >= win->resize2.y) ||
resizing) {
onResize = 1;
guiMousePointerSet(MOUSE_RESIZE);
}
}
// Is the left mouse button down?
if (event->buttons & BUTTON_LEFT) {
// DEBUG - draw active regions.
surfaceSet(__guiBackBuffer);
surfaceBox(win->base.r.x, win->base.r.y, x2, y2, GUI_YELLOW);
surfaceBox(win->bounds.x, win->bounds.y, win->bounds.x2, win->bounds.y2, GUI_YELLOW);
surfaceBox(win->close.x, win->close.y, win->close.x2, win->close.y2, GUI_LIGHTBLUE);
surfaceBox(win->titlebar.x, win->titlebar.y, win->titlebar.x2, win->titlebar.y2, GUI_LIGHTCYAN);
surfaceBox(win->minimize.x, win->minimize.y, win->minimize.x2, win->minimize.y2, GUI_LIGHTGREEN);
surfaceBox(win->maximize.x, win->maximize.y, win->maximize.x2, win->maximize.y2, GUI_RED);
/*
surfaceBox(win->scrollv.x, win->scrollv.y, win->scrollv.x2, win->scrollv.y2, GUI_BLUE);
surfaceBox(win->scrollh.x, win->scrollh.y, win->scrollh.x2, win->scrollh.y2, GUI_BROWN);
*/
// Are we currently dragging?
if (dragging) {
// Move window to new mouse location.
windowMove(win, event->x - dragOffset.x, event->y - dragOffset.y);
break;
} else { // Dragging.
if (resizing) {
// If they try to resize outside the window bounds, cancel it.
if (event->x < win->base.r.x || event->y < win->base.r.y) break;
// Resize it.
windowResize(win, event->x - resizeOffset.x, event->y - resizeOffset.y);
break;
} else { // Resizing.
// Did the button just go down?
if (event->flags & EVENT_FLAG_LEFT_DOWN) {
// Are we on the topmost window?
if (win && event->x <= x2 && event->x >= win->base.r.x && event->y <= y2 && event->y >= win->base.r.y) {
// Are we on the resizing area?
if (onResize) {
resizing = 1;
resizeOffset.x = event->x - win->base.r.w;
resizeOffset.y = event->y - win->base.r.h;
break;
}
// Are we over a widget?
widget = wuWidgetUnderMouseGet(win, event, &windowLocalX, &windowLocalY);
if (widget) {
if (widget->reg->click) widget->reg->click(widget, windowLocalX - widget->r.x, windowLocalY - widget->r.y, CLICK_LEFT_DOWN, widget->data);
widgetDown = widget;
}
// Are we inside the close button? Does not include button frame on purpose.
if (win->flags & WIN_CLOSE && event->x < win->close.x2 && event->x > win->close.x && event->y < win->close.y2 && event->y > win->close.y) {
//***TODO*** Close.
}
// Are we inside the title bar to begin dragging? Does not include titlebar frame on purpose.
if (event->x < win->titlebar.x2 && event->x > win->titlebar.x && event->y < win->titlebar.y2 && event->y > win->titlebar.y) {
dragging = 1;
dragOffset.x = event->x - win->base.r.x;
dragOffset.y = event->y - win->base.r.y;
break;
}
// Are we inside the minimize button? Does not include button frame on purpose.
if (win->flags & WIN_MINIMIZE &&event->x < win->minimize.x2 && event->x > win->minimize.x && event->y < win->minimize.y2 && event->y > win->minimize.y) {
windowMinimize(win);
break;
}
// Are we inside the maximize button? Does not include button frame on purpose.
if (win->flags & WIN_MAXIMIZE &&event->x < win->maximize.x2 && event->x > win->maximize.x && event->y < win->maximize.y2 && event->y > win->maximize.y) {
windowMaximizeRestore(win);
break;
}
} else { // On topmost window.
// Not over topmost window. Search backwards to find first window we're inside.
i = arrlen(_windowList) - 1;
if (i >= 0) {
for (; i>=0; i--) {
win = _windowList[i];
// Is this the current window?
if (win == _windowTop) continue;
// Is this window minimized?
if (win->flags & WIN_IS_ICON) continue;
// Get right/bottom of window.
x2 = win->base.r.x + win->base.r.w - 1;
y2 = win->base.r.y + win->base.r.h - 1;
// Inside this window?
if (event->x <= x2 && event->x >= win->base.r.x && event->y <= y2 && event->y >= win->base.r.y) {
// Bring this window forward.
windowFocusSet(win);
// If we happened to be in the title bar, go ahead and start dragging. Does not include titlebar frame on purpose.
if (event->x < win->titlebar.x2 && event->x > win->titlebar.x && event->y < win->titlebar.y2 && event->y > win->titlebar.y) {
dragging = 1;
dragOffset.x = event->x - win->base.r.x;
dragOffset.y = event->y - win->base.r.y;
}
break;
}
}
}
} // On topmost window.
// Do we have minimized windows?
if (_iconCount > 0) {
// Are we inside the minimized icons area?
if (event->x < (ICON_SIZE + 1) * _iconCount && event->y >= videoDisplayHeightGet() - ICON_SIZE) {
// Figure out which icon we're over.
x2 = 0;
for (i=0; i<arrlen(_windowList); i++) {
if (_windowList[i]->flags & WIN_IS_ICON) {
x2 += ICON_SIZE;
// This window?
if (event->x < x2) {
// Restore it and stop looking for icons.
_windowList[i]->flags &= ~WIN_IS_ICON;
windowFocusSet(_windowList[i]);
break;
}
// Next! (Skips pixel between icons.)
x2++;
}
}
} // Minimized windows?
}
} else { // Button just went down / button being held down.
// Nada
} // Left button held down.
} // Resizing.
} // Dragging.
} else { // Left mouse button.
// Left mouse not down.
// Can no longer be dragging.
dragging = 0;
// Can no longer be resizing.
resizing = 0;
// Did the left button just come up?
if (event->flags & EVENT_FLAG_LEFT_UP) {
// Did this click begin over a widget?
if (widgetDown) {
// Are we still inside the window content?
if (event->x <= win->bounds.x2 && event->x >= win->bounds.x && event->y <= win->bounds.y2 && event->y >= win->bounds.y) {
// Are we stll over the same widget?
widget = wuWidgetUnderMouseGet(win, event, &windowLocalX, &windowLocalY);
// Send to widget for processing or send cancel. Convert mouse to widget-local coordinates.
if (widgetDown->reg->click) widgetDown->reg->click(widgetDown, windowLocalX - widgetDown->r.x, windowLocalY - widgetDown->r.y, (widget == widgetDown) ? CLICK_LEFT_UP : CLICK_LEFT_CANCEL, widgetDown->data);
}
widgetDown = NULL;
} // widgetDown
}
}
break;
} // Left button processing.
} // Do we have windows?
}
static WidgetT *wuWidgetUnderMouseGet(WindowT *win, EventT *event, int16_t *localX, int16_t *localY) {
int16_t i;
int16_t x2;
int16_t y2;
PointT offset;
WidgetT *widget;
// If we have scrollbars, get the offset for the view.
if (win->scroll->scrollh) offset.x = hscrollValueGet(win->scroll->scrollh);
if (win->scroll->scrollv) offset.y = vscrollValueGet(win->scroll->scrollv);
// Find window bounds.
x2 = win->base.r.x + win->base.r.w - 1;
y2 = win->base.r.y + win->base.r.h - 1;
// Are we over the provided window?
if (event->x <= x2 && event->x >= win->base.r.x && event->y <= y2 && event->y >= win->base.r.y) {
// Find window-local mouse coordinates.
*localX = event->x - win->base.r.x;// + offset.x;
*localY = event->y - win->base.r.y;// + offset.y;
// Find widget under mouse.
for (i=0; i<arrlen(win->base.children); i++) {
widget = win->base.children[i];
if (*localX >= widget->r.x && *localX < widget->r.x + widget->r.w && *localY >= widget->r.y && *localY < widget->r.y + widget->r.h) {
//logWrite("Over %s\n", widget->reg->widgetName);
// Return this widget.
return widget;
}
}
}
return NULL;
}
WindowT *wmWindowOnTopGet(void) {
return _windowTop;
}