diff --git a/declarativeloader.d b/declarativeloader.d index 28299c2c..c3e92c47 100644 --- a/declarativeloader.d +++ b/declarativeloader.d @@ -21,6 +21,10 @@ struct Tagged(alias field) {} auto Tag(T)(T t) { return TagStruct!T(t); } +/// For example `@presentIf("version >= 2") int addedInVersion2;` +struct presentIf { string code; } + + struct TagStruct(T) { T t; } struct MustBeStruct(T) { T t; } /// The marked field is not in the actual file @@ -66,14 +70,25 @@ union N(ty) { ubyte[ty.sizeof] bytes; } +static bool fieldPresent(alias field, T)(T t) { + bool p = true; + static foreach(attr; __traits(getAttributes, field)) { + static if(is(typeof(attr) == presentIf)) { + bool p2 = false; + with(t) p2 = mixin(attr.code); + p = p && p2; + } + } + return p; +} + /// input range of ubytes... int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) { int bytesConsumed; string currentItem; import std.conv; - scope(failure) - throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t)); + try { ubyte next() { if(r.empty) @@ -90,7 +105,8 @@ int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) static if(is(typeof(__traits(getMember, T, memberName)))) { alias f = __traits(getMember, T, memberName); alias ty = typeof(f); - static if(fieldSaved!f) { + static if(fieldSaved!f) + if(fieldPresent!f(t)) { endianness = bigEndian!f(endianness); // FIXME VariableLength static if(is(ty : ulong) || is(ty : double)) { @@ -200,5 +216,141 @@ int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) } }} + } catch(Exception e) { + throw new Exception(T.stringof ~ "." ~ currentItem ~ " trouble " ~ to!string(t), e.file, e.line, e); + } + return bytesConsumed; } + +int saveTo(T, Range)(ref T t, ref Range r, bool assumeBigEndian = false) { + int bytesWritten; + string currentItem; + + import std.conv; + try { + + void write(ubyte b) { + bytesWritten++; + static if(is(Range == ubyte[])) + r ~= b; + else + r.put(b); + } + + bool endianness = bigEndian!T(assumeBigEndian); + static foreach(memberName; __traits(allMembers, T)) {{ + currentItem = memberName; + static if(is(typeof(__traits(getMember, T, memberName)))) { + alias f = __traits(getMember, T, memberName); + alias ty = typeof(f); + static if(fieldSaved!f) + if(fieldPresent!f(t)) { + endianness = bigEndian!f(endianness); + // FIXME VariableLength + static if(is(ty : ulong) || is(ty : double)) { + N!ty n; + n.member = __traits(getMember, t, memberName); + if(endianness) { + foreach(i; 0 .. ty.sizeof) { + version(BigEndian) + write(n.bytes[i]); + else + write(n.bytes[$ - 1 - i]); + } + } else { + foreach(i; 0 .. ty.sizeof) { + version(BigEndian) + write(n.bytes[$ - 1 - i]); + else + write(n.bytes[i]); + } + } + + // FIXME: MustBe + } else static if(is(ty == struct)) { + bytesWritten += saveTo(__traits(getMember, t, memberName), r, endianness); + } else static if(is(ty == union)) { + static foreach(attr; __traits(getAttributes, ty)) + static if(is(attr == Tagged!Field, alias Field)) + enum tagField = __traits(identifier, Field); + static assert(is(typeof(tagField)), "Unions need a Tagged UDA on the union type (not the member) indicating the field that identifies the union"); + + auto tag = __traits(getMember, t, tagField); + // find the child of the union matching the tag... + bool found = false; + static foreach(um; __traits(allMembers, ty)) { + if(tag == getTag!(__traits(getMember, ty, um))) { + bytesWritten += saveTo(__traits(getMember, __traits(getMember, t, memberName), um), r, endianness); + found = true; + } + } + if(!found) { + import std.format; + throw new Exception(format("found unknown union tag %s at %s", tag, t)); + } + } else static if(is(ty == E[], E)) { + + // the numBytesRemaining / numElementsRemaining thing here ASSUMING the + // arrays are already the correct size. the struct itself could invariant that maybe + + foreach(item; __traits(getMember, t, memberName)) { + static if(is(typeof(item) == struct)) { + bytesWritten += saveTo(item, r, endianness); + } else { + static struct dummy { + typeof(item) i; + } + dummy d = dummy(item); + bytesWritten += saveTo(d, r, endianness); + } + } + + } else static assert(0, ty.stringof); + } + } + }} + + } catch(Exception e) { + throw new Exception(T.stringof ~ "." ~ currentItem ~ " save trouble " ~ to!string(t), e.file, e.line, e); + } + + return bytesWritten; +} + +unittest { + static struct A { + int a; + @presentIf("a > 5") int b; + int c; + @NumElements!c ubyte[] d; + } + + A a; + a.loadFrom(cast(ubyte[]) [1, 1, 0, 0, 7, 0, 0, 0, 3, 0, 0, 0, 6, 7, 8]); + + assert(a.a == 257); + assert(a.b == 7); + assert(a.c == 3); + assert(a.d == [6,7,8]); + + a = A.init; + + a.loadFrom(cast(ubyte[]) [0, 0, 0, 0, 7, 0, 0, 0,1,2,3,4,5,6,7]); + assert(a.b == 0); + assert(a.c == 7); + assert(a.d == [1,2,3,4,5,6,7]); + + a.a = 44; + a.c = 3; + a.d = [5,4,3]; + + ubyte[] saved; + + a.saveTo(saved); + + A b; + b.loadFrom(saved); + + assert(a == b); +} diff --git a/gamehelpers.d b/gamehelpers.d index 43de1e8d..f041d07f 100644 --- a/gamehelpers.d +++ b/gamehelpers.d @@ -234,10 +234,10 @@ void runGame(T : GameHelperBase)(T game, int maxUpdateRate = 20, int maxRedrawRa if(update.buttonWasJustPressed(l1)) game.snes[L] = true; if(update.buttonWasJustPressed(r1)) game.snes[R] = true; // note: no need to check analog stick here cuz joystick.d already does it for us (per old playstation tradition) - if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < -20000) game.snes[Left] = true; - if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > 20000) game.snes[Right] = true; - if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < -20000) game.snes[Up] = true; - if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > 20000) game.snes[Down] = true; + if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < -8) game.snes[Left] = true; + if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > 8) game.snes[Right] = true; + if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < -8) game.snes[Up] = true; + if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > 8) game.snes[Down] = true; if(update.buttonWasJustReleased(square)) game.snes[Y] = false; if(update.buttonWasJustReleased(triangle)) game.snes[X] = false; @@ -247,14 +247,15 @@ void runGame(T : GameHelperBase)(T game, int maxUpdateRate = 20, int maxRedrawRa if(update.buttonWasJustReleased(start)) game.snes[Start] = false; if(update.buttonWasJustReleased(l1)) game.snes[L] = false; if(update.buttonWasJustReleased(r1)) game.snes[R] = false; - if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > -20000) game.snes[Left] = false; - if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < 20000) game.snes[Right] = false; - if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > -20000) game.snes[Up] = false; - if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < 20000) game.snes[Down] = false; + if(update.axisChange(Axis.horizontalDpad) > 0 && update.axisPosition(Axis.horizontalDpad) > -8) game.snes[Left] = false; + if(update.axisChange(Axis.horizontalDpad) < 0 && update.axisPosition(Axis.horizontalDpad) < 8) game.snes[Right] = false; + if(update.axisChange(Axis.verticalDpad) > 0 && update.axisPosition(Axis.verticalDpad) > -8) game.snes[Up] = false; + if(update.axisChange(Axis.verticalDpad) < 0 && update.axisPosition(Axis.verticalDpad) < 8) game.snes[Down] = false; } } else static if(__traits(isSame, Button, XBox360Buttons)) { + static assert(0); // XBox style mapping // the reason this exists is if the programmer wants to use the xbox details, but // might also want the basic controller in here. joystick.d already does translations diff --git a/joystick.d b/joystick.d index 9b7f6193..cbf50ef6 100644 --- a/joystick.d +++ b/joystick.d @@ -117,6 +117,10 @@ version(Windows) { WindowsXInput wxi; } +version(OSX) { + struct JoystickState {} +} + JoystickState[4] joystickState; version(linux) { diff --git a/jsvar.d b/jsvar.d index 87a4f824..7a027a28 100644 --- a/jsvar.d +++ b/jsvar.d @@ -617,7 +617,13 @@ struct var { // if it is var, we'll just blit it over public var opAssign(T)(T t) if(!is(T == var)) { static if(__traits(compiles, this = t.toArsdJsvar())) { - this = t.toArsdJsvar(); + static if(__traits(compiles, t is null)) { + if(t is null) + this = null; + else + this = t.toArsdJsvar(); + } else + this = t.toArsdJsvar(); } else static if(isFloatingPoint!T) { this._type = Type.Floating; this._payload._floating = t; @@ -943,7 +949,15 @@ struct var { auto pl = this._payload._array; static if(isSomeString!T) { return to!string(pl); - } else static if(isArray!T) { + } else static if(is(T == E[N], E, size_t N)) { + T ret; + foreach(i; 0 .. N) { + if(i >= pl.length) + break; + ret[i] = pl[i].get!E; + } + return ret; + } else static if(is(T == E[], E)) { T ret; static if(is(ElementType!T == void)) { static assert(0, "try wrapping the function to get rid of void[] args"); @@ -1366,6 +1380,11 @@ struct var { return var.fromJsonValue(decoded); } + static var fromJsonFile(string filename) { + import std.file; + return var.fromJson(readText(filename)); + } + static var fromJsonValue(JSONValue v) { var ret; diff --git a/minigui.d b/minigui.d index 18e217fd..e977fd7b 100644 --- a/minigui.d +++ b/minigui.d @@ -1,5 +1,7 @@ // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx +// osx style menu search. + // would be cool for a scroll bar to have marking capabilities // kinda like vim's marks just on clicks etc and visual representation // generically. may be cool to add an up arrow to the bottom too @@ -301,13 +303,19 @@ abstract class ComboboxBase : Widget { event.dispatch(); } - override int minHeight() { return Window.lineHeight + 4; } - override int maxHeight() { return Window.lineHeight + 4; } + version(win32_widgets) { + override int minHeight() { return Window.lineHeight + 6; } + override int maxHeight() { return Window.lineHeight + 6; } + } else { + override int minHeight() { return Window.lineHeight + 4; } + override int maxHeight() { return Window.lineHeight + 4; } + } version(custom_widgets) { SimpleWindow dropDown; void popup() { auto w = width; + // FIXME: suggestedDropdownHeight see below auto h = cast(int) this.options.length * Window.lineHeight + 8; auto coord = this.globalCoordinates(); @@ -396,7 +404,21 @@ class DropDownSelection : ComboboxBase { painter.pen = Pen(Color.black, 1, Pen.Style.Solid); } + } + version(win32_widgets) + override void registerMovement() { + version(win32_widgets) { + if(hwnd) { + auto pos = getChildPositionRelativeToParentHwnd(this); + // the height given to this from Windows' perspective is supposed + // to include the drop down's height. so I add to it to give some + // room for that. + // FIXME: maybe make the subclass provide a suggestedDropdownHeight thing + MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true); + } + } + sendResizeEvent(); } } @@ -448,7 +470,7 @@ class FreeEntrySelection : ComboboxBase { class ComboBox : ComboboxBase { this(Widget parent = null) { version(win32_widgets) - super(1 /* CBS_SIMPLE */, parent); + super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent); else version(custom_widgets) { super(parent); lineEdit = new LineEdit(this); @@ -1054,15 +1076,44 @@ version(win32_widgets) { //assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen } + extern(Windows) + private + int HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow { + if(iMessage == WM_ERASEBKGND) { + auto dc = GetDC(hWnd); + auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE)); + auto p = SelectObject(dc, GetStockObject(NULL_PEN)); + RECT r; + GetWindowRect(hWnd, &r); + // since the pen is null, to fill the whole space, we need the +1 on both. + gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1); + SelectObject(dc, p); + SelectObject(dc, b); + ReleaseDC(hWnd, dc); + return 1; + } + return HookedWndProc(hWnd, iMessage, wParam, lParam); + } + // className MUST be a string literal void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) { assert(p.parentWindow !is null); assert(p.parentWindow.win.impl.hwnd !is null); + auto bsgroupbox = style == BS_GROUPBOX; + HWND phwnd; - if(p.parent !is null && p.parent.hwnd !is null) - phwnd = p.parent.hwnd; - else + + auto wtf = p.parent; + while(wtf) { + if(wtf.hwnd !is null) { + phwnd = wtf.hwnd; + break; + } + wtf = wtf.parent; + } + + if(phwnd is null) phwnd = p.parentWindow.win.impl.hwnd; assert(phwnd !is null); @@ -1070,6 +1121,8 @@ version(win32_widgets) { WCharzBuffer wt = WCharzBuffer(windowText); style |= WS_VISIBLE | WS_CHILD; + //if(className != WC_TABCONTROL) + style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS; p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style, CW_USEDEFAULT, CW_USEDEFAULT, 100, 100, phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null); @@ -1093,6 +1146,9 @@ version(win32_widgets) { p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false; Widget.nativeMapping[p.hwnd] = p; + if(bsgroupbox) + p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK); + else p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc); EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p); @@ -1150,6 +1206,11 @@ struct WidgetPainter { class Widget { mixin LayoutInfo!(); + protected void sendResizeEvent() { + auto event = new Event("resize", this); + event.sendDirectly(); + } + deprecated("Change ScreenPainter to WidgetPainter") final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); } @@ -1450,6 +1511,7 @@ class Widget { MoveWindow(hwnd, pos[0], pos[1], width, height, true); } } + sendResizeEvent(); } Window parentWindow; @@ -1741,9 +1803,35 @@ class OpenGlWidget : Widget { /// this(Widget parent) { this.parentWindow = parent.parentWindow; - win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, this.parentWindow.win); + + SimpleWindow pwin = this.parentWindow.win; + + + version(win32_widgets) { + HWND phwnd; + auto wtf = parent; + while(wtf) { + if(wtf.hwnd) { + phwnd = wtf.hwnd; + break; + } + wtf = wtf.parent; + } + // kinda a hack here just because the ctor below just needs a SimpleWindow wrapper.... + if(phwnd) + pwin = new SimpleWindow(phwnd); + } + + win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, pwin); super(parent); + /* + win.onFocusChange = (bool getting) { + if(getting) + this.focus(); + }; + */ + version(win32_widgets) { Widget.nativeMapping[win.hwnd] = this; this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc); @@ -1799,6 +1887,9 @@ class OpenGlWidget : Widget { else auto pos = getChildPositionRelativeToParentOrigin(this); win.moveResize(pos[0], pos[1], width, height); + + win.setAsCurrentOpenGlContext(); + sendResizeEvent(); } //void delegate() drawFrame; @@ -3118,16 +3209,8 @@ class TabWidget : Widget { } override void recomputeChildLayout() { - this.registerMovement(); version(win32_widgets) { - - // Windows doesn't actually parent widgets to the - // tab control, so we will temporarily pretend this isn't - // a native widget as we do the changes. A bit of a filthy - // hack, but a functional one. - auto hwnd = this.hwnd; - this.hwnd = null; - scope(exit) this.hwnd = hwnd; + this.registerMovement(); RECT rect; GetWindowRect(hwnd, &rect); @@ -3144,6 +3227,7 @@ class TabWidget : Widget { child.recomputeChildLayout(); } } else version(custom_widgets) { + this.registerMovement(); foreach(child; children) { child.x = 2; child.y = tabBarHeight + 2; // for the border @@ -3350,7 +3434,7 @@ class TabWidgetPage : Widget { this.title = title; super(parent); - /* + ///* version(win32_widgets) { static bool classRegistered = false; if(!classRegistered) { @@ -3358,6 +3442,7 @@ class TabWidgetPage : Widget { WNDCLASSEX wc; wc.cbSize = wc.sizeof; wc.hInstance = hInstance; + wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH); wc.lpfnWndProc = &DefWindowProc; wc.lpszClassName = "arsd_minigui_TabWidgetPage"w.ptr; if(!RegisterClassExW(&wc)) @@ -3368,7 +3453,7 @@ class TabWidgetPage : Widget { createWin32Window(this, "arsd_minigui_TabWidgetPage"w, "", 0); } - */ + //*/ } override int minHeight() { @@ -3502,6 +3587,17 @@ class ScrollMessageWidget : Widget { magic = true; } + /// + void scrollUp() { + vsb.setPosition(vsb.position - 1); + notify(); + } + /// Ditto + void scrollDown() { + vsb.setPosition(vsb.position + 1); + notify(); + } + /// VerticalScrollbar verticalScrollBar() { return vsb; } /// @@ -4039,6 +4135,8 @@ class Window : Widget { event.key = ev.key; event.state = ev.modifierState; event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false; + event.altKey = (ev.modifierState & ModifierState.alt) ? true : false; + event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false; event.dispatch(); return true; @@ -4095,6 +4193,7 @@ class Window : Widget { event = new Event("click", ele); event.clientX = eleR.x; event.clientY = eleR.y; + event.state = ev.modifierState; event.button = ev.button; event.buttonLinear = ev.buttonLinear; event.dispatch(); @@ -6607,6 +6706,16 @@ class Event { string stringValue; /// bool shiftKey; /// + /++ + NOTE: only set on key events right now + + History: + Added April 15, 2020 + +/ + bool ctrlKey; + + /// ditto + bool altKey; private bool isBubbling; diff --git a/minigui_addons/color_dialog.d b/minigui_addons/color_dialog.d index 18cce3f9..2103356a 100644 --- a/minigui_addons/color_dialog.d +++ b/minigui_addons/color_dialog.d @@ -101,7 +101,7 @@ class ColorPickerDialog : Dialog { override int minHeight() { return hslImage ? hslImage.height : 4; } override int maxHeight() { return hslImage ? hslImage.height : 4; } override int marginBottom() { return 4; } - override void paint(ScreenPainter painter) { + override void paint(WidgetPainter painter) { if(hslImage) hslImage.drawAt(painter, Point(0, 0)); } @@ -223,7 +223,7 @@ class ColorPickerDialog : Dialog { super(s); } - override void paint(ScreenPainter painter) { + override void paint(WidgetPainter painter) { auto c = currentColor(); auto c1 = alphaBlend(c, Color(64, 64, 64)); diff --git a/mvd.d b/mvd.d index b72d8f48..0731c65b 100644 --- a/mvd.d +++ b/mvd.d @@ -89,5 +89,7 @@ unittest { assert(mvd!foo(new DerivedClass, new DerivedClass) == 3); assert(mvd!foo(new OtherClass, new OtherClass) == 1); assert(mvd!foo(new OtherClass, new MyClass) == 1); + assert(mvd!foo(new DerivedClass, new DerivedClass) == 3); + assert(mvd!foo(new OtherClass, new MyClass) == 1); } } diff --git a/simpledisplay.d b/simpledisplay.d index e569668a..76405dbf 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -78,7 +78,7 @@ On Win32, you can pass `-L/subsystem:windows` if you don't want a console to be automatically allocated. - On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. + On Mac, when compiling with X11, you need XQuartz and -L-L/usr/X11R6/lib passed to dmd. If using the Cocoa implementation on Mac, you need to pass `-L-framework -LCocoa` to dmd. For OpenGL, add `-L-framework -LOpenGL` to the build command. On Ubuntu, you might need to install X11 development libraries to successfully link. @@ -1408,6 +1408,9 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { _width = 1; _height = 1; nativeMapping[nativeWindow] = this; + + beingOpenKeepsAppOpen = false; + CapableOfHandlingNativeEvent.nativeHandleMapping[nativeWindow] = this; _suppressDestruction = true; // so it doesn't try to close } @@ -8326,6 +8329,7 @@ version(Windows) { } int style; + uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files // FIXME: windowType and customizationFlags final switch(windowType) { @@ -8342,17 +8346,17 @@ version(Windows) { case WindowTypes.popupMenu: case WindowTypes.notification: style = WS_POPUP; + flags |= WS_EX_NOACTIVATE; break; case WindowTypes.nestedChild: style = WS_CHILD; break; } - uint flags = WS_EX_ACCEPTFILES; // accept drag-drop files if ((customizationFlags & WindowFlags.extraComposite) != 0) flags |= WS_EX_LAYERED; // composite window for better performance and effects support - hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN, // the clip children helps avoid flickering in minigui and doesn't seem to harm other use (mostly, sdpy is no child windows anyway) sooo i think it is ok + hwnd = CreateWindowEx(flags, cn.ptr, toWStringz(title), style | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // the clip children helps avoid flickering in minigui and doesn't seem to harm other use (mostly, sdpy is no child windows anyway) sooo i think it is ok CW_USEDEFAULT, CW_USEDEFAULT, width, height, parent is null ? null : parent.impl.hwnd, null, hInstance, null); @@ -8488,7 +8492,7 @@ version(Windows) { static int triggerEvents(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam, int offsetX, int offsetY, SimpleWindow wind) { MouseEvent mouse; - void mouseEvent(bool isScreen = false) { + void mouseEvent(bool isScreen, ulong mods) { auto x = LOWORD(lParam); auto y = HIWORD(lParam); if(isScreen) { @@ -8503,7 +8507,7 @@ version(Windows) { mouse.y = y + offsetY; wind.mdx(mouse); - mouse.modifierState = cast(int) wParam; + mouse.modifierState = cast(int) mods; mouse.window = wind; if(wind.handleMouseEvent) @@ -8597,61 +8601,67 @@ version(Windows) { wind.handleKeyEvent(ev); break; case 0x020a /*WM_MOUSEWHEEL*/: + // send click mouse.type = cast(MouseEventType) 1; mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); - mouseEvent(true); + mouseEvent(true, LOWORD(wParam)); + + // also send release + mouse.type = cast(MouseEventType) 2; + mouse.button = ((HIWORD(wParam) > 120) ? MouseButton.wheelDown : MouseButton.wheelUp); + mouseEvent(true, LOWORD(wParam)); break; case WM_MOUSEMOVE: mouse.type = cast(MouseEventType) 0; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: mouse.type = cast(MouseEventType) 1; mouse.button = MouseButton.left; mouse.doubleClick = msg == WM_LBUTTONDBLCLK; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_LBUTTONUP: mouse.type = cast(MouseEventType) 2; mouse.button = MouseButton.left; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: mouse.type = cast(MouseEventType) 1; mouse.button = MouseButton.right; mouse.doubleClick = msg == WM_RBUTTONDBLCLK; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_RBUTTONUP: mouse.type = cast(MouseEventType) 2; mouse.button = MouseButton.right; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: mouse.type = cast(MouseEventType) 1; mouse.button = MouseButton.middle; mouse.doubleClick = msg == WM_MBUTTONDBLCLK; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_MBUTTONUP: mouse.type = cast(MouseEventType) 2; mouse.button = MouseButton.middle; - mouseEvent(); + mouseEvent(false, wParam); break; case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: mouse.type = cast(MouseEventType) 1; mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; mouse.doubleClick = msg == WM_XBUTTONDBLCLK; - mouseEvent(); + mouseEvent(false, wParam); return 1; // MSDN says special treatment here, return TRUE to bypass simulation programs case WM_XBUTTONUP: mouse.type = cast(MouseEventType) 2; mouse.button = HIWORD(wParam) == 1 ? MouseButton.backButton : MouseButton.forwardButton; - mouseEvent(); + mouseEvent(false, wParam); return 1; // see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms646246(v=vs.85).aspx default: return 1;