Window tiling/cascading

This commit is contained in:
Scott Duensing 2026-03-16 18:01:02 -05:00
parent 7129035bed
commit b5488a6a9e
3 changed files with 354 additions and 1 deletions

View file

@ -1039,6 +1039,80 @@ const char *dvxClipboardGet(int32_t *outLen) {
} }
// ============================================================
// dvxCascadeWindows
// ============================================================
//
// Arrange all visible, non-minimized windows in a staggered
// diagonal pattern from the top-left corner.
void dvxCascadeWindows(AppContextT *ctx) {
int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height;
int32_t offsetX = 0;
int32_t offsetY = 0;
int32_t step = CHROME_TITLE_HEIGHT + CHROME_BORDER_WIDTH;
// Default cascade size: 2/3 of screen
int32_t winW = screenW * 2 / 3;
int32_t winH = screenH * 2 / 3;
if (winW < MIN_WINDOW_W) {
winW = MIN_WINDOW_W;
}
if (winH < MIN_WINDOW_H) {
winH = MIN_WINDOW_H;
}
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->minimized || !win->visible) {
continue;
}
// Un-maximize if needed
if (win->maximized) {
win->maximized = false;
}
// Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = offsetX;
win->y = offsetY;
win->w = winW;
win->h = winH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
// Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
offsetX += step;
offsetY += step;
// Wrap around if we'd go off screen
if (offsetX + winW > screenW || offsetY + winH > screenH) {
offsetX = 0;
offsetY = 0;
}
}
}
// ============================================================ // ============================================================
// dvxCreateAccelTable // dvxCreateAccelTable
// ============================================================ // ============================================================
@ -1347,6 +1421,244 @@ int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) {
} }
// ============================================================
// dvxTileWindows
// ============================================================
//
// Tile all visible, non-minimized windows in a grid pattern.
// Columns = ceil(sqrt(count)), rows = ceil(count / cols).
// Last row may have fewer windows, which are widened to fill.
void dvxTileWindows(AppContextT *ctx) {
int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height;
// Count eligible windows
int32_t count = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (!win->minimized && win->visible) {
count++;
}
}
if (count == 0) {
return;
}
// Integer ceil(sqrt(count)) for column count
int32_t cols = 1;
while (cols * cols < count) {
cols++;
}
int32_t rows = (count + cols - 1) / cols;
int32_t tileW = screenW / cols;
int32_t tileH = screenH / rows;
if (tileW < MIN_WINDOW_W) {
tileW = MIN_WINDOW_W;
}
if (tileH < MIN_WINDOW_H) {
tileH = MIN_WINDOW_H;
}
int32_t slot = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->minimized || !win->visible) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
int32_t row = slot / cols;
int32_t col = slot % cols;
// Last row: fewer windows get wider tiles
int32_t remaining = count - row * cols;
int32_t rowCols = (remaining < cols) ? remaining : cols;
int32_t cellW = screenW / rowCols;
win->x = col * cellW;
win->y = row * tileH;
win->w = cellW;
win->h = tileH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
slot++;
}
}
// ============================================================
// dvxTileWindowsH
// ============================================================
//
// Tile all visible, non-minimized windows horizontally:
// side by side left to right, each window gets full screen height.
void dvxTileWindowsH(AppContextT *ctx) {
int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height;
// Count eligible windows
int32_t count = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (!win->minimized && win->visible) {
count++;
}
}
if (count == 0) {
return;
}
int32_t tileW = screenW / count;
if (tileW < MIN_WINDOW_W) {
tileW = MIN_WINDOW_W;
}
int32_t slot = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->minimized || !win->visible) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = slot * tileW;
win->y = 0;
win->w = tileW;
win->h = screenH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
slot++;
}
}
// ============================================================
// dvxTileWindowsV
// ============================================================
//
// Tile all visible, non-minimized windows vertically:
// stacked top to bottom, each window gets full screen width.
void dvxTileWindowsV(AppContextT *ctx) {
int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height;
// Count eligible windows
int32_t count = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (!win->minimized && win->visible) {
count++;
}
}
if (count == 0) {
return;
}
int32_t tileH = screenH / count;
if (tileH < MIN_WINDOW_H) {
tileH = MIN_WINDOW_H;
}
int32_t slot = 0;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->minimized || !win->visible) {
continue;
}
if (win->maximized) {
win->maximized = false;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->x = 0;
win->y = slot * tileH;
win->w = screenW;
win->h = tileH;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
slot++;
}
}
// ============================================================ // ============================================================
// executeSysMenuCmd // executeSysMenuCmd
// ============================================================ // ============================================================

View file

@ -118,6 +118,18 @@ void dvxFreeAccelTable(AccelTableT *table);
// Add an entry to an accelerator table // Add an entry to an accelerator table
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId); void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId);
// Arrange windows in a staggered diagonal cascade pattern
void dvxCascadeWindows(AppContextT *ctx);
// Tile windows in a grid pattern
void dvxTileWindows(AppContextT *ctx);
// Tile windows horizontally (side by side left to right, full height)
void dvxTileWindowsH(AppContextT *ctx);
// Tile windows vertically (stacked top to bottom, full width)
void dvxTileWindowsV(AppContextT *ctx);
// Copy text to the shared clipboard // Copy text to the shared clipboard
void dvxClipboardCopy(const char *text, int32_t len); void dvxClipboardCopy(const char *text, int32_t len);

View file

@ -32,7 +32,11 @@
#define CMD_VIEW_SIZE_SMALL 307 #define CMD_VIEW_SIZE_SMALL 307
#define CMD_VIEW_SIZE_MED 308 #define CMD_VIEW_SIZE_MED 308
#define CMD_VIEW_SIZE_LARGE 309 #define CMD_VIEW_SIZE_LARGE 309
#define CMD_HELP_ABOUT 400 #define CMD_WIN_CASCADE 350
#define CMD_WIN_TILE_H 351
#define CMD_WIN_TILE_V 352
#define CMD_WIN_TILE 353
#define CMD_HELP_ABOUT 400
#define CMD_CTX_CUT 500 #define CMD_CTX_CUT 500
#define CMD_CTX_COPY 501 #define CMD_CTX_COPY 501
#define CMD_CTX_PASTE 502 #define CMD_CTX_PASTE 502
@ -205,6 +209,22 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
setupControlsWindow(ctx); setupControlsWindow(ctx);
break; break;
case CMD_WIN_CASCADE:
dvxCascadeWindows(ctx);
break;
case CMD_WIN_TILE_H:
dvxTileWindowsH(ctx);
break;
case CMD_WIN_TILE_V:
dvxTileWindowsV(ctx);
break;
case CMD_WIN_TILE:
dvxTileWindows(ctx);
break;
case CMD_HELP_ABOUT: case CMD_HELP_ABOUT:
dvxMessageBox(sCtx, "About DV/X Demo", dvxMessageBox(sCtx, "About DV/X Demo",
"DV/X GUI Demonstration\n\n" "DV/X GUI Demonstration\n\n"
@ -855,6 +875,15 @@ static void setupMainWindow(AppContextT *ctx) {
} }
} }
MenuT *winMenu = wmAddMenu(bar, "&Window");
if (winMenu) {
wmAddMenuItem(winMenu, "&Cascade", CMD_WIN_CASCADE);
wmAddMenuItem(winMenu, "Tile &Grid", CMD_WIN_TILE);
wmAddMenuItem(winMenu, "Tile &Horizontal", CMD_WIN_TILE_H);
wmAddMenuItem(winMenu, "Tile &Vertical", CMD_WIN_TILE_V);
}
MenuT *helpMenu = wmAddMenu(bar, "&Help"); MenuT *helpMenu = wmAddMenu(bar, "&Help");
if (helpMenu) { if (helpMenu) {