diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index b034276..7ee1df3 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -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 // ============================================================ @@ -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 // ============================================================ diff --git a/dvx/dvxApp.h b/dvx/dvxApp.h index a218fd9..c2b1158 100644 --- a/dvx/dvxApp.h +++ b/dvx/dvxApp.h @@ -118,6 +118,18 @@ void dvxFreeAccelTable(AccelTableT *table); // Add an entry to an accelerator table 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 void dvxClipboardCopy(const char *text, int32_t len); diff --git a/dvxdemo/demo.c b/dvxdemo/demo.c index bbebae3..afb904a 100644 --- a/dvxdemo/demo.c +++ b/dvxdemo/demo.c @@ -32,7 +32,11 @@ #define CMD_VIEW_SIZE_SMALL 307 #define CMD_VIEW_SIZE_MED 308 #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_COPY 501 #define CMD_CTX_PASTE 502 @@ -205,6 +209,22 @@ static void onMenuCb(WindowT *win, int32_t menuId) { setupControlsWindow(ctx); 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: dvxMessageBox(sCtx, "About DV/X Demo", "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"); if (helpMenu) {