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 "shellInfo.h"
|
||||||
#include "dvxResource.h"
|
#include "dvxResource.h"
|
||||||
#include "widgetImageButton.h"
|
#include "widgetImageButton.h"
|
||||||
|
#include "widgetScrollPane.h"
|
||||||
|
#include "widgetWrapBox.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
@ -42,18 +44,16 @@
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include "dvxMem.h"
|
#include "dvxMem.h"
|
||||||
|
#include "stb_ds_wrap.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Constants
|
// 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
|
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
||||||
#define MAX_PATH_LEN 260
|
#define MAX_PATH_LEN 260
|
||||||
// Grid layout for app buttons: 4 columns, rows created dynamically
|
// Grid layout for app buttons: 4 columns, rows created dynamically
|
||||||
#define PM_TOOLTIP_LEN 128
|
#define PM_TOOLTIP_LEN 128
|
||||||
#define PM_GRID_COLS 4
|
|
||||||
#define PM_BTN_W 100
|
#define PM_BTN_W 100
|
||||||
#define PM_BTN_H 24
|
#define PM_BTN_H 24
|
||||||
#define PM_CELL_W 80
|
#define PM_CELL_W 80
|
||||||
|
|
@ -102,7 +102,7 @@ static WidgetT *sStatusLabel = NULL;
|
||||||
static PrefsHandleT *sPrefs = NULL;
|
static PrefsHandleT *sPrefs = NULL;
|
||||||
static bool sMinOnRun = false;
|
static bool sMinOnRun = false;
|
||||||
static bool sRestoreAlone = false;
|
static bool sRestoreAlone = false;
|
||||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
static AppEntryT *sAppFiles = NULL; // stb_ds dynamic array
|
||||||
static int32_t sAppCount = 0;
|
static int32_t sAppCount = 0;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -196,18 +196,19 @@ static void buildPmWindow(void) {
|
||||||
if (sAppCount == 0) {
|
if (sAppCount == 0) {
|
||||||
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
||||||
} else {
|
} else {
|
||||||
// Build grid of app icons. Each cell is a VBox with an image
|
// ScrollPane provides scrollbars when icons overflow.
|
||||||
// button (or text button if no icon) and a label underneath.
|
// WrapBox inside flows cells left-to-right, wrapping to
|
||||||
WidgetT *hbox = NULL;
|
// 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++) {
|
for (int32_t i = 0; i < sAppCount; i++) {
|
||||||
if (i % PM_GRID_COLS == 0) {
|
WidgetT *cell = wgtVBox(wrap);
|
||||||
hbox = wgtHBox(appFrame);
|
|
||||||
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
|
||||||
hbox->align = AlignCenterE;
|
|
||||||
}
|
|
||||||
|
|
||||||
WidgetT *cell = wgtVBox(hbox);
|
|
||||||
cell->align = AlignCenterE;
|
cell->align = AlignCenterE;
|
||||||
cell->minW = wgtPixels(PM_CELL_W);
|
cell->minW = wgtPixels(PM_CELL_W);
|
||||||
cell->minH = wgtPixels(PM_CELL_H);
|
cell->minH = wgtPixels(PM_CELL_H);
|
||||||
|
|
@ -252,10 +253,6 @@ static void buildPmWindow(void) {
|
||||||
sStatusLabel->weight = 100;
|
sStatusLabel->weight = 100;
|
||||||
updateStatusText();
|
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
|
// The apps/ path is relative to the working directory, which the shell sets
|
||||||
// to the root of the DVX install before loading any apps.
|
// to the root of the DVX install before loading any apps.
|
||||||
static void scanAppsDir(void) {
|
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;
|
sAppCount = 0;
|
||||||
scanAppsDirRecurse("apps");
|
scanAppsDirRecurse("apps");
|
||||||
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||||
|
|
@ -406,7 +410,7 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
|
|
||||||
struct dirent *ent;
|
struct dirent *ent;
|
||||||
|
|
||||||
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_FILES) {
|
while ((ent = readdir(dir)) != NULL) {
|
||||||
// Skip . and ..
|
// Skip . and ..
|
||||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -432,20 +436,18 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
// Check for .app extension (case-insensitive)
|
// Check for .app extension (case-insensitive)
|
||||||
const char *ext = ent->d_name + len - 4;
|
const char *ext = ent->d_name + len - 4;
|
||||||
|
|
||||||
if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) {
|
if (strcasecmp(ext, ".app") != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip ourselves
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppEntryT *entry = &sAppFiles[sAppCount];
|
AppEntryT newEntry;
|
||||||
|
memset(&newEntry, 0, sizeof(newEntry));
|
||||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
snprintf(newEntry.path, sizeof(newEntry.path), "%s", fullPath);
|
||||||
entry->iconData = NULL;
|
|
||||||
entry->tooltip[0] = '\0';
|
|
||||||
|
|
||||||
// Default name from filename (without .app extension)
|
// Default name from filename (without .app extension)
|
||||||
int32_t nameLen = len - 4;
|
int32_t nameLen = len - 4;
|
||||||
|
|
@ -454,25 +456,28 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
||||||
nameLen = SHELL_APP_NAME_MAX - 1;
|
nameLen = SHELL_APP_NAME_MAX - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(entry->name, ent->d_name, nameLen);
|
memcpy(newEntry.name, ent->d_name, nameLen);
|
||||||
entry->name[nameLen] = '\0';
|
newEntry.name[nameLen] = '\0';
|
||||||
|
|
||||||
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
if (newEntry.name[0] >= 'a' && newEntry.name[0] <= 'z') {
|
||||||
entry->name[0] -= 32;
|
newEntry.name[0] -= 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override from embedded resources if available
|
// 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);
|
dvxLog("Progman: no icon32 resource in %s", ent->d_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
dvxResLoadText(fullPath, "name", entry->name, SHELL_APP_NAME_MAX);
|
dvxResLoadText(fullPath, "name", newEntry.name, SHELL_APP_NAME_MAX);
|
||||||
dvxResLoadText(fullPath, "description", entry->tooltip, sizeof(entry->tooltip));
|
dvxResLoadText(fullPath, "description", newEntry.tooltip, sizeof(newEntry.tooltip));
|
||||||
|
|
||||||
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
|
dvxLog("Progman: found %s (%s) icon=%s", newEntry.name, ent->d_name, newEntry.iconData ? "yes" : "no");
|
||||||
sAppCount++;
|
arrput(sAppFiles, newEntry);
|
||||||
|
sAppCount = (int32_t)arrlen(sAppFiles);
|
||||||
|
|
||||||
|
dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
|
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
|
|
|
||||||
|
|
@ -4063,6 +4063,20 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
int32_t newW = win->widgetRoot->calcMinW + CHROME_TOTAL_SIDE * 2;
|
int32_t newW = win->widgetRoot->calcMinW + CHROME_TOTAL_SIDE * 2;
|
||||||
int32_t newH = win->widgetRoot->calcMinH + topChrome + CHROME_TOTAL_BOTTOM;
|
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);
|
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);
|
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
|
// wmMinimizedIconPos declared in dvxWm.h
|
||||||
static int32_t scrollbarThumbInfo(const ScrollbarT *sb, int32_t *thumbPos, int32_t *thumbSize);
|
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.
|
// This function is called on every resize move event, so it must be cheap.
|
||||||
// No allocations, no string operations -- just arithmetic on cached values.
|
// 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 gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2;
|
||||||
int32_t gadgetPad = GADGET_PAD;
|
int32_t gadgetPad = GADGET_PAD;
|
||||||
int32_t charW = FONT_CHAR_WIDTH;
|
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;
|
*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) {
|
if (win->menuBar && win->menuBar->menuCount > 0) {
|
||||||
MenuT *last = &win->menuBar->menus[win->menuBar->menuCount - 1];
|
int32_t menuW = CHROME_TOTAL_SIDE;
|
||||||
int32_t menuW = last->barX + last->barW + 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) {
|
if (menuW > *minW) {
|
||||||
*minW = menuW;
|
*minW = menuW;
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ MenuBarT *wmAddMenuBar(WindowT *win);
|
||||||
// Free the menu bar and reclaim the content area.
|
// Free the menu bar and reclaim the content area.
|
||||||
void wmDestroyMenuBar(WindowT *win);
|
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
|
// Append a dropdown menu to the menu bar. Returns the MenuT to populate
|
||||||
// with items. The label supports & accelerator markers (e.g. "&File").
|
// with items. The label supports & accelerator markers (e.g. "&File").
|
||||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
|
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) {
|
int main(int argc, char **argv) {
|
||||||
if (argc != 3) {
|
if (argc != 3) {
|
||||||
fprintf(stderr, "Usage: mkwgticon <output.bmp> <type>\n");
|
fprintf(stderr, "Usage: mkwgticon <output.bmp> <type>\n");
|
||||||
|
|
@ -544,6 +565,7 @@ int main(int argc, char **argv) {
|
||||||
{"separator", drawSeparator},
|
{"separator", drawSeparator},
|
||||||
{"spacer", drawSpacer},
|
{"spacer", drawSpacer},
|
||||||
{"terminal", drawTerminal},
|
{"terminal", drawTerminal},
|
||||||
|
{"wrapbox", drawWrapbox},
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@ WIDGETS = \
|
||||||
textinpt:textInput:widgetTextInput:textinpt \
|
textinpt:textInput:widgetTextInput:textinpt \
|
||||||
timer:timer:widgetTimer:timer \
|
timer:timer:widgetTimer:timer \
|
||||||
toolbar:toolbar:widgetToolbar:toolbar \
|
toolbar:toolbar:widgetToolbar:toolbar \
|
||||||
treeview:treeView:widgetTreeView:treeview
|
treeview:treeView:widgetTreeView:treeview \
|
||||||
|
wrapbox:wrapBox:widgetWrapBox:wrapbox
|
||||||
|
|
||||||
# Extract lists
|
# Extract lists
|
||||||
WGT_NAMES = $(foreach w,$(WIDGETS),$(word 1,$(subst :, ,$w)))
|
WGT_NAMES = $(foreach w,$(WIDGETS),$(word 1,$(subst :, ,$w)))
|
||||||
|
|
|
||||||
|
|
@ -381,6 +381,73 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
||||||
// Recurse into child containers
|
// Recurse into child containers
|
||||||
widgetLayoutChildren(c, font);
|
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