more improvements to input handling and web

This commit is contained in:
Adam D. Ruppe 2022-04-19 12:07:08 -04:00
parent 2501e6e575
commit 7d81f250fe
3 changed files with 358 additions and 44 deletions

185
minigui.d
View File

@ -1925,6 +1925,18 @@ class Widget : ReflectableProperties {
return StyleInformation(this);
}
int focusableWidgets(scope int delegate(Widget) dg) {
foreach(widget; WidgetStream(this)) {
if(widget.tabStop && !widget.hidden) {
int result = dg(widget);
if (result)
return result;
}
}
return 0;
}
// FIXME: I kinda want to hide events from implementation widgets
// so it just catches them all and stops propagation...
// i guess i can do it with a event listener on star.
@ -7084,7 +7096,7 @@ class HorizontalLayout : Layout {
}
version(Windows)
version(win32_widgets)
private
extern(Windows)
LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
@ -7944,6 +7956,12 @@ class Window : Widget {
Window.newWindowCreated(this);
}
version(custom_widgets)
override void defaultEventHandler_click(ClickEvent event) {
if(event.target && event.target.tabStop)
event.target.focus();
}
private static void delegate(Window) newWindowCreated;
version(win32_widgets)
@ -8082,13 +8100,13 @@ class Window : Widget {
}
win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
/+
///+
// for input proxy
auto display = XDisplayConnection.get;
auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask);
XMapWindow(display, inputProxy);
import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
//import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
this.inputProxy = new SimpleWindow(inputProxy);
XEvent lastEvent;
@ -8108,8 +8126,11 @@ class Window : Widget {
if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
auto thing = nw.focusableWindow();
if(thing && thing.window) {
import std.stdio; writeln("sending event ", lastEvent.xkey);
XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
lastEvent.xkey.window = thing.window;
// import std.stdio; writeln("sending event ", lastEvent.xkey);
trapXErrors( {
XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
});
}
}
}
@ -8121,7 +8142,7 @@ class Window : Widget {
},
);
// done
+/
//+/
@ -8133,13 +8154,7 @@ class Window : Widget {
SimpleWindow inputProxy;
private SimpleWindow setRequestedInputFocus() {
// return inputProxy;
if(auto fw = cast(NestedChildWindowWidget) focusedWidget) {
// sdpyPrintDebugString("heaven");
return fw.focusableWindow;
}
return win;
return inputProxy;
}
/// ditto
@ -8173,14 +8188,15 @@ class Window : Widget {
event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
event.dispatch();
return !event.defaultPrevented;
return !event.propagationStopped;
}
// returns true if propagation should continue into nested things.... prolly not a great thing to do.
bool dispatchCharEvent(dchar ch) {
if(focusedWidget) {
auto event = new CharEvent(focusedWidget, ch);
event.dispatch();
return !event.defaultPrevented;
return !event.propagationStopped;
}
return true;
}
@ -8343,18 +8359,29 @@ class Window : Widget {
}
static Widget getFirstFocusable(Widget start) {
if(start.tabStop && !start.hidden)
return start;
if(start is null)
return null;
if(!start.hidden)
foreach(child; start.children) {
auto f = getFirstFocusable(child);
if(f !is null)
return f;
foreach(widget; &start.focusableWidgets) {
return widget;
}
return null;
}
static Widget getLastFocusable(Widget start) {
if(start is null)
return null;
Widget last;
foreach(widget; &start.focusableWidgets) {
last = widget;
}
return last;
}
mixin Emits!ClosingEvent;
mixin Emits!ClosedEvent;
}
@ -10457,9 +10484,10 @@ class Menu : Window {
if(!menuParent.parentWindow.win.closed) {
if(auto maw = cast(MouseActivatedWidget) menuParent) {
maw.setDynamicState(DynamicState.depressed, false);
maw.setDynamicState(DynamicState.hover, false);
maw.redraw();
}
menuParent.parentWindow.win.focus();
// menuParent.parentWindow.win.focus();
}
clickListener.disconnect();
}
@ -14225,6 +14253,117 @@ interface ReflectableProperties {
}
}
private struct Stack(T) {
this(int maxSize) {
internalLength = 0;
arr = initialBuffer[];
}
///.
void push(T t) {
if(internalLength >= arr.length) {
auto oldarr = arr;
if(arr.length < 4096)
arr = new T[arr.length * 2];
else
arr = new T[arr.length + 4096];
arr[0 .. oldarr.length] = oldarr[];
}
arr[internalLength] = t;
internalLength++;
}
///.
T pop() {
assert(internalLength);
internalLength--;
return arr[internalLength];
}
///.
T peek() {
assert(internalLength);
return arr[internalLength - 1];
}
///.
@property bool empty() {
return internalLength ? false : true;
}
///.
private T[] arr;
private size_t internalLength;
private T[64] initialBuffer;
// the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep),
// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
// function thanks to this, and push() was actually one of the slowest individual functions in the code!
}
/// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively.
private struct WidgetStream {
///.
@property Widget front() {
return current.widget;
}
/// Use Widget.tree instead.
this(Widget start) {
current.widget = start;
current.childPosition = -1;
isEmpty = false;
stack = typeof(stack)(0);
}
/*
Handle it
handle its children
*/
///.
void popFront() {
more:
if(isEmpty) return;
// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
current.childPosition++;
if(current.childPosition >= current.widget.children.length) {
if(stack.empty())
isEmpty = true;
else {
current = stack.pop();
goto more;
}
} else {
stack.push(current);
current.widget = current.widget.children[current.childPosition];
current.childPosition = -1;
}
}
///.
@property bool empty() {
return isEmpty;
}
private:
struct Current {
Widget widget;
int childPosition;
}
Current current;
Stack!(Current) stack;
bool isEmpty;
}
/+

View File

@ -360,7 +360,7 @@ class WebViewWidget_CEF : WebViewWidgetBase {
//semaphore = new Semaphore;
assert(CefApp.active);
this(new MiniguiCefClient(openNewWindow), parent);
this(new MiniguiCefClient(openNewWindow), parent, false);
cef_window_info_t window_info;
window_info.parent_window = containerWindow.nativeWindowHandle;
@ -398,7 +398,7 @@ class WebViewWidget_CEF : WebViewWidgetBase {
.destroy(this); // but this is ok to do some memory management cleanup
}
private this(MiniguiCefClient client, Widget parent) {
private this(MiniguiCefClient client, Widget parent, bool isDevTools) {
super(parent);
this.client = client;
@ -407,22 +407,56 @@ class WebViewWidget_CEF : WebViewWidgetBase {
mapping[containerWindow.nativeWindowHandle()] = this;
this.parentWindow.addEventListener((FocusEvent fe) {
if(!browserHandle) return;
//browserHandle.get_host.set_focus(true);
executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
this.addEventListener(delegate(KeyDownEvent ke) {
if(ke.key == Key.Tab)
ke.preventDefault();
});
this.parentWindow.addEventListener((BlurEvent be) {
this.addEventListener((FocusEvent fe) {
if(!browserHandle) return;
executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
XFocusChangeEvent ev;
ev.type = arsd.simpledisplay.EventType.FocusIn;
ev.display = XDisplayConnection.get;
ev.window = ozone;
ev.mode = NotifyModes.NotifyNormal;
ev.detail = NotifyDetail.NotifyVirtual;
trapXErrors( {
XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
});
// this also works if the message is buggy and it avoids weirdness from raising window etc
//executeJavascript("if(window.__arsdPreviouslyFocusedNode) window.__arsdPreviouslyFocusedNode.focus(); window.dispatchEvent(new FocusEvent(\"focus\"));");
});
this.addEventListener((BlurEvent be) {
if(!browserHandle) return;
XFocusChangeEvent ev;
ev.type = arsd.simpledisplay.EventType.FocusOut;
ev.display = XDisplayConnection.get;
ev.window = ozone;
ev.mode = NotifyModes.NotifyNormal;
ev.detail = NotifyDetail.NotifyNonlinearVirtual;
trapXErrors( {
XSendEvent(XDisplayConnection.get, ozone, false, 0, cast(XEvent*) &ev);
});
//executeJavascript("if(document.activeElement) { window.__arsdPreviouslyFocusedNode = document.activeElement; document.activeElement.blur(); } window.dispatchEvent(new FocusEvent(\"blur\"));");
});
bool closeAttempted = false;
if(isDevTools)
this.parentWindow.addEventListener((scope ClosingEvent ce) {
this.parentWindow.hide();
ce.preventDefault();
});
else
this.parentWindow.addEventListener((scope ClosingEvent ce) {
if(devTools)
devTools.close();
if(!closeAttempted && browserHandle) {
browserHandle.get_host.close_browser(true);
ce.preventDefault();
@ -450,6 +484,7 @@ class WebViewWidget_CEF : WebViewWidgetBase {
}
private NativeWindowHandle browserWindow;
private NativeWindowHandle ozone;
private RC!cef_browser_t browserHandle;
private static WebViewWidget[NativeWindowHandle] mapping;
@ -475,9 +510,37 @@ class WebViewWidget_CEF : WebViewWidgetBase {
browserHandle.get_main_frame.execute_java_script(&c, &u, line);
}
private Window devTools;
override void showDevTools() {
if(!browserHandle) return;
browserHandle.get_host.show_dev_tools(null /* window info */, client.passable, null /* settings */, null /* inspect element at coordinates */);
if(devTools is null) {
auto host = browserHandle.get_host;
if(host.has_dev_tools()) {
host.close_dev_tools();
return;
}
cef_window_info_t windowinfo;
version(linux) {
auto sw = new Window("DevTools");
//sw.win.beingOpenKeepsAppOpen = false;
devTools = sw;
auto wv = new WebViewWidget_CEF(client, sw, true);
sw.show();
windowinfo.parent_window = wv.containerWindow.nativeWindowHandle;
}
host.show_dev_tools(&windowinfo, client.passable, null /* settings */, null /* inspect element at coordinates */);
} else {
if(devTools.hidden)
devTools.show();
else
devTools.hide();
}
}
// FYI the cef browser host also allows things like custom spelling dictionaries and getting navigation entries.
@ -534,7 +597,7 @@ version(cef) {
cef_dictionary_value_t** extra_info,
int* no_javascript_access
) {
sdpyPrintDebugString("on_before_popup");
if(this.client.openNewWindow is null)
return 1; // new windows disabled
@ -551,7 +614,7 @@ version(cef) {
scope WebViewWidget delegate(Widget, BrowserSettings) accept = (parent, passed_settings) {
ret = 0;
if(parent !is null) {
auto widget = new WebViewWidget_CEF(this.client, parent);
auto widget = new WebViewWidget_CEF(this.client, parent, false);
(*windowInfo).parent_window = widget.containerWindow.nativeWindowHandle;
passed_settings.set(browser_settings);
@ -589,10 +652,13 @@ version(cef) {
import arsd.simpledisplay : Window;
Window root;
Window parent;
Window ozone;
uint c = 0;
auto display = XDisplayConnection.get;
Window* children;
XQueryTree(display, handle, &root, &parent, &children, &c);
if(c == 1)
ozone = children[0];
XFree(children);
} else static assert(0);
@ -600,8 +666,9 @@ version(cef) {
auto wv = *wvp;
wv.browserWindow = handle;
wv.browserHandle = RC!cef_browser_t(ptr);
wv.ozone = ozone ? ozone : handle;
wv.browserWindowWrapped = new SimpleWindow(wv.browserWindow);
wv.browserWindowWrapped = new SimpleWindow(wv.ozone);
/+
XSelectInput(XDisplayConnection.get, handle, EventMask.FocusChangeMask);
@ -831,17 +898,70 @@ version(cef) {
}
}
class MiniguiRequestHandler : CEF!cef_request_handler_t {
override int on_before_browse(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int) nothrow {
return 0;
}
override int on_open_urlfrom_tab(RC!(cef_browser_t), RC!(cef_frame_t), const(cef_string_utf16_t)*, cef_window_open_disposition_t, int) nothrow {
return 0;
}
override cef_resource_request_handler_t* get_resource_request_handler(RC!(cef_browser_t), RC!(cef_frame_t), RC!(cef_request_t), int, int, const(cef_string_utf16_t)*, int*) nothrow {
return null;
}
override int get_auth_credentials(RC!(cef_browser_t), const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, int, const(cef_string_utf16_t)*, const(cef_string_utf16_t)*, RC!(cef_auth_callback_t)) nothrow {
// this is for http basic auth popup.....
return 0;
}
override int on_quota_request(RC!(cef_browser_t), const(cef_string_utf16_t)*, long, RC!(cef_callback_t)) nothrow {
return 0;
}
override int on_certificate_error(RC!(cef_browser_t), cef_errorcode_t, const(cef_string_utf16_t)*, RC!(cef_sslinfo_t), RC!(cef_callback_t)) nothrow {
return 0;
}
override int on_select_client_certificate(RC!(cef_browser_t), int, const(cef_string_utf16_t)*, int, ulong, cef_x509certificate_t**, RC!(cef_select_client_certificate_callback_t)) nothrow {
return 0;
}
override void on_plugin_crashed(RC!(cef_browser_t), const(cef_string_utf16_t)*) nothrow {
}
override void on_render_view_ready(RC!(cef_browser_t) p) nothrow {
}
override void on_render_process_terminated(RC!(cef_browser_t), cef_termination_status_t) nothrow {
}
override void on_document_available_in_main_frame(RC!(cef_browser_t) browser) nothrow {
browser.runOnWebView(delegate(wv) {
wv.executeJavascript("console.log('here');");
});
}
}
class MiniguiFocusHandler : CEF!cef_focus_handler_t {
override void on_take_focus(RC!(cef_browser_t) browser, int next) nothrow {
// sdpyPrintDebugString("take");
browser.runOnWebView(delegate(wv) {
Widget f;
if(next) {
f = Window.getFirstFocusable(wv.parentWindow);
} else {
foreach(w; &wv.parentWindow.focusableWidgets) {
if(w is wv)
break;
f = w;
}
}
if(f)
f.focus();
});
}
override int on_set_focus(RC!(cef_browser_t) browser, cef_focus_source_t source) nothrow {
/+
browser.runOnWebView((ev) {
sdpyPrintDebugString("setting");
ev.parentWindow.focusedWidget = ev;
ev.focus(); // even this can steal focus from other parts of my application!
});
+/
//sdpyPrintDebugString("setting");
return 1; // otherwise, cancel because this bullshit tends to steal focus from other applications and i never, ever, ever want that to happen.
// seems to happen because of race condition in it getting a focus event and then stealing the focus from the parent
@ -850,7 +970,12 @@ version(cef) {
// it also breaks its own pop up menus and drop down boxes to allow this! wtf
}
override void on_got_focus(RC!(cef_browser_t) browser) nothrow {
// sdpyPrintDebugString("got");
browser.runOnWebView((ev) {
// this sometimes steals from the app too but it is relatively acceptable
// steals when i mouse in from the side of the window quickly, but still
// i want the minigui state to match so i'll allow it
ev.focus();
});
}
}
@ -865,6 +990,7 @@ version(cef) {
MiniguiDownloadHandler downloadHandler;
MiniguiKeyboardHandler keyboardHandler;
MiniguiFocusHandler focusHandler;
MiniguiRequestHandler requestHandler;
this(void delegate(scope OpenNewWindowParams) openNewWindow) {
this.openNewWindow = openNewWindow;
lsh = new MiniguiCefLifeSpanHandler(this);
@ -874,6 +1000,7 @@ version(cef) {
downloadHandler = new MiniguiDownloadHandler();
keyboardHandler = new MiniguiKeyboardHandler();
focusHandler = new MiniguiFocusHandler();
requestHandler = new MiniguiRequestHandler();
}
override cef_audio_handler_t* get_audio_handler() {
@ -917,10 +1044,12 @@ version(cef) {
override cef_render_handler_t* get_render_handler() {
// this thing might work for an off-screen thing
// like to an image or to a video stream maybe
//
// might be useful to have it render here then send it over too for remote X sharing a process
return null;
}
override cef_request_handler_t* get_request_handler() {
return null;
return requestHandler.returnable;
}
override int on_process_message_received(RC!cef_browser_t, RC!cef_frame_t, cef_process_id_t, RC!cef_process_message_t) {
return 0; // return 1 if you can actually handle the message

View File

@ -12296,6 +12296,13 @@ version(X11) {
if(width == 0 || height == 0) {
XSetClipMask(display, gc, None);
if(xrenderPicturePainter) {
XRectangle[1] rects;
rects[0] = XRectangle(short.min, short.min, short.max, short.max);
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
}
version(with_xft) {
if(xftFont is null || xftDraw is null)
return;
@ -12306,6 +12313,9 @@ version(X11) {
rects[0] = XRectangle(cast(short)(x), cast(short)(y), cast(short) width, cast(short) height);
XSetClipRectangles(XDisplayConnection.get, gc, 0, 0, rects.ptr, 1, 0);
if(xrenderPicturePainter)
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
version(with_xft) {
if(xftFont is null || xftDraw is null)
return;
@ -12572,6 +12582,12 @@ version(X11) {
XRenderPictureAttributes attrs;
// FIXME: I can prolly reuse this as long as the pixmap itself is valid.
xrenderPicturePainter = XRenderCreatePicture(display, d, Sprite.RGB24, 0, &attrs);
// need to initialize the clip
XRectangle[1] rects;
rects[0] = XRectangle(cast(short)(_clipRectangle.left), cast(short)(_clipRectangle.top), cast(short) _clipRectangle.width, cast(short) _clipRectangle.height);
XRenderSetPictureClipRectangles(display, xrenderPicturePainter, 0, 0, rects.ptr, cast(int) rects.length);
}
XRenderComposite(
@ -13613,6 +13629,35 @@ mixin DynamicLoad!(XRandr, "Xrandr", 2, XRandrLibrarySuccessfullyLoaded) XRandrL
}
}
/++
Platform-specific for X11. Traps errors for the duration of `dg`. Avoid calling this from inside a call to this.
Please note that it returns
+/
XErrorEvent[] trapXErrors(scope void delegate() dg) {
static XErrorEvent[] errorBuffer;
static extern(C) int handler (Display* dpy, XErrorEvent* evt) nothrow {
errorBuffer ~= *evt;
return 0;
}
auto savedErrorHandler = XSetErrorHandler(&handler);
try {
dg();
} finally {
XSync(XDisplayConnection.get, 0/*False*/);
XSetErrorHandler(savedErrorHandler);
}
auto bfr = errorBuffer;
errorBuffer = null;
return bfr;
}
/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a `Display*`.
class XDisplayConnection {
private __gshared Display* display;
@ -16986,6 +17031,9 @@ extern(C) {
extern(C) alias XIOErrorHandler = int function (Display* display);
}
extern(C) nothrow
alias XErrorHandler = int function(Display*, XErrorEvent*);
extern(C) nothrow @nogc {
struct Screen{
XExtData *ext_data; /* hook for extension to hang data */
@ -17190,8 +17238,6 @@ struct Visual
byte pad;
}
alias XErrorHandler = int function(Display*, XErrorEvent*);
struct XRectangle {
short x;
short y;