WrapBox added.
This commit is contained in:
parent
7bc92549f7
commit
7cd7388607
11 changed files with 513 additions and 44 deletions
|
|
@ -33,6 +33,8 @@
|
|||
#include "shellInfo.h"
|
||||
#include "dvxResource.h"
|
||||
#include "widgetImageButton.h"
|
||||
#include "widgetScrollPane.h"
|
||||
#include "widgetWrapBox.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
|
@ -42,18 +44,16 @@
|
|||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include "dvxMem.h"
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
// 64 entries is generous; limited by screen real estate before this cap
|
||||
#define MAX_APP_FILES 64
|
||||
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
||||
#define MAX_PATH_LEN 260
|
||||
// Grid layout for app buttons: 4 columns, rows created dynamically
|
||||
#define PM_TOOLTIP_LEN 128
|
||||
#define PM_GRID_COLS 4
|
||||
#define PM_BTN_W 100
|
||||
#define PM_BTN_H 24
|
||||
#define PM_CELL_W 80
|
||||
|
|
@ -102,7 +102,7 @@ static WidgetT *sStatusLabel = NULL;
|
|||
static PrefsHandleT *sPrefs = NULL;
|
||||
static bool sMinOnRun = false;
|
||||
static bool sRestoreAlone = false;
|
||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
||||
static AppEntryT *sAppFiles = NULL; // stb_ds dynamic array
|
||||
static int32_t sAppCount = 0;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -196,18 +196,19 @@ static void buildPmWindow(void) {
|
|||
if (sAppCount == 0) {
|
||||
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
||||
} else {
|
||||
// Build grid of app icons. Each cell is a VBox with an image
|
||||
// button (or text button if no icon) and a label underneath.
|
||||
WidgetT *hbox = NULL;
|
||||
// ScrollPane provides scrollbars when icons overflow.
|
||||
// WrapBox inside flows cells left-to-right, wrapping to
|
||||
// the next row when the window width is exceeded.
|
||||
WidgetT *scroll = wgtScrollPane(appFrame);
|
||||
scroll->weight = 100;
|
||||
|
||||
WidgetT *wrap = wgtWrapBox(scroll);
|
||||
wrap->weight = 100;
|
||||
wrap->spacing = wgtPixels(PM_GRID_SPACING);
|
||||
wrap->align = AlignCenterE;
|
||||
|
||||
for (int32_t i = 0; i < sAppCount; i++) {
|
||||
if (i % PM_GRID_COLS == 0) {
|
||||
hbox = wgtHBox(appFrame);
|
||||
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
||||
hbox->align = AlignCenterE;
|
||||
}
|
||||
|
||||
WidgetT *cell = wgtVBox(hbox);
|
||||
WidgetT *cell = wgtVBox(wrap);
|
||||
cell->align = AlignCenterE;
|
||||
cell->minW = wgtPixels(PM_CELL_W);
|
||||
cell->minH = wgtPixels(PM_CELL_H);
|
||||
|
|
@ -232,8 +233,8 @@ static void buildPmWindow(void) {
|
|||
btn->prefH = wgtPixels(PM_BTN_H);
|
||||
}
|
||||
|
||||
btn->userData = &sAppFiles[i];
|
||||
btn->onClick = onAppButtonClick;
|
||||
btn->userData = &sAppFiles[i];
|
||||
btn->onClick = onAppButtonClick;
|
||||
|
||||
if (sAppFiles[i].tooltip[0]) {
|
||||
wgtSetTooltip(btn, sAppFiles[i].tooltip);
|
||||
|
|
@ -252,10 +253,6 @@ static void buildPmWindow(void) {
|
|||
sStatusLabel->weight = 100;
|
||||
updateStatusText();
|
||||
|
||||
// dvxFitWindow sizes the window to tightly fit the widget tree,
|
||||
// honoring preferred sizes. Without this, the window would use the
|
||||
// initial dimensions from dvxCreateWindow even if widgets don't fit.
|
||||
dvxFitWindow(sAc, sPmWindow);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -383,6 +380,13 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
// The apps/ path is relative to the working directory, which the shell sets
|
||||
// to the root of the DVX install before loading any apps.
|
||||
static void scanAppsDir(void) {
|
||||
// Free icons from previous scan
|
||||
for (int32_t i = 0; i < sAppCount; i++) {
|
||||
free(sAppFiles[i].iconData);
|
||||
}
|
||||
|
||||
arrfree(sAppFiles);
|
||||
sAppFiles = NULL;
|
||||
sAppCount = 0;
|
||||
scanAppsDirRecurse("apps");
|
||||
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||
|
|
@ -406,7 +410,7 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_FILES) {
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
// Skip . and ..
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
|
|
@ -432,20 +436,18 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
// Check for .app extension (case-insensitive)
|
||||
const char *ext = ent->d_name + len - 4;
|
||||
|
||||
if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) {
|
||||
if (strcasecmp(ext, ".app") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip ourselves
|
||||
if (strcmp(ent->d_name, "progman.app") == 0 || strcmp(ent->d_name, "PROGMAN.APP") == 0) {
|
||||
if (strcasecmp(ent->d_name, "progman.app") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppEntryT *entry = &sAppFiles[sAppCount];
|
||||
|
||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
||||
entry->iconData = NULL;
|
||||
entry->tooltip[0] = '\0';
|
||||
AppEntryT newEntry;
|
||||
memset(&newEntry, 0, sizeof(newEntry));
|
||||
snprintf(newEntry.path, sizeof(newEntry.path), "%s", fullPath);
|
||||
|
||||
// Default name from filename (without .app extension)
|
||||
int32_t nameLen = len - 4;
|
||||
|
|
@ -454,25 +456,28 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
nameLen = SHELL_APP_NAME_MAX - 1;
|
||||
}
|
||||
|
||||
memcpy(entry->name, ent->d_name, nameLen);
|
||||
entry->name[nameLen] = '\0';
|
||||
memcpy(newEntry.name, ent->d_name, nameLen);
|
||||
newEntry.name[nameLen] = '\0';
|
||||
|
||||
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
||||
entry->name[0] -= 32;
|
||||
if (newEntry.name[0] >= 'a' && newEntry.name[0] <= 'z') {
|
||||
newEntry.name[0] -= 32;
|
||||
}
|
||||
|
||||
// Override from embedded resources if available
|
||||
entry->iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &entry->iconW, &entry->iconH, &entry->iconPitch);
|
||||
newEntry.iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &newEntry.iconW, &newEntry.iconH, &newEntry.iconPitch);
|
||||
|
||||
if (!entry->iconData) {
|
||||
if (!newEntry.iconData) {
|
||||
dvxLog("Progman: no icon32 resource in %s", ent->d_name);
|
||||
}
|
||||
|
||||
dvxResLoadText(fullPath, "name", entry->name, SHELL_APP_NAME_MAX);
|
||||
dvxResLoadText(fullPath, "description", entry->tooltip, sizeof(entry->tooltip));
|
||||
dvxResLoadText(fullPath, "name", newEntry.name, SHELL_APP_NAME_MAX);
|
||||
dvxResLoadText(fullPath, "description", newEntry.tooltip, sizeof(newEntry.tooltip));
|
||||
|
||||
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
|
||||
sAppCount++;
|
||||
dvxLog("Progman: found %s (%s) icon=%s", newEntry.name, ent->d_name, newEntry.iconData ? "yes" : "no");
|
||||
arrput(sAppFiles, newEntry);
|
||||
sAppCount = (int32_t)arrlen(sAppFiles);
|
||||
|
||||
dvxUpdate(sAc);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
|
|
|||
|
|
@ -4063,6 +4063,20 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
|
|||
int32_t newW = win->widgetRoot->calcMinW + CHROME_TOTAL_SIDE * 2;
|
||||
int32_t newH = win->widgetRoot->calcMinH + topChrome + CHROME_TOTAL_BOTTOM;
|
||||
|
||||
// Ensure the window is at least as wide/tall as the WM minimum
|
||||
// (accounts for menu bar width, title bar gadgets, etc.)
|
||||
int32_t wmMinW;
|
||||
int32_t wmMinH;
|
||||
wmMinWindowSize(win, &wmMinW, &wmMinH);
|
||||
|
||||
if (newW < wmMinW) {
|
||||
newW = wmMinW;
|
||||
}
|
||||
|
||||
if (newH < wmMinH) {
|
||||
newH = wmMinH;
|
||||
}
|
||||
|
||||
dvxResizeWindow(ctx, win, newW, newH);
|
||||
}
|
||||
|
||||
|
|
|
|||
32
core/dvxWm.c
32
core/dvxWm.c
|
|
@ -101,7 +101,7 @@ static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
|
|||
static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size);
|
||||
// wmMinimizedIconPos declared in dvxWm.h
|
||||
static int32_t scrollbarThumbInfo(const ScrollbarT *sb, int32_t *thumbPos, int32_t *thumbSize);
|
||||
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -1957,7 +1957,7 @@ int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t
|
|||
// This function is called on every resize move event, so it must be cheap.
|
||||
// No allocations, no string operations -- just arithmetic on cached values.
|
||||
|
||||
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
||||
int32_t gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2;
|
||||
int32_t gadgetPad = GADGET_PAD;
|
||||
int32_t charW = FONT_CHAR_WIDTH;
|
||||
|
|
@ -1970,10 +1970,32 @@ static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
|||
|
||||
*minW = titleMinW + CHROME_BORDER_WIDTH * 2;
|
||||
|
||||
// Menu bar width: ensure window is wide enough to show all menu labels
|
||||
// Menu bar width: compute directly from label text (don't rely on
|
||||
// cached barX/barW which may not be computed yet before first paint).
|
||||
if (win->menuBar && win->menuBar->menuCount > 0) {
|
||||
MenuT *last = &win->menuBar->menus[win->menuBar->menuCount - 1];
|
||||
int32_t menuW = last->barX + last->barW + CHROME_TOTAL_SIDE;
|
||||
int32_t menuW = CHROME_TOTAL_SIDE;
|
||||
|
||||
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
|
||||
// Count visible characters (skip & accelerator prefix)
|
||||
const char *lbl = win->menuBar->menus[i].label;
|
||||
int32_t visChars = 0;
|
||||
|
||||
for (const char *c = lbl; *c; c++) {
|
||||
if (*c == '&' && *(c + 1) != '&' && *(c + 1) != '\0') {
|
||||
continue; // skip accelerator marker
|
||||
}
|
||||
|
||||
visChars++;
|
||||
}
|
||||
|
||||
menuW += visChars * charW + CHROME_TITLE_PAD * 2;
|
||||
|
||||
if (i < win->menuBar->menuCount - 1) {
|
||||
menuW += MENU_BAR_GAP;
|
||||
}
|
||||
}
|
||||
|
||||
menuW += CHROME_TOTAL_SIDE;
|
||||
|
||||
if (menuW > *minW) {
|
||||
*minW = menuW;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ MenuBarT *wmAddMenuBar(WindowT *win);
|
|||
// Free the menu bar and reclaim the content area.
|
||||
void wmDestroyMenuBar(WindowT *win);
|
||||
|
||||
// Get the minimum window size (accounts for chrome, gadgets, and menu bar).
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||
|
||||
// Append a dropdown menu to the menu bar. Returns the MenuT to populate
|
||||
// with items. The label supports & accelerator markers (e.g. "&File").
|
||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
|
||||
|
|
|
|||
|
|
@ -505,6 +505,27 @@ static void writeBmp(const char *path) {
|
|||
}
|
||||
|
||||
|
||||
static void drawWrapbox(void) {
|
||||
clear(192, 192, 192);
|
||||
box(2, 2, 20, 20, 0, 0, 180);
|
||||
// Row 1: three small boxes
|
||||
rect(4, 4, 5, 4, 200, 200, 255);
|
||||
box(4, 4, 5, 4, 0, 0, 180);
|
||||
rect(10, 4, 5, 4, 200, 200, 255);
|
||||
box(10, 4, 5, 4, 0, 0, 180);
|
||||
rect(16, 4, 5, 4, 200, 200, 255);
|
||||
box(16, 4, 5, 4, 0, 0, 180);
|
||||
// Row 2: two boxes (wrapped)
|
||||
rect(4, 10, 5, 4, 200, 200, 255);
|
||||
box(4, 10, 5, 4, 0, 0, 180);
|
||||
rect(10, 10, 5, 4, 200, 200, 255);
|
||||
box(10, 10, 5, 4, 0, 0, 180);
|
||||
// Row 3: one box
|
||||
rect(4, 16, 5, 4, 200, 200, 255);
|
||||
box(4, 16, 5, 4, 0, 0, 180);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: mkwgticon <output.bmp> <type>\n");
|
||||
|
|
@ -544,6 +565,7 @@ int main(int argc, char **argv) {
|
|||
{"separator", drawSeparator},
|
||||
{"spacer", drawSpacer},
|
||||
{"terminal", drawTerminal},
|
||||
{"wrapbox", drawWrapbox},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ WIDGETS = \
|
|||
textinpt:textInput:widgetTextInput:textinpt \
|
||||
timer:timer:widgetTimer:timer \
|
||||
toolbar:toolbar:widgetToolbar:toolbar \
|
||||
treeview:treeView:widgetTreeView:treeview
|
||||
treeview:treeView:widgetTreeView:treeview \
|
||||
wrapbox:wrapBox:widgetWrapBox:wrapbox
|
||||
|
||||
# Extract lists
|
||||
WGT_NAMES = $(foreach w,$(WIDGETS),$(word 1,$(subst :, ,$w)))
|
||||
|
|
|
|||
|
|
@ -381,6 +381,73 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
// Recurse into child containers
|
||||
widgetLayoutChildren(c, font);
|
||||
}
|
||||
|
||||
// Children with custom layout (e.g., WrapBox) may have updated
|
||||
// their calcMinH after layout. Re-check if scrollbars are needed.
|
||||
{
|
||||
int32_t newMinW2;
|
||||
int32_t newMinH2;
|
||||
int32_t newInnerW2;
|
||||
int32_t newInnerH2;
|
||||
bool newNeedV2;
|
||||
bool newNeedH2;
|
||||
|
||||
spCalcNeeds(w, font, &newMinW2, &newMinH2, &newInnerW2, &newInnerH2, &newNeedV2, &newNeedH2);
|
||||
|
||||
if (newNeedV2 != needVSb || newNeedH2 != needHSb) {
|
||||
// Scrollbar needs changed — redo layout with updated sizes
|
||||
contentMinH = newMinH2;
|
||||
contentMinW = newMinW2;
|
||||
innerW = newInnerW2;
|
||||
innerH = newInnerH2;
|
||||
|
||||
maxScrollV = contentMinH - innerH;
|
||||
maxScrollH = contentMinW - innerW;
|
||||
|
||||
if (maxScrollV < 0) {
|
||||
maxScrollV = 0;
|
||||
}
|
||||
|
||||
if (maxScrollH < 0) {
|
||||
maxScrollH = 0;
|
||||
}
|
||||
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
|
||||
virtualW = DVX_MAX(innerW, contentMinW);
|
||||
virtualH = DVX_MAX(innerH, contentMinH);
|
||||
baseX = w->x + SP_BORDER - sp->scrollPosH;
|
||||
baseY = w->y + SP_BORDER - sp->scrollPosV;
|
||||
childW = virtualW - pad * 2;
|
||||
|
||||
if (childW < 0) {
|
||||
childW = 0;
|
||||
}
|
||||
|
||||
pos = baseY + pad;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (!c->visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t ms = c->calcMinH;
|
||||
|
||||
if (totalWeight > 0 && c->weight > 0 && extraSpace > 0) {
|
||||
ms += (extraSpace * c->weight) / totalWeight;
|
||||
}
|
||||
|
||||
c->x = baseX + pad;
|
||||
c->y = pos;
|
||||
c->w = childW;
|
||||
c->h = ms;
|
||||
pos += ms + gap;
|
||||
|
||||
widgetLayoutChildren(c, font);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
21
widgets/widgetWrapBox.h
Normal file
21
widgets/widgetWrapBox.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// widgetWrapBox.h -- Flow/wrap layout container
|
||||
//
|
||||
// Lays out children left-to-right, wrapping to the next row when
|
||||
// the available width is exceeded.
|
||||
|
||||
#ifndef WIDGET_WRAPBOX_H
|
||||
#define WIDGET_WRAPBOX_H
|
||||
|
||||
#include "dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} WrapBoxApiT;
|
||||
|
||||
static inline const WrapBoxApiT *dvxWrapBoxApi(void) {
|
||||
return (const WrapBoxApiT *)wgtGetApi("wrapbox");
|
||||
}
|
||||
|
||||
#define wgtWrapBox(parent) dvxWrapBoxApi()->create(parent)
|
||||
|
||||
#endif // WIDGET_WRAPBOX_H
|
||||
306
widgets/wrapBox/widgetWrapBox.c
Normal file
306
widgets/wrapBox/widgetWrapBox.c
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetWrapBox.c -- Flow/wrap layout container
|
||||
//
|
||||
// Lays out children left-to-right, wrapping to the next row when
|
||||
// the available width is exceeded. Each row's height is the max
|
||||
// child height in that row. Supports spacing between items and
|
||||
// rows. Supports per-row alignment (center, right) for short rows.
|
||||
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
#define DEFAULT_PAD 2
|
||||
#define DEFAULT_GAP 4
|
||||
|
||||
// ============================================================
|
||||
// Type ID and per-instance data
|
||||
// ============================================================
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
int32_t wrappedH; // actual height from last layout pass (-1 = not yet computed)
|
||||
} WrapBoxDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetWrapBoxCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetWrapBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
|
||||
int32_t maxChildW = 0;
|
||||
int32_t maxChildH = 0;
|
||||
|
||||
if (pad == 0) {
|
||||
pad = DEFAULT_PAD;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (!c->visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
widgetCalcMinSizeTree(c, font);
|
||||
|
||||
if (c->calcMinW > maxChildW) {
|
||||
maxChildW = c->calcMinW;
|
||||
}
|
||||
|
||||
if (c->calcMinH > maxChildH) {
|
||||
maxChildH = c->calcMinH;
|
||||
}
|
||||
}
|
||||
|
||||
w->calcMinW = maxChildW + pad * 2;
|
||||
|
||||
WrapBoxDataT *d = (WrapBoxDataT *)w->data;
|
||||
|
||||
if (d && d->wrappedH > 0) {
|
||||
w->calcMinH = d->wrappedH;
|
||||
} else {
|
||||
w->calcMinH = maxChildH + pad * 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetWrapBoxLayout
|
||||
// ============================================================
|
||||
//
|
||||
// Three-pass layout:
|
||||
// 1. Place children left-to-right with wrapping
|
||||
// 2. Apply per-row alignment offsets
|
||||
// 3. Recurse into child containers (so children see final positions)
|
||||
|
||||
void widgetWrapBoxLayout(WidgetT *w, const BitmapFontT *font) {
|
||||
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
|
||||
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||
|
||||
if (pad == 0) {
|
||||
pad = DEFAULT_PAD;
|
||||
}
|
||||
|
||||
if (gap == 0) {
|
||||
gap = DEFAULT_GAP;
|
||||
}
|
||||
|
||||
int32_t availW = w->w - pad * 2;
|
||||
int32_t baseX = w->x + pad;
|
||||
|
||||
// Pass 1: position children left-aligned with wrapping
|
||||
int32_t curX = baseX;
|
||||
int32_t curY = w->y + pad;
|
||||
int32_t rowH = 0;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (!c->visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t childW = c->calcMinW;
|
||||
int32_t childH = c->calcMinH;
|
||||
|
||||
if (curX > baseX && (curX - baseX) + childW > availW) {
|
||||
curX = baseX;
|
||||
curY += rowH + gap;
|
||||
rowH = 0;
|
||||
}
|
||||
|
||||
c->x = curX;
|
||||
c->y = curY;
|
||||
c->w = childW;
|
||||
c->h = childH;
|
||||
|
||||
if (childH > rowH) {
|
||||
rowH = childH;
|
||||
}
|
||||
|
||||
curX += childW + gap;
|
||||
}
|
||||
|
||||
// Pass 2: apply alignment offsets per row
|
||||
if (w->align == AlignCenterE || w->align == AlignEndE) {
|
||||
WidgetT *rowStart = NULL;
|
||||
int32_t rowY = -1;
|
||||
int32_t rowEndX = 0;
|
||||
|
||||
for (WidgetT *c = w->firstChild; ; c = c ? c->nextSibling : NULL) {
|
||||
bool newRow = false;
|
||||
|
||||
if (!c) {
|
||||
newRow = (rowStart != NULL);
|
||||
} else if (c->visible && c->y != rowY && rowStart != NULL) {
|
||||
newRow = true;
|
||||
}
|
||||
|
||||
if (newRow) {
|
||||
int32_t rowW = rowEndX - baseX;
|
||||
int32_t slack = availW - rowW;
|
||||
|
||||
if (slack > 0) {
|
||||
int32_t offset = (w->align == AlignCenterE) ? slack / 2 : slack;
|
||||
|
||||
for (WidgetT *r = rowStart; r != c; r = r->nextSibling) {
|
||||
if (r->visible && r->y == rowY) {
|
||||
r->x += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rowStart = NULL;
|
||||
}
|
||||
|
||||
if (!c) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!c->visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rowStart == NULL || c->y != rowY) {
|
||||
rowStart = c;
|
||||
rowY = c->y;
|
||||
}
|
||||
|
||||
rowEndX = c->x + c->w;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: recurse into child containers AFTER alignment
|
||||
// so children see their parent's final position.
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible) {
|
||||
widgetLayoutChildren(c, font);
|
||||
}
|
||||
}
|
||||
|
||||
// Store actual wrapped height for subsequent measure passes.
|
||||
int32_t usedH = (curY + rowH + pad) - w->y;
|
||||
WrapBoxDataT *d = (WrapBoxDataT *)w->data;
|
||||
|
||||
if (d) {
|
||||
d->wrappedH = usedH;
|
||||
}
|
||||
|
||||
if (usedH > w->calcMinH) {
|
||||
w->calcMinH = usedH;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetWrapBoxPaint
|
||||
// ============================================================
|
||||
|
||||
void widgetWrapBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)w;
|
||||
(void)disp;
|
||||
(void)ops;
|
||||
(void)font;
|
||||
(void)colors;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// BASIC-facing accessors
|
||||
// ============================================================
|
||||
|
||||
static int32_t wrapBoxGetAlign(const WidgetT *w) {
|
||||
return (int32_t)w->align;
|
||||
}
|
||||
|
||||
|
||||
static void wrapBoxSetAlign(WidgetT *w, int32_t align) {
|
||||
w->align = (WidgetAlignE)align;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
static const char *sAlignNames[] = { "Left", "Center", "Right", NULL };
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "Alignment", WGT_IFACE_ENUM, (void *)wrapBoxGetAlign, (void *)wrapBoxSetAlign, sAlignNames }
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static void wrapBoxDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
static const WidgetClassT sClass = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = 0,
|
||||
.handlers = {
|
||||
[WGT_METHOD_PAINT] = (void *)widgetWrapBoxPaint,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetWrapBoxCalcMinSize,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetWrapBoxLayout,
|
||||
[WGT_METHOD_DESTROY] = (void *)wrapBoxDestroy,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static WidgetT *wrapBoxCreate(WidgetT *parent) {
|
||||
if (!parent) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
WrapBoxDataT *d = (WrapBoxDataT *)calloc(1, sizeof(WrapBoxDataT));
|
||||
|
||||
if (d) {
|
||||
d->wrappedH = -1;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} sApi = {
|
||||
.create = wrapBoxCreate
|
||||
};
|
||||
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "WrapBox",
|
||||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
.isContainer = true,
|
||||
.defaultEvent = "Click",
|
||||
.namePrefix = "WrapBox"
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClass);
|
||||
wgtRegisterApi("wrapbox", &sApi);
|
||||
wgtRegisterIface("wrapbox", &sIface);
|
||||
}
|
||||
BIN
widgets/wrapBox/wrapbox.bmp
(Stored with Git LFS)
Normal file
BIN
widgets/wrapBox/wrapbox.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
5
widgets/wrapBox/wrapbox.res
Normal file
5
widgets/wrapBox/wrapbox.res
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
icon24 icon wrapbox.bmp
|
||||
name text "WrapBox"
|
||||
author text "DVX Project"
|
||||
description text "Flow/wrap layout container"
|
||||
version text "1.0"
|
||||
Loading…
Add table
Reference in a new issue