minigui getting more usable on linux

This commit is contained in:
Adam D. Ruppe 2017-03-31 14:42:25 -04:00
parent fc1cfc1f8a
commit f0ac35c83b
2 changed files with 328 additions and 169 deletions

376
minigui.d
View File

@ -507,31 +507,36 @@ version(win32_widgets) {
int HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
if(auto te = hWnd in Widget.nativeMapping) {
if(iMessage == WM_SETFOCUS) {
auto lol = *te;
while(lol !is null && lol.implicitlyCreated)
lol = lol.parent;
(*te).parentWindow.focusedWidget = lol;
}
try {
if(iMessage == WM_SETFOCUS) {
auto lol = *te;
while(lol !is null && lol.implicitlyCreated)
lol = lol.parent;
lol.focus();
//(*te).parentWindow.focusedWidget = lol;
}
if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
SetBkMode(cast(HDC) wParam, TRANSPARENT);
return cast(typeof(return))
//GetStockObject(NULL_BRUSH);
// this is the window background color...
GetSysColorBrush(COLOR_3DFACE);
}
if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
SetBkMode(cast(HDC) wParam, TRANSPARENT);
return cast(typeof(return))
//GetStockObject(NULL_BRUSH);
// this is the window background color...
GetSysColorBrush(COLOR_3DFACE);
}
auto pos = getChildPositionRelativeToParentOrigin(*te);
lastDefaultPrevented = false;
// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
else {
// it was something we recognized, should only call the window procedure if the default was not prevented
auto pos = getChildPositionRelativeToParentOrigin(*te);
lastDefaultPrevented = false;
// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
else {
// it was something we recognized, should only call the window procedure if the default was not prevented
}
} catch(Exception e) {
assert(0, e.toString());
}
return 0;
}
@ -649,6 +654,10 @@ class Widget {
parent.addChild(this);
}
bool isFocused() {
return parentWindow && parentWindow.focusedWidget is this;
}
bool showing = true;
void show() { showing = true; redraw(); }
void hide() { showing = false; }
@ -695,6 +704,31 @@ class Widget {
return false;
}
void focus() {
assert(parentWindow !is null);
if(parentWindow.focusedWidget is this)
return;
if(parentWindow.focusedWidget) {
// FIXME: more details here? like from and to
auto evt = new Event("blur", parentWindow.focusedWidget);
parentWindow.focusedWidget = null;
evt.sendDirectly();
}
version(win32_widgets) {
if(this.hwnd !is null)
SetFocus(this.hwnd);
}
parentWindow.focusedWidget = this;
auto evt = new Event("focus", this);
evt.sendDirectly();
}
void attachedToWindow(Window w) {}
void addedTo(Widget w) {}
@ -783,6 +817,9 @@ class Widget {
if(!showing) return;
assert(parentWindow !is null);
if(parentWindow.win.closed())
return;
auto ugh = this.parent;
int lox, loy;
while(ugh) {
@ -872,7 +909,7 @@ class Window : Widget {
this(int width = 500, int height = 500, string title = null) {
super(null);
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing);
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow);
this.width = win.width;
this.height = win.height;
this.parentWindow = this;
@ -885,6 +922,15 @@ class Window : Widget {
redraw();
};
win.onFocusChange = (bool getting) {
if(this.focusedWidget) {
auto evt = new Event(getting ? "focus" : "blur", this.focusedWidget);
evt.sendDirectly();
}
auto evt = new Event(getting ? "focus" : "blur", this);
evt.sendDirectly();
};
win.setEventHandlers(
(MouseEvent e) {
dispatchMouseEvent(e);
@ -968,12 +1014,15 @@ class Window : Widget {
if(recipient !is null) {
// import std.stdio; writeln(typeid(recipient));
recipient.focus();
/*
version(win32_widgets) {
if(recipient.hwnd !is null)
SetFocus(recipient.hwnd);
} else {
focusedWidget = recipient;
}
*/
skipNextChar = true;
}
@ -1117,6 +1166,7 @@ class Window : Widget {
void loop() {
recomputeChildLayout();
win.show();
redraw();
win.eventLoop(0);
}
@ -1821,6 +1871,7 @@ class MouseActivatedWidget : Widget {
addEventListener("mouseleave", delegate (Widget _this, Event ev) {
isHovering = false;
isDepressed = false;
redraw();
});
@ -1834,9 +1885,34 @@ class MouseActivatedWidget : Widget {
redraw();
});
defaultEventHandlers["focus"] = delegate (Widget _this, Event ev) {
_this.redraw();
};
defaultEventHandlers["blur"] = delegate (Widget _this, Event ev) {
isDepressed = false;
isHovering = false;
_this.redraw();
};
defaultEventHandlers["keydown"] = delegate (Widget _this, Event ev) {
if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
isDepressed = true;
_this.redraw();
}
};
defaultEventHandlers["keyup"] = delegate (Widget _this, Event ev) {
if(!isDepressed)
return;
isDepressed = false;
_this.redraw();
auto event = new Event("triggered", this);
event.sendDirectly();
};
defaultEventHandlers["click"] = (Widget w, Event ev) {
auto event = new Event("triggered", this);
event.dispatch();
event.sendDirectly();
};
}
}
@ -2012,17 +2088,24 @@ class Button : MouseActivatedWidget {
painter.drawRectangle(Point(0, 0), width, height);
painter.outlineColor = (isHovering && isDepressed) ? Color(128, 128, 128) : Color.white;
painter.outlineColor = (isDepressed) ? Color(128, 128, 128) : Color.white;
painter.drawLine(Point(0, 0), Point(width, 0));
painter.drawLine(Point(0, 0), Point(0, height - 1));
painter.outlineColor = (isHovering && isDepressed) ? Color.white : Color(128, 128, 128);
painter.outlineColor = (isDepressed) ? Color.white : Color(128, 128, 128);
painter.drawLine(Point(width - 1, 1), Point(width - 1, height - 1));
painter.drawLine(Point(1, height - 1), Point(width - 1, height - 1));
painter.outlineColor = Color.black;
painter.drawText(Point(0, 0), label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
if(isFocused()) {
painter.fillColor = Color.transparent;
painter.outlineColor = Color.black;
painter.drawRectangle(Point(2, 2), width - 4, height - 4);
}
};
}
@ -2073,141 +2156,15 @@ class TextLabel : Widget {
}
///
class LineEdit : Widget {
version(win32_widgets)
/// Contains the implementation of text editing
abstract class EditableTextWidget : Widget {
this(Widget parent = null) {
super(parent);
parentWindow = parent.parentWindow;
createWin32Window(this, "edit", "",
0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
}
else
this(Widget parent = null) {
super(parent);
this.paint = (ScreenPainter painter) {
painter.fillColor = Color.white;
painter.drawRectangle(Point(0, 0), width, height);
painter.outlineColor = Color.black;
painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
};
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
this.focus();
};
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
content = content() ~ cast(char) ev.character;
redraw();
};
static if(UsingSimpledisplayX11)
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
//super();
}
string _content;
@property string content() {
version(win32_widgets) {
char[4096] buffer;
// FIXME: GetWindowTextW
// FIXME: GetWindowTextLength
auto l = GetWindowTextA(hwnd, buffer.ptr, buffer.length - 1);
if(l >= 0)
_content = buffer[0 .. l].idup;
}
return _content;
}
@property void content(string s) {
_content = s;
version(win32_widgets)
SetWindowTextA(hwnd, toStringzInternal(s));
else
redraw();
}
void focus() {
assert(parentWindow !is null);
parentWindow.focusedWidget = this;
}
override int minHeight() { return Window.lineHeight; }
override int maxHeight() { return Window.lineHeight; }
override int minHeight() { return Window.lineHeight + 0; } // the +0 is to leave room for the padding
override int maxHeight() { return Window.lineHeight + 0; }
override int widthStretchiness() { return 3; }
}
///
class TextEdit : Widget {
// FIXME
mixin ExperimentalTextComponent;
override int minHeight() { return Window.lineHeight; }
override int heightStretchiness() { return 3; }
override int widthStretchiness() { return 3; }
version(win32_widgets)
this(Widget parent = null) {
super(parent);
parentWindow = parent.parentWindow;
createWin32Window(this, "edit", "",
0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
}
else
this(Widget parent = null) {
super(parent);
textLayout = new TextLayout(Rectangle(0, 0, width, height));
this.paint = (ScreenPainter painter) {
painter.fillColor = Color.white;
painter.drawRectangle(Point(0, 0), width, height);
textLayout.boundingBox = Rectangle(4, 4, width - 8, height - 8);
painter.outlineColor = Color.black;
// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
textLayout.drawInto(painter);
};
caratTimer = new Timer(500, {
if(!parentWindow.win.closed && parentWindow.focusedWidget is this) {
auto painter = this.draw();
painter.pen = Pen(Color.white, 1);
painter.rasterOp = RasterOp.xor;
if(lastClick.element) {
painter.drawLine(
Point(lastClick.element.xOfIndex(lastClick.offset + 1), lastClick.element.boundingBox.top),
Point(lastClick.element.xOfIndex(lastClick.offset + 1), lastClick.element.boundingBox.bottom)
);
} else {
painter.drawLine(
Point(4, 4),
Point(4, 10)
);
}
}
});
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
this.focus();
lastClick = textLayout.identify(ev.clientX, ev.clientY);
};
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
textLayout.addText("" ~ cast(char) ev.character); // FIXME
redraw();
};
static if(UsingSimpledisplayX11)
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
//super();
}
@property string content() {
version(win32_widgets) {
@ -2233,17 +2190,110 @@ class TextEdit : Widget {
}
}
void focus() {
assert(parentWindow !is null);
parentWindow.focusedWidget = this;
}
version(win32_widgets) { /* will do it with Windows calls in the classes */ }
else {
// FIXME
mixin ExperimentalTextComponent;
version(win32_widgets) {
} else {
Timer caratTimer;
TextLayout textLayout;
TextIdentifyResult lastClick;
void setupCustomTextEditing() {
textLayout = new TextLayout(Rectangle(0, 0, width, height));
this.paint = (ScreenPainter painter) {
if(parentWindow.win.closed) return;
painter.fillColor = Color.white;
painter.drawRectangle(Point(0, 0), width, height);
textLayout.boundingBox = Rectangle(4, 0, width - 8, height - 0);
painter.outlineColor = Color.black;
// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
textLayout.caratShowingOnScreen = false;
textLayout.drawInto(painter, !parentWindow.win.closed && parentWindow.focusedWidget is this);
};
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
if(parentWindow.win.closed) return;
this.focus();
textLayout.moveCaratToPixelCoordinates(ev.clientX, ev.clientY);
};
defaultEventHandlers["focus"] = delegate (Widget _this, Event ev) {
if(parentWindow.win.closed) return;
auto painter = this.draw();
textLayout.drawCarat(painter);
if(caratTimer) {
caratTimer.destroy();
caratTimer = null;
}
caratTimer = new Timer(500, {
if(parentWindow.win.closed) {
caratTimer.destroy();
return;
}
if(parentWindow.focusedWidget is this) {
auto painter = this.draw();
textLayout.drawCarat(painter);
} else if(textLayout.caratShowingOnScreen) {
auto painter = this.draw();
textLayout.eraseCarat(painter);
}
});
};
defaultEventHandlers["blur"] = delegate (Widget _this, Event ev) {
if(parentWindow.win.closed) return;
auto painter = this.draw();
textLayout.eraseCarat(painter);
if(caratTimer) {
caratTimer.destroy();
caratTimer = null;
}
};
defaultEventHandlers["char"] = delegate (Widget _this, Event ev) {
textLayout.addText("" ~ cast(char) ev.character); // FIXME
redraw();
};
static if(UsingSimpledisplayX11)
cursor = XCreateFontCursor(XDisplayConnection.get(), 152 /* XC_xterm, a text input thingy */);
}
}
}
///
class LineEdit : EditableTextWidget {
this(Widget parent = null) {
super(parent);
version(win32_widgets) {
parentWindow = parent.parentWindow;
createWin32Window(this, "edit", "",
0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
} else {
setupCustomTextEditing();
}
}
}
///
class TextEdit : EditableTextWidget {
this(Widget parent = null) {
super(parent);
version(win32_widgets) {
parentWindow = parent.parentWindow;
createWin32Window(this, "edit", "",
0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
} else {
setupCustomTextEditing();
}
}
}
@ -2265,12 +2315,14 @@ class MessageBox : Window {
auto button = new Button("OK", this);
button. x = this.width / 2 - button.width / 2;
button.y = height - (button.height + 10);
button.addEventListener(EventType.click, () {
button.addEventListener(EventType.triggered, () {
close();
});
button.registerMovement();
button.focus();
win.show();
redraw();
}

View File

@ -75,7 +75,6 @@
)
Jump_list:
Don't worry, you don't have to read this whole documentation file!
@ -365,6 +364,50 @@
minigui still needs a lot of work to be finished at this time, but it already offers a number of useful classes.
$(H2 Platform-specific tips and tricks)
Windows_tips:
You can add icons or manifest files to your exe using a resource file.
To create a Windows .ico file, use the gimp or something. I'll write a helper
program later.
Create `yourapp.rc`:
```rc
1 ICON filename.ico
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "YourApp.exe.manifest"
```
And `yourapp.exe.manifest`:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="CompanyName.ProductName.YourApplication"
type="win32"
/>
<description>Your application description here.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
```
$(H2 $(ID developer-notes) Developer notes)
I don't have a Mac, so that code isn't maintained. I would like to have a Cocoa
@ -404,7 +447,7 @@
I live in the eastern United States, so I will most likely not be around at night in
that US east timezone.
License: Copyright Adam D. Ruppe, 2011-2016. Released under the Boost Software License.
License: Copyright Adam D. Ruppe, 2011-2017. Released under the Boost Software License.
Building documentation: You may wish to use the `arsd.ddoc` file from my github with
building the documentation for simpledisplay yourself. It will give it a bit more style.
@ -3901,7 +3944,10 @@ void displayImage(Image image, SimpleWindow win = null) {
}
/**
The 2D drawing proxy.
The 2D drawing proxy. You acquire one of these with [SimpleWindow.draw] rather
than constructing it directly. Then, it is reference counted so you can pass it
at around and when the last ref goes out of scope, the buffered drawing activities
are all carried out.
Most functions use the outlineColor instead of taking a color themselves.
@ -3911,6 +3957,8 @@ void displayImage(Image image, SimpleWindow win = null) {
struct ScreenPainter {
SimpleWindow window;
this(SimpleWindow window, NativeWindowHandle handle) {
if(window.closed)
throw new Exception("cannot draw on a closed window");
this.window = window;
if(window.activeScreenPainter !is null) {
impl = window.activeScreenPainter;
@ -3936,8 +3984,6 @@ struct ScreenPainter {
}
}
// @disable this(this) { } // compiler bug? the linker is bitching about it beind defined twice
this(this) {
impl.referenceCount++;
//writeln("refcount ++ ", impl.referenceCount);
@ -6115,7 +6161,7 @@ version(X11) {
auto h = y2 - y;
if(textHeight < h) {
cy += (h - textHeight) / 2;
cy -= lineHeight / 4;
//cy -= lineHeight / 4;
}
}
@ -10419,6 +10465,7 @@ mixin template ExperimentalTextComponent() {
ie.text = arg[lastLineIndex .. $];
blocks[$-1].parts ~= ie;
carat = Carat(this, &(blocks[$-1].parts[$-1]), ie.text.length);
}
}
}
@ -10449,7 +10496,13 @@ mixin template ExperimentalTextComponent() {
return TextIdentifyResult(null, 0);
}
void drawInto(ScreenPainter painter) {
void moveCaratToPixelCoordinates(int x, int y) {
auto result = identify(x, y);
carat.inlineElement = result.element;
carat.offset = result.offset;
}
void drawInto(ScreenPainter painter, bool focused = false) {
auto pos = Point(boundingBox.left, boundingBox.top);
int lastHeight;
@ -10488,6 +10541,60 @@ mixin template ExperimentalTextComponent() {
}
}
if(focused) {
highlightSelection(painter);
drawCarat(painter);
} else {
eraseCarat(painter);
}
}
void highlightSelection(ScreenPainter painter) {
}
int caratLastDrawnX, caratLastDrawnY1, caratLastDrawnY2;
bool caratShowingOnScreen = false;
void drawCarat(ScreenPainter painter) {
int x, y1, y2;
if(carat.inlineElement is null) {
x = boundingBox.left;
y1 = boundingBox.top + 2;
y2 = boundingBox.bottom - 2;
} else {
x = carat.inlineElement.xOfIndex(carat.offset + 1);
y1 = carat.inlineElement.boundingBox.top + 2;
y2 = carat.inlineElement.boundingBox.bottom - 2;
}
if(caratShowingOnScreen && (x != caratLastDrawnX || y1 != caratLastDrawnY1 || y2 != caratLastDrawnY2))
eraseCarat(painter);
painter.pen = Pen(Color.white, 1);
painter.rasterOp = RasterOp.xor;
painter.drawLine(
Point(x, y1),
Point(x, y2)
);
caratShowingOnScreen = !caratShowingOnScreen;
if(caratShowingOnScreen) {
caratLastDrawnX = x;
caratLastDrawnY1 = y1;
caratLastDrawnY2 = y2;
}
}
void eraseCarat(ScreenPainter painter) {
if(!caratShowingOnScreen) return;
painter.pen = Pen(Color.white, 1);
painter.rasterOp = RasterOp.xor;
painter.drawLine(
Point(caratLastDrawnX, caratLastDrawnY1),
Point(caratLastDrawnX, caratLastDrawnY2)
);
caratShowingOnScreen = false;
}
/// Carat movement api