mirror of https://github.com/adamdruppe/arsd.git
more cool features
This commit is contained in:
parent
307c6acb28
commit
948926df49
73
apng.d
73
apng.d
|
|
@ -417,8 +417,8 @@ struct ApngRenderBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/+
|
/++
|
||||||
|
Class that represents an apng file.
|
||||||
+/
|
+/
|
||||||
class ApngAnimation {
|
class ApngAnimation {
|
||||||
PngHeader header;
|
PngHeader header;
|
||||||
|
|
@ -427,7 +427,10 @@ class ApngAnimation {
|
||||||
ApngFrame[] frames;
|
ApngFrame[] frames;
|
||||||
// default image? tho i can just load it as a png for that too.
|
// default image? tho i can just load it as a png for that too.
|
||||||
|
|
||||||
/// This is an uninitialized thing, you're responsible for filling in all data yourself. You probably don't want this.
|
/++
|
||||||
|
This is an uninitialized thing, you're responsible for filling in all data yourself. You probably don't want to
|
||||||
|
use this except for use in the `factory` function you pass to [readApng].
|
||||||
|
+/
|
||||||
this() {
|
this() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -467,6 +470,50 @@ class ApngAnimation {
|
||||||
ApngRenderBuffer renderer() {
|
ApngRenderBuffer renderer() {
|
||||||
return ApngRenderBuffer(this, new TrueColorImage(header.width, header.height), 0);
|
return ApngRenderBuffer(this, new TrueColorImage(header.width, header.height), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Hook for subclasses to handle custom chunks in the png file as it is loaded by [readApng].
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
---
|
||||||
|
override void handleOtherChunkWhenLoading(Chunk chunk) {
|
||||||
|
if(chunk.stype == "mine") {
|
||||||
|
ubyte[] data = chunk.payload;
|
||||||
|
// process it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 26, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
protected void handleOtherChunkWhenLoading(Chunk chunk) {
|
||||||
|
// intentionally blank to ignore it since the main function does the whole base functionality
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Hook for subclasses to add custom chunks to the png file as it is written by [writeApngToData] and [writeApngToFile].
|
||||||
|
|
||||||
|
Standards:
|
||||||
|
See the png spec for guidelines on how to create non-essential, private chunks in a file:
|
||||||
|
|
||||||
|
http://www.libpng.org/pub/png/spec/1.2/PNG-Encoders.html#E.Use-of-private-chunks
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
---
|
||||||
|
override createOtherChunksWhenSaving(scope void delegate(Chunk c) sink) {
|
||||||
|
sink(*Chunk.create("mine", [payload, bytes, here]));
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 26, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
protected void createOtherChunksWhenSaving(scope void delegate(Chunk c) sink) {
|
||||||
|
// no other chunks by default
|
||||||
|
|
||||||
|
// I can now do the repeat frame thing for start / cycle / end bits of the animation in the game!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -495,14 +542,25 @@ enum APNG_BLEND_OP : byte {
|
||||||
If false, it will use the default image as the first
|
If false, it will use the default image as the first
|
||||||
(and only) frame of animation if there are no apng chunks.
|
(and only) frame of animation if there are no apng chunks.
|
||||||
|
|
||||||
|
factory = factory function for constructing the [ApngAnimation]
|
||||||
|
object the function returns. You can use this to override the
|
||||||
|
allocation pattern or to return a subclass instead, which can handle
|
||||||
|
custom chunks and other things.
|
||||||
|
|
||||||
History:
|
History:
|
||||||
Parameter `strictApng` added February 27, 2021
|
Parameter `strictApng` added February 27, 2021
|
||||||
|
Parameter `factory` added December 26, 2021
|
||||||
+/
|
+/
|
||||||
ApngAnimation readApng(in ubyte[] data, bool strictApng = false) {
|
ApngAnimation readApng(in ubyte[] data, bool strictApng = false, scope ApngAnimation delegate() factory = null) {
|
||||||
auto png = readPng(data);
|
auto png = readPng(data);
|
||||||
auto header = PngHeader.fromChunk(png.chunks[0]);
|
auto header = PngHeader.fromChunk(png.chunks[0]);
|
||||||
|
|
||||||
auto obj = new ApngAnimation();
|
ApngAnimation obj;
|
||||||
|
if(factory)
|
||||||
|
obj = factory();
|
||||||
|
else
|
||||||
|
obj = new ApngAnimation();
|
||||||
|
|
||||||
obj.header = header;
|
obj.header = header;
|
||||||
|
|
||||||
if(header.type == 3) {
|
if(header.type == 3) {
|
||||||
|
|
@ -670,7 +728,7 @@ ApngAnimation readApng(in ubyte[] data, bool strictApng = false) {
|
||||||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// ignore
|
obj.handleOtherChunk(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -680,7 +738,8 @@ ApngAnimation readApng(in ubyte[] data, bool strictApng = false) {
|
||||||
|
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
It takes the apng file and feeds the file data to your `sink` delegate, the given file,
|
||||||
|
or simply returns it as an in-memory array.
|
||||||
+/
|
+/
|
||||||
void writeApngToData(ApngAnimation apng, scope void delegate(in ubyte[] data) sink) {
|
void writeApngToData(ApngAnimation apng, scope void delegate(in ubyte[] data) sink) {
|
||||||
|
|
||||||
|
|
|
||||||
4
cgi.d
4
cgi.d
|
|
@ -4858,10 +4858,12 @@ version(cgi_use_fiber) {
|
||||||
|
|
||||||
int epfd = -1; // thread local because EPOLLEXCLUSIVE works much better this way... weirdly.
|
int epfd = -1; // thread local because EPOLLEXCLUSIVE works much better this way... weirdly.
|
||||||
} else version(Windows) {
|
} else version(Windows) {
|
||||||
__gshared HANDLE iocp;
|
// declaring the iocp thing below...
|
||||||
} else static assert(0, "The hybrid fiber server is not implemented on your OS.");
|
} else static assert(0, "The hybrid fiber server is not implemented on your OS.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version(Windows)
|
||||||
|
__gshared HANDLE iocp;
|
||||||
|
|
||||||
version(cgi_use_fiber) {
|
version(cgi_use_fiber) {
|
||||||
version(linux)
|
version(linux)
|
||||||
|
|
|
||||||
182
http2.d
182
http2.d
|
|
@ -2041,6 +2041,188 @@ class HttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Waits for the first of the given requests to be either aborted or completed.
|
||||||
|
Returns the first one in that state, or `null` if the operation was interrupted
|
||||||
|
or reached the given timeout before any completed. (If it returns null even before
|
||||||
|
the timeout, it might be because the user pressed ctrl+c, so you should consider
|
||||||
|
checking if you should cancel the operation. If not, you can simply call it again
|
||||||
|
with the same arguments to start waiting again.)
|
||||||
|
|
||||||
|
You MUST check for null, even if you don't specify a timeout!
|
||||||
|
|
||||||
|
Note that if an individual request times out before any others request, it will
|
||||||
|
return that timed out request, since that counts as completion.
|
||||||
|
|
||||||
|
If the return is not null, you should call `waitForCompletion` on the given request
|
||||||
|
to get the response out. It will not have to wait since it is guaranteed to be
|
||||||
|
finished when returned by this function; that will just give you the cached response.
|
||||||
|
|
||||||
|
(I thought about just having it return the response, but tying a response back to
|
||||||
|
a request is harder than just getting the original request object back and taking
|
||||||
|
the response out of it.)
|
||||||
|
|
||||||
|
Please note: if a request in the set has already completed or been aborted, it will
|
||||||
|
always return the first one it sees upon calling the function. You may wish to remove
|
||||||
|
them from the list before calling the function.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 24, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
HttpRequest waitForFirstToComplete(Duration timeout, HttpRequest[] requests...) {
|
||||||
|
|
||||||
|
foreach(request; requests) {
|
||||||
|
if(request.state == HttpRequest.State.unsent)
|
||||||
|
request.send();
|
||||||
|
else if(request.state == HttpRequest.State.complete)
|
||||||
|
return request;
|
||||||
|
else if(request.state == HttpRequest.State.aborted)
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
if(auto err = HttpRequest.advanceConnections(timeout)) {
|
||||||
|
switch(err) {
|
||||||
|
case 1: return null;
|
||||||
|
case 2: throw new Exception("HttpRequest.advanceConnections returned 2: nothing to do");
|
||||||
|
case 3: return null;
|
||||||
|
default: throw new Exception("HttpRequest.advanceConnections got err " ~ to!string(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(request; requests) {
|
||||||
|
if(request.state == HttpRequest.State.aborted || request.state == HttpRequest.State.complete) {
|
||||||
|
request.waitForCompletion();
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
HttpRequest waitForFirstToComplete(HttpRequest[] requests...) {
|
||||||
|
return waitForFirstToComplete(1.weeks, requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
An input range that runs [waitForFirstToComplete] but only returning each request once.
|
||||||
|
Before you loop over it, you can set some properties to customize behavior.
|
||||||
|
|
||||||
|
If it times out or is interrupted, it will prematurely run empty. You can set the delegate
|
||||||
|
to process this.
|
||||||
|
|
||||||
|
Implementation note: each iteration through the loop does a O(n) check over each item remaining.
|
||||||
|
This shouldn't matter, but if it does become an issue for you, let me know.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 24, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
struct HttpRequestsAsTheyComplete {
|
||||||
|
/++
|
||||||
|
Seeds it with an overall timeout and the initial requests.
|
||||||
|
It will send all the requests before returning, then will process
|
||||||
|
the responses as they come.
|
||||||
|
|
||||||
|
Please note that it modifies the array of requests you pass in! It
|
||||||
|
will keep a reference to it and reorder items on each call of popFront.
|
||||||
|
You might want to pass a duplicate if you have another purpose for your
|
||||||
|
array and don't want to see it shuffled.
|
||||||
|
+/
|
||||||
|
this(Duration timeout, HttpRequest[] requests) {
|
||||||
|
remainingRequests = requests;
|
||||||
|
this.timeout = timeout;
|
||||||
|
popFront();
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
You can set this delegate to decide how to handle an interruption. Returning true
|
||||||
|
from this will keep working. Returning false will terminate the loop.
|
||||||
|
|
||||||
|
If this is null, an interruption will always terminate the loop.
|
||||||
|
|
||||||
|
Note that interruptions can be caused by the garbage collector being triggered by
|
||||||
|
another thread as well as by user action. If you don't set a SIGINT handler, it
|
||||||
|
might be reasonable to always return true here.
|
||||||
|
+/
|
||||||
|
bool delegate() onInterruption;
|
||||||
|
|
||||||
|
private HttpRequest[] remainingRequests;
|
||||||
|
|
||||||
|
/// The timeout you set in the constructor. You can change it if you want.
|
||||||
|
Duration timeout;
|
||||||
|
|
||||||
|
/++
|
||||||
|
Adds another request to the work queue. It is safe to call this from inside the loop
|
||||||
|
as you process other requests.
|
||||||
|
+/
|
||||||
|
void appendRequest(HttpRequest request) {
|
||||||
|
remainingRequests ~= request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
If the loop exited, it might be due to an interruption or a time out. If you like, you
|
||||||
|
can call this to pick up the work again,
|
||||||
|
|
||||||
|
If it returns `false`, the work is indeed all finished and you should not re-enter the loop.
|
||||||
|
|
||||||
|
---
|
||||||
|
auto range = HttpRequestsAsTheyComplete(10.seconds, your_requests);
|
||||||
|
process_loop: foreach(req; range) {
|
||||||
|
// process req
|
||||||
|
}
|
||||||
|
// make sure we weren't interrupted because the user requested we cancel!
|
||||||
|
// but then try to re-enter the range if possible
|
||||||
|
if(!user_quit && range.reenter()) {
|
||||||
|
// there's still something unprocessed in there
|
||||||
|
// range.reenter returning true means it is no longer
|
||||||
|
// empty, so we should try to loop over it again
|
||||||
|
goto process_loop; // re-enter the loop
|
||||||
|
}
|
||||||
|
---
|
||||||
|
+/
|
||||||
|
bool reenter() {
|
||||||
|
if(remainingRequests.length == 0)
|
||||||
|
return false;
|
||||||
|
empty = false;
|
||||||
|
popFront();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Standard range primitives. I reserve the right to change the variables to read-only properties in the future without notice.
|
||||||
|
HttpRequest front;
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
bool empty;
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
void popFront() {
|
||||||
|
resume:
|
||||||
|
if(remainingRequests.length == 0) {
|
||||||
|
empty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
front = waitForFirstToComplete(timeout, remainingRequests);
|
||||||
|
|
||||||
|
if(front is null) {
|
||||||
|
if(onInterruption) {
|
||||||
|
if(onInterruption())
|
||||||
|
goto resume;
|
||||||
|
}
|
||||||
|
empty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach(idx, req; remainingRequests) {
|
||||||
|
if(req is front) {
|
||||||
|
remainingRequests[idx] = remainingRequests[$ - 1];
|
||||||
|
remainingRequests = remainingRequests[0 .. $ - 1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
struct HttpRequestParameters {
|
struct HttpRequestParameters {
|
||||||
// FIXME: implement these
|
// FIXME: implement these
|
||||||
|
|
|
||||||
282
minigui.d
282
minigui.d
|
|
@ -2138,6 +2138,7 @@ abstract class ComboboxBase : Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SelectionChangedEvent : Event {
|
static class SelectionChangedEvent : Event {
|
||||||
|
enum EventString = "change";
|
||||||
this(Widget target, int iv, string sv) {
|
this(Widget target, int iv, string sv) {
|
||||||
super("change", target);
|
super("change", target);
|
||||||
this.iv = iv;
|
this.iv = iv;
|
||||||
|
|
@ -6148,14 +6149,48 @@ class InlineBlockLayout : Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
A tab widget is a set of clickable tab buttons followed by a content area.
|
A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
|
||||||
|
to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
|
||||||
|
the [TabWidget] will automatically change pages of child widgets.
|
||||||
|
|
||||||
|
This allows you to react to it however you see fit rather than having to
|
||||||
|
be tied to just the new sets of child widgets.
|
||||||
|
|
||||||
Tabs can change existing content or can be new pages.
|
It sends the message in the form of `this.emitCommand!"changetab"();`.
|
||||||
|
|
||||||
When the user picks a different tab, a `change` message is generated.
|
History:
|
||||||
|
Added December 24, 2021 (dub v10.5)
|
||||||
+/
|
+/
|
||||||
class TabWidget : Widget {
|
class TabMessageWidget : Widget {
|
||||||
|
|
||||||
|
protected void tabIndexClicked(int item) {
|
||||||
|
this.emitCommand!"changetab"();
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Adds the a new tab to the control with the given title.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The index of the newly added tab. You will need to know
|
||||||
|
this index to refer to it later and to know which tab to
|
||||||
|
change to when you get a changetab message.
|
||||||
|
+/
|
||||||
|
int addTab(string title, int pos = int.max) {
|
||||||
|
version(win32_widgets) {
|
||||||
|
TCITEM item;
|
||||||
|
item.mask = TCIF_TEXT;
|
||||||
|
WCharzBuffer buf = WCharzBuffer(title);
|
||||||
|
item.pszText = buf.ptr;
|
||||||
|
return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
|
||||||
|
} else version(custom_widgets) {
|
||||||
|
tabs ~= title;
|
||||||
|
return cast(int) tabs.length - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
version(custom_widgets)
|
||||||
|
string[] tabs;
|
||||||
|
|
||||||
this(Widget parent) {
|
this(Widget parent) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
|
|
@ -6204,27 +6239,118 @@ class TabWidget : Widget {
|
||||||
switch(code) {
|
switch(code) {
|
||||||
case TCN_SELCHANGE:
|
case TCN_SELCHANGE:
|
||||||
auto sel = TabCtrl_GetCurSel(hwnd);
|
auto sel = TabCtrl_GetCurSel(hwnd);
|
||||||
showOnly(sel);
|
tabIndexClicked(sel);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version(custom_widgets) {
|
||||||
|
private int currentTab_;
|
||||||
|
private int tabBarHeight() { return defaultLineHeight; }
|
||||||
|
int tabWidth = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
version(win32_widgets)
|
||||||
|
override void paint(WidgetPainter painter) {}
|
||||||
|
|
||||||
|
version(custom_widgets)
|
||||||
|
override void paint(WidgetPainter painter) {
|
||||||
|
auto cs = getComputedStyle();
|
||||||
|
|
||||||
|
draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
|
||||||
|
|
||||||
|
int posX = 0;
|
||||||
|
// FIXME: addTab broken here
|
||||||
|
foreach(idx, child; children) {
|
||||||
|
if(auto twp = cast(TabWidgetPage) child) {
|
||||||
|
auto isCurrent = idx == getCurrentTab();
|
||||||
|
|
||||||
|
painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
|
||||||
|
|
||||||
|
draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
|
||||||
|
painter.outlineColor = cs.foregroundColor;
|
||||||
|
painter.drawText(Point(posX + 4, 2), twp.title);
|
||||||
|
|
||||||
|
if(isCurrent) {
|
||||||
|
painter.outlineColor = cs.windowBackgroundColor;
|
||||||
|
painter.fillColor = Color.transparent;
|
||||||
|
painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
|
||||||
|
painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
|
||||||
|
|
||||||
|
painter.outlineColor = Color.white;
|
||||||
|
painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
|
||||||
|
painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
|
||||||
|
painter.outlineColor = cs.activeTabColor;
|
||||||
|
painter.drawPixel(Point(posX, tabBarHeight - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
posX += tabWidth - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@scriptable
|
||||||
|
void setCurrentTab(int item) {
|
||||||
|
version(win32_widgets)
|
||||||
|
TabCtrl_SetCurSel(hwnd, item);
|
||||||
|
else version(custom_widgets)
|
||||||
|
currentTab_ = item;
|
||||||
|
else static assert(0);
|
||||||
|
|
||||||
|
tabIndexClicked(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@scriptable
|
||||||
|
int getCurrentTab() {
|
||||||
|
version(win32_widgets)
|
||||||
|
return TabCtrl_GetCurSel(hwnd);
|
||||||
|
else version(custom_widgets)
|
||||||
|
return currentTab_; // FIXME
|
||||||
|
else static assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@scriptable
|
||||||
|
void removeTab(int item) {
|
||||||
|
if(item && item == getCurrentTab())
|
||||||
|
setCurrentTab(item - 1);
|
||||||
|
|
||||||
|
version(win32_widgets) {
|
||||||
|
TabCtrl_DeleteItem(hwnd, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int a = item; a < children.length - 1; a++)
|
||||||
|
this._children[a] = this._children[a + 1];
|
||||||
|
this._children = this._children[0 .. $-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/++
|
||||||
|
A tab widget is a set of clickable tab buttons followed by a content area.
|
||||||
|
|
||||||
|
|
||||||
|
Tabs can change existing content or can be new pages.
|
||||||
|
|
||||||
|
When the user picks a different tab, a `change` message is generated.
|
||||||
|
+/
|
||||||
|
class TabWidget : TabMessageWidget {
|
||||||
|
this(Widget parent) {
|
||||||
|
super(parent);
|
||||||
|
}
|
||||||
|
|
||||||
override void addChild(Widget child, int pos = int.max) {
|
override void addChild(Widget child, int pos = int.max) {
|
||||||
if(auto twp = cast(TabWidgetPage) child) {
|
if(auto twp = cast(TabWidgetPage) child) {
|
||||||
super.addChild(child, pos);
|
super.addChild(child, pos);
|
||||||
if(pos == int.max)
|
if(pos == int.max)
|
||||||
pos = cast(int) this.children.length - 1;
|
pos = cast(int) this.children.length - 1;
|
||||||
|
|
||||||
version(win32_widgets) {
|
super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
|
||||||
TCITEM item;
|
|
||||||
item.mask = TCIF_TEXT;
|
|
||||||
WCharzBuffer buf = WCharzBuffer(twp.title);
|
|
||||||
item.pszText = buf.ptr;
|
|
||||||
SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
|
|
||||||
} else version(custom_widgets) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if(pos != getCurrentTab) {
|
if(pos != getCurrentTab) {
|
||||||
child.showing = false;
|
child.showing = false;
|
||||||
|
|
@ -6266,94 +6392,50 @@ class TabWidget : Widget {
|
||||||
} else static assert(0);
|
} else static assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
version(custom_widgets) {
|
// FIXME: add tab icons at some point, Windows supports them
|
||||||
private int currentTab_;
|
/++
|
||||||
private int tabBarHeight() { return defaultLineHeight; }
|
Adds a page and its associated tab with the given label to the widget.
|
||||||
int tabWidth = 80;
|
|
||||||
}
|
|
||||||
|
|
||||||
version(win32_widgets)
|
Returns:
|
||||||
override void paint(WidgetPainter painter) {}
|
The added page object, to which you can add other widgets.
|
||||||
|
+/
|
||||||
version(custom_widgets)
|
|
||||||
override void paint(WidgetPainter painter) {
|
|
||||||
auto cs = getComputedStyle();
|
|
||||||
|
|
||||||
draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
|
|
||||||
|
|
||||||
int posX = 0;
|
|
||||||
foreach(idx, child; children) {
|
|
||||||
if(auto twp = cast(TabWidgetPage) child) {
|
|
||||||
auto isCurrent = idx == getCurrentTab();
|
|
||||||
|
|
||||||
painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
|
|
||||||
|
|
||||||
draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
|
|
||||||
painter.outlineColor = cs.foregroundColor;
|
|
||||||
painter.drawText(Point(posX + 4, 2), twp.title);
|
|
||||||
|
|
||||||
if(isCurrent) {
|
|
||||||
painter.outlineColor = cs.windowBackgroundColor;
|
|
||||||
painter.fillColor = Color.transparent;
|
|
||||||
painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
|
|
||||||
painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
|
|
||||||
|
|
||||||
painter.outlineColor = Color.white;
|
|
||||||
painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
|
|
||||||
painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
|
|
||||||
painter.outlineColor = cs.activeTabColor;
|
|
||||||
painter.drawPixel(Point(posX, tabBarHeight - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
posX += tabWidth - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@scriptable
|
|
||||||
void setCurrentTab(int item) {
|
|
||||||
version(win32_widgets)
|
|
||||||
TabCtrl_SetCurSel(hwnd, item);
|
|
||||||
else version(custom_widgets)
|
|
||||||
currentTab_ = item;
|
|
||||||
else static assert(0);
|
|
||||||
|
|
||||||
showOnly(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@scriptable
|
|
||||||
int getCurrentTab() {
|
|
||||||
version(win32_widgets)
|
|
||||||
return TabCtrl_GetCurSel(hwnd);
|
|
||||||
else version(custom_widgets)
|
|
||||||
return currentTab_; // FIXME
|
|
||||||
else static assert(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@scriptable
|
|
||||||
void removeTab(int item) {
|
|
||||||
if(item && item == getCurrentTab())
|
|
||||||
setCurrentTab(item - 1);
|
|
||||||
|
|
||||||
version(win32_widgets) {
|
|
||||||
TabCtrl_DeleteItem(hwnd, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int a = item; a < children.length - 1; a++)
|
|
||||||
this._children[a] = this._children[a + 1];
|
|
||||||
this._children = this._children[0 .. $-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@scriptable
|
@scriptable
|
||||||
TabWidgetPage addPage(string title) {
|
TabWidgetPage addPage(string title) {
|
||||||
return new TabWidgetPage(title, this);
|
return new TabWidgetPage(title, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOnly(int item) {
|
/++
|
||||||
|
Gets the page at the given tab index, or `null` if the index is bad.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 24, 2021.
|
||||||
|
+/
|
||||||
|
TabWidgetPage getPage(int index) {
|
||||||
|
if(index < this.children.length)
|
||||||
|
return null;
|
||||||
|
return cast(TabWidgetPage) this.children[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
While you can still use the addTab from the parent class,
|
||||||
|
*strongly* recommend you use [addPage] insteaad.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 24, 2021 to fulful the interface
|
||||||
|
requirement that came from adding [TabMessageWidget].
|
||||||
|
|
||||||
|
You should not use it though since the [addPage] function
|
||||||
|
is much easier to use here.
|
||||||
|
+/
|
||||||
|
override int addTab(string title, int pos = int.max) {
|
||||||
|
auto p = addPage(title);
|
||||||
|
foreach(idx, child; this.children)
|
||||||
|
if(child is p)
|
||||||
|
return cast(int) idx;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void tabIndexClicked(int item) {
|
||||||
foreach(idx, child; children) {
|
foreach(idx, child; children) {
|
||||||
child.showing(false, false); // batch the recalculates for the end
|
child.showing(false, false); // batch the recalculates for the end
|
||||||
}
|
}
|
||||||
|
|
@ -6376,6 +6458,7 @@ class TabWidget : Widget {
|
||||||
this.redraw();
|
this.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
|
@ -6911,6 +6994,10 @@ class ScrollMessageWidget : Widget {
|
||||||
|
|
||||||
///
|
///
|
||||||
void setViewableArea(int width, int height) {
|
void setViewableArea(int width, int height) {
|
||||||
|
|
||||||
|
if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
|
||||||
|
return; // no need to do what is already done
|
||||||
|
|
||||||
hsb.setViewableArea(width);
|
hsb.setViewableArea(width);
|
||||||
vsb.setViewableArea(height);
|
vsb.setViewableArea(height);
|
||||||
|
|
||||||
|
|
@ -8485,11 +8572,6 @@ private class TableViewWidgetInner : Widget {
|
||||||
// given struct / array / number / string / etc, make it viewable and editable
|
// given struct / array / number / string / etc, make it viewable and editable
|
||||||
class DataViewerWidget : Widget {
|
class DataViewerWidget : Widget {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is just the tab list with no associated page
|
|
||||||
class TabMessageWidget : Widget {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+/
|
+/
|
||||||
|
|
||||||
|
|
|
||||||
10
png.d
10
png.d
|
|
@ -9,6 +9,16 @@ MemoryImage readPng(string filename) {
|
||||||
return imageFromPng(readPng(cast(ubyte[]) read(filename)));
|
return imageFromPng(readPng(cast(ubyte[]) read(filename)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
Easily reads a png from a data array into a MemoryImage.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 29, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
MemoryImage readPngFromBytes(const(ubyte)[] bytes) {
|
||||||
|
return imageFromPng(readPng(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
/// Saves a MemoryImage to a png. See also: writeImageToPngFile which uses memory a little more efficiently
|
/// Saves a MemoryImage to a png. See also: writeImageToPngFile which uses memory a little more efficiently
|
||||||
void writePng(string filename, MemoryImage mi) {
|
void writePng(string filename, MemoryImage mi) {
|
||||||
// FIXME: it would be nice to write the file lazily so we don't have so many intermediate buffers here
|
// FIXME: it would be nice to write the file lazily so we don't have so many intermediate buffers here
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// FIXME: add a query devices thing
|
||||||
/**
|
/**
|
||||||
The purpose of this module is to provide audio functions for
|
The purpose of this module is to provide audio functions for
|
||||||
things like playback, capture, and volume on both Windows
|
things like playback, capture, and volume on both Windows
|
||||||
|
|
|
||||||
|
|
@ -1527,9 +1527,8 @@ float[2] getDpi() {
|
||||||
char* resourceString = XResourceManagerString(display);
|
char* resourceString = XResourceManagerString(display);
|
||||||
XrmInitialize();
|
XrmInitialize();
|
||||||
|
|
||||||
auto db = XrmGetStringDatabase(resourceString);
|
|
||||||
|
|
||||||
if (resourceString) {
|
if (resourceString) {
|
||||||
|
auto db = XrmGetStringDatabase(resourceString);
|
||||||
XrmValue value;
|
XrmValue value;
|
||||||
char* type;
|
char* type;
|
||||||
if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
|
if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) {
|
||||||
|
|
@ -9792,7 +9791,7 @@ public bool thisThreadRunningGui() {
|
||||||
void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
|
void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @trusted {
|
||||||
try {
|
try {
|
||||||
version(Windows) {
|
version(Windows) {
|
||||||
import core.sys.windows.windows;
|
import core.sys.windows.wincon;
|
||||||
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
||||||
AllocConsole();
|
AllocConsole();
|
||||||
const(char)* fn = "CONOUT$";
|
const(char)* fn = "CONOUT$";
|
||||||
|
|
@ -21249,7 +21248,7 @@ private mixin template DynamicLoad(Iface, string library, int majorVersion, alia
|
||||||
return dlsym(l, name);
|
return dlsym(l, name);
|
||||||
}
|
}
|
||||||
} else version(Windows) {
|
} else version(Windows) {
|
||||||
import core.sys.windows.windows;
|
import core.sys.windows.winbase;
|
||||||
libHandle = LoadLibrary(library ~ ".dll");
|
libHandle = LoadLibrary(library ~ ".dll");
|
||||||
static void* loadsym(void* l, const char* name) {
|
static void* loadsym(void* l, const char* name) {
|
||||||
import core.stdc.stdlib;
|
import core.stdc.stdlib;
|
||||||
|
|
@ -21274,7 +21273,7 @@ private mixin template DynamicLoad(Iface, string library, int majorVersion, alia
|
||||||
import core.sys.posix.dlfcn;
|
import core.sys.posix.dlfcn;
|
||||||
dlclose(libHandle);
|
dlclose(libHandle);
|
||||||
} else version(Windows) {
|
} else version(Windows) {
|
||||||
import core.sys.windows.windows;
|
import core.sys.windows.winbase;
|
||||||
FreeLibrary(libHandle);
|
FreeLibrary(libHandle);
|
||||||
}
|
}
|
||||||
foreach(name; __traits(derivedMembers, Iface))
|
foreach(name; __traits(derivedMembers, Iface))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue