From 9e9be7cc0de149828fc8f18342a1ff8245476753 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 23 Nov 2021 17:23:42 -0500 Subject: [PATCH] gradient stuff and windows tweaks --- simpledisplay.d | 318 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 285 insertions(+), 33 deletions(-) diff --git a/simpledisplay.d b/simpledisplay.d index f98c151..18ede6a 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -3786,6 +3786,7 @@ struct EventLoopImpl { } } else if(waitResult == handles.length + WAIT_OBJECT_0) { // message ready + int count; while(PeekMessage(&message, null, 0, 0, PM_NOREMOVE)) { // need to peek since sometimes MsgWaitForMultipleObjectsEx returns even though GetMessage can block. tbh i don't fully understand it but the docs say it is foreground activation ret = GetMessage(&message, null, 0, 0); if(ret == -1) @@ -3793,6 +3794,10 @@ struct EventLoopImpl { TranslateMessage(&message); DispatchMessage(&message); + count++; + if(count > 10) + break; // take the opportunity to catch up on other events + if(ret == 0) // WM_QUIT break; } @@ -8535,7 +8540,7 @@ struct ScreenPainter { impl.drawArc(upperLeft.x, upperLeft.y, width, height, start, finish); } - //this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius + /// this function draws a circle with the drawEllipse() function above, it requires the upper left point and the radius void drawCircle(Point upperLeft, int diameter) { drawEllipse(upperLeft, Point(upperLeft.x + diameter, upperLeft.y + diameter)); } @@ -8657,6 +8662,26 @@ class Sprite : CapableOfBeingDrawnUpon { private Picture xrenderPicture; } + version(X11) + private static void requireXRender() { + if(!XRenderLibrary.loadAttempted) { + XRenderLibrary.loadDynamicLibrary(); + } + + if(!XRenderLibrary.loadSuccessful) + throw new Exception("XRender library load failure"); + + auto display = XDisplayConnection.get; + + // FIXME: if we migrate X displays, these need to be changed + if(RGB24 is null) + RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); + if(ARGB32 is null) + ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); + } + + protected this() {} + this(SimpleWindow win, int width, int height, bool enableAlpha = false) { this._width = width; this._height = height; @@ -8666,22 +8691,12 @@ class Sprite : CapableOfBeingDrawnUpon { auto display = XDisplayConnection.get(); if(enableAlpha) { - if(!XRenderLibrary.loadAttempted) { - XRenderLibrary.loadDynamicLibrary(); - } - - if(!XRenderLibrary.loadSuccessful) - throw new Exception("XRender library load failure"); + requireXRender(); } handle = XCreatePixmap(display, cast(Drawable) win.window, width, height, enableAlpha ? 32 : DefaultDepthOfDisplay(display)); if(enableAlpha) { - if(RGB24 is null) - RGB24 = XRenderFindStandardFormat(display, PictStandardRGB24); - if(ARGB32 is null) - ARGB32 = XRenderFindStandardFormat(display, PictStandardARGB32); - XRenderPictureAttributes attrs; xrenderPicture = XRenderCreatePicture(display, handle, ARGB32, 0, &attrs); } @@ -8816,6 +8831,213 @@ class Sprite : CapableOfBeingDrawnUpon { } /++ + Represents a display-side gradient pseudo-image. Actually construct it with [LinearGradient], [RadialGradient], or [ConicalGradient]. + + History: + Added November 20, 2021 (dub v10.4) ++/ +abstract class Gradient : Sprite { + protected this(int w, int h) { + version(X11) { + Sprite.requireXRender(); + + super(); + enableAlpha = true; + _width = w; + _height = h; + } else version(Windows) { + super(null, w, h, true); // on Windows i'm just making a bitmap myself + } + } + + version(Windows) + final void forEachPixel(scope Color delegate(int x, int y) dg) { + auto ptr = rawData; + foreach(j; 0 .. _height) + foreach(i; 0 .. _width) { + auto color = dg(i, _height - j - 1); // cuz of upside down bitmap + *rawData = (color.a * color.b) / 255; rawData++; + *rawData = (color.a * color.g) / 255; rawData++; + *rawData = (color.a * color.r) / 255; rawData++; + *rawData = color.a; rawData++; + } + } + + version(X11) + protected void helper(scope Stop[] stops, scope Picture delegate(scope XFixed[] stopsPositions, scope XRenderColor[] colors) dg) { + assert(stops.length > 0); + assert(stops.length <= 16, "I got lazy with buffers"); + + XFixed[16] stopsPositions = void; + XRenderColor[16] colors = void; + + foreach(idx, stop; stops) { + stopsPositions[idx] = cast(int)(stop.percentage * ushort.max); + auto c = stop.c; + colors[idx] = XRenderColor( + cast(ushort)(c.r * ushort.max / 255), + cast(ushort)(c.g * ushort.max / 255), + cast(ushort)(c.b * ushort.max / 255), + cast(ushort)(c.a * ubyte.max) // max value here is fractional + ); + } + + xrenderPicture = dg(stopsPositions, colors); + } + + /// + static struct Stop { + float percentage; /// between 0 and 1.0 + Color c; + } +} + +/++ + Creates a linear gradient between p1 and p2. + + X ONLY RIGHT NOW + + History: + Added November 20, 2021 (dub v10.4) + + Bugs: + Not yet implemented on Windows. ++/ +class LinearGradient : Gradient { + /++ + + +/ + this(Point p1, Point p2, Stop[] stops...) { + super(p2.x, p2.y); + + version(X11) { + XLinearGradient gradient; + gradient.p1 = XPointFixed(p1.x * ushort.max, p1.y * ushort.max); + gradient.p2 = XPointFixed(p2.x * ushort.max, p2.y * ushort.max); + + helper(stops, (stopsPositions, colors) { + return XRenderCreateLinearGradient( + XDisplayConnection.get, + &gradient, + stopsPositions.ptr, + colors.ptr, + cast(int) stops.length); + }); + } else version(Windows) { + // FIXME + forEachPixel((int x, int y) { + import core.stdc.math; + + //sqrtf( + + return Color.transparent; + // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful + }); + } + } +} + +/++ + A conical gradient goes from color to color around a circumference from a center point. + + X ONLY RIGHT NOW + + History: + Added November 20, 2021 (dub v10.4) + + Bugs: + Not yet implemented on Windows. ++/ +class ConicalGradient : Gradient { + /++ + + +/ + this(Point center, float angleInDegrees, Stop[] stops...) { + super(center.x * 2, center.y * 2); + + version(X11) { + XConicalGradient gradient; + gradient.center = XPointFixed(center.x * ushort.max, center.y * ushort.max); + gradient.angle = cast(int)(angleInDegrees * ushort.max); + + helper(stops, (stopsPositions, colors) { + return XRenderCreateConicalGradient( + XDisplayConnection.get, + &gradient, + stopsPositions.ptr, + colors.ptr, + cast(int) stops.length); + }); + } else version(Windows) { + // FIXME + forEachPixel((int x, int y) { + import core.stdc.math; + + //sqrtf( + + return Color.transparent; + // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful + }); + + } + } +} + +/++ + A radial gradient goes from color to color based on distance from the center. + It is like rings of color. + + X ONLY RIGHT NOW + + + More specifically, you create two circles: an inner circle and an outer circle. + The gradient is only drawn in the area outside the inner circle but inside the outer + circle. The closest line between those two circles forms the line for the gradient + and the stops are calculated the same as the [LinearGradient]. Then it just sweeps around. + + History: + Added November 20, 2021 (dub v10.4) + + Bugs: + Not yet implemented on Windows. ++/ +class RadialGradient : Gradient { + /++ + + +/ + this(Point innerCenter, float innerRadius, Point outerCenter, float outerRadius, Stop[] stops...) { + super(cast(int)(outerCenter.x + outerRadius + 0.5), cast(int)(outerCenter.y + outerRadius + 0.5)); + + version(X11) { + XRadialGradient gradient; + gradient.inner = XCircle(innerCenter.x * ushort.max, innerCenter.y * ushort.max, cast(int) (innerRadius * ushort.max)); + gradient.outer = XCircle(outerCenter.x * ushort.max, outerCenter.y * ushort.max, cast(int) (outerRadius * ushort.max)); + + helper(stops, (stopsPositions, colors) { + return XRenderCreateRadialGradient( + XDisplayConnection.get, + &gradient, + stopsPositions.ptr, + colors.ptr, + cast(int) stops.length); + }); + } else version(Windows) { + // FIXME + forEachPixel((int x, int y) { + import core.stdc.math; + + //sqrtf( + + return Color.transparent; + // return Color(x * 2, y * 2, 0, 128); // this result is so beautiful + }); + } + } +} + + + +/+ NOT IMPLEMENTED A display-stored image optimized for relatively quick drawing, like @@ -10564,6 +10786,9 @@ version(Windows) { SelectObject(hdcBmp, oldPen); DeleteDC(hdcBmp); + bmpWidth = width; + bmpHeight = height; + ReleaseDC(hwnd, hdc); // we keep this in opengl mode since it is a class member now } @@ -10793,6 +11018,9 @@ version(Windows) { int oldHeight; bool inSizeMove; + int bmpWidth; + int bmpHeight; + // the extern(Windows) wndproc should just forward to this LRESULT windowProcedure(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) { assert(hwnd is this.hwnd); @@ -10841,6 +11069,11 @@ version(Windows) { if(!inSizeMove) goto size_changed; break; + /+ + case WM_SIZING: + import std.stdio; writeln("size"); + break; + +/ // I don't like the tearing I get when redrawing on WM_SIZE // (I know there's other ways to fix that but I don't like that behavior anyway) // so instead it is going to redraw only at the end of a size. @@ -10851,39 +11084,44 @@ version(Windows) { break; case 0x0232: /* WM_EXITSIZEMOVE */ inSizeMove = false; + + size_changed: + // nothing relevant changed, don't bother redrawing if(oldWidth == width && oldHeight == height) break; - size_changed: - // note: OpenGL windows don't use a backing bmp, so no need to change them // if resizability is anything other than allowResizing, it is meant to either stretch the one image or just do nothing if(openglMode == OpenGlOptions.no) { // && resizability == Resizability.allowResizing) { // gotta get the double buffer bmp to match the window - // FIXME: could this be more efficient? It isn't really necessary to make - // a new buffer if we're sizing down at least. - auto hdc = GetDC(hwnd); - auto oldBuffer = buffer; - buffer = CreateCompatibleBitmap(hdc, width, height); + // FIXME: could this be more efficient? it never relinquishes a large bitmap + if(width > bmpWidth || height > bmpHeight) { + auto hdc = GetDC(hwnd); + auto oldBuffer = buffer; + buffer = CreateCompatibleBitmap(hdc, width, height); - auto hdcBmp = CreateCompatibleDC(hdc); - auto oldBmp = SelectObject(hdcBmp, buffer); + auto hdcBmp = CreateCompatibleDC(hdc); + auto oldBmp = SelectObject(hdcBmp, buffer); - auto hdcOldBmp = CreateCompatibleDC(hdc); - auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); + auto hdcOldBmp = CreateCompatibleDC(hdc); + auto oldOldBmp = SelectObject(hdcOldBmp, oldBmp); - BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); + BitBlt(hdcBmp, 0, 0, width, height, hdcOldBmp, oldWidth, oldHeight, SRCCOPY); - SelectObject(hdcOldBmp, oldOldBmp); - DeleteDC(hdcOldBmp); + bmpWidth = width; + bmpHeight = height; - SelectObject(hdcBmp, oldBmp); - DeleteDC(hdcBmp); + SelectObject(hdcOldBmp, oldOldBmp); + DeleteDC(hdcOldBmp); - ReleaseDC(hwnd, hdc); + SelectObject(hdcBmp, oldBmp); + DeleteDC(hdcBmp); - DeleteObject(oldBuffer); + ReleaseDC(hwnd, hdc); + + DeleteObject(oldBuffer); + } } version(without_opengl) {} else @@ -11067,7 +11305,7 @@ version(Windows) { rawData[offset + 1] = (a * what[idx + 1]) / 255; // g rawData[offset + 0] = (a * what[idx + 2]) / 255; // b rawData[offset + 3] = a; // a - premultiplyBgra(rawData[offset .. offset + 4]); + //premultiplyBgra(rawData[offset .. offset + 4]); offset++; } else { rawData[offset + 2] = what[idx + 0]; // r @@ -16897,6 +17135,8 @@ extern(System) nothrow @nogc { GLint glGetUniformLocation(GLuint, const(GLchar)*); void glGenBuffers(GLsizei, GLuint*); void glUniform4fv(GLint, GLsizei, const(GLfloat)*); + void glUniform1f(GLint, float); + void glUniform2f(GLint, float, float); void glUniform4f(GLint, float, float, float, float); void glColorMask(GLboolean, GLboolean, GLboolean, GLboolean); void glStencilOpSeparate(GLenum, GLenum, GLenum, GLenum); @@ -17323,8 +17563,19 @@ final class OpenGlShader { /// Assigns the 4 floats. You will probably have to call this via the .opAssign name void opAssign(float x, float y, float z, float w) { + if(id != -1) glUniform4f(id, x, y, z, w); } + + void opAssign(float x) { + if(id != -1) + glUniform1f(id, x); + } + + void opAssign(float x, float y) { + if(id != -1) + glUniform2f(id, x, y); + } } static struct UniformsHelper { @@ -17332,8 +17583,9 @@ final class OpenGlShader { @property Uniform opDispatch(string name)() { auto i = glGetUniformLocation(_shader.shaderProgram, name.ptr); - if(i == -1) - throw new Exception("Could not find uniform " ~ name); + // FIXME: decide what to do here; the exception is liable to be swallowed by the event syste + //if(i == -1) + //throw new Exception("Could not find uniform " ~ name); return Uniform(i); } }