Compare commits
No commits in common. "93b912d932a591d8c1cad666e39e032bdeca9e05" and "7bc92549f715418930c220be7d1a31d8c597e95d" have entirely different histories.
93b912d932
...
7bc92549f7
20 changed files with 75 additions and 541 deletions
|
|
@ -33,8 +33,6 @@
|
|||
#include "shellInfo.h"
|
||||
#include "dvxResource.h"
|
||||
#include "widgetImageButton.h"
|
||||
#include "widgetScrollPane.h"
|
||||
#include "widgetWrapBox.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
|
@ -44,16 +42,18 @@
|
|||
#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 = NULL; // stb_ds dynamic array
|
||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
||||
static int32_t sAppCount = 0;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -196,19 +196,18 @@ static void buildPmWindow(void) {
|
|||
if (sAppCount == 0) {
|
||||
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
||||
} else {
|
||||
// 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;
|
||||
// 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;
|
||||
|
||||
for (int32_t i = 0; i < sAppCount; i++) {
|
||||
WidgetT *cell = wgtVBox(wrap);
|
||||
if (i % PM_GRID_COLS == 0) {
|
||||
hbox = wgtHBox(appFrame);
|
||||
hbox->spacing = wgtPixels(PM_GRID_SPACING);
|
||||
hbox->align = AlignCenterE;
|
||||
}
|
||||
|
||||
WidgetT *cell = wgtVBox(hbox);
|
||||
cell->align = AlignCenterE;
|
||||
cell->minW = wgtPixels(PM_CELL_W);
|
||||
cell->minH = wgtPixels(PM_CELL_H);
|
||||
|
|
@ -233,8 +232,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);
|
||||
|
|
@ -253,6 +252,10 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -380,13 +383,6 @@ 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);
|
||||
|
|
@ -410,7 +406,7 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_FILES) {
|
||||
// Skip . and ..
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
|
|
@ -436,18 +432,20 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
// Check for .app extension (case-insensitive)
|
||||
const char *ext = ent->d_name + len - 4;
|
||||
|
||||
if (strcasecmp(ext, ".app") != 0) {
|
||||
if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip ourselves
|
||||
if (strcasecmp(ent->d_name, "progman.app") == 0) {
|
||||
if (strcmp(ent->d_name, "progman.app") == 0 || strcmp(ent->d_name, "PROGMAN.APP") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppEntryT newEntry;
|
||||
memset(&newEntry, 0, sizeof(newEntry));
|
||||
snprintf(newEntry.path, sizeof(newEntry.path), "%s", fullPath);
|
||||
AppEntryT *entry = &sAppFiles[sAppCount];
|
||||
|
||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
||||
entry->iconData = NULL;
|
||||
entry->tooltip[0] = '\0';
|
||||
|
||||
// Default name from filename (without .app extension)
|
||||
int32_t nameLen = len - 4;
|
||||
|
|
@ -456,28 +454,25 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
nameLen = SHELL_APP_NAME_MAX - 1;
|
||||
}
|
||||
|
||||
memcpy(newEntry.name, ent->d_name, nameLen);
|
||||
newEntry.name[nameLen] = '\0';
|
||||
memcpy(entry->name, ent->d_name, nameLen);
|
||||
entry->name[nameLen] = '\0';
|
||||
|
||||
if (newEntry.name[0] >= 'a' && newEntry.name[0] <= 'z') {
|
||||
newEntry.name[0] -= 32;
|
||||
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
|
||||
entry->name[0] -= 32;
|
||||
}
|
||||
|
||||
// Override from embedded resources if available
|
||||
newEntry.iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &newEntry.iconW, &newEntry.iconH, &newEntry.iconPitch);
|
||||
entry->iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &entry->iconW, &entry->iconH, &entry->iconPitch);
|
||||
|
||||
if (!newEntry.iconData) {
|
||||
if (!entry->iconData) {
|
||||
dvxLog("Progman: no icon32 resource in %s", ent->d_name);
|
||||
}
|
||||
|
||||
dvxResLoadText(fullPath, "name", newEntry.name, SHELL_APP_NAME_MAX);
|
||||
dvxResLoadText(fullPath, "description", newEntry.tooltip, sizeof(newEntry.tooltip));
|
||||
dvxResLoadText(fullPath, "name", entry->name, SHELL_APP_NAME_MAX);
|
||||
dvxResLoadText(fullPath, "description", entry->tooltip, sizeof(entry->tooltip));
|
||||
|
||||
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);
|
||||
dvxLog("Progman: found %s (%s) icon=%s", entry->name, ent->d_name, entry->iconData ? "yes" : "no");
|
||||
sAppCount++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
|
|
|||
|
|
@ -4063,20 +4063,6 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ typedef enum {
|
|||
// Flags encode static properties checked by the framework.
|
||||
// Widgets set these in their WidgetClassT definition.
|
||||
#define WCLASS_FOCUSABLE 0x00000001
|
||||
#define WCLASS_BOX_CONTAINER 0x00000002
|
||||
#define WCLASS_HORIZ_CONTAINER 0x00000004
|
||||
#define WCLASS_PAINTS_CHILDREN 0x00000008
|
||||
#define WCLASS_NO_HIT_RECURSE 0x00000010
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
|||
int32_t widgetCountVisibleChildren(const WidgetT *w);
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w);
|
||||
bool widgetIsFocusable(int32_t type);
|
||||
bool widgetIsBoxContainer(int32_t type);
|
||||
bool widgetIsHorizContainer(int32_t type);
|
||||
int32_t multiClickDetect(int32_t vx, int32_t vy);
|
||||
|
||||
|
|
|
|||
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);
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
||||
static 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.
|
||||
|
||||
void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
||||
static 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,32 +1970,10 @@ void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
|
|||
|
||||
*minW = titleMinW + CHROME_BORDER_WIDTH * 2;
|
||||
|
||||
// Menu bar width: compute directly from label text (don't rely on
|
||||
// cached barX/barW which may not be computed yet before first paint).
|
||||
// Menu bar width: ensure window is wide enough to show all menu labels
|
||||
if (win->menuBar && win->menuBar->menuCount > 0) {
|
||||
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;
|
||||
MenuT *last = &win->menuBar->menus[win->menuBar->menuCount - 1];
|
||||
int32_t menuW = last->barX + last->barW + CHROME_TOTAL_SIDE;
|
||||
|
||||
if (menuW > *minW) {
|
||||
*minW = menuW;
|
||||
|
|
|
|||
|
|
@ -62,9 +62,6 @@ 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);
|
||||
|
|
|
|||
|
|
@ -527,6 +527,21 @@ bool widgetIsFocusable(int32_t type) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetIsBoxContainer
|
||||
// ============================================================
|
||||
//
|
||||
// Returns true for widget types that use the generic box layout.
|
||||
|
||||
bool widgetIsBoxContainer(int32_t type) {
|
||||
if (type < 0 || type >= arrlen(widgetClassTable) || !widgetClassTable[type]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (widgetClassTable[type]->flags & WCLASS_BOX_CONTAINER) != 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetIsHorizContainer
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
// size calculation. Hints only increase the minimum, never shrink it.
|
||||
|
||||
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
||||
if (wclsHas(w, WGT_METHOD_CALC_MIN_SIZE)) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetCalcMinSizeBox(w, font);
|
||||
} else if (wclsHas(w, WGT_METHOD_CALC_MIN_SIZE)) {
|
||||
wclsCalcMinSize(w, font);
|
||||
} else {
|
||||
w->calcMinW = 0;
|
||||
|
|
@ -355,7 +357,9 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
// do nothing (they have no children to lay out).
|
||||
|
||||
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font) {
|
||||
if (wclsHas(w, WGT_METHOD_LAYOUT)) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetLayoutBox(w, font);
|
||||
} else if (wclsHas(w, WGT_METHOD_LAYOUT)) {
|
||||
wclsLayout(w, font);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -505,27 +505,6 @@ 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");
|
||||
|
|
@ -565,7 +544,6 @@ int main(int argc, char **argv) {
|
|||
{"separator", drawSeparator},
|
||||
{"spacer", drawSpacer},
|
||||
{"terminal", drawTerminal},
|
||||
{"wrapbox", drawWrapbox},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ WIDGETS = \
|
|||
textinpt:textInput:widgetTextInput:textinpt \
|
||||
timer:timer:widgetTimer:timer \
|
||||
toolbar:toolbar:widgetToolbar:toolbar \
|
||||
treeview:treeView:widgetTreeView:treeview \
|
||||
wrapbox:wrapBox:widgetWrapBox:wrapbox
|
||||
treeview:treeView:widgetTreeView:treeview
|
||||
|
||||
# Extract lists
|
||||
WGT_NAMES = $(foreach w,$(WIDGETS),$(word 1,$(subst :, ,$w)))
|
||||
|
|
|
|||
|
|
@ -141,31 +141,21 @@ void widgetFrameDestroy(WidgetT *w) {
|
|||
|
||||
static const WidgetClassT sClassVBox = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = 0,
|
||||
.handlers = {
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassHBox = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_HORIZ_CONTAINER,
|
||||
.handlers = {
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassFrame = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_FOCUS_FORWARD,
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_FOCUS_FORWARD,
|
||||
.handlers = {
|
||||
[WGT_METHOD_PAINT] = (void *)widgetFramePaint,
|
||||
[WGT_METHOD_DESTROY] = (void *)widgetFrameDestroy,
|
||||
[WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetFrameGetLayoutMetrics,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -310,11 +310,9 @@ void widgetRadioSetText(WidgetT *w, const char *text) {
|
|||
|
||||
static const WidgetClassT sClassRadioGroup = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = 0,
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.handlers = {
|
||||
[WGT_METHOD_DESTROY] = (void *)widgetRadioGroupDestroy,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
[WGT_METHOD_DESTROY] = (void *)widgetRadioGroupDestroy,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -381,73 +381,6 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -72,12 +72,10 @@ void widgetStatusBarGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font,
|
|||
|
||||
static const WidgetClassT sClassStatusBar = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_HORIZ_CONTAINER,
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
.handlers = {
|
||||
[WGT_METHOD_PAINT] = (void *)widgetStatusBarPaint,
|
||||
[WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetStatusBarGetLayoutMetrics,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -596,12 +596,10 @@ static const WidgetClassT sClassTabControl = {
|
|||
|
||||
static const WidgetClassT sClassTabPage = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_ACCEL_WHEN_HIDDEN,
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_ACCEL_WHEN_HIDDEN,
|
||||
.handlers = {
|
||||
[WGT_METHOD_ON_ACCEL_ACTIVATE] = (void *)widgetTabPageAccelActivate,
|
||||
[WGT_METHOD_DESTROY] = (void *)widgetTabPageDestroy,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -64,12 +64,10 @@ void widgetToolbarGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, in
|
|||
|
||||
static const WidgetClassT sClassToolbar = {
|
||||
.version = WGT_CLASS_VERSION,
|
||||
.flags = WCLASS_HORIZ_CONTAINER,
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
.handlers = {
|
||||
[WGT_METHOD_PAINT] = (void *)widgetToolbarPaint,
|
||||
[WGT_METHOD_GET_LAYOUT_METRICS] = (void *)widgetToolbarGetLayoutMetrics,
|
||||
[WGT_METHOD_CALC_MIN_SIZE] = (void *)widgetCalcMinSizeBox,
|
||||
[WGT_METHOD_LAYOUT] = (void *)widgetLayoutBox,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
// 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
|
||||
|
|
@ -1,306 +0,0 @@
|
|||
#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)
BIN
widgets/wrapBox/wrapbox.bmp
(Stored with Git LFS)
Binary file not shown.
|
|
@ -1,5 +0,0 @@
|
|||
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