diff --git a/extras/204.diff b/extras/204.diff new file mode 100644 index 00000000..9b8866af --- /dev/null +++ b/extras/204.diff @@ -0,0 +1,41 @@ +diff --git a/windows/colorbutton.cpp b/windows/colorbutton.cpp +index c1ba6954e..7f63110ed 100644 +--- a/windows/colorbutton.cpp ++++ b/windows/colorbutton.cpp +@@ -61,7 +61,9 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) + ID2D1SolidColorBrush *brush; + uiWindowsSizing sizing; + int x, y; ++ float dpi_x, dpi_y; + HRESULT hr; ++ D2D1_MATRIX_3X2_F dm; + + if (nmhdr->code != NM_CUSTOMDRAW) + return FALSE; +@@ -73,6 +75,13 @@ static BOOL onWM_NOTIFY(uiControl *c, HWND hwnd, NMHDR *nmhdr, LRESULT *lResult) + rt = makeHDCRenderTarget(nm->hdc, &client); + rt->BeginDraw(); + ++ // scale Direct2D's rendering to be in pixels instead of DIPs ++ rt->GetDpi(&dpi_x, &dpi_y); ++ ZeroMemory(&dm, sizeof (D2D1_MATRIX_3X2_F)); ++ dm._11 = 96.f/dpi_x; ++ dm._22 = 96.f/dpi_y; ++ rt->SetTransform(&dm); ++ + uiWindowsGetSizing(b->hwnd, &sizing); + x = 3; // should be enough + y = 3; +diff --git a/windows/init.cpp b/windows/init.cpp +index c91929f9c..608346298 100644 +--- a/windows/init.cpp ++++ b/windows/init.cpp +@@ -70,7 +70,7 @@ const char *uiInit(uiInitOptions *o) + if ((si.dwFlags & STARTF_USESHOWWINDOW) != 0) + nCmdShow = si.wShowWindow; + +- // LONGTERM set DPI awareness ++ SetProcessDPIAware(); + + hDefaultIcon = LoadIconW(NULL, IDI_APPLICATION); + if (hDefaultIcon == NULL) diff --git a/extras/209.diff b/extras/209.diff new file mode 100644 index 00000000..12e90036 --- /dev/null +++ b/extras/209.diff @@ -0,0 +1,432 @@ +diff --git a/common/controlsigs.h b/common/controlsigs.h +index 03e675ccc..8f0040421 100644 +--- a/common/controlsigs.h ++++ b/common/controlsigs.h +@@ -13,6 +13,7 @@ + #define uiFormSignature 0x466F726D + #define uiGridSignature 0x47726964 + #define uiGroupSignature 0x47727062 ++#define uiImageSignature 0x4723BDC6 + #define uiLabelSignature 0x4C61626C + #define uiMultilineEntrySignature 0x4D6C6E45 + #define uiProgressBarSignature 0x50426172 +diff --git a/darwin/CMakeLists.txt b/darwin/CMakeLists.txt +index e7fe45077..4d0b20757 100644 +--- a/darwin/CMakeLists.txt ++++ b/darwin/CMakeLists.txt +@@ -21,6 +21,7 @@ list(APPEND _LIBUI_SOURCES + darwin/form.m + darwin/grid.m + darwin/group.m ++ darwin/image.m + darwin/label.m + darwin/main.m + darwin/map.m +diff --git a/darwin/image.m b/darwin/image.m +new file mode 100644 +index 000000000..434652b12 +--- /dev/null ++++ b/darwin/image.m +@@ -0,0 +1,37 @@ ++// 14 september 2016 ++#import "uipriv_darwin.h" ++ ++struct uiImage { ++ uiDarwinControl c; ++ NSImage *image; ++ NSImageView *imageView; ++}; ++ ++uiDarwinControlAllDefaults(uiImage, imageView) ++ ++void uiImageSetSize(uiImage *i, unsigned int width, unsigned int height) ++{ ++ i->image.size = NSMakeSize(width, height); ++ [i->imageView setNeedsDisplay:YES]; ++} ++ ++void uiImageGetSize(uiImage *i, unsigned int *width, unsigned int *height) ++{ ++ NSSize size = i->image.size; ++ *width = size.width; ++ *height = size.height; ++} ++ ++uiImage *uiNewImage(const char *filename) ++{ ++ uiImage *i; ++ ++ uiDarwinNewControl(uiImage, i); ++ ++ i->image = [[NSImage alloc] initWithContentsOfFile:[NSString stringWithUTF8String:filename]]; ++ ++ i->imageView = [[NSImageView alloc] init]; ++ [i->imageView setImage:i->image]; ++ ++ return i; ++} +diff --git a/ui.h b/ui.h +index 70c2f121f..5f21c0be0 100644 +--- a/ui.h ++++ b/ui.h +@@ -153,6 +153,12 @@ _UI_EXTERN uiEntry *uiNewEntry(void); + _UI_EXTERN uiEntry *uiNewPasswordEntry(void); + _UI_EXTERN uiEntry *uiNewSearchEntry(void); + ++typedef struct uiImage uiImage; ++#define uiImage(this) ((uiImage *) (this)) ++_UI_EXTERN void uiImageSetSize(uiImage *i, unsigned int width, unsigned int height); ++_UI_EXTERN void uiImageGetSize(uiImage *i, unsigned int *width, unsigned int *height); ++_UI_EXTERN uiImage *uiNewImage(const char *filename); ++ + typedef struct uiLabel uiLabel; + #define uiLabel(this) ((uiLabel *) (this)) + _UI_EXTERN char *uiLabelText(uiLabel *l); +diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt +index 56d653ad3..4e713aa22 100644 +--- a/unix/CMakeLists.txt ++++ b/unix/CMakeLists.txt +@@ -26,6 +26,7 @@ list(APPEND _LIBUI_SOURCES + unix/graphemes.c + unix/grid.c + unix/group.c ++ unix/image.c + unix/label.c + unix/main.c + unix/menu.c +diff --git a/unix/image.c b/unix/image.c +new file mode 100644 +index 000000000..efdbb38de +--- /dev/null ++++ b/unix/image.c +@@ -0,0 +1,42 @@ ++// 13 september 2016 ++#include "uipriv_unix.h" ++ ++struct uiImage { ++ uiUnixControl c; ++ GtkWidget *widget; ++}; ++ ++uiUnixControlAllDefaults(uiImage) ++ ++void uiImageSetSize(uiImage *i, unsigned int width, unsigned int height) ++{ ++ GdkPixbuf *pixbuf; ++ ++ pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(i->widget)); ++ pixbuf = gdk_pixbuf_scale_simple(pixbuf, ++ width, ++ height, ++ GDK_INTERP_BILINEAR); ++ gtk_image_set_from_pixbuf(GTK_IMAGE(i->widget), pixbuf); ++ g_object_unref(pixbuf); ++} ++ ++void uiImageGetSize(uiImage *i, unsigned int *width, unsigned int *height) ++{ ++ GdkPixbuf *pixbuf; ++ ++ pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(i->widget)); ++ *width = gdk_pixbuf_get_width(pixbuf); ++ *height = gdk_pixbuf_get_height(pixbuf); ++} ++ ++uiImage *uiNewImage(const char *filename) ++{ ++ uiImage *img; ++ ++ uiUnixNewControl(uiImage, img); ++ ++ img->widget = gtk_image_new_from_file(filename); ++ ++ return img; ++} +diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt +index 0f1d61def..0a3c62be9 100644 +--- a/windows/CMakeLists.txt ++++ b/windows/CMakeLists.txt +@@ -32,6 +32,7 @@ list(APPEND _LIBUI_SOURCES + windows/graphemes.cpp + windows/grid.cpp + windows/group.cpp ++ windows/image.cpp + windows/init.cpp + windows/label.cpp + windows/main.cpp +@@ -80,7 +81,7 @@ endmacro() + # notice that usp10 comes before gdi32 + # TODO prune this list + set(_LIBUI_LIBS +- user32 kernel32 usp10 gdi32 comctl32 uxtheme msimg32 comdlg32 d2d1 dwrite ole32 oleaut32 oleacc uuid ++ user32 kernel32 usp10 gdi32 comctl32 uxtheme msimg32 comdlg32 d2d1 dwrite ole32 oleaut32 oleacc uuid windowscodecs + PARENT_SCOPE) + + if(NOT MSVC) +diff --git a/windows/image.cpp b/windows/image.cpp +new file mode 100644 +index 000000000..9e60913a8 +--- /dev/null ++++ b/windows/image.cpp +@@ -0,0 +1,219 @@ ++// 13 september 2016 ++#include "uipriv_windows.hpp" ++ ++struct uiImage { ++ uiWindowsControl c; ++ HWND hwnd; ++ WCHAR *wfilename; ++ HBITMAP bmpSource; ++ HDC hdcSource; ++ LONG width; ++ LONG height; ++ LONG displayWidth; ++ LONG displayHeight; ++ BOOL originalSize; ++}; ++ ++static HBITMAP ConvertToHBitmap(IWICBitmapSource * ipBitmap) ++{ ++ HBITMAP hbmp = NULL; ++ void * pvImageBits = NULL; ++ HDC hdcScreen = GetDC(NULL); ++ ++ UINT width = 0; ++ UINT height = 0; ++ if (FAILED(ipBitmap->GetSize(&width, &height)) || width == 0 || height == 0) { ++ return NULL; ++ } ++ ++ BITMAPINFO bminfo = {0}; ++ bminfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); ++ bminfo.bmiHeader.biWidth = width; ++ bminfo.bmiHeader.biHeight = -((LONG) height); ++ bminfo.bmiHeader.biPlanes = 1; ++ bminfo.bmiHeader.biBitCount = 32; ++ bminfo.bmiHeader.biCompression = BI_RGB; ++ ++ hbmp = CreateDIBSection(hdcScreen, &bminfo, DIB_RGB_COLORS, &pvImageBits, NULL, 0); ++ ReleaseDC(NULL, hdcScreen); ++ if (hbmp == NULL) { ++ return NULL; ++ } ++ ++ UINT stride = width * 4; ++ if (FAILED(ipBitmap->CopyPixels(NULL, stride, stride * height, static_cast(pvImageBits)))) { ++ DeleteObject(hbmp); ++ hbmp = NULL; ++ } ++ ++ return hbmp; ++} ++ ++static HBITMAP LoadImageFromFile(const WCHAR *filename) ++{ ++ IWICImagingFactory *wicFactory = NULL; ++ IWICBitmapDecoder *decoder = NULL; ++ IWICBitmapFrameDecode *frame = NULL; ++ IWICBitmapSource *bitmap = NULL; ++ HRESULT hr; ++ ++ hr = CoCreateInstance( ++ CLSID_WICImagingFactory, ++ NULL, ++ CLSCTX_INPROC_SERVER, ++ IID_PPV_ARGS(&wicFactory) ++ ); ++ ++ if (FAILED(hr)) { ++ return NULL; ++ } ++ ++ hr = wicFactory->CreateDecoderFromFilename( ++ filename, ++ NULL, ++ GENERIC_READ, ++ WICDecodeMetadataCacheOnDemand, ++ &decoder ++ ); ++ ++ if (SUCCEEDED(hr)) { ++ hr = decoder->GetFrame(0, &frame); ++ } ++ ++ WICConvertBitmapSource(GUID_WICPixelFormat32bppPBGRA, frame, &bitmap); ++ frame->Release(); ++ decoder->Release(); ++ ++ return ConvertToHBitmap(bitmap); ++} ++ ++static LRESULT CALLBACK imageWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ++{ ++ uiImage *i; ++ CREATESTRUCTW *cs = (CREATESTRUCTW *) lParam; ++ PAINTSTRUCT ps; ++ HDC hdcDestination; ++ BITMAP bm = {0}; ++ ++ i = (uiImage *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); ++ if (i == NULL) { ++ if (uMsg == WM_CREATE) { ++ i = (uiImage *) (cs->lpCreateParams); ++ // assign i->hwnd here so we can use it immediately ++ i->hwnd = hwnd; ++ SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) i); ++ ++ // Load the image through the windows imaging component ++ i->bmpSource = LoadImageFromFile(i->wfilename); ++ ++ if(i->bmpSource == NULL) { ++ logLastError(L"Failed to load the image"); ++ } ++ GetObject(i->bmpSource, sizeof(bm), &bm); ++ i->width = bm.bmWidth; ++ i->height = bm.bmHeight; ++ i->displayWidth = i->width; ++ i->displayHeight = i->height; ++ i->hdcSource = CreateCompatibleDC(GetDC(0)); ++ SelectObject(i->hdcSource, i->bmpSource); ++ return 0; ++ } ++ // fall through to DefWindowProcW() anyway ++ return DefWindowProcW(hwnd, uMsg, wParam, lParam); ++ } ++ ++ if (uMsg == WM_PAINT) { ++ hdcDestination = BeginPaint(hwnd, &ps); ++ if (i->originalSize) { ++ BitBlt(hdcDestination, 0, 0, i->width, i->height, i->hdcSource, 0, 0, SRCCOPY); ++ } else { ++ SetStretchBltMode(hdcDestination,HALFTONE); ++ StretchBlt(hdcDestination, ++ 0, 0, ++ i->displayWidth, i->displayHeight, ++ i->hdcSource, ++ 0, 0, ++ i->width, i->height, ++ SRCCOPY); ++ } ++ EndPaint(hwnd, &ps); ++ return 0; ++ } ++ ++ // nothing done ++ return DefWindowProc(hwnd, uMsg, wParam, lParam); ++} ++ ++static void uiImageDestroy(uiControl *c) ++{ ++ uiImage *i = uiImage(c); ++ ++ uiFree(i->wfilename); ++ uiWindowsEnsureDestroyWindow(i->hwnd); ++ uiFreeControl(uiControl(i)); ++} ++ ++uiWindowsControlAllDefaultsExceptDestroy(uiImage) ++ ++static void uiImageMinimumSize(uiWindowsControl *c, int *width, int *height) ++{ ++ uiImage *i = uiImage(c); ++ *width = i->displayWidth; ++ *height = i->displayHeight; ++} ++ ++void uiImageSetSize(uiImage *i, unsigned int width, unsigned int height) ++{ ++ i->displayWidth = width; ++ i->displayHeight = height; ++ i->originalSize = width == i->width && height == i->height; ++ ++ invalidateRect(i->hwnd, NULL, FALSE); ++ ++ // If the image is smaller in either direction the part of the window that ++ // used to be covered by the image will not get redrawn because it is no ++ // longer part of the image. To fix this just redraw the entire parent ++ // window. This feels wrong though. ++ invalidateRect(GetParent(i->hwnd), NULL, FALSE); ++} ++ ++void uiImageGetSize(uiImage *i, unsigned int *width, unsigned int *height) ++{ ++ *width = i->displayWidth; ++ *height = i->displayHeight; ++} ++ ++void unregisterImage(void) ++{ ++ if (UnregisterClassW(imageClass, hInstance) == 0) ++ logLastError(L"error unregistering uiImage window class"); ++} ++ ++ATOM registerImageClass(HICON hDefaultIcon, HCURSOR hDefaultCursor) ++{ ++ WNDCLASSW wc; ++ ++ ZeroMemory(&wc, sizeof (WNDCLASSW)); ++ wc.lpszClassName = imageClass; ++ wc.lpfnWndProc = imageWndProc; ++ wc.hInstance = hInstance; ++ wc.hIcon = hDefaultIcon; ++ wc.hCursor = hDefaultCursor; ++ return RegisterClassW(&wc); ++} ++ ++uiImage *uiNewImage(const char *filename) ++{ ++ uiImage *image; ++ ++ uiWindowsNewControl(uiImage, image); ++ image->wfilename = toUTF16(filename); ++ ++ uiWindowsEnsureCreateControlHWND(0, ++ imageClass, L"", ++ 0, ++ hInstance, image, ++ FALSE); ++ ++ return image; ++} +diff --git a/windows/init.cpp b/windows/init.cpp +index c91929f9c..9edbd332c 100644 +--- a/windows/init.cpp ++++ b/windows/init.cpp +@@ -124,6 +124,9 @@ const char *uiInit(uiInitOptions *o) + if (registerAreaClass(hDefaultIcon, hDefaultCursor) == 0) + return ieLastErr("registering uiArea window class"); + ++ if (registerImageClass(hDefaultIcon, hDefaultCursor) == 0) ++ return ieLastErr("registering uiImage window class"); ++ + if (registerMessageFilter() == 0) + return ieLastErr("registering libui message filter"); + +diff --git a/windows/uipriv_windows.hpp b/windows/uipriv_windows.hpp +index 6ffe09f1a..a0b2adb3f 100644 +--- a/windows/uipriv_windows.hpp ++++ b/windows/uipriv_windows.hpp +@@ -111,6 +111,11 @@ extern HWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC + extern ATOM registerAreaClass(HICON, HCURSOR); + extern void unregisterArea(void); + ++// image.cpp ++#define imageClass L"libui_uiImageClass" ++extern ATOM registerImageClass(HICON, HCURSOR); ++extern void unregisterImage(void); ++ + // areaevents.cpp + extern BOOL areaFilter(MSG *); + +diff --git a/windows/winapi.hpp b/windows/winapi.hpp +index 86aba5d7e..8133d3046 100644 +--- a/windows/winapi.hpp ++++ b/windows/winapi.hpp +@@ -35,6 +35,7 @@ + #include + #include + #include ++#include + + #include + #include diff --git a/extras/290.diff b/extras/290.diff new file mode 100644 index 00000000..94e71de5 --- /dev/null +++ b/extras/290.diff @@ -0,0 +1,326 @@ +diff --git a/darwin/entry.m b/darwin/entry.m +index 219d08057..b10c2edac 100644 +--- a/darwin/entry.m ++++ b/darwin/entry.m +@@ -59,6 +59,8 @@ - (NSSize)intrinsicContentSize + NSTextField *textfield; + void (*onChanged)(uiEntry *, void *); + void *onChangedData; ++ void (*onFinished)(uiEntry *, void *); ++ void *onFinishedData; + }; + + static BOOL isSearchField(NSTextField *tf) +@@ -69,6 +71,7 @@ static BOOL isSearchField(NSTextField *tf) + @interface entryDelegateClass : NSObject { + struct mapTable *entries; + } ++- (void)controlTextDidEndEditing:(NSNotification *)note; + - (void)controlTextDidChange:(NSNotification *)note; + - (IBAction)onSearch:(id)sender; + - (void)registerEntry:(uiEntry *)e; +@@ -91,17 +94,30 @@ - (void)dealloc + [super dealloc]; + } + ++- (void)controlTextDidEndEditing:(NSNotification *)note ++{ ++ uiEntry *e; ++ e = (uiEntry *) mapGet(self->entries, [note object]); ++ (*(e->onFinished))(e, e->onFinishedData); ++} ++ ++ + - (void)controlTextDidChange:(NSNotification *)note + { +- [self onSearch:[note object]]; ++ uiEntry *e; ++ e = (uiEntry *) mapGet(self->entries, [note object]); ++ (*(e->onChanged))(e, e->onChangedData); + } + + - (IBAction)onSearch:(id)sender + { + uiEntry *e; +- + e = (uiEntry *) mapGet(self->entries, sender); ++ ++ NSSearchField *s; ++ s = (NSSearchField *) (e->textfield); + (*(e->onChanged))(e, e->onChangedData); ++ (*(e->onFinished))(e, e->onFinishedData); + } + + - (void)registerEntry:(uiEntry *)e +@@ -155,6 +171,31 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) + e->onChangedData = data; + } + ++// NOTE: for search widgets on OSX, setting OnFinished() alters the behaviour ++// (by setting sendsWholeSearchString). ++// Standard text and password entry widgets are not affected. ++// background: ++// On OSX, there doesn't seem to be any simple way to catch ++// both 'changed' events and 'enter' (finished editing) events on ++// search widgets. Instead, there just a single 'search' event, and flags ++// to determine when it triggers. By default, it triggers after each keypress ++// (with a little delay in case the user is still typing). ++// There's also an option to change the behaviour to trigger only when the ++// enter key is hit, or the search icon is pressed. This is the ++// sendsWholeSearchString flag, and we'll set it if (and only if) OnFinished() ++// is used. ++void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) ++{ ++ e->onFinished = f; ++ e->onFinishedData = data; ++ if (isSearchField(e->textfield)) { ++ NSSearchField *s; ++ s = (NSSearchField *) (e->textfield); ++ // TODO requires OSX >= 10.10 (is that an issue?) ++ [s setSendsWholeSearchString:YES]; ++ } ++} ++ + int uiEntryReadOnly(uiEntry *e) + { + return [e->textfield isEditable] == NO; +@@ -175,6 +216,11 @@ static void defaultOnChanged(uiEntry *e, void *data) + // do nothing + } + ++static void defaultOnFinished(uiEntry *e, void *data) ++{ ++ // do nothing ++} ++ + // these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/ + void finishNewTextField(NSTextField *t, BOOL isEntry) + { +@@ -219,8 +265,13 @@ void finishNewTextField(NSTextField *t, BOOL isEntry) + [delegates addObject:entryDelegate]; + } + [entryDelegate registerEntry:e]; +- uiEntryOnChanged(e, defaultOnChanged, NULL); + ++ // set the callbacks directly, so as to not trigger the ++ // sendsWholeSearchString flag set in OnFinished() for search widgets. ++ e->onFinished = defaultOnFinished; ++ e->onFinishedData = NULL; ++ e->onChanged = defaultOnChanged; ++ e->onChangedData = NULL; + return e; + } + +diff --git a/examples/controlgallery/main.c b/examples/controlgallery/main.c +index c0d536c16..760093298 100644 +--- a/examples/controlgallery/main.c ++++ b/examples/controlgallery/main.c +@@ -17,12 +17,27 @@ static int onShouldQuit(void *data) + return 1; + } + ++ ++static void onEntryChanged(uiEntry* e, void* data) ++{ ++ const char* msg = data; ++ printf("%s: OnChanged\n", msg); ++} ++ ++static void onEntryFinished(uiEntry* e, void* data) ++{ ++ const char* msg = data; ++ printf("%s: OnFinished\n", msg); ++} ++ ++ + static uiControl *makeBasicControlsPage(void) + { + uiBox *vbox; + uiBox *hbox; + uiGroup *group; + uiForm *entryForm; ++ uiEntry *e; + + vbox = uiNewVerticalBox(); + uiBoxSetPadded(vbox, 1); +@@ -54,18 +69,30 @@ static uiControl *makeBasicControlsPage(void) + uiFormSetPadded(entryForm, 1); + uiGroupSetChild(group, uiControl(entryForm)); + ++ e = uiNewEntry(); ++ uiEntryOnChanged(e, onEntryChanged, "entry"); ++ uiEntryOnFinished(e, onEntryFinished, "entry"); + uiFormAppend(entryForm, + "Entry", +- uiControl(uiNewEntry()), ++ uiControl(e), + 0); ++ ++ e = uiNewPasswordEntry(); ++ uiEntryOnChanged(e, onEntryChanged, "password"); ++ uiEntryOnFinished(e, onEntryFinished, "password"); + uiFormAppend(entryForm, + "Password Entry", +- uiControl(uiNewPasswordEntry()), ++ uiControl(e), + 0); ++ ++ e = uiNewSearchEntry(); ++ uiEntryOnChanged(e, onEntryChanged, "search"); ++ uiEntryOnFinished(e, onEntryFinished, "search"); + uiFormAppend(entryForm, + "Search Entry", +- uiControl(uiNewSearchEntry()), ++ uiControl(e), + 0); ++ + uiFormAppend(entryForm, + "Multiline Entry", + uiControl(uiNewMultilineEntry()), +diff --git a/ui.h b/ui.h +index ce3e4104e..99d8c34fa 100644 +--- a/ui.h ++++ b/ui.h +@@ -162,6 +162,7 @@ typedef struct uiEntry uiEntry; + _UI_EXTERN char *uiEntryText(uiEntry *e); + _UI_EXTERN void uiEntrySetText(uiEntry *e, const char *text); + _UI_EXTERN void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); ++_UI_EXTERN void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *e, void *data), void *data); + _UI_EXTERN int uiEntryReadOnly(uiEntry *e); + _UI_EXTERN void uiEntrySetReadOnly(uiEntry *e, int readonly); + _UI_EXTERN uiEntry *uiNewEntry(void); +diff --git a/unix/entry.c b/unix/entry.c +index 4a9a1d041..20fa0aa00 100644 +--- a/unix/entry.c ++++ b/unix/entry.c +@@ -9,6 +9,8 @@ struct uiEntry { + void (*onChanged)(uiEntry *, void *); + void *onChangedData; + gulong onChangedSignal; ++ void (*onFinished)(uiEntry *, void *); ++ void *onFinishedData; + }; + + uiUnixControlAllDefaults(uiEntry) +@@ -16,7 +18,6 @@ uiUnixControlAllDefaults(uiEntry) + static void onChanged(GtkEditable *editable, gpointer data) + { + uiEntry *e = uiEntry(data); +- + (*(e->onChanged))(e, e->onChangedData); + } + +@@ -25,6 +26,17 @@ static void defaultOnChanged(uiEntry *e, void *data) + // do nothing + } + ++static void onFinished(GtkEditable *editable, gpointer data) ++{ ++ uiEntry *e = uiEntry(data); ++ (*(e->onFinished))(e, e->onFinishedData); ++} ++ ++static void defaultOnFinished(uiEntry *e, void *data) ++{ ++ // do nothing ++} ++ + char *uiEntryText(uiEntry *e) + { + return uiUnixStrdupText(gtk_entry_get_text(e->entry)); +@@ -45,6 +57,13 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) + e->onChangedData = data; + } + ++void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) ++{ ++ e->onFinished = f; ++ e->onFinishedData = data; ++} ++ ++ + int uiEntryReadOnly(uiEntry *e) + { + return gtk_editable_get_editable(e->editable) == FALSE; +@@ -73,6 +92,9 @@ static uiEntry *finishNewEntry(GtkWidget *w, const gchar *signal) + e->onChangedSignal = g_signal_connect(e->widget, signal, G_CALLBACK(onChanged), e); + uiEntryOnChanged(e, defaultOnChanged, NULL); + ++ g_signal_connect(e->widget, "activate", G_CALLBACK(onFinished), e); ++ uiEntryOnFinished(e, defaultOnFinished, NULL); ++ + return e; + } + +diff --git a/windows/entry.cpp b/windows/entry.cpp +index a7a077f20..9e35f7630 100644 +--- a/windows/entry.cpp ++++ b/windows/entry.cpp +@@ -7,19 +7,27 @@ struct uiEntry { + void (*onChanged)(uiEntry *, void *); + void *onChangedData; + BOOL inhibitChanged; ++ void (*onFinished)(uiEntry *, void *); ++ void *onFinishedData; + }; + + static BOOL onWM_COMMAND(uiControl *c, HWND hwnd, WORD code, LRESULT *lResult) + { + uiEntry *e = uiEntry(c); + +- if (code != EN_CHANGE) +- return FALSE; +- if (e->inhibitChanged) +- return FALSE; +- (*(e->onChanged))(e, e->onChangedData); +- *lResult = 0; +- return TRUE; ++ if (code == EN_CHANGE) { ++ if (e->inhibitChanged) ++ return FALSE; ++ (*(e->onChanged))(e, e->onChangedData); ++ *lResult = 0; ++ return TRUE; ++ } ++ if (code == EN_KILLFOCUS) { ++ (*(e->onFinished))(e, e->onFinishedData); ++ *lResult = 0; ++ return TRUE; ++ } ++ return FALSE; + } + + static void uiEntryDestroy(uiControl *c) +@@ -56,6 +64,11 @@ static void defaultOnChanged(uiEntry *e, void *data) + // do nothing + } + ++static void defaultOnFinished(uiEntry *e, void *data) ++{ ++ // do nothing ++} ++ + char *uiEntryText(uiEntry *e) + { + return uiWindowsWindowText(e->hwnd); +@@ -76,6 +89,12 @@ void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data) + e->onChangedData = data; + } + ++void uiEntryOnFinished(uiEntry *e, void (*f)(uiEntry *, void *), void *data) ++{ ++ e->onFinished = f; ++ e->onFinishedData = data; ++} ++ + int uiEntryReadOnly(uiEntry *e) + { + return (getStyle(e->hwnd) & ES_READONLY) != 0; +@@ -106,6 +125,7 @@ static uiEntry *finishNewEntry(DWORD style) + + uiWindowsRegisterWM_COMMANDHandler(e->hwnd, onWM_COMMAND, uiControl(e)); + uiEntryOnChanged(e, defaultOnChanged, NULL); ++ uiEntryOnFinished(e, defaultOnFinished, NULL); + + return e; + } diff --git a/extras/386.diff b/extras/386.diff new file mode 100644 index 00000000..f953152b --- /dev/null +++ b/extras/386.diff @@ -0,0 +1,253 @@ +diff --git a/ui.h b/ui.h +index b5fb9a271..a4a5737f3 100644 +--- a/ui.h ++++ b/ui.h +@@ -366,6 +366,8 @@ typedef struct uiDrawMatrix uiDrawMatrix; + + typedef struct uiDrawBrushGradientStop uiDrawBrushGradientStop; + ++typedef struct uiDrawBitmap uiDrawBitmap; ++ + _UI_ENUM(uiDrawBrushType) { + uiDrawBrushTypeSolid, + uiDrawBrushTypeLinearGradient, +@@ -453,6 +455,15 @@ struct uiDrawStrokeParams { + double DashPhase; + }; + ++struct uiRect { ++ int X; ++ int Y; ++ int Width; ++ int Height; ++}; ++ ++typedef struct uiRect uiRect; ++ + _UI_EXTERN uiDrawPath *uiDrawNewPath(uiDrawFillMode fillMode); + _UI_EXTERN void uiDrawFreePath(uiDrawPath *p); + +@@ -499,6 +510,12 @@ _UI_EXTERN void uiDrawClip(uiDrawContext *c, uiDrawPath *path); + _UI_EXTERN void uiDrawSave(uiDrawContext *c); + _UI_EXTERN void uiDrawRestore(uiDrawContext *c); + ++// bitmap API ++_UI_EXTERN uiDrawBitmap* uiDrawNewBitmap(uiDrawContext* c, int width, int height); ++_UI_EXTERN void uiDrawBitmapUpdate(uiDrawBitmap* bmp, const void* data); ++_UI_EXTERN void uiDrawBitmapDraw(uiDrawContext* c, uiDrawBitmap* bmp, uiRect* srcrect, uiRect* dstrect, int filter); ++_UI_EXTERN void uiDrawFreeBitmap(uiDrawBitmap* bmp); ++ + // uiAttribute stores information about an attribute in a + // uiAttributedString. + // +diff --git a/unix/draw.c b/unix/draw.c +index a8f26d7f7..8df798bc9 100644 +--- a/unix/draw.c ++++ b/unix/draw.c +@@ -141,3 +141,69 @@ void uiDrawRestore(uiDrawContext *c) + { + cairo_restore(c->cr); + } ++ ++// bitmap API ++ ++uiDrawBitmap* uiDrawNewBitmap(uiDrawContext* c, int width, int height) ++{ ++ uiDrawBitmap* bmp; ++ ++ bmp = uiprivNew(uiDrawBitmap); ++ ++ bmp->bmp = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); ++ if (cairo_surface_status(bmp->bmp) != CAIRO_STATUS_SUCCESS) ++ uiprivImplBug("error creating bitmap: %s", ++ cairo_status_to_string(cairo_surface_status(bmp->bmp))); ++ ++ bmp->Width = width; ++ bmp->Height = height; ++ bmp->Stride = cairo_image_surface_get_stride(bmp->bmp); ++ ++ return bmp; ++} ++ ++void uiDrawBitmapUpdate(uiDrawBitmap* bmp, const void* data) ++{ ++ unsigned char* src = data; ++ unsigned char* dst = cairo_image_surface_get_data(bmp->bmp); ++ int y; ++ ++ if (bmp->Stride == bmp->Width * 4) { ++ // stride 'good', can just directly copy ++ memcpy(dst, src, bmp->Stride*bmp->Height); ++ } else { ++ for (y = 0; y < bmp->Height; y++) { ++ memcpy(dst, src, bmp->Width * 4); ++ src += bmp->Width * 4; ++ dst += bmp->Stride; ++ } ++ } ++ ++ cairo_surface_mark_dirty(bmp->bmp); ++} ++ ++void uiDrawBitmapDraw(uiDrawContext* c, uiDrawBitmap* bmp, uiRect* srcrect, uiRect* dstrect, int filter) ++{ ++ cairo_save(c->cr); ++ cairo_rectangle(c->cr, dstrect->X, dstrect->Y, dstrect->Width, dstrect->Height); ++ ++ cairo_translate(c->cr, dstrect->X, dstrect->Y); ++ if ((dstrect->Width != srcrect->Width) || (dstrect->Height != srcrect->Height)) { ++ double sx = dstrect->Width / (double)srcrect->Width; ++ double sy = dstrect->Height / (double)srcrect->Height; ++ cairo_scale(c->cr, sx, sy); ++ } ++ ++ cairo_set_source_surface(c->cr, bmp->bmp, -srcrect->X, -srcrect->Y); ++ cairo_pattern_set_filter(cairo_get_source(c->cr), filter ? CAIRO_FILTER_BILINEAR : CAIRO_FILTER_NEAREST); ++ cairo_clip(c->cr); ++ cairo_paint(c->cr); ++ ++ cairo_restore(c->cr); ++} ++ ++void uiDrawFreeBitmap(uiDrawBitmap* bmp) ++{ ++ cairo_surface_destroy(bmp->bmp); ++ uiprivFree(bmp); ++} +diff --git a/unix/draw.h b/unix/draw.h +index d46d074f6..cd7ce4a19 100644 +--- a/unix/draw.h ++++ b/unix/draw.h +@@ -6,6 +6,14 @@ struct uiDrawContext { + GtkStyleContext *style; + }; + ++struct uiDrawBitmap { ++ int Width; ++ int Height; ++ int Stride; ++ ++ cairo_surface_t* bmp; ++}; ++ + // drawpath.c + extern void uiprivRunPath(uiDrawPath *p, cairo_t *cr); + extern uiDrawFillMode uiprivPathFillMode(uiDrawPath *path); +diff --git a/unix/window.c b/unix/window.c +index c5ba20384..aaeaa8ef4 100644 +--- a/unix/window.c ++++ b/unix/window.c +@@ -260,6 +260,7 @@ uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar) + gtk_widget_set_halign(w->childHolderWidget, GTK_ALIGN_FILL); + gtk_widget_set_vexpand(w->childHolderWidget, TRUE); + gtk_widget_set_valign(w->childHolderWidget, GTK_ALIGN_FILL); ++ gtk_box_set_homogeneous(GTK_BOX(w->childHolderWidget), TRUE); + gtk_container_add(w->vboxContainer, w->childHolderWidget); + + // show everything in the vbox, but not the GtkWindow itself +diff --git a/windows/draw.cpp b/windows/draw.cpp +index a5e5033a5..95d8abf16 100644 +--- a/windows/draw.cpp ++++ b/windows/draw.cpp +@@ -38,7 +38,7 @@ ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd) + logLastError(L"error getting DC to find DPI"); + + ZeroMemory(&props, sizeof (D2D1_RENDER_TARGET_PROPERTIES)); +- props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; ++ props.type = D2D1_RENDER_TARGET_TYPE_HARDWARE; + props.pixelFormat.format = DXGI_FORMAT_UNKNOWN; + props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_UNKNOWN; + props.dpiX = GetDeviceCaps(dc, LOGPIXELSX); +@@ -62,8 +62,16 @@ ID2D1HwndRenderTarget *makeHWNDRenderTarget(HWND hwnd) + &props, + &hprops, + &rt); +- if (hr != S_OK) +- logHRESULT(L"error creating HWND render target", hr); ++ if (hr != S_OK) { ++ props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; ++ hr = d2dfactory->CreateHwndRenderTarget( ++ &props, ++ &hprops, ++ &rt); ++ if (hr != S_OK) ++ logHRESULT(L"error creating HWND render target", hr); ++ } ++ + return rt; + } + +@@ -509,3 +517,54 @@ void uiDrawRestore(uiDrawContext *c) + // no need to explicitly addref or release; just transfer the ref + c->currentClip = state.clip; + } ++ ++ ++// bitmap API ++ ++uiDrawBitmap* uiDrawNewBitmap(uiDrawContext* c, int width, int height) ++{ ++ uiDrawBitmap* bmp; ++ HRESULT hr; ++ ++ bmp = uiprivNew(uiDrawBitmap); ++ ++ D2D1_BITMAP_PROPERTIES bp2 = D2D1::BitmapProperties(); ++ bp2.dpiX = 0; ++ bp2.dpiY = 0; ++ bp2.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE); ++ ++ //c->rt->BeginDraw(); ++ ++ hr = c->rt->CreateBitmap(D2D1::SizeU(width,height), NULL, 0, &bp2, &bmp->bmp); ++ if (hr != S_OK) ++ logHRESULT(L"error creating bitmap", hr); ++ ++ //c->rt->EndDraw(); ++ ++ bmp->Width = width; ++ bmp->Height = height; ++ bmp->Stride = width*4; ++ ++ return bmp; ++} ++ ++void uiDrawBitmapUpdate(uiDrawBitmap* bmp, const void* data) ++{ ++ D2D1_RECT_U rekt = D2D1::RectU(0, 0, bmp->Width, bmp->Height); ++ bmp->bmp->CopyFromMemory(&rekt, data, bmp->Stride); ++} ++ ++void uiDrawBitmapDraw(uiDrawContext* c, uiDrawBitmap* bmp, uiRect* srcrect, uiRect* dstrect, int filter) ++{ ++ D2D_RECT_F _srcrect = D2D1::RectF(srcrect->X, srcrect->Y, srcrect->X+srcrect->Width, srcrect->Y+srcrect->Height); ++ D2D_RECT_F _dstrect = D2D1::RectF(dstrect->X, dstrect->Y, dstrect->X+dstrect->Width, dstrect->Y+dstrect->Height); ++ ++ c->rt->DrawBitmap(bmp->bmp, &_dstrect, 1.0f, ++ filter ? D2D1_BITMAP_INTERPOLATION_MODE_LINEAR : D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, &_srcrect); ++} ++ ++void uiDrawFreeBitmap(uiDrawBitmap* bmp) ++{ ++ bmp->bmp->Release(); ++ uiprivFree(bmp); ++} +diff --git a/windows/draw.hpp b/windows/draw.hpp +index c271e4db8..b4bbc6c00 100644 +--- a/windows/draw.hpp ++++ b/windows/draw.hpp +@@ -11,6 +11,14 @@ struct uiDrawContext { + ID2D1PathGeometry *currentClip; + }; + ++struct uiDrawBitmap { ++ int Width; ++ int Height; ++ int Stride; ++ ++ ID2D1Bitmap* bmp; ++}; ++ + // drawpath.cpp + extern ID2D1PathGeometry *pathGeometry(uiDrawPath *p); +