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 {
|
||||
PngHeader header;
|
||||
|
|
@ -427,7 +427,10 @@ class ApngAnimation {
|
|||
ApngFrame[] frames;
|
||||
// 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() {
|
||||
|
||||
}
|
||||
|
|
@ -467,6 +470,50 @@ class ApngAnimation {
|
|||
ApngRenderBuffer renderer() {
|
||||
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
|
||||
(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:
|
||||
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 header = PngHeader.fromChunk(png.chunks[0]);
|
||||
|
||||
auto obj = new ApngAnimation();
|
||||
ApngAnimation obj;
|
||||
if(factory)
|
||||
obj = factory();
|
||||
else
|
||||
obj = new ApngAnimation();
|
||||
|
||||
obj.header = header;
|
||||
|
||||
if(header.type == 3) {
|
||||
|
|
@ -670,7 +728,7 @@ ApngAnimation readApng(in ubyte[] data, bool strictApng = false) {
|
|||
obj.frames[frameNumber - 1].compressedDatastream ~= chunk.payload[offset .. $];
|
||||
break;
|
||||
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) {
|
||||
|
||||
|
|
|
|||
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.
|
||||
} 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.");
|
||||
}
|
||||
|
||||
version(Windows)
|
||||
__gshared HANDLE iocp;
|
||||
|
||||
version(cgi_use_fiber) {
|
||||
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 {
|
||||
// FIXME: implement these
|
||||
|
|
|
|||
282
minigui.d
282
minigui.d
|
|
@ -2138,6 +2138,7 @@ abstract class ComboboxBase : Widget {
|
|||
}
|
||||
|
||||
static class SelectionChangedEvent : Event {
|
||||
enum EventString = "change";
|
||||
this(Widget target, int iv, string sv) {
|
||||
super("change", target);
|
||||
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) {
|
||||
super(parent);
|
||||
|
||||
|
|
@ -6204,27 +6239,118 @@ class TabWidget : Widget {
|
|||
switch(code) {
|
||||
case TCN_SELCHANGE:
|
||||
auto sel = TabCtrl_GetCurSel(hwnd);
|
||||
showOnly(sel);
|
||||
tabIndexClicked(sel);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
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) {
|
||||
if(auto twp = cast(TabWidgetPage) child) {
|
||||
super.addChild(child, pos);
|
||||
if(pos == int.max)
|
||||
pos = cast(int) this.children.length - 1;
|
||||
|
||||
version(win32_widgets) {
|
||||
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) {
|
||||
}
|
||||
super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
|
||||
|
||||
if(pos != getCurrentTab) {
|
||||
child.showing = false;
|
||||
|
|
@ -6266,94 +6392,50 @@ class TabWidget : Widget {
|
|||
} else static assert(0);
|
||||
}
|
||||
|
||||
version(custom_widgets) {
|
||||
private int currentTab_;
|
||||
private int tabBarHeight() { return defaultLineHeight; }
|
||||
int tabWidth = 80;
|
||||
}
|
||||
// FIXME: add tab icons at some point, Windows supports them
|
||||
/++
|
||||
Adds a page and its associated tab with the given label to the widget.
|
||||
|
||||
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;
|
||||
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];
|
||||
}
|
||||
|
||||
///
|
||||
Returns:
|
||||
The added page object, to which you can add other widgets.
|
||||
+/
|
||||
@scriptable
|
||||
TabWidgetPage addPage(string title) {
|
||||
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) {
|
||||
child.showing(false, false); // batch the recalculates for the end
|
||||
}
|
||||
|
|
@ -6376,6 +6458,7 @@ class TabWidget : Widget {
|
|||
this.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/++
|
||||
|
|
@ -6911,6 +6994,10 @@ class ScrollMessageWidget : Widget {
|
|||
|
||||
///
|
||||
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);
|
||||
vsb.setViewableArea(height);
|
||||
|
||||
|
|
@ -8485,11 +8572,6 @@ private class TableViewWidgetInner : Widget {
|
|||
// given struct / array / number / string / etc, make it viewable and editable
|
||||
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)));
|
||||
}
|
||||
|
||||
/++
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// FIXME: add a query devices thing
|
||||
/**
|
||||
The purpose of this module is to provide audio functions for
|
||||
things like playback, capture, and volume on both Windows
|
||||
|
|
|
|||
|
|
@ -1527,9 +1527,8 @@ float[2] getDpi() {
|
|||
char* resourceString = XResourceManagerString(display);
|
||||
XrmInitialize();
|
||||
|
||||
auto db = XrmGetStringDatabase(resourceString);
|
||||
|
||||
if (resourceString) {
|
||||
auto db = XrmGetStringDatabase(resourceString);
|
||||
XrmValue value;
|
||||
char* type;
|
||||
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 {
|
||||
try {
|
||||
version(Windows) {
|
||||
import core.sys.windows.windows;
|
||||
import core.sys.windows.wincon;
|
||||
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
AllocConsole();
|
||||
const(char)* fn = "CONOUT$";
|
||||
|
|
@ -21249,7 +21248,7 @@ private mixin template DynamicLoad(Iface, string library, int majorVersion, alia
|
|||
return dlsym(l, name);
|
||||
}
|
||||
} else version(Windows) {
|
||||
import core.sys.windows.windows;
|
||||
import core.sys.windows.winbase;
|
||||
libHandle = LoadLibrary(library ~ ".dll");
|
||||
static void* loadsym(void* l, const char* name) {
|
||||
import core.stdc.stdlib;
|
||||
|
|
@ -21274,7 +21273,7 @@ private mixin template DynamicLoad(Iface, string library, int majorVersion, alia
|
|||
import core.sys.posix.dlfcn;
|
||||
dlclose(libHandle);
|
||||
} else version(Windows) {
|
||||
import core.sys.windows.windows;
|
||||
import core.sys.windows.winbase;
|
||||
FreeLibrary(libHandle);
|
||||
}
|
||||
foreach(name; __traits(derivedMembers, Iface))
|
||||
|
|
|
|||
Loading…
Reference in New Issue