diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 4f33033..9e61bb5 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -33,6 +33,8 @@ #include "shellInfo.h" #include "dvxResource.h" #include "widgetImageButton.h" +#include "widgetScrollPane.h" +#include "widgetWrapBox.h" #include #include @@ -42,18 +44,16 @@ #include #include #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); diff --git a/core/dvxApp.c b/core/dvxApp.c index a5fc58d..0f7e15e 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -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); } diff --git a/core/dvxWm.c b/core/dvxWm.c index e81eb5a..d112858 100644 --- a/core/dvxWm.c +++ b/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; diff --git a/core/dvxWm.h b/core/dvxWm.h index 8fa82ab..ec874c7 100644 --- a/core/dvxWm.h +++ b/core/dvxWm.h @@ -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); diff --git a/tools/mkwgticon.c b/tools/mkwgticon.c index ece9bcf..efb088b 100644 --- a/tools/mkwgticon.c +++ b/tools/mkwgticon.c @@ -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 \n"); @@ -544,6 +565,7 @@ int main(int argc, char **argv) { {"separator", drawSeparator}, {"spacer", drawSpacer}, {"terminal", drawTerminal}, + {"wrapbox", drawWrapbox}, {NULL, NULL} }; diff --git a/widgets/Makefile b/widgets/Makefile index a3bf403..6c33bea 100644 --- a/widgets/Makefile +++ b/widgets/Makefile @@ -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))) diff --git a/widgets/scrollPane/widgetScrollPane.c b/widgets/scrollPane/widgetScrollPane.c index 000ce54..be0bccb 100644 --- a/widgets/scrollPane/widgetScrollPane.c +++ b/widgets/scrollPane/widgetScrollPane.c @@ -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); + } + } + } } diff --git a/widgets/widgetWrapBox.h b/widgets/widgetWrapBox.h new file mode 100644 index 0000000..79955d5 --- /dev/null +++ b/widgets/widgetWrapBox.h @@ -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 diff --git a/widgets/wrapBox/widgetWrapBox.c b/widgets/wrapBox/widgetWrapBox.c new file mode 100644 index 0000000..ce90cdb --- /dev/null +++ b/widgets/wrapBox/widgetWrapBox.c @@ -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 +#include + +// ============================================================ +// 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); +} diff --git a/widgets/wrapBox/wrapbox.bmp b/widgets/wrapBox/wrapbox.bmp new file mode 100644 index 0000000..08bbbb3 --- /dev/null +++ b/widgets/wrapBox/wrapbox.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bd9d9e3552cee1d8fa7a033f869672539c64b5a57def091f76993126ccb2027 +size 1782 diff --git a/widgets/wrapBox/wrapbox.res b/widgets/wrapBox/wrapbox.res new file mode 100644 index 0000000..3961be9 --- /dev/null +++ b/widgets/wrapBox/wrapbox.res @@ -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"