This commit is contained in:
Adam D. Ruppe 2020-04-11 09:57:22 -04:00
parent d52eca78a2
commit 1fc4f4263c
3 changed files with 178 additions and 12 deletions

27
color.d
View File

@ -258,7 +258,12 @@ struct Color {
throw new Exception("Unknown color " ~ s);
}
/// Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
/++
Reads a CSS style string to get the color. Understands #rrggbb, rgba(), hsl(), and rrggbbaa
History:
The short-form hex string parsing (`#fff`) was added on April 10, 2020. (v7.2.0)
+/
static Color fromString(scope const(char)[] s) {
s = s.stripInternal();
@ -334,6 +339,17 @@ struct Color {
if(s.length && s[0] == '#')
s = s[1 .. $];
// support short form #fff for example
if(s.length == 3 || s.length == 4) {
string n;
n.reserve(8);
foreach(ch; s) {
n ~= ch;
n ~= ch;
}
s = n;
}
// not a built in... do it as a hex string
if(s.length >= 2) {
c.r = fromHexInternal(s[0 .. 2]);
@ -417,6 +433,15 @@ struct Color {
}
}
unittest {
Color c = Color.fromString("#fff");
assert(c == Color.white);
assert(c == Color.fromString("#ffffff"));
c = Color.fromString("#f0f");
assert(c == Color.fromString("rgb(255, 0, 255)"));
}
nothrow @safe
private string toHexInternal(ubyte b) {
string s;

View File

@ -105,6 +105,14 @@
See the examples and topics list below to learn more.
$(WARNING
There should only be one GUI thread per application,
and all windows should be created in it and your
event loop should run there.
To do otherwise is undefined behavior and has no
cross platform guarantees.
)
$(H2 About this documentation)
@ -1339,6 +1347,8 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon {
parent = the parent window, if applicable
+/
this(int width = 640, int height = 480, string title = null, OpenGlOptions opengl = OpenGlOptions.no, Resizability resizable = Resizability.automaticallyScaleIfPossible, WindowTypes windowType = WindowTypes.normal, int customizationFlags = WindowFlags.normal, SimpleWindow parent = null) {
claimGuiThread();
version(sdpy_thread_checks) assert(thisIsGuiThread);
this._width = width;
this._height = height;
this.openglMode = opengl;
@ -2383,7 +2393,7 @@ private:
}
// wake up event processor
bool eventWakeUp () {
static bool eventWakeUp () {
version(X11) {
import core.sys.posix.unistd : write;
ulong n = 1;
@ -2529,6 +2539,31 @@ private:
if (sw is null || sw.closed) continue;
sw.processCustomEvents();
}
// run pending [runInGuiThread] delegates
more:
RunQueueMember* next;
synchronized(runInGuiThreadLock) {
if(runInGuiThreadQueue.length) {
next = runInGuiThreadQueue[0];
runInGuiThreadQueue = runInGuiThreadQueue[1 .. $];
} else {
next = null;
}
}
if(next) {
try {
next.dg();
next.thrown = null;
} catch(Throwable t) {
next.thrown = t;
}
next.signal.notify();
goto more;
}
}
// 0: infinite (i.e. no scheduled events in queue)
@ -2704,18 +2739,24 @@ struct EventLoop {
return EventLoop(0, null);
}
__gshared static Object monitor = new Object(); // deliberate CTFE usage here fyi
/// Construct an application-global event loop for yourself
/// See_Also: [SimpleWindow.setEventHandlers]
this(long pulseTimeout, void delegate() handlePulse) {
if(impl is null)
impl = new EventLoopImpl(pulseTimeout, handlePulse);
else {
if(pulseTimeout) {
impl.pulseTimeout = pulseTimeout;
impl.handlePulse = handlePulse;
synchronized(monitor) {
if(impl is null) {
claimGuiThread();
version(sdpy_thread_checks) assert(thisIsGuiThread);
impl = new EventLoopImpl(pulseTimeout, handlePulse);
} else {
if(pulseTimeout) {
impl.pulseTimeout = pulseTimeout;
impl.handlePulse = handlePulse;
}
}
impl.refcount++;
}
impl.refcount++;
}
~this() {
@ -2752,7 +2793,7 @@ struct EventLoop {
return impl.signalHandler;
}
static EventLoopImpl* impl;
__gshared static EventLoopImpl* impl;
}
version(linux)
@ -6993,6 +7034,81 @@ void flushGui() {
}
}
/++
Runs the given code in the GUI thread when its event loop
is available, blocking until it completes. This allows you
to create and manipulate windows from another thread without
invoking undefined behavior.
If this is the gui thread, it runs the code immediately.
If no gui thread exists yet, the current thread is assumed
to be it. Attempting to create windows or run the event loop
in any other thread will cause an assertion failure.
$(TIP
Did you know you can use UFCS on delegate literals?
() {
// code here
}.runInGuiThread;
)
History:
Added April 10, 2020 (v7.2.0)
+/
void runInGuiThread(scope void delegate() dg) @trusted {
claimGuiThread();
if(thisIsGuiThread) {
dg();
return;
}
import core.sync.semaphore;
static Semaphore sc;
if(sc is null)
sc = new Semaphore();
static RunQueueMember* rqm;
if(rqm is null)
rqm = new RunQueueMember;
rqm.dg = cast(typeof(rqm.dg)) dg;
rqm.signal = sc;
rqm.thrown = null;
synchronized(runInGuiThreadLock) {
runInGuiThreadQueue ~= rqm;
}
if(!SimpleWindow.eventWakeUp())
throw new Error("runInGuiThread impossible; eventWakeUp failed");
rqm.signal.wait();
if(rqm.thrown)
throw rqm.thrown;
}
private void claimGuiThread() {
import core.atomic;
if(cas(&guiThreadExists, false, true))
thisIsGuiThread = true;
}
private struct RunQueueMember {
void delegate() dg;
import core.sync.semaphore;
Semaphore signal;
Throwable thrown;
}
private __gshared RunQueueMember*[] runInGuiThreadQueue;
private __gshared Object runInGuiThreadLock = new Object; // intentional CTFE
private bool thisIsGuiThread = false;
private shared bool guiThreadExists = false;
/// Used internal to dispatch events to various classes.
interface CapableOfHandlingNativeEvent {
NativeEventHandler getNativeEventHandler();

View File

@ -1280,6 +1280,9 @@ struct Terminal {
writeln("\n\n<exited>");
setTitle(tew.terminalEmulator.currentTitle ~ " <exited>");
tew.term = null;
if(integratedTerminalEmulatorConfiguration.closeOnExit)
tew.parentWindow.close();
} else
if(terminalInFamily("xterm", "rxvt", "screen", "tmux")) {
writeStringRaw("\033[23;0t"); // restore window title from the stack
@ -5866,7 +5869,17 @@ int approximate16Color(RGB color) {
version(TerminalDirectToEmulator) {
///
/++
Indicates the TerminalDirectToEmulator features
are present. You can check this with `static if`.
$(WARNING
This will cause the [Terminal] constructor to spawn a GUI thread with [arsd.minigui]/[arsd.simpledisplay].
This means you can NOT use those libraries in your
own thing without using the [arsd.simpledisplay.runInGuiThread] helper since otherwise the main thread is inaccessible, since having two different threads creating event loops or windows is undefined behavior with those libraries.
)
+/
enum IntegratedEmulator = true;
/++
@ -5902,11 +5915,23 @@ version(TerminalDirectToEmulator) {
/// ditto
int fontSize = 14;
/// Requested initial terminal size in character cells. You may not actually get exactly this.
/++
Requested initial terminal size in character cells. You may not actually get exactly this.
+/
int initialWidth = 80;
/// ditto
int initialHeight = 40;
/++
If `true`, the window will close automatically when the main thread exits.
Otherwise, the window will remain open so the user can work with output before
it disappears.
History:
Added April 10, 2020 (v7.2.0)
+/
bool closeOnExit = false;
/++
Gives you a chance to modify the window as it is constructed. Intended
to let you add custom menu options.