summaryrefslogtreecommitdiff
path: root/alf
diff options
context:
space:
mode:
Diffstat (limited to 'alf')
-rw-r--r--alf/alf.cpp850
-rw-r--r--alf/alf.h125
2 files changed, 975 insertions, 0 deletions
diff --git a/alf/alf.cpp b/alf/alf.cpp
new file mode 100644
index 0000000..13839e4
--- /dev/null
+++ b/alf/alf.cpp
@@ -0,0 +1,850 @@
+#include "alf.h"
+
+#include <windows.h>
+#include <commctrl.h>
+#include <windowsx.h>
+#include <stdio.h>
+#include <stddef.h>
+
+typedef struct ALFListHeader {
+ struct ALFListHeader *prev;
+ struct ALFListHeader *next;
+} ALFListHeader;
+
+#define ALF_LIST_CONTAINER(ContainerType, containermember, listp) \
+ ((ContainerType *)((char *)(listp) - offsetof(ContainerType, containermember)))
+
+#define ALF_FOR_LIST(ContainerType, containermember, listp, iteratorvar) \
+ for (ALFListHeader *alf_list_##iteratorvar##_curr = (listp)->next, \
+ *alf_list_##iteratorvar##_next = alf_list_##iteratorvar##_curr->next; \
+ alf_list_##iteratorvar##_curr != (listp); \
+ alf_list_##iteratorvar##_curr = alf_list_##iteratorvar##_next, \
+ alf_list_##iteratorvar##_next = alf_list_##iteratorvar##_next->next) \
+ for (ContainerType *iteratorvar = (ContainerType *)((char *)alf_list_##iteratorvar##_curr - offsetof(ContainerType, containermember)); \
+ iteratorvar; iteratorvar = NULL) \
+
+
+static inline BOOL
+ALF_ListIsEmpty(ALFListHeader *list)
+{
+ return list->next == list;
+}
+
+static inline void
+ALF_ListInsert(ALFListHeader *list, ALFListHeader *newel)
+{
+ newel->prev = list;
+ newel->next = list->next;
+ newel->next->prev = newel;
+ list->next = newel;
+}
+
+static inline void
+ALF_ListRemove(ALFListHeader *member)
+{
+ member->prev->next = member->next;
+ member->next->prev = member->prev;
+ member->next = NULL;
+ member->prev = NULL;
+}
+
+static inline void
+ALF_ListInit(ALFListHeader *list)
+{
+ list->next = list->prev = list;
+}
+
+typedef struct {
+ ALFListHeader list;
+ HWND hwnd;
+ UINT x;
+ UINT y;
+ UINT cptWidth;
+ UINT cptHeight;
+ UINT cptMarginTop;
+ UINT cptMarginRight;
+ UINT cptMarginBottom;
+ UINT cptMarginLeft;
+ DWORD flags;
+} ALFWidgetPriv;
+
+typedef struct {
+ int minWidth;
+ int allocatedWidth;
+ int allocatedPosition;
+ int expand : 1;
+} ALFRowOrColumn;
+
+typedef struct {
+ ALFRowOrColumn *columns;
+ ALFRowOrColumn *rows;
+ int nColumns;
+ int nRows;
+ int totalMinWidth;
+ int totalMinHeight;
+ int occupiedColumnCount;
+ int occupiedRowCount;
+} ALFLayout;
+
+typedef struct {
+ ALFWindowVTable *vtbl;
+ void *closure;
+ ALFWindowFonts fonts;
+ ALFListHeader widgets;
+ int modalResult;
+ ALFLayout layout;
+} ALFWindowPriv;
+
+static void
+ALF_InitializeWindowPriv(HWND hwnd, ALFWindowPriv *priv, ALFWindowInstanceParams *params)
+{
+ priv->vtbl = (ALFWindowVTable*)GetClassLongPtr(hwnd, 0);
+ priv->closure = params->closure;
+ ALF_ListInit(&priv->widgets);
+}
+
+static void
+ALF_DestroyWindowPriv(ALFWindowPriv *priv)
+{
+ if (priv->vtbl->postdestroy)
+ priv->vtbl->postdestroy(priv->closure);
+
+ ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, w) {
+ HeapFree(GetProcessHeap(), 0, w);
+ }
+ ALF_ListInit(&priv->widgets);
+
+ HeapFree(GetProcessHeap(), 0, priv->layout.columns);
+ HeapFree(GetProcessHeap(), 0, priv->layout.rows);
+ HeapFree(GetProcessHeap(), 0, priv);
+}
+
+int
+ALF_CentipointsToPxPriv(ALFWindowPriv *priv, int cptValue)
+{
+ return MulDiv(cptValue, priv->fonts.dpi, 7200);
+}
+
+static void
+ALF_UpdateFontForWidget(ALFWindowPriv *priv, ALFWidgetPriv *widget)
+{
+ if (widget->hwnd && (widget->flags & ALF_MESSAGEFONT) == ALF_MESSAGEFONT) {
+ SendMessage(widget->hwnd, WM_SETFONT, (WPARAM)priv->fonts.hMessageFont, (LPARAM)NULL);
+ }
+}
+
+void
+ALF_UpdateFonts(HWND win)
+{
+ // TODO per-monitor DPI aware: GetDpiForWindow, SystemParametersInfoForDpi etc.
+ ALFWindowPriv *priv = (ALFWindowPriv*)GetWindowLongPtr(win, 0);
+
+ priv->fonts.dpi = 0;
+ HDC hdcScreen = GetDC(NULL);
+ if (hdcScreen) {
+ priv->fonts.dpi = GetDeviceCaps(hdcScreen, LOGPIXELSY);
+ ReleaseDC(NULL, hdcScreen);
+ }
+
+ if (!priv->fonts.dpi) {
+ priv->fonts.dpi = 96; // FIXME! fallback to default DPI
+ }
+
+ NONCLIENTMETRICS ncm;
+ ZeroMemory(&ncm, sizeof(ncm));
+ ncm.cbSize = sizeof(ncm);
+
+ if (SystemParametersInfoW(SPI_GETNONCLIENTMETRICS, ncm.cbSize, &ncm, 0)) {
+ priv->fonts.lfMessageFont = ncm.lfMessageFont;
+ } else {
+ // FIXME! fallback to default font, 8pt MS Shell Dlg
+ ZeroMemory(&priv->fonts.lfMessageFont, sizeof(priv->fonts.lfMessageFont));
+
+ priv->fonts.lfMessageFont.lfHeight = -MulDiv(8, priv->fonts.dpi, 72);
+ lstrcpyW(priv->fonts.lfMessageFont.lfFaceName, L"MS Shell Dlg");
+ }
+
+ if (priv->fonts.hMessageFont) {
+ DeleteObject(priv->fonts.hMessageFont);
+ }
+ priv->fonts.hMessageFont = CreateFontIndirect(&priv->fonts.lfMessageFont);
+
+ ALF_FOR_LIST(ALFWidgetPriv, list, &priv->widgets, i) {
+ ALF_UpdateFontForWidget(priv, i);
+ }
+
+ if (priv->vtbl->updatefonts) {
+ priv->vtbl->updatefonts(priv->closure, win, &priv->fonts);
+ }
+}
+
+void
+ALF_RecalculateLayout(HWND hwnd)
+{
+ SIZE dummy;
+ SendMessage(hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&dummy);
+ SendMessage(hwnd, ALF_WM_APPLYLAYOUT, 0, 0);
+}
+
+static void
+ALF_CalculateSizes(ALFWindowPriv *win)
+{
+ for (int i = 0; i < win->layout.nColumns; ++i) {
+ ZeroMemory(&win->layout.columns[i], sizeof(win->layout.columns[i]));
+ }
+ for (int i = 0; i < win->layout.nRows; ++i) {
+ ZeroMemory(&win->layout.rows[i], sizeof(win->layout.rows[i]));
+ }
+
+ ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) {
+ while ((int)c->x >= win->layout.nColumns) {
+ // FIXME! overflow, use reallocarray(2) equivalent
+ if (win->layout.nColumns == 0) {
+ win->layout.nColumns = 1;
+ win->layout.columns = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nColumns*sizeof(win->layout.columns[0]));
+ } else {
+ win->layout.nColumns *= 2;
+ win->layout.columns = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.columns, win->layout.nColumns*sizeof(win->layout.columns[0]));
+ }
+ }
+ while ((int)c->y >= win->layout.nRows) {
+ // FIXME! overflow, use reallocarray(2) equivalent
+ if (win->layout.nRows == 0) {
+ win->layout.nRows = 1;
+ win->layout.rows = (ALFRowOrColumn*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.nRows*sizeof(win->layout.rows[0]));
+ } else {
+ win->layout.nRows *= 2;
+ win->layout.rows = (ALFRowOrColumn*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_GENERATE_EXCEPTIONS, win->layout.rows, win->layout.nRows*sizeof(win->layout.rows[0]));
+ }
+ }
+
+ // TODO: ignore spanning cell
+
+ SIZE qs = { ALF_CentipointsToPxPriv(win, c->cptWidth),
+ ALF_CentipointsToPxPriv(win, c->cptHeight) };
+ if ((c->flags & ALF_QUERYSIZE) == ALF_QUERYSIZE) {
+ SendMessage(c->hwnd, ALF_WM_QUERYSIZE, 0, (LPARAM)&qs);
+ }
+
+ qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginLeft);
+ qs.cx += ALF_CentipointsToPxPriv(win, c->cptMarginRight);
+ qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginTop);
+ qs.cy += ALF_CentipointsToPxPriv(win, c->cptMarginBottom);
+
+ if (qs.cx > win->layout.columns[c->x].minWidth)
+ win->layout.columns[c->x].minWidth = qs.cx;
+ if (qs.cy > win->layout.rows[c->y].minWidth)
+ win->layout.rows[c->y].minWidth = qs.cy;
+
+ // TODO: expand flag
+ }
+
+ // TODO: second pass for spanning cells
+
+ // update min width bookkeeping
+ win->layout.totalMinWidth = 0;
+ win->layout.occupiedColumnCount = 0;
+ for (int i = 0; i < win->layout.nColumns; ++i) {
+ if (win->layout.columns[i].minWidth > 0) {
+ win->layout.totalMinWidth += win->layout.columns[i].minWidth;
+ win->layout.occupiedColumnCount++;
+ // TODO: expand flag
+ }
+ }
+ win->layout.totalMinHeight = 0;
+ win->layout.occupiedRowCount = 0;
+ for (int i = 0; i < win->layout.nRows; ++i) {
+ if (win->layout.rows[i].minWidth > 0) {
+ win->layout.totalMinHeight += win->layout.rows[i].minWidth;
+ win->layout.occupiedRowCount++;
+ // TODO: expand flag
+ }
+ }
+
+ // TODO: split here into allocation function
+}
+
+static void
+ALF_ApplyLayout(HWND hwnd, ALFWindowPriv *win)
+{
+ // allocate cell positions
+ // distribute free space
+ int extraWidth = 0;
+ int extraHeight = 0;
+ RECT client;
+ if (GetClientRect(hwnd, &client)) {
+ if (client.right - client.left > win->layout.totalMinWidth)
+ extraWidth = client.right - client.left - win->layout.totalMinWidth;
+ if (client.bottom - client.top > win->layout.totalMinHeight)
+ extraHeight = client.bottom - client.top - win->layout.totalMinHeight;
+ }
+
+ int extraWidthPart = 0;
+ int extraWidthError = 0;
+ if (win->layout.occupiedColumnCount) {
+ extraWidthPart = extraWidth / win->layout.occupiedColumnCount;
+ extraWidthError = extraWidth - extraWidthPart * win->layout.occupiedColumnCount;
+ }
+
+ for (int i = 0; i < win->layout.nColumns; ++i) {
+ win->layout.columns[i].allocatedWidth = win->layout.columns[i].minWidth;
+
+ if (win->layout.columns[i].minWidth > 0) {
+ win->layout.columns[i].allocatedWidth += extraWidthPart;
+ if (extraWidthError) {
+ win->layout.columns[i].allocatedWidth++;
+ extraWidthError--;
+ }
+ }
+
+ if (i == 0) {
+ win->layout.columns[i].allocatedPosition = 0;
+ } else {
+ win->layout.columns[i].allocatedPosition =
+ win->layout.columns[i-1].allocatedPosition + win->layout.columns[i-1].allocatedWidth;
+ }
+ }
+
+
+ int extraHeightPart = 0;
+ int extraHeightError = 0;
+ if (win->layout.occupiedRowCount) {
+ extraHeightPart = extraHeight / win->layout.occupiedRowCount;
+ extraHeightError = extraHeight - extraWidthPart * win->layout.occupiedRowCount;
+ }
+
+ for (int i = 0; i < win->layout.nRows; ++i) {
+ win->layout.rows[i].allocatedWidth = win->layout.rows[i].minWidth;
+
+ if (win->layout.rows[i].minWidth > 0) {
+ win->layout.rows[i].allocatedWidth += extraHeightPart;
+ if (extraHeightError) {
+ win->layout.rows[i].allocatedWidth++;
+ extraHeightError--;
+ }
+ }
+
+ if (i == 0) {
+ win->layout.rows[i].allocatedPosition = 0;
+ } else {
+ win->layout.rows[i].allocatedPosition =
+ win->layout.rows[i-1].allocatedPosition + win->layout.rows[i-1].allocatedWidth;
+ }
+ }
+
+ HDWP hdwp = BeginDeferWindowPos(win->layout.occupiedColumnCount * win->layout.occupiedRowCount);
+
+ ALF_FOR_LIST(ALFWidgetPriv, list, &win->widgets, c) {
+ int marginleft = ALF_CentipointsToPxPriv(win, c->cptMarginLeft);
+ int marginright = ALF_CentipointsToPxPriv(win, c->cptMarginRight);
+ int margintop = ALF_CentipointsToPxPriv(win, c->cptMarginTop);
+ int marginbottom = ALF_CentipointsToPxPriv(win, c->cptMarginBottom);
+
+ hdwp = DeferWindowPos(hdwp,
+ c->hwnd, 0,
+ win->layout.columns[c->x].allocatedPosition + marginleft,
+ win->layout.rows[c->y].allocatedPosition + margintop,
+ win->layout.columns[c->x].allocatedWidth - marginleft - marginright,
+ win->layout.rows[c->y].allocatedWidth - margintop - marginbottom,
+ SWP_NOACTIVATE | SWP_NOZORDER);
+ }
+
+ EndDeferWindowPos(hdwp);
+}
+
+static void
+ALF_InternalAddWidget(HWND hwnd, ALFWindowPriv *priv, ALFAddWidgetParams *params)
+{
+ ALFWidgetPriv *w = (ALFWidgetPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWidgetPriv));
+ w->hwnd = params->hwnd;
+ w->x = params->x;
+ w->y = params->y;
+ w->cptWidth = params->width;
+ w->cptHeight = params->height;
+ w->flags = params->flags;
+ w->cptMarginTop = params->margins[0];
+ w->cptMarginRight = params->margins[1];
+ w->cptMarginBottom = params->margins[2];
+ w->cptMarginLeft = params->margins[3];
+
+ if (GetParent(w->hwnd) != hwnd)
+ SetParent(w->hwnd, hwnd);
+
+ // TODO: QUERYUISTATE on parent, then replicate in child. But wait, XP appears to do this automatically!??
+
+ ALF_ListInsert(priv->widgets.prev, &w->list);
+ ALF_UpdateFontForWidget(priv, w);
+}
+
+LRESULT
+ALF_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ ALFWindowPriv *priv = (ALFWindowPriv*)GetWindowLongPtr(hwnd, 0);
+
+ if (msg == ALF_WM_QUERYSIZE) {
+ ALF_CalculateSizes(priv);
+ SIZE *ps = (SIZE*)lparam;
+ ps->cx = priv->layout.totalMinWidth;
+ ps->cy = priv->layout.totalMinHeight;
+ return 0;
+ }
+
+ if (msg == ALF_WM_APPLYLAYOUT) {
+ ALF_ApplyLayout(hwnd, priv);
+ return 0;
+ }
+
+ if (msg == ALF_WM_GETMODALRESULT) {
+ return (LRESULT)priv->modalResult;
+ }
+ if (msg == ALF_WM_SETMODALRESULT) {
+ priv->modalResult = (int)wparam;
+ return 0;
+ }
+
+ if (msg == ALF_WM_CENTIPOINTTOPX) {
+ return (LRESULT)ALF_CentipointsToPxPriv(priv, (int)wparam);
+ }
+
+ if (msg == ALF_WM_ADDWIDGET) {
+ ALF_InternalAddWidget(hwnd, priv, (ALFAddWidgetParams *)lparam);
+ }
+
+ if (msg == WM_SIZE) {
+ ALF_ApplyLayout(hwnd, priv);
+ return 0;
+ }
+
+ if (msg == WM_GETMINMAXINFO) {
+ RECT tmp;
+ ZeroMemory(&tmp, sizeof(tmp));
+ tmp.right = priv->layout.totalMinWidth;
+ tmp.bottom = priv->layout.totalMinHeight;
+
+ // TODO ..ForDpi
+ if (AdjustWindowRectEx(&tmp,
+ GetWindowLong(hwnd, GWL_STYLE),
+ GetMenu(hwnd) != NULL,
+ GetWindowLong(hwnd, GWL_EXSTYLE))) {
+ MINMAXINFO *i = (MINMAXINFO *)lparam;
+ i->ptMinTrackSize.x = tmp.right - tmp.left;
+ i->ptMinTrackSize.y = tmp.bottom - tmp.top;
+ return 0;
+ }
+ }
+
+ if (msg == WM_CREATE) {
+ if (priv->vtbl->create) {
+ priv->vtbl->create(priv->closure, hwnd);
+ }
+ BOOL alwaysUnderline = FALSE;
+ SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &alwaysUnderline, 0);
+ if (!alwaysUnderline) {
+ SendMessage(hwnd, WM_UPDATEUISTATE, MAKEWPARAM(UIS_INITIALIZE, 0), 0);
+ }
+ ALF_UpdateFonts(hwnd);
+ ALF_RecalculateLayout(hwnd);
+ }
+
+ if (msg == WM_DESTROY && priv->vtbl->destroy) {
+ priv->vtbl->destroy(priv->closure, hwnd);
+ }
+
+ if (msg == WM_CLOSE) {
+ if (!priv->vtbl->close || !priv->vtbl->close(priv->closure, hwnd)) {
+ priv->modalResult = 2;
+ ShowWindow(hwnd, FALSE);
+ }
+ return TRUE;
+ }
+
+ if (msg == WM_NCDESTROY) {
+ SetWindowLongPtr(hwnd, 0, 0);
+ ALF_DestroyWindowPriv(priv);
+ }
+
+ return DefWindowProc(hwnd, msg, wparam, lparam);
+}
+
+void
+ALF_AddWidget(HWND win, UINT x, UINT y, HWND widget, UINT minWidth, UINT minHeight, DWORD flags)
+{
+ ALFAddWidgetParams params;
+ ZeroMemory(&params, sizeof(params));
+ params.hwnd = widget;
+ params.x = x;
+ params.y = y;
+ params.width = minWidth;
+ params.height = minHeight;
+ params.flags = flags;
+ ALF_AddWidgetEx(win, &params);
+}
+
+void
+ALF_AddWidgetEx(HWND win, const ALFAddWidgetParams *p)
+{
+ SendMessage(win, ALF_WM_ADDWIDGET, 0, (LPARAM)p);
+}
+
+static LRESULT CALLBACK
+ALF_WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
+{
+ if (msg == WM_NCCREATE) {
+ CREATESTRUCT *cs = (CREATESTRUCT*)(void*)lparam;
+ ALFWindowInstanceParams *params = (ALFWindowInstanceParams*)cs->lpCreateParams;
+ ALFWindowPriv *priv = (ALFWindowPriv*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowPriv));
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv);
+ ALF_InitializeWindowPriv(hwnd, priv, params);
+ }
+
+ ALFWindowPriv *priv = (ALFWindowPriv*)(void*)GetWindowLongPtr(hwnd, 0);
+ if (priv != NULL) {
+ if (priv->vtbl->message) {
+ return priv->vtbl->message(priv->closure, hwnd, msg, wparam, lparam);
+ } else {
+ return ALF_DefWindowProc(hwnd, msg, wparam, lparam);
+ }
+ } else {
+ return DefWindowProc(hwnd, msg, wparam, lparam);
+ }
+}
+
+LPTSTR
+ALF_RegisterWindowClass(HINSTANCE hInstance, const ALFWindowClassParams *params)
+{
+ WNDCLASS cls;
+ ZeroMemory(&cls, sizeof(cls));
+
+ // TODO: autogenerate class name
+
+ cls.style = params->classStyle;
+ cls.hInstance = hInstance;
+ cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW);
+ cls.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
+ cls.lpszClassName = params->className;
+ cls.cbWndExtra = sizeof(void*);
+ cls.cbClsExtra = sizeof(void*);
+ cls.lpfnWndProc = DefWindowProc;
+
+ ATOM classatom = RegisterClass(&cls);
+ if (!classatom)
+ return NULL;
+
+ ALFWindowVTable *pvtbl = (ALFWindowVTable*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, sizeof(ALFWindowVTable));
+ *pvtbl = params->vtbl;
+
+ HWND tmp = CreateWindowEx(0, MAKEINTATOM(classatom), TEXT("dummy"), 0, 0, 0, 0, 0, HWND_MESSAGE, 0, hInstance, 0);
+ SetClassLongPtr(tmp, 0, (LONG_PTR)pvtbl);
+ SetClassLongPtr(tmp, GCLP_WNDPROC, (LONG_PTR)ALF_WindowProc);
+ DestroyWindow(tmp);
+
+ return MAKEINTATOM(classatom);
+}
+
+HWND
+ALF_InstantiateWindow(HINSTANCE hInstance, LPCTSTR className, const ALFWindowInstanceParams *params)
+{
+ return CreateWindowEx(params->windowExStyle,
+ className,
+ L"Window",
+ params->windowStyle,
+ CW_USEDEFAULT, CW_USEDEFAULT,
+ 300, 100, //FIXME
+ params->hwndParent,
+ NULL,
+ hInstance,
+ (void*)params);
+}
+
+int
+ALF_ShowModal(HWND win)
+{
+ MSG msg;
+
+ ALF_SetModalResult(win, 0);
+
+ // TODO: disable parent window
+ ShowWindow(win, SW_SHOW);
+
+ while (GetMessage(&msg, NULL, 0, 0) > 0) {
+ // TODO: call application message hooks
+ // TODO: call preprocess message hook
+
+ if (!IsDialogMessage(win, &msg)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ int mr = ALF_GetModalResult(win);
+ if (mr)
+ return mr;
+ }
+
+ return 0;
+}
+
+
+void
+ALF_SetModalResult(HWND win, int result)
+{
+ SendMessage(win, ALF_WM_SETMODALRESULT, (WPARAM)result, 0);
+}
+
+int
+ALF_GetModalResult(HWND win)
+{
+ return (int)SendMessage(win, ALF_WM_GETMODALRESULT, 0, 0);
+}
+
+int
+ALF_CentipointsToPixels(HWND win, int cptValue)
+{
+ return (int)SendMessage(win, ALF_WM_CENTIPOINTTOPX, (WPARAM)cptValue, 0);
+}
+
+void
+ALF_ResizeWindow(HWND win, UINT cptWidth, UINT cptHeight)
+{
+ int pxwidth = ALF_CentipointsToPixels(win, cptWidth);
+ int pxheight = ALF_CentipointsToPixels(win, cptHeight);
+
+ MINMAXINFO tmp = {
+ { 0, 0 },
+ { pxwidth, pxheight },
+ { 0, 0 },
+ { pxwidth, pxheight },
+ { pxwidth, pxheight }
+ };
+ SendMessage(win, WM_GETMINMAXINFO, 0, (LPARAM)&tmp);
+
+ if (tmp.ptMinTrackSize.x > pxwidth)
+ pxwidth = tmp.ptMinTrackSize.x;
+ if (tmp.ptMinTrackSize.y > pxheight)
+ pxheight = tmp.ptMinTrackSize.y;
+
+ SetWindowPos(win, NULL, 0, 0, pxwidth, pxheight, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
+}
+
+/* LABEL */
+
+static LRESULT CALLBACK
+ALF__LabelSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ (void)uIdSubclass;
+ (void)dwRefData;
+
+ if (uMsg == ALF_WM_QUERYSIZE) {
+ HDC hdcLabel = GetDC(hwnd);
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ HFONT oldFont = 0;
+ if (font)
+ oldFont = SelectFont(hdcLabel, font);
+
+ // calc drawtext style
+ DWORD style = GetWindowLong(hwnd, GWL_STYLE);
+ UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT;
+ if (style & SS_NOPREFIX)
+ format |= DT_NOPREFIX;
+ if (style & SS_EDITCONTROL)
+ format |= DT_EDITCONTROL;
+ if (style & SS_ENDELLIPSIS || style & SS_PATHELLIPSIS || style & SS_WORDELLIPSIS)
+ format |= DT_SINGLELINE;
+
+ RECT r = { 0, 0, 100, 100 };
+
+ int textlen = GetWindowTextLength(hwnd);
+ TCHAR *textbuf = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, (textlen + 1)*sizeof(TCHAR));
+ GetWindowText(hwnd, textbuf, textlen+1);
+
+ DrawText(hdcLabel, textbuf, -1, &r, format);
+
+ HeapFree(GetProcessHeap(), 0, textbuf);
+
+ SIZE *pSize = (SIZE*)(void*)lParam;
+ if (pSize->cx == 0)
+ pSize->cx = r.right - r.left;
+ if (pSize->cy == 0)
+ pSize->cy = r.bottom - r.top;
+
+ if (font)
+ SelectFont(hdcLabel, oldFont);
+
+ ReleaseDC(hwnd, hdcLabel);
+ }
+
+ return DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+
+HWND
+ALF_AddLabel(HWND win, int id, UINT x, UINT y, const WCHAR *text)
+{
+ HWND hwndLabel = CreateWindow(L"STATIC",
+ text,
+ WS_CHILD | WS_VISIBLE | SS_LEFTNOWORDWRAP,
+ 0, 0, 100, 100,
+ win,
+ (HMENU)id,
+ (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE),
+ NULL);
+
+ SetWindowSubclass(hwndLabel, ALF__LabelSubclassProc, 0, 0);
+
+ ALFAddWidgetParams p;
+ ZeroMemory(&p, sizeof(p));
+ p.hwnd = hwndLabel;
+ p.x = x;
+ p.y = y;
+ p.width = 0;
+ p.height = 0;
+ p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT;
+ p.margins[0] = 300;
+ p.margins[2] = 375;
+
+ ALF_AddWidgetEx(win, &p);
+
+ return hwndLabel;
+}
+
+/* EDIT */
+static LRESULT CALLBACK
+ALF__EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ (void)uIdSubclass;
+ (void)dwRefData;
+
+ if (uMsg == ALF_WM_QUERYSIZE) {
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ if (font) {
+ LOGFONT lf;
+ ZeroMemory(&lf, sizeof(lf));
+ if (GetObject(font, sizeof(lf), &lf)) {
+ SIZE *ps = (SIZE*)lParam;
+ if (!ps->cx) {
+ ps->cx = ALF_CentipointsToPixels(GetParent(hwnd), 12000);
+ }
+
+ if (!ps->cy) {
+ ps->cy = ALF_CentipointsToPixels(GetParent(hwnd), 525)
+ + MulDiv(16, -lf.lfHeight, 11);
+ }
+ }
+ }
+ return 0;
+ }
+
+ return DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+
+HWND
+ALF_AddEdit(HWND win, int id, UINT x, UINT y, const WCHAR *text)
+{
+ HWND hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE,
+ L"EDIT",
+ text,
+ WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER,
+ 0, 0, 100, 100,
+ win,
+ (HMENU)id,
+ (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE),
+ NULL);
+
+ SetWindowSubclass(hwndEdit, ALF__EditSubclassProc, 0, 0);
+
+ ALFAddWidgetParams p;
+ ZeroMemory(&p, sizeof(p));
+ p.hwnd = hwndEdit;
+ p.x = x;
+ p.y = y;
+ p.width = 0;
+ p.height = 0;
+ p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT;
+
+ ALF_AddWidgetEx(win, &p);
+
+ return hwndEdit;
+}
+
+/* BUTTON */
+static LRESULT CALLBACK
+ALF__ButtonSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ (void)uIdSubclass;
+ (void)dwRefData;
+
+ if (uMsg == ALF_WM_QUERYSIZE) {
+ HDC hdc = GetDC(hwnd);
+ HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0);
+ HFONT oldFont = 0;
+ if (font)
+ oldFont = SelectFont(hdc, font);
+
+ // calc drawtext style
+ DWORD style = GetWindowLong(hwnd, GWL_STYLE);
+ UINT format = DT_LEFT | DT_EXPANDTABS | DT_CALCRECT;
+ if ((style & BS_MULTILINE) == 0)
+ format |= DT_SINGLELINE;
+
+ RECT r = { 0, 0, 100, 100 };
+
+ int textlen = GetWindowTextLength(hwnd);
+ TCHAR *textbuf = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, (textlen + 1)*sizeof(TCHAR));
+ GetWindowText(hwnd, textbuf, textlen+1);
+
+ DrawText(hdc, textbuf, -1, &r, format);
+
+ HeapFree(GetProcessHeap(), 0, textbuf);
+
+ int padding = ALF_CentipointsToPixels(GetParent(hwnd), 525);
+ int minheight = ALF_CentipointsToPixels(GetParent(hwnd), 1725);
+
+ SIZE *pSize = (SIZE*)(void*)lParam;
+ if (pSize->cx < r.right - r.left + padding) {
+ pSize->cx = r.right - r.left + padding;
+ }
+ if (pSize->cx < minheight) {
+ pSize->cx = minheight;
+ }
+ if (pSize->cy < r.bottom - r.top + padding) {
+ pSize->cy = r.bottom - r.top + padding;
+ }
+ if (pSize->cy < minheight) {
+ pSize->cy = minheight;
+ }
+
+ if (font)
+ SelectFont(hdc, oldFont);
+
+ ReleaseDC(hwnd, hdc);
+ }
+
+ return DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+
+HWND
+ALF_AddButton(HWND win, int id, UINT x, UINT y, const WCHAR *text)
+{
+ HWND hwndButton = CreateWindowEx(0,
+ L"BUTTON",
+ text,
+ WS_CHILD | WS_TABSTOP | WS_VISIBLE | BS_PUSHBUTTON | BS_MULTILINE,
+ 0, 0, 100, 100,
+ win,
+ (HMENU)id,
+ (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE),
+ NULL);
+
+ SetWindowSubclass(hwndButton, ALF__ButtonSubclassProc, 0, 0);
+
+ ALFAddWidgetParams p;
+ ZeroMemory(&p, sizeof(p));
+ p.hwnd = hwndButton;
+ p.x = x;
+ p.y = y;
+ p.width = 5625;
+ p.height = 0;
+ p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT;
+
+ ALF_AddWidgetEx(win, &p);
+
+ return hwndButton;
+}
diff --git a/alf/alf.h b/alf/alf.h
new file mode 100644
index 0000000..d50bd67
--- /dev/null
+++ b/alf/alf.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ int dpi;
+ LOGFONT lfMessageFont;
+ HFONT hMessageFont;
+} ALFWindowFonts;
+
+typedef struct {
+ void (*create)(void * /*closure*/, HWND /*window*/);
+ void (*destroy)(void * /*closure*/, HWND /*window*/);
+ BOOL (*close)(void * /*closure*/, HWND /*window*/);
+ void (*postdestroy)(void * /*closure*/);
+ void (*updatefonts)(void * /*closure*/, HWND /*window*/, const ALFWindowFonts *fonts);
+ LRESULT (*message)(void * /*closure*/, HWND, UINT, WPARAM, LPARAM);
+ LRESULT (*command)(void * /*closure*/, HWND /*window*/, UINT /*sourceid*/, UINT /*notificationcode*/, LPARAM);
+ LRESULT (*notify)(void * /*closure*/, HWND /*window*/, WPARAM /*sourceid*/, NMHDR *);
+} ALFWindowVTable;
+
+// layout flags
+#define ALF_QUERYSIZE 0x01
+#define ALF_HEXPAND 0x02
+#define ALF_VEXPAND 0x04
+#define ALF_MESSAGEFONT 0x08
+
+// messages
+#define ALF_WM__BASE 0x2800
+#define ALF_WM_QUERYSIZE (ALF_WM__BASE + 1)
+#define ALF_WM_APPLYLAYOUT (ALF_WM__BASE + 2)
+#define ALF_WM_UPDATEFONTS (ALF_WM__BASE + 3)
+#define ALF_WM_ADDWIDGET (ALF_WM__BASE + 4)
+#define ALF_WM_WIDGETBYID (ALF_WM__BASE + 5)
+#define ALF_WM_REMOVEWIDGET (ALF_WM__BASE + 6)
+#define ALF_WM_SETMODALRESULT (ALF_WM__BASE + 7)
+#define ALF_WM_GETMODALRESULT (ALF_WM__BASE + 8)
+#define ALF_WM_CENTIPOINTTOPX (ALF_WM__BASE + 9)
+
+
+typedef struct {
+ const WCHAR *className;
+ UINT classStyle;
+ ALFWindowVTable vtbl;
+} ALFWindowClassParams;
+
+typedef struct {
+ void *closure;
+ HWND hwndParent;
+ UINT windowStyle;
+ UINT windowExStyle;
+} ALFWindowInstanceParams;
+
+typedef struct {
+ HWND hwnd;
+ UINT x;
+ UINT y;
+ UINT width;
+ UINT height;
+ DWORD flags;
+ UINT margins[4];
+} ALFAddWidgetParams;
+
+LPTSTR
+ALF_RegisterWindowClass(HINSTANCE hInstance, const ALFWindowClassParams *params);
+
+void
+ALF_UnregisterWindowClass(HINSTANCE hInstance, LPCTSTR className);
+
+HWND
+ALF_InstantiateWindow(HINSTANCE hInstance, LPCTSTR className, const ALFWindowInstanceParams *params);
+
+int
+ALF_CentipointsToPixels(HWND win, int cptValue);
+
+LRESULT
+ALF_DefWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
+
+HWND
+ALF_AddLabel(HWND win, int id, UINT x, UINT y, const WCHAR *text);
+
+HWND
+ALF_AddEdit(HWND win, int id, UINT x, UINT y, const WCHAR *text);
+
+HWND
+ALF_AddButton(HWND win, int id, UINT x, UINT y, const WCHAR *text);
+
+void
+ALF_DestroyWidget(HWND win, int id);
+
+void
+ALF_AddWidget(HWND win, UINT x, UINT y, HWND widget, UINT cptWidth, UINT cptHeight, DWORD flags);
+
+void
+ALF_AddWidgetEx(HWND win, const ALFAddWidgetParams *params);
+
+HWND
+ALF_WidgetHwndById(HWND win, int id);
+
+void
+ALF_RecalculateLayout(HWND win);
+
+void
+ALF_UpdateFonts(HWND win);
+
+void
+ALF_ResizeWindow(HWND win, UINT cptWidth, UINT cptHeight);
+
+int
+ALF_ShowModal(HWND win);
+
+void
+ALF_SetModalResult(HWND win, int result);
+
+int
+ALF_GetModalResult(HWND win);
+
+#ifdef __cplusplus
+} // extern C
+#endif