Started trying to move the Windows uiArea to Direct2D.

This commit is contained in:
Pietro Gagliardi 2015-09-16 23:15:42 -04:00
parent 6a3bae98ca
commit 16c07905c9
9 changed files with 1815 additions and 0 deletions

665
winarea_d2d/area.c Normal file
View File

@ -0,0 +1,665 @@
// 8 september 2015
#include "area.h"
#define areaClass L"libui_uiAreaClass"
struct uiArea {
// uiWindowsControl c;
HWND hwnd;
uiAreaHandler *ah;
intmax_t hscrollpos;
intmax_t vscrollpos;
int hwheelCarry;
int vwheelCarry;
clickCounter cc;
BOOL capturing;
};
static void doPaint(uiArea *a, HDC dc, RECT *client, RECT *clip)
{
uiAreaHandler *ah = a->ah;
uiAreaDrawParams dp;
dp.Context = newContext(dc);
dp.ClientWidth = client->right - client->left;
dp.ClientHeight = client->bottom - client->top;
dp.ClipX = clip->left;
dp.ClipY = clip->top;
dp.ClipWidth = clip->right - clip->left;
dp.ClipHeight = clip->bottom - clip->top;
// TODO is this really the best for multimonitor setups?
dp.DPIX = GetDeviceCaps(dc, LOGPIXELSX);
dp.DPIY = GetDeviceCaps(dc, LOGPIXELSY);
dp.HScrollPos = a->hscrollpos;
dp.VScrollPos = a->vscrollpos;
(*(ah->Draw))(ah, a, &dp);
}
struct scrollParams {
intmax_t *pos;
intmax_t pagesize;
intmax_t length;
int *wheelCarry;
UINT wheelSPIAction;
};
void scrollto(uiArea *a, int which, struct scrollParams *p, intmax_t pos)
{
SCROLLINFO si;
intmax_t xamount, yamount;
// note that the pos < 0 check is /after/ the p->length - p->pagesize check
// it used to be /before/; this was actually a bug in Raymond Chen's original algorithm: if there are fewer than a page's worth of items, p->length - p->pagesize will be negative and our content draw at the bottom of the window
// this SHOULD have the same effect with that bug fixed and no others introduced... (thanks to devin on irc.badnik.net for confirming this logic)
// TODO verify this still holds with uiArea
if (pos > p->length - p->pagesize)
pos = p->length - p->pagesize;
if (pos < 0)
pos = 0;
// negative because ScrollWindowEx() is "backwards"
xamount = -(pos - *(p->pos));
yamount = 0;
if (which == SB_VERT) {
yamount = xamount;
xamount = 0;
}
if (ScrollWindowEx(a->hwnd, xamount, yamount,
NULL, NULL, NULL, NULL,
SW_ERASE | SW_INVALIDATE) == ERROR)
;//TODO logLastError("error scrolling area in scrollto()");
*(p->pos) = pos;
// now commit our new scrollbar setup...
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
si.nPage = p->pagesize;
si.nMin = 0;
si.nMax = p->length - 1; // endpoint inclusive
si.nPos = *(p->pos);
SetScrollInfo(a->hwnd, which, &si, TRUE);
}
void scrollby(uiArea *a, int which, struct scrollParams *p, intmax_t delta)
{
scrollto(a, which, p, *(p->pos) + delta);
}
void scroll(uiArea *a, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam)
{
intmax_t pos;
SCROLLINFO si;
pos = *(p->pos);
switch (LOWORD(wParam)) {
case SB_LEFT: // also SB_TOP
pos = 0;
break;
case SB_RIGHT: // also SB_BOTTOM
pos = p->length - p->pagesize;
break;
case SB_LINELEFT: // also SB_LINEUP
pos--;
break;
case SB_LINERIGHT: // also SB_LINEDOWN
pos++;
break;
case SB_PAGELEFT: // also SB_PAGEUP
pos -= p->pagesize;
break;
case SB_PAGERIGHT: // also SB_PAGEDOWN
pos += p->pagesize;
break;
case SB_THUMBPOSITION:
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_POS;
if (GetScrollInfo(a->hwnd, which, &si) == 0)
logLastError("error getting thumb position for area in scroll()");
pos = si.nPos;
break;
case SB_THUMBTRACK:
ZeroMemory(&si, sizeof (SCROLLINFO));
si.cbSize = sizeof (SCROLLINFO);
si.fMask = SIF_TRACKPOS;
if (GetScrollInfo(a->hwnd, which, &si) == 0)
logLastError("error getting thumb track position for area in scroll()");
pos = si.nTrackPos;
break;
}
scrollto(a, which, p, pos);
}
static void wheelscroll(uiArea *a, int which, struct scrollParams *p, WPARAM wParam, LPARAM lParam)
{
int delta;
int lines;
UINT scrollAmount;
delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (SystemParametersInfoW(p->wheelSPIAction, 0, &scrollAmount, 0) == 0)
// TODO use scrollAmount == 3 (for both v and h) instead?
logLastError("error getting area wheel scroll amount in wheelscroll()");
if (scrollAmount == WHEEL_PAGESCROLL)
scrollAmount = p->pagesize;
if (scrollAmount == 0) // no mouse wheel scrolling (or t->pagesize == 0)
return;
// the rest of this is basically http://blogs.msdn.com/b/oldnewthing/archive/2003/08/07/54615.aspx and http://blogs.msdn.com/b/oldnewthing/archive/2003/08/11/54624.aspx
// see those pages for information on subtleties
delta += *(p->wheelCarry);
lines = delta * ((int) scrollAmount) / WHEEL_DELTA;
*(p->wheelCarry) = delta - lines * WHEEL_DELTA / ((int) scrollAmount);
return scrollby(a, which, p, -lines);
}
static void hscrollParams(uiArea *a, struct scrollParams *p)
{
RECT r;
ZeroMemory(p, sizeof (struct scrollParams));
p->pos = &(a->hscrollpos);
if (GetClientRect(a->hwnd, &r) == 0)
logLastError("error getting area client rect in hscrollParams()");
p->pagesize = r.right - r.left;
p->length = (*(a->ah->HScrollMax))(a->ah, a);
p->wheelCarry = &(a->hwheelCarry);
p->wheelSPIAction = SPI_GETWHEELSCROLLCHARS;
}
static void hscrollto(uiArea *a, intmax_t pos)
{
struct scrollParams p;
hscrollParams(a, &p);
scrollto(a, SB_HORZ, &p, pos);
}
static void hscrollby(uiArea *a, intmax_t delta)
{
struct scrollParams p;
hscrollParams(a, &p);
scrollby(a, SB_HORZ, &p, delta);
}
static void hscroll(uiArea *a, WPARAM wParam, LPARAM lParam)
{
struct scrollParams p;
hscrollParams(a, &p);
scroll(a, SB_HORZ, &p, wParam, lParam);
}
static void hwheelscroll(uiArea *a, WPARAM wParam, LPARAM lParam)
{
struct scrollParams p;
hscrollParams(a, &p);
wheelscroll(a, SB_HORZ, &p, wParam, lParam);
}
static void vscrollParams(uiArea *a, struct scrollParams *p)
{
RECT r;
ZeroMemory(p, sizeof (struct scrollParams));
p->pos = &(a->vscrollpos);
if (GetClientRect(a->hwnd, &r) == 0)
logLastError("error getting area client rect in vscrollParams()");
p->pagesize = r.bottom - r.top;
p->length = (*(a->ah->VScrollMax))(a->ah, a);
p->wheelCarry = &(a->vwheelCarry);
p->wheelSPIAction = SPI_GETWHEELSCROLLLINES;
}
static void vscrollto(uiArea *a, intmax_t pos)
{
struct scrollParams p;
vscrollParams(a, &p);
scrollto(a, SB_VERT, &p, pos);
}
static void vscrollby(uiArea *a, intmax_t delta)
{
struct scrollParams p;
vscrollParams(a, &p);
scrollby(a, SB_VERT, &p, delta);
}
static void vscroll(uiArea *a, WPARAM wParam, LPARAM lParam)
{
struct scrollParams p;
vscrollParams(a, &p);
scroll(a, SB_VERT, &p, wParam, lParam);
}
static void vwheelscroll(uiArea *a, WPARAM wParam, LPARAM lParam)
{
struct scrollParams p;
vscrollParams(a, &p);
wheelscroll(a, SB_VERT, &p, wParam, lParam);
}
static uiModifiers getModifiers(void)
{
uiModifiers m = 0;
if ((GetKeyState(VK_CONTROL) & 0x80) != 0)
m |= uiModifierCtrl;
if ((GetKeyState(VK_MENU) & 0x80) != 0)
m |= uiModifierAlt;
if ((GetKeyState(VK_SHIFT) & 0x80) != 0)
m |= uiModifierShift;
if ((GetKeyState(VK_LWIN) & 0x80) != 0)
m |= uiModifierSuper;
if ((GetKeyState(VK_RWIN) & 0x80) != 0)
m |= uiModifierSuper;
return m;
}
static void areaMouseEvent(uiArea *a, uintmax_t down, uintmax_t up, WPARAM wParam, LPARAM lParam)
{
uiAreaMouseEvent me;
uintmax_t button;
RECT r;
me.X = GET_X_LPARAM(lParam);
me.Y = GET_Y_LPARAM(lParam);
if (GetClientRect(a->hwnd, &r) == 0)
logLastError("error getting client rect of area in areaMouseEvent()");
me.ClientWidth = r.right - r.left;
me.ClientHeight = r.bottom - r.top;
me.HScrollPos = a->hscrollpos;
me.VScrollPos = a->vscrollpos;
me.Down = down;
me.Up = up;
me.Count = 0;
if (me.Down != 0)
// GetMessageTime() returns LONG and GetDoubleClckTime() returns UINT, which are int32 and uint32, respectively, but we don't need to worry about the signedness because for the same bit widths and two's complement arithmetic, s1-s2 == u1-u2 if bits(s1)==bits(s2) and bits(u1)==bits(u2) (and Windows requires two's complement: http://blogs.msdn.com/b/oldnewthing/archive/2005/05/27/422551.aspx)
// signedness isn't much of an issue for these calls anyway because http://stackoverflow.com/questions/24022225/what-are-the-sign-extension-rules-for-calling-windows-api-functions-stdcall-t and that we're only using unsigned values (think back to how you (didn't) handle signedness in assembly language) AND because of the above AND because the statistics below (time interval and width/height) really don't make sense if negative
me.Count = clickCounterClick(&(a->cc), me.Down,
me.X, me.Y,
GetMessageTime(), GetDoubleClickTime(),
GetSystemMetrics(SM_CXDOUBLECLK) / 2,
GetSystemMetrics(SM_CYDOUBLECLK) / 2);
// though wparam will contain control and shift state, let's just one function to get modifiers for both keyboard and mouse events; it'll work the same anyway since we have to do this for alt and windows key (super)
me.Modifiers = getModifiers();
button = me.Down;
if (button == 0)
button = me.Up;
me.Held1To64 = 0;
if (button != 1 && (wParam & MK_LBUTTON) != 0)
me.Held1To64 |= 1 << 0;
if (button != 2 && (wParam & MK_MBUTTON) != 0)
me.Held1To64 |= 1 << 1;
if (button != 3 && (wParam & MK_RBUTTON) != 0)
me.Held1To64 |= 1 << 2;
if (button != 4 && (wParam & MK_XBUTTON1) != 0)
me.Held1To64 |= 1 << 3;
if (button != 5 && (wParam & MK_XBUTTON2) != 0)
me.Held1To64 |= 1 << 4;
// on Windows, we have to capture on drag ourselves
if (me.Down != 0 && !a->capturing) {
SetCapture(a->hwnd);
a->capturing = TRUE;
}
// only release capture when all buttons released
if (me.Up != 0 && a->capturing && me.Held1To64 == 0) {
// unset flag first as ReleaseCapture() sends WM_CAPTURECHANGED
a->capturing = FALSE;
if (ReleaseCapture() == 0)
logLastError("error releasing capture on drag in areaMouseEvent()");
}
(*(a->ah->MouseEvent))(a->ah, a, &me);
}
// we use VK_SNAPSHOT as a sentinel because libui will never support the print screen key; that key belongs to the user
struct extkeymap {
WPARAM vk;
uiExtKey extkey;
};
// all mappings come from GLFW - https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152
static const struct extkeymap numpadExtKeys[] = {
{ VK_HOME, uiExtKeyN7 },
{ VK_UP, uiExtKeyN8 },
{ VK_PRIOR, uiExtKeyN9 },
{ VK_LEFT, uiExtKeyN4 },
{ VK_CLEAR, uiExtKeyN5 },
{ VK_RIGHT, uiExtKeyN6 },
{ VK_END, uiExtKeyN1 },
{ VK_DOWN, uiExtKeyN2 },
{ VK_NEXT, uiExtKeyN3 },
{ VK_INSERT, uiExtKeyN0 },
{ VK_DELETE, uiExtKeyNDot },
{ VK_SNAPSHOT, 0 },
};
static const struct extkeymap extKeys[] = {
{ VK_ESCAPE, uiExtKeyEscape },
{ VK_INSERT, uiExtKeyInsert },
{ VK_DELETE, uiExtKeyDelete },
{ VK_HOME, uiExtKeyHome },
{ VK_END, uiExtKeyEnd },
{ VK_PRIOR, uiExtKeyPageUp },
{ VK_NEXT, uiExtKeyPageDown },
{ VK_UP, uiExtKeyUp },
{ VK_DOWN, uiExtKeyDown },
{ VK_LEFT, uiExtKeyLeft },
{ VK_RIGHT, uiExtKeyRight },
{ VK_F1, uiExtKeyF1 },
{ VK_F2, uiExtKeyF2 },
{ VK_F3, uiExtKeyF3 },
{ VK_F4, uiExtKeyF4 },
{ VK_F5, uiExtKeyF5 },
{ VK_F6, uiExtKeyF6 },
{ VK_F7, uiExtKeyF7 },
{ VK_F8, uiExtKeyF8 },
{ VK_F9, uiExtKeyF9 },
{ VK_F10, uiExtKeyF10 },
{ VK_F11, uiExtKeyF11 },
{ VK_F12, uiExtKeyF12 },
// numpad numeric keys and . are handled in events.c
// numpad enter is handled in code below
{ VK_ADD, uiExtKeyNAdd },
{ VK_SUBTRACT, uiExtKeyNSubtract },
{ VK_MULTIPLY, uiExtKeyNMultiply },
{ VK_DIVIDE, uiExtKeyNDivide },
{ VK_SNAPSHOT, 0 },
};
static const struct {
WPARAM vk;
uiModifiers mod;
} modKeys[] = {
// even if the separate left/right aren't necessary, have them here anyway, just to be safe
{ VK_CONTROL, uiModifierCtrl },
{ VK_LCONTROL, uiModifierCtrl },
{ VK_RCONTROL, uiModifierCtrl },
{ VK_MENU, uiModifierAlt },
{ VK_LMENU, uiModifierAlt },
{ VK_RMENU, uiModifierAlt },
{ VK_SHIFT, uiModifierShift },
{ VK_LSHIFT, uiModifierShift },
{ VK_RSHIFT, uiModifierShift },
// there's no combined Windows key virtual-key code as there is with the others
{ VK_LWIN, uiModifierSuper },
{ VK_RWIN, uiModifierSuper },
{ VK_SNAPSHOT, 0 },
};
static int areaKeyEvent(uiArea *a, int up, WPARAM wParam, LPARAM lParam)
{
uiAreaKeyEvent ke;
int righthand;
int i;
ke.Key = 0;
ke.ExtKey = 0;
ke.Modifier = 0;
ke.Modifiers = getModifiers();
ke.Up = up;
// the numeric keypad keys when Num Lock is off are considered left-hand keys as the separate navigation buttons were added later
// the numeric keypad enter, however, is a right-hand key because it has the same virtual-key code as the typewriter enter
righthand = (lParam & 0x01000000) != 0;
if (righthand) {
if (wParam == VK_RETURN) {
ke.ExtKey = uiExtKeyNEnter;
goto keyFound;
}
} else
// this is special handling for numpad keys to ignore the state of Num Lock and Shift; see http://blogs.msdn.com/b/oldnewthing/archive/2004/09/06/226045.aspx and https://github.com/glfw/glfw/blob/master/src/win32_window.c#L152
for (i = 0; numpadExtKeys[i].vk != VK_SNAPSHOT; i++)
if (numpadExtKeys[i].vk == wParam) {
ke.ExtKey = numpadExtKeys[i].extkey;
goto keyFound;
}
// okay, those above cases didn't match anything
// first try the extended keys
for (i = 0; extKeys[i].vk != VK_SNAPSHOT; i++)
if (extKeys[i].vk == wParam) {
ke.ExtKey = extKeys[i].extkey;
goto keyFound;
}
// then try modifier keys
for (i = 0; modKeys[i].vk != VK_SNAPSHOT; i++)
if (modKeys[i].vk == wParam) {
ke.Modifier = modKeys[i].mod;
// and don't include the key in Modifiers
ke.Modifiers &= ~ke.Modifier;
goto keyFound;
}
// and finally everything else
if (fromScancode((lParam >> 16) & 0xFF, &ke))
goto keyFound;
// not a supported key, assume unhandled
// TODO the original code only did this if ke.Modifiers == 0 - why?
return 0;
keyFound:
return (*(a->ah->KeyEvent))(a->ah, a, &ke);
}
enum {
// start at 0x40 to avoid clobbering dialog messages
msgAreaKeyDown = WM_USER + 0x40,
msgAreaKeyUp,
};
static LRESULT CALLBACK areaWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
uiArea *a;
CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam;
HDC dc;
PAINTSTRUCT ps;
RECT client;
WINDOWPOS *wp = (WINDOWPOS *) lParam;
a = (uiArea *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (a == NULL) {
if (uMsg == WM_CREATE)
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) (cs->lpCreateParams));
// fall through to DefWindowProcW() anyway
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_PAINT:
dc = BeginPaint(a->hwnd, &ps);
if (dc == NULL)
logLastError("error beginning paint in areaWndProc");
if (GetClientRect(a->hwnd, &client) == 0)
logLastError("error getting client rect in WM_PAINT in areaWndProc()");
doPaint(a, dc, &client, &(ps.rcPaint));
EndPaint(a->hwnd, &ps);
return 0;
case WM_PRINTCLIENT:
if (GetClientRect(a->hwnd, &client) == 0)
logLastError("error getting client rect in WM_PRINTCLIENT in areaWndProc()");
doPaint(a, (HDC) wParam, &client, &client);
return 0;
case WM_WINDOWPOSCHANGED:
if ((wp->flags & SWP_NOSIZE) != 0)
break;
if ((*(a->ah->RedrawOnResize))(a->ah, a))
if (InvalidateRect(a->hwnd, NULL, TRUE) == 0)
logLastError("error redrawing area on resize in areaWndProc()");
return 0;
case WM_HSCROLL:
hscroll(a, wParam, lParam);
return 0;
case WM_MOUSEHWHEEL:
hwheelscroll(a, wParam, lParam);
return 0;
case WM_VSCROLL:
vscroll(a, wParam, lParam);
return 0;
case WM_MOUSEWHEEL:
vwheelscroll(a, wParam, lParam);
return 0;
case WM_ACTIVATE:
// don't keep the double-click timer running if the user switched programs in between clicks
clickCounterReset(&(a->cc));
return 0;
case WM_MOUSEMOVE:
areaMouseEvent(a, 0, 0, wParam, lParam);
return 0;
case WM_LBUTTONDOWN:
SetFocus(a->hwnd);
areaMouseEvent(a, 1, 0, wParam, lParam);
return 0;
case WM_LBUTTONUP:
areaMouseEvent(a, 0, 1, wParam, lParam);
return 0;
case WM_MBUTTONDOWN:
SetFocus(a->hwnd);
areaMouseEvent(a, 2, 0, wParam, lParam);
return 0;
case WM_MBUTTONUP:
areaMouseEvent(a, 0, 2, wParam, lParam);
return 0;
case WM_RBUTTONDOWN:
SetFocus(a->hwnd);
areaMouseEvent(a, 3, 0, wParam, lParam);
return 0;
case WM_RBUTTONUP:
areaMouseEvent(a, 0, 3, wParam, lParam);
return 0;
case WM_XBUTTONDOWN:
SetFocus(a->hwnd);
// values start at 1; we want them to start at 4
areaMouseEvent(a,
GET_XBUTTON_WPARAM(wParam) + 3, 0,
GET_KEYSTATE_WPARAM(wParam), lParam);
return TRUE; // XBUTTON messages are different!
case WM_XBUTTONUP:
areaMouseEvent(a,
0, GET_XBUTTON_WPARAM(wParam) + 3,
GET_KEYSTATE_WPARAM(wParam), lParam);
return TRUE; // XBUTTON messages are different!
case WM_CAPTURECHANGED:
if (a->capturing) {
a->capturing = FALSE;
(*(a->ah->DragBroken))(a->ah, a);
}
return 0;
case msgAreaKeyDown:
return (LRESULT) areaKeyEvent(a, 0, wParam, lParam);
case msgAreaKeyUp:
return (LRESULT) areaKeyEvent(a, 1, wParam, lParam);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// TODO affect visibility properly
BOOL processAreaMessage(MSG *msg)
{
LRESULT handled;
// TODO get rid of this part
WCHAR classname[260 + 1];
GetClassNameW(msg->hwnd, classname, 260);
if (wcscmp(classname, areaClass) != 0) return FALSE;
HWND active;
active = GetActiveWindow();
if (active == NULL) return FALSE;
handled = 0;
switch (msg->message) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
handled = SendMessageW(msg->hwnd, msgAreaKeyDown, msg->wParam, msg->lParam);
break;
case WM_KEYUP:
case WM_SYSKEYUP:
handled = SendMessageW(msg->hwnd, msgAreaKeyUp, msg->wParam, msg->lParam);
break;
}
if (handled)
return TRUE;
// don't call TranslateMessage(); we do our own keyboard handling
// TODO should we just return to the standard message loop?
if (IsDialogMessage(active, msg) != 0)
return TRUE;
DispatchMessageW(msg);
return TRUE;
}
ATOM registerAreaClass(void)
{
WNDCLASSW wc;
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = areaClass;
wc.lpfnWndProc = areaWndProc;
wc.hInstance = hInstance;
//TODO wc.hIcon = hDefaultIcon;
//TODO wc.hCursor = hDefaultCursor;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
// don't specify CS_HREDRAW or CS_VREDRAW; that's decided by the uiAreaHandler in RedrawOnResize()
return RegisterClassW(&wc);
}
void unregisterAreaClass(void)
{
if (UnregisterClassW(areaClass, hInstance) == 0)
logLastError("error unregistering uiArea window class in unregisterAreaClass()");
}
HWND makeArea(DWORD exstyle, DWORD style, int x, int y, int cx, int cy, HWND parent, uiAreaHandler *ah)
{
uiArea *a;
// TODO
a = malloc(sizeof (uiArea));
a->ah = ah;
a->hwnd = CreateWindowExW(exstyle,
areaClass, L"",
style | WS_HSCROLL | WS_VSCROLL,
x, y, cx, cy,
parent, NULL, hInstance, a);
clickCounterReset(&(a->cc));
a->capturing = FALSE;
return a->hwnd;
}
void areaUpdateScroll(HWND area)
{
uiArea *a;
a = (uiArea *) GetWindowLongPtrW(area, GWLP_USERDATA);
// use a no-op scroll to simulate scrolling
hscrollby(a, 0);
vscrollby(a, 0);
}

21
winarea_d2d/area.h Normal file
View File

@ -0,0 +1,21 @@
// 8 september 2015
#include "../windows/winapi.h"
#include <d2d1.h>
#include <stdint.h>
#include "ui.h"
#include "uipriv.h"
#include "uipriv_windows.h"
extern HINSTANCE hInstance;
extern ATOM registerAreaClass(void);
extern void unregisterAreaClass(void);
extern HWND makeArea(DWORD exstyle, DWORD style, int x, int y, int cx, int cy, HWND parent, uiAreaHandler *ah);
extern HRESULT logLastError(const char *);
extern HRESULT logHRESULT(const char *, HRESULT);
extern HRESULT logMemoryExhausted(const char *);
extern uiDrawContext *newContext(HDC);
extern void areaUpdateScroll(HWND);

111
winarea_d2d/debug.c Normal file
View File

@ -0,0 +1,111 @@
// 25 february 2015
#include "area.h"
// uncomment the following line to enable debug messages
#define tableDebug
// uncomment the following line to halt on a debug message
#define tableDebugStop
#ifdef tableDebug
#include <stdio.h>
HRESULT logLastError(const char *context)
{
DWORD le;
WCHAR *msg;
BOOL parenthesize = FALSE;
BOOL localFreeFailed = FALSE;
DWORD localFreeLastError;
le = GetLastError();
fprintf(stderr, "%s: ", context);
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, le, 0, (LPWSTR) (&msg), 0, NULL) != 0) {
fprintf(stderr, "%S (", msg);
if (LocalFree(msg) != NULL) {
localFreeFailed = TRUE;
localFreeLastError = GetLastError();
}
parenthesize = TRUE;
}
fprintf(stderr, "GetLastError() == %I32u", le);
if (parenthesize)
fprintf(stderr, ")");
if (localFreeFailed)
fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError);
fprintf(stderr, "\n");
#ifdef tableDebugStop
DebugBreak();
#endif
SetLastError(le);
// a function does not have to set a last error
// if the last error we get is actually 0, then HRESULT_FROM_WIN32(0) will return S_OK (0 cast to an HRESULT, since 0 <= 0), which we don't want
// prevent this by returning E_FAIL, so the rest of the Table code doesn't barge onward
if (le == 0)
return E_FAIL;
return HRESULT_FROM_WIN32(le);
}
HRESULT logHRESULT(const char *context, HRESULT hr)
{
WCHAR *msg;
BOOL parenthesize = FALSE;
BOOL localFreeFailed = FALSE;
DWORD localFreeLastError;
fprintf(stderr, "%s: ", context);
// this isn't technically documented, but everyone does it, including Microsoft (see the implementation of _com_error::ErrorMessage() in a copy of comdef.h that comes with the Windows DDK)
if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) hr, 0, (LPWSTR) (&msg), 0, NULL) != 0) {
fprintf(stderr, "%S (", msg);
if (LocalFree(msg) != NULL) {
localFreeFailed = TRUE;
localFreeLastError = GetLastError();
}
parenthesize = TRUE;
}
fprintf(stderr, "HRESULT == 0x%I32X", hr);
if (parenthesize)
fprintf(stderr, ")");
if (localFreeFailed)
fprintf(stderr, "; local free of system message failed with last error %I32u", localFreeLastError);
fprintf(stderr, "\n");
#ifdef tableDebugStop
DebugBreak();
#endif
return hr;
}
HRESULT logMemoryExhausted(const char *reason)
{
fprintf(stderr, "memory exhausted %s\n", reason);
#ifdef tableDebugStop
DebugBreak();
#endif
return E_OUTOFMEMORY;
}
#else
HRESULT logLastError(const char *reason)
{
DWORD le;
le = GetLastError();
// we shouldn't need to do this, but let's do this anyway just to be safe
SetLastError(le);
if (le == 0)
return E_FAIL;
return HRESULT_FROM_WIN32(le);
}
HRESULT logHRESULT(const char *reason, HRESULT hr)
{
return hr;
}
HRESULT logMemoryExhausted(const char *reason)
{
return E_OUTOFMEMORY;
}
#endif

389
winarea_d2d/draw.c Normal file
View File

@ -0,0 +1,389 @@
// 7 september 2015
#include "area.h"
static ID2D1Factory *d2dfactory = NULL;
HRESULT initDraw(void)
{
D2D1_FACTORY_OPTIONS opts;
ZeroMemory(&opts, sizeof (D2D1_FACTORY_OPTIONS));
// TODO make this an option
opts.debugLevel = D2D1_DEBUG_LEVEL_NONE;
return D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
&IID_ID2D1Factory,
&opts,
&d2dfactory);
}
void uninitDraw(void)
{
ID2D1Factory_Release(d2dfactory);
}
struct uiDrawContext {
HDC dc;
// source color
BOOL useRGB;
BOOL useAlpha;
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
uiDrawContext *newContext(HDC dc)
{
uiDrawContext *c;
// TODO use uiNew
c = (uiDrawContext *) malloc(sizeof (uiDrawContext));
c->dc = dc;
return c;
}
void uiDrawBeginPathRGB(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b)
{
c->useRGB = TRUE;
c->useAlpha = FALSE;
c->r = r;
c->g = g;
c->b = b;
if (BeginPath(c->dc) == 0)
logLastError("error beginning new path in uiDrawBeginPathRGB()");
}
void uiDrawBeginPathRGBA(uiDrawContext *c, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
c->useRGB = TRUE;
c->useAlpha = TRUE;
c->r = r;
c->g = g;
c->b = b;
c->a = a;
if (BeginPath(c->dc) == 0)
logLastError("error beginning new path in uiDrawBeginPathRGB()");
}
void uiDrawMoveTo(uiDrawContext *c, intmax_t x, intmax_t y)
{
if (MoveToEx(c->dc, x, y, NULL) == 0)
logLastError("error moving to point in uiDrawMoveTo()");
}
void uiDrawLineTo(uiDrawContext *c, intmax_t x, intmax_t y)
{
if (LineTo(c->dc, x, y) == 0)
logLastError("error drawing line in uiDrawLineTo()");
}
void uiDrawRectangle(uiDrawContext *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height)
{
if (Rectangle(c->dc, x, y, x + width, y + height) == 0)
logLastError("error drawing rectangle in uiDrawRectangle()");
}
void uiDrawArcTo(uiDrawContext *c, intmax_t xCenter, intmax_t yCenter, intmax_t radius, double startAngle, double endAngle, int lineFromCurrentPointToStart)
{
if (!lineFromCurrentPointToStart) {
int bx, by, bx2, by2;
int sx, sy, ex, ey;
// see http://stackoverflow.com/questions/32465446/how-do-i-inhibit-the-initial-line-segment-of-an-anglearc
// the idea for floor(x + 0.5) is inspired by wine
// TODO make sure this is an accurate imitation of AngleArc()
bx = xCenter - radius;
by = yCenter - radius;
bx2 = xCenter + radius;
by2 = yCenter + radius;
sx = xCenter + floor((double) radius * cos(startAngle));
sy = yCenter - floor((double) radius * sin(startAngle));
ex = xCenter + floor((double) radius * cos(endAngle));
ey = yCenter - floor((double) radius * sin(endAngle));
if (Arc(c->dc, bx, by, bx2, by2, sx, sy, ex, ey) == 0)
logLastError("error drawing current point arc in uiDrawArc()");
return;
}
// AngleArc() expects degrees
startAngle *= (180.0 / M_PI);
endAngle *= (180.0 / M_PI);
if (AngleArc(c->dc,
xCenter, yCenter,
radius,
startAngle,
// the "sweep angle" is relative to the start angle, not to 0
endAngle - startAngle) == 0)
logLastError("error drawing arc in uiDrawArc()");
}
void uiDrawBezierTo(uiDrawContext *c, intmax_t c1x, intmax_t c1y, intmax_t c2x, intmax_t c2y, intmax_t endX, intmax_t endY)
{
POINT points[3];
points[0].x = c1x;
points[0].y = c1y;
points[1].x = c2x;
points[1].y = c2y;
points[2].x = endX;
points[2].y = endY;
if (PolyBezierTo(c->dc, points, 3) == 0)
logLastError("error drawing bezier curve in uiDrawBezierTo()");
}
void uiDrawCloseFigure(uiDrawContext *c)
{
if (CloseFigure(c->dc) == 0)
logLastError("error closing figure in uiDrawCloseFigure()");
}
static HPEN toPen(uiDrawContext *c, uiDrawStrokeParams *p)
{
DWORD style;
LOGBRUSH lb;
HPEN pen;
style = PS_GEOMETRIC;
switch (p->Cap) {
case uiDrawLineCapFlat:
style |= PS_ENDCAP_FLAT;
break;
case uiDrawLineCapRound:
style |= PS_ENDCAP_ROUND;
break;
case uiDrawLineCapSquare:
style |= PS_ENDCAP_SQUARE;
break;
}
switch (p->Join) {
case uiDrawLineJoinMiter:
style |= PS_JOIN_MITER;
break;
case uiDrawLineJoinRound:
style |= PS_JOIN_ROUND;
break;
case uiDrawLineJoinBevel:
style |= PS_JOIN_BEVEL;
break;
}
ZeroMemory(&lb, sizeof (LOGBRUSH));
if (c->useRGB) {
lb.lbStyle = BS_SOLID;
// we don't need to worry about alpha premultiplication; see below
lb.lbColor = RGB(c->r, c->g, c->b);
} else {
// TODO
}
pen = ExtCreatePen(style,
p->Thickness,
&lb,
0,
NULL);
if (pen == NULL)
logLastError("error creating pen in toPen()");
return pen;
}
// unfortunately alpha blending in GDI is limited to bitmap on bitmap
// to do alpha vector graphics, we have to fake it by drawing to an offscreen bitmap first
// also we can't just use regions, we have to copy the path...
// we don't need to worry about alpha premultiplication; see below
struct alpha {
HDC dc;
HBITMAP bitmap;
VOID *ppvBits;
HBITMAP prevbitmap;
RECT r;
};
static void startAlpha(uiDrawContext *c, struct alpha *a)
{
int n;
POINT *points;
BYTE *ops;
HRGN region;
BITMAPINFO bi;
POINT prevWindowOrigin;
DWORD le;
ZeroMemory(a, sizeof (struct alpha));
// first, get the path
// we need to do this first because PathToRegion() clears the path
SetLastError(0);
n = GetPath(c->dc, NULL, NULL, 0);
if (n == 0) {
le = GetLastError();
SetLastError(le); // just in case
if (le != 0)
logLastError("error getting path point count in startAlpha()");
}
// TODO switch to uiAlloc
points = (POINT *) malloc(n * sizeof (POINT));
ops = (BYTE *) malloc(n * sizeof (BYTE));
if (GetPath(c->dc, points, ops, n) != n)
logLastError("error getting path in startAlpha()");
// next we need to know the bounding box of the path so we can create the bitmap
region = PathToRegion(c->dc);
if (region == NULL)
logLastError("error getting path region in startAlpha()");
if (GetRgnBox(region, &(a->r)) == 0)
logLastError("error getting region bounding box in startAlpha()");
if (DeleteObject(region) == 0)
logLastError("error deleting path region in startAlpha()");
// now create a DC compatible with the original that we can copy the path to and AlphaBlend() from
a->dc = CreateCompatibleDC(c->dc);
if (a->dc == NULL)
logLastError("error creating compatible DC in startAlpha()");
// now create and select in a *device-independent* bitmap that will hold the data that we're going to alpha blend
ZeroMemory(&bi, sizeof (BITMAPINFO));
bi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bi.bmiHeader.biWidth = a->r.right - a->r.left;
bi.bmiHeader.biHeight = -(a->r.bottom - a->r.top); // negative to draw top-down
bi.bmiHeader.biPlanes = 1;
bi.bmiHeader.biBitCount = 32;
bi.bmiHeader.biCompression = BI_RGB;
a->bitmap = CreateDIBSection(c->dc, &bi, DIB_RGB_COLORS,
&(a->ppvBits), NULL, 0);
if (a->bitmap == NULL)
logLastError("error creating bitmap in startAlpha()");
a->prevbitmap = SelectObject(a->dc, a->bitmap);
if (a->prevbitmap == NULL)
logLastError("error selecting bitmap into DC in startAlpha()");
// now we can finally copy the path like we were going to earlier
// we need to change the window origin so the path draws in the right place
if (SetWindowOrgEx(a->dc, a->r.left, a->r.top, &prevWindowOrigin) == 0)
logLastError("error setting window origin in startAlpha()");
if (BeginPath(a->dc) == 0)
logLastError("error beginning path in startAlpha()");
if (PolyDraw(a->dc, points, ops, n) == 0)
logLastError("error copying path in startAlpha()");
if (EndPath(a->dc) == 0)
logLastError("error ending path in startAlpha()");
if (SetWindowOrgEx(a->dc, prevWindowOrigin.x, prevWindowOrigin.y, NULL) == 0)
logLastError("error resetting window origin in startAlpha()");
free(points);
free(ops);
}
static void finishAlpha(uiDrawContext *c, struct alpha *a)
{
BLENDFUNCTION bf;
bf.BlendOp = AC_SRC_OVER;
bf.BlendFlags = 0;
// fortunately blending this is easy; we don't have to touch the pixels at all because BLENDFUNCTION has a way to take care of this...
bf.SourceConstantAlpha = c->a;
bf.AlphaFormat = 0; // no per-pixel apha
// a consequence of this setup is that the image data doesn't need to be alpha-premultiplied, because SourceConstantAlpha will be multiplied for us (according to the docs, anyway)
// TODO is this actually the case?
// and we can finally alpha-blend this!
if (AlphaBlend(c->dc, a->r.left, a->r.top,
a->r.right - a->r.left, a->r.bottom - a->r.top,
a->dc, 0, 0,
a->r.right - a->r.left, a->r.bottom - a->r.top,
bf) == FALSE)
logLastError("error alpha blending in finishAlpha()");
// and clean it all up
if (SelectObject(a->dc, a->prevbitmap) != a->bitmap)
logLastError("error deselecting bitmap in finishAlpha()");
if (DeleteObject(a->bitmap) == 0)
logLastError("error deleting object in finishAlpha()");
if (DeleteDC(a->dc) == 0)
logLastError("error deleting DC in finishAlpha()");
}
void uiDrawStroke(uiDrawContext *c, uiDrawStrokeParams *p)
{
HPEN pen, prevpen;
HDC strokeDC;
struct alpha alpha;
if (EndPath(c->dc) == 0)
logLastError("error ending path in uiDrawStroke()");
if (c->useAlpha) {
startAlpha(c, &alpha);
strokeDC = alpha.dc;
} else // no alpha; just stroke directly
strokeDC = c->dc;
pen = toPen(c, p);
if (p->Join == uiDrawLineJoinMiter)
if (SetMiterLimit(strokeDC, p->MiterLimit, NULL) == 0)
logLastError("error setting miter limit in uiDrawStroke()");
prevpen = SelectObject(strokeDC, pen);
if (prevpen == NULL)
logLastError("error selecting pen into DC in uiDrawStroke()");
if (StrokePath(strokeDC) == 0)
logLastError("error stroking path in uiDrawStroke()");
if (SelectObject(strokeDC, prevpen) != pen)
logLastError("error deselecting pen from DC in uiDrawStroke()");
if (DeleteObject(pen) == 0)
logLastError("error deleting pen in uiDrawStroke()");
if (c->useAlpha)
finishAlpha(c, &alpha);
}
static HBRUSH toBrush(uiDrawContext *c)
{
HBRUSH brush;
if (c->useRGB) {
brush = CreateSolidBrush(RGB(c->r, c->g, c->b));
if (brush == NULL)
logLastError("error creating solid brush in toBrush()");
return brush;
}
// TODO
return NULL;
}
void uiDrawFill(uiDrawContext *c, uiDrawFillMode mode)
{
HBRUSH brush, prevbrush;
HDC fillDC;
struct alpha alpha;
int pfm;
if (EndPath(c->dc) == 0)
logLastError("error ending path in uiDrawFill()");
if (c->useAlpha) {
startAlpha(c, &alpha);
fillDC = alpha.dc;
} else // no alpha; just stroke directly
fillDC = c->dc;
switch (mode) {
case uiDrawFillModeWinding:
pfm = WINDING;
break;
case uiDrawFillModeAlternate:
pfm = ALTERNATE;
break;
}
if (SetPolyFillMode(fillDC, pfm) == 0)
logLastError("error setting fill mode in uiDrawFill()");
brush = toBrush(c);
prevbrush = SelectObject(fillDC, brush);
if (prevbrush == NULL)
logLastError("error selecting brush into DC in uiDrawFill()");
if (FillPath(fillDC) == 0)
logLastError("error filling path in uiDrawFill()");
if (SelectObject(fillDC, prevbrush) != brush)
logLastError("error deselecting brush from DC in uiDrawFill()");
if (DeleteObject(brush) == 0)
logLastError("error deleting pen in uiDrawStroke()");
if (c->useAlpha)
finishAlpha(c, &alpha);
}

169
winarea_d2d/events.c Normal file
View File

@ -0,0 +1,169 @@
// 29 march 2014
// TODO remove
#include <stdint.h>
#include "ui.h"
#include "uipriv.h"
/*
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
*/
// x, y, xdist, ydist, and c.rect must have the same units
// so must time, maxTime, and c.prevTime
uintmax_t clickCounterClick(clickCounter *c, uintmax_t button, intmax_t x, intmax_t y, uintptr_t time, uintptr_t maxTime, intmax_t xdist, intmax_t ydist)
{
// different button than before? if so, don't count
if (button != c->curButton)
c->count = 0;
// (x, y) in the allowed region for a double-click? if not, don't count
if (x < c->rectX0)
c->count = 0;
if (y < c->rectY0)
c->count = 0;
if (x >= c->rectX1)
c->count = 0;
if (y >= c->rectY1)
c->count = 0;
// too slow? if so, don't count
// note the below expression; time > (c.prevTime + maxTime) can overflow!
if ((time - c->prevTime) > maxTime) // too slow; don't count
c->count = 0;
c->count++; // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
// now we need to update the internal structures for the next test
c->curButton = button;
c->prevTime = time;
c->rectX0 = x - xdist;
c->rectY0 = y - ydist;
c->rectX1 = x + xdist;
c->rectY1 = y + ydist;
return c->count;
}
void clickCounterReset(clickCounter *c)
{
c->curButton = 0;
c->rectX0 = 0;
c->rectY0 = 0;
c->rectX1 = 0;
c->rectY1 = 0;
c->prevTime = 0;
c->count = 0;
}
/*
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
Windows provides the scancodes directly in the LPARAM.
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
*/
// use uintptr_t to be safe; the size of the scancode/hardware key code field on each platform is different
static const struct {
uintptr_t scancode;
char equiv;
} scancodeKeys[] = {
{ 0x02, '1' },
{ 0x03, '2' },
{ 0x04, '3' },
{ 0x05, '4' },
{ 0x06, '5' },
{ 0x07, '6' },
{ 0x08, '7' },
{ 0x09, '8' },
{ 0x0A, '9' },
{ 0x0B, '0' },
{ 0x0C, '-' },
{ 0x0D, '=' },
{ 0x0E, '\b' },
{ 0x0F, '\t' },
{ 0x10, 'q' },
{ 0x11, 'w' },
{ 0x12, 'e' },
{ 0x13, 'r' },
{ 0x14, 't' },
{ 0x15, 'y' },
{ 0x16, 'u' },
{ 0x17, 'i' },
{ 0x18, 'o' },
{ 0x19, 'p' },
{ 0x1A, '[' },
{ 0x1B, ']' },
{ 0x1C, '\n' },
{ 0x1E, 'a' },
{ 0x1F, 's' },
{ 0x20, 'd' },
{ 0x21, 'f' },
{ 0x22, 'g' },
{ 0x23, 'h' },
{ 0x24, 'j' },
{ 0x25, 'k' },
{ 0x26, 'l' },
{ 0x27, ';' },
{ 0x28, '\'' },
{ 0x29, '`' },
{ 0x2B, '\\' },
{ 0x2C, 'z' },
{ 0x2D, 'x' },
{ 0x2E, 'c' },
{ 0x2F, 'v' },
{ 0x30, 'b' },
{ 0x31, 'n' },
{ 0x32, 'm' },
{ 0x33, ',' },
{ 0x34, '.' },
{ 0x35, '/' },
{ 0x39, ' ' },
{ 0xFFFF, 0 },
};
static const struct {
uintptr_t scancode;
uiExtKey equiv;
} scancodeExtKeys[] = {
{ 0x47, uiExtKeyN7 },
{ 0x48, uiExtKeyN8 },
{ 0x49, uiExtKeyN9 },
{ 0x4B, uiExtKeyN4 },
{ 0x4C, uiExtKeyN5 },
{ 0x4D, uiExtKeyN6 },
{ 0x4F, uiExtKeyN1 },
{ 0x50, uiExtKeyN2 },
{ 0x51, uiExtKeyN3 },
{ 0x52, uiExtKeyN0 },
{ 0x53, uiExtKeyNDot },
{ 0xFFFF, 0 },
};
int fromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)
{
int i;
for (i = 0; scancodeKeys[i].scancode != 0xFFFF; i++)
if (scancodeKeys[i].scancode == scancode) {
ke->Key = scancodeKeys[i].equiv;
return 1;
}
for (i = 0; scancodeExtKeys[i].scancode != 0xFFFF; i++)
if (scancodeExtKeys[i].scancode == scancode) {
ke->ExtKey = scancodeExtKeys[i].equiv;
return 1;
}
return 0;
}

262
winarea_d2d/main.c Normal file
View File

@ -0,0 +1,262 @@
// 4 september 2015
#define _GNU_SOURCE
#include "area.h"
#include <math.h>
// #qo LIBS: user32 kernel32 d2d1 ole32 uuid gdi32 msimg32
struct handler {
uiAreaHandler ah;
};
static HWND area;
static struct handler h;
static HWND nhspinb;
static HWND nvspinb;
static void handlerDraw(uiAreaHandler *a, uiArea *area, uiAreaDrawParams *p)
{
uiDrawStrokeParams sp;
uiDrawBeginPathRGB(p->Context, 0xFF, 0x00, 0x00);
uiDrawMoveTo(p->Context, p->ClipX + 5, p->ClipY + 5);
uiDrawLineTo(p->Context, (p->ClipX + p->ClipWidth) - 5, (p->ClipY + p->ClipHeight) - 5);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x00, 0x00, 0xC0);
uiDrawMoveTo(p->Context, p->ClipX, p->ClipY);
uiDrawLineTo(p->Context, p->ClipX + p->ClipWidth, p->ClipY);
uiDrawLineTo(p->Context, 50, 150);
uiDrawLineTo(p->Context, 50, 50);
uiDrawCloseFigure(p->Context);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinRound;
sp.Thickness = 5;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGBA(p->Context, 0x00, 0xC0, 0x00, 0x80);
uiDrawRectangle(p->Context, 120, 80, 50, 50);
uiDrawFill(p->Context, uiDrawFillModeWinding);
uiDrawBeginPathRGB(p->Context, 0x00, 0x80, 0x00);
uiDrawMoveTo(p->Context, 5, 10);
uiDrawLineTo(p->Context, 5, 50);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x80, 0xC0, 0x00);
uiDrawMoveTo(p->Context, 400, 100);
uiDrawArcTo(p->Context,
400, 100,
50,
30. * (M_PI / 180.),
// note the end angle here
// in GDI, the second angle to AngleArc() is relative to the start, not to 0
330. * (M_PI / 180.),
1);
// TODO add a checkbox for this
uiDrawLineTo(p->Context, 400, 100);
uiDrawArcTo(p->Context,
510, 100,
50,
30. * (M_PI / 180.),
330. * (M_PI / 180.),
0);
uiDrawCloseFigure(p->Context);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
uiDrawBeginPathRGB(p->Context, 0x00, 0x80, 0xC0);
uiDrawMoveTo(p->Context, 300, 300);
uiDrawBezierTo(p->Context,
350, 320,
310, 390,
435, 372);
sp.Cap = uiDrawLineCapFlat;
sp.Join = uiDrawLineJoinMiter;
sp.Thickness = 1;
sp.MiterLimit = uiDrawDefaultMiterLimit;
uiDrawStroke(p->Context, &sp);
}
static uintmax_t handlerHScrollMax(uiAreaHandler *a, uiArea *area)
{
WCHAR c[50];
GetWindowTextW(nhspinb, c, 50);
return _wtoi(c);
}
static uintmax_t handlerVScrollMax(uiAreaHandler *a, uiArea *area)
{
WCHAR c[50];
GetWindowTextW(nvspinb, c, 50);
return _wtoi(c);
}
static int handlerRedrawOnResize(uiAreaHandler *a, uiArea *area)
{
return 1;
}
static void handlerMouseEvent(uiAreaHandler *a, uiArea *area, uiAreaMouseEvent *e)
{
printf("mouse (%d,%d):(%d,%d) down:%d up:%d count:%d mods:%x held:%I64x\n",
(int) e->X,
(int) e->Y,
(int) e->HScrollPos,
(int) e->VScrollPos,
(int) e->Down,
(int) e->Up,
(int) e->Count,
(uint32_t) e->Modifiers,
e->Held1To64);
}
static void handlerDragBroken(uiAreaHandler *ah, uiArea *a)
{
printf("drag broken\n");
}
static int handlerKeyEvent(uiAreaHandler *ah, uiArea *a, uiAreaKeyEvent *e)
{
char k[4];
k[0] = '\'';
k[1] = e->Key;
k[2] = '\'';
k[3] = '\0';
if (e->Key == 0) {
k[0] = '0';
k[1] = '\0';
}
printf("key key:%s extkey:%d mod:%d mods:%d up:%d\n",
k,
(int) e->ExtKey,
(int) e->Modifier,
(int) e->Modifiers,
e->Up);
return 0;
}
static void repos(HWND hwnd)
{
RECT r;
GetClientRect(hwnd, &r);
SetWindowPos(nhspinb, NULL, r.left + 12, r.top + 12,
120, 20,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
SetWindowPos(nvspinb, NULL, r.left + 12 + 120 + 6, r.top + 12,
120, 20,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
SetWindowPos(area, NULL, r.left + 12, r.top + 12 + 20 + 6,
r.right - r.left - 24, r.bottom - r.top - 24 - 20 - 6,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
static LRESULT CALLBACK wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WINDOWPOS *wp = (WINDOWPOS *) lParam;
switch (uMsg) {
case WM_COMMAND:
if (((HWND) lParam) == nhspinb || ((HWND) lParam) == nvspinb)
if (HIWORD(wParam) == EN_CHANGE)
areaUpdateScroll(area);
break;
case WM_WINDOWPOSCHANGED:
if ((wp->flags & SWP_NOSIZE) != 0)
break;
repos(hwnd);
return 0;
case WM_CLOSE:
PostQuitMessage(0);
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
HINSTANCE hInstance;
int main(void)
{
WNDCLASSW wc;
HWND mainwin;
MSG msg;
CoInitialize(NULL);
initDraw();
hInstance = GetModuleHandle(NULL);
h.ah.Draw = handlerDraw;
h.ah.HScrollMax = handlerHScrollMax;
h.ah.VScrollMax = handlerVScrollMax;
h.ah.RedrawOnResize = handlerRedrawOnResize;
h.ah.MouseEvent = handlerMouseEvent;
h.ah.DragBroken = handlerDragBroken;
h.ah.KeyEvent = handlerKeyEvent;
registerAreaClass();
ZeroMemory(&wc, sizeof (WNDCLASSW));
wc.lpszClassName = L"mainwin";
wc.lpfnWndProc = wndproc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
RegisterClassW(&wc);
mainwin = CreateWindowExW(0,
L"mainwin", L"mainwin",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
area = makeArea(0,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
mainwin,
(uiAreaHandler *) (&h));
nhspinb = CreateWindowExW(WS_EX_CLIENTEDGE,
L"edit", L"0",
ES_NUMBER | WS_CHILD | WS_VISIBLE,
0, 0, 100, 100,
mainwin, NULL, hInstance, NULL);
nvspinb = CreateWindowExW(WS_EX_CLIENTEDGE,
L"edit", L"0",
ES_NUMBER | WS_CHILD | WS_VISIBLE,
0, 0, 100, 100,
mainwin, NULL, hInstance, NULL);
// initial state
repos(mainwin);
areaUpdateScroll(area);
ShowWindow(mainwin, SW_SHOWDEFAULT);
UpdateWindow(mainwin);
extern BOOL processAreaMessage(MSG *);
while (GetMessage(&msg, NULL, 0, 0)) {
if (processAreaMessage(&msg)) continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
uninitDraw();
CoUninitialize();
return 0;
}

177
winarea_d2d/ui.h Normal file
View File

@ -0,0 +1,177 @@
// 4 september 2015
typedef struct uiArea uiArea;
typedef struct uiAreaHandler uiAreaHandler;
typedef struct uiAreaDrawParams uiAreaDrawParams;
typedef struct uiAreaMouseEvent uiAreaMouseEvent;
typedef struct uiAreaKeyEvent uiAreaKeyEvent;
typedef struct uiDrawContext uiDrawContext;
struct uiAreaHandler {
void (*Draw)(uiAreaHandler *, uiArea *, uiAreaDrawParams *);
uintmax_t (*HScrollMax)(uiAreaHandler *, uiArea *);
uintmax_t (*VScrollMax)(uiAreaHandler *, uiArea *);
int (*RedrawOnResize)(uiAreaHandler *, uiArea *);
void (*MouseEvent)(uiAreaHandler *, uiArea *, uiAreaMouseEvent *);
void (*DragBroken)(uiAreaHandler *, uiArea *);
int (*KeyEvent)(uiAreaHandler *, uiArea *, uiAreaKeyEvent *);
};
struct uiAreaDrawParams {
uiDrawContext *Context;
intmax_t ClientWidth;
intmax_t ClientHeight;
intmax_t ClipX;
intmax_t ClipY;
intmax_t ClipWidth;
intmax_t ClipHeight;
int DPIX;
int DPIY;
intmax_t HScrollPos;
intmax_t VScrollPos;
};
// TODO proper sources
// TODO dotting/dashing
typedef struct uiDrawStrokeParams uiDrawStrokeParams;
typedef enum uiDrawLineCap uiDrawLineCap;
typedef enum uiDrawLineJoin uiDrawLineJoin;
typedef enum uiDrawFillMode uiDrawFillMode;
enum uiDrawLineCap {
uiDrawLineCapFlat,
uiDrawLineCapRound,
uiDrawLineCapSquare,
};
enum uiDrawLineJoin {
uiDrawLineJoinMiter,
uiDrawLineJoinRound,
uiDrawLineJoinBevel,
};
// this is the default for botoh cairo and GDI
// Core Graphics doesn't explicitly specify a default, but NSBezierPath allows you to choose one, and this is the initial value
// so we're good to use it too!
#define uiDrawDefaultMiterLimit 10.0
enum uiDrawFillMode {
uiDrawFillModeWinding,
uiDrawFillModeAlternate,
};
struct uiDrawStrokeParams {
uiDrawLineCap Cap;
uiDrawLineJoin Join;
intmax_t Thickness;
double MiterLimit;
};
void uiDrawBeginPathRGB(uiDrawContext *, uint8_t, uint8_t, uint8_t);
// TODO verify these aren't alpha premultiplied anywhere
void uiDrawBeginPathRGBA(uiDrawContext *, uint8_t, uint8_t, uint8_t, uint8_t);
void uiDrawMoveTo(uiDrawContext *, intmax_t, intmax_t);
void uiDrawLineTo(uiDrawContext *, intmax_t, intmax_t);
void uiDrawRectangle(uiDrawContext *, intmax_t, intmax_t, intmax_t, intmax_t);
// notes: angles are both relative to 0 and go counterclockwise
void uiDrawArcTo(uiDrawContext *, intmax_t, intmax_t, intmax_t, double, double, int);
// TODO behavior when there is no initial point on Windows and OS X
void uiDrawBezierTo(uiDrawContext *, intmax_t, intmax_t, intmax_t, intmax_t, intmax_t, intmax_t);
void uiDrawCloseFigure(uiDrawContext *);
void uiDrawStroke(uiDrawContext *, uiDrawStrokeParams *);
void uiDrawFill(uiDrawContext *, uiDrawFillMode);
// TODO primitives:
// - rounded rectangles
// - elliptical arcs
// - quadratic bezier curves
typedef enum uiModifiers uiModifiers;
enum uiModifiers {
uiModifierCtrl = 1 << 0,
uiModifierAlt = 1 << 1,
uiModifierShift = 1 << 2,
uiModifierSuper = 1 << 3,
};
struct uiAreaMouseEvent {
intmax_t X;
intmax_t Y;
intmax_t ClientWidth;
intmax_t ClientHeight;
intmax_t HScrollPos;
intmax_t VScrollPos;
uintmax_t Down;
uintmax_t Up;
uintmax_t Count;
uiModifiers Modifiers;
uint64_t Held1To64;
};
typedef enum uiExtKey uiExtKey;
enum uiExtKey {
uiExtKeyEscape = 1,
uiExtKeyInsert, // equivalent to "Help" on Apple keyboards
uiExtKeyDelete,
uiExtKeyHome,
uiExtKeyEnd,
uiExtKeyPageUp,
uiExtKeyPageDown,
uiExtKeyUp,
uiExtKeyDown,
uiExtKeyLeft,
uiExtKeyRight,
uiExtKeyF1, // F1..F12 are guaranteed to be consecutive
uiExtKeyF2,
uiExtKeyF3,
uiExtKeyF4,
uiExtKeyF5,
uiExtKeyF6,
uiExtKeyF7,
uiExtKeyF8,
uiExtKeyF9,
uiExtKeyF10,
uiExtKeyF11,
uiExtKeyF12,
uiExtKeyN0, // numpad keys; independent of Num Lock state
uiExtKeyN1, // N0..N9 are guaranteed to be consecutive
uiExtKeyN2,
uiExtKeyN3,
uiExtKeyN4,
uiExtKeyN5,
uiExtKeyN6,
uiExtKeyN7,
uiExtKeyN8,
uiExtKeyN9,
uiExtKeyNDot,
uiExtKeyNEnter,
uiExtKeyNAdd,
uiExtKeyNSubtract,
uiExtKeyNMultiply,
uiExtKeyNDivide,
};
struct uiAreaKeyEvent {
char Key;
uiExtKey ExtKey;
uiModifiers Modifier;
uiModifiers Modifiers;
int Up;
};

16
winarea_d2d/uipriv.h Normal file
View File

@ -0,0 +1,16 @@
typedef struct clickCounter clickCounter;
// you should call Reset() to zero-initialize a new instance
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
struct clickCounter {
uintmax_t curButton;
intmax_t rectX0;
intmax_t rectY0;
intmax_t rectX1;
intmax_t rectY1;
uintptr_t prevTime;
uintmax_t count;
};
extern uintmax_t clickCounterClick(clickCounter *, uintmax_t, intmax_t, intmax_t, uintptr_t, uintptr_t, intmax_t, intmax_t);
extern void clickCounterReset(clickCounter *);
extern int fromScancode(uintptr_t, uiAreaKeyEvent *);

View File

@ -0,0 +1,5 @@
// 16 september 2015
// draw.c
extern HRESULT initDraw(void);
extern void uninitDraw(void);