From 49829f3f48d2f026620a5beb8975bfebbe269cd8 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Tue, 22 Mar 2022 18:02:03 -0400 Subject: [PATCH] mroe dpi stuff --- minigui.d | 60 +++++++++++++++++++++--- simpledisplay.d | 120 ++++++++++++++++++++++++++++++++++++++---------- terminal.d | 21 ++++++++- 3 files changed, 168 insertions(+), 33 deletions(-) diff --git a/minigui.d b/minigui.d index 9c5b798..d3c07b9 100644 --- a/minigui.d +++ b/minigui.d @@ -604,7 +604,19 @@ class Widget : ReflectableProperties { // avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable // I'll think up something better eventually protected final int defaultLineHeight() { - return scaleWithDpi(Window.lineHeight); + auto cs = getComputedStyle(); + if(cs.font && !cs.font.isNull) + return cs.font.height() * 5 / 4; + else + return scaleWithDpi(Window.lineHeight); + } + + protected final int defaultTextWidth(const(char)[] text) { + auto cs = getComputedStyle(); + if(cs.font && !cs.font.isNull) + return cs.font.stringWidth(text); + else + return scaleWithDpi(Window.lineHeight * cast(int) text.length / 2); } /++ @@ -3951,8 +3963,16 @@ struct StyleInformation { this.visualTheme = WidgetPainter.visualTheme; } - /// Forwards to [Widget.Style] - // through the [VisualTheme] + /++ + Forwards to [Widget.Style] + + Bugs: + It is supposed to fall back to the [VisualTheme] if + the style doesn't override the default, but that is + not generally implemented. Many of them may end up + being explicit overloads instead of the generic + opDispatch fallback, like [font] is now. + +/ public @property opDispatch(string name)() { typeof(__traits(getMember, Widget.Style.init, name)()) prop; w.useStyleProperties((scope Widget.Style props) { @@ -3962,6 +3982,25 @@ struct StyleInformation { return prop; } + /++ + Returns the cached font object associated with the widget, + if overridden by the [Widget.Style|Style], or the [VisualTheme] if not. + + History: + Prior to March 21, 2022 (dub v10.7), `font` went through + [opDispatch], which did not use the cache. You can now call it + repeatedly without guilt. + +/ + public @property OperatingSystemFont font() { + OperatingSystemFont prop; + w.useStyleProperties((scope Widget.Style props) { + prop = props.fontCached; + }); + if(prop is null) + prop = visualTheme.defaultFontCached; + return prop; + } + @property { // Layout helpers. Currently just forwarding since I haven't made up my mind on a better way. /** */ int paddingLeft() { return w.paddingLeft(); } @@ -8982,6 +9021,8 @@ class Labeled(T) : Widget { override int marginTop() { return 4; } override int marginBottom() { return 4; } + // FIXME: i should prolly call it value as well as content tbh + /// @property string content() { return lineEdit.content; @@ -10265,10 +10306,10 @@ class MenuItem : MouseActivatedWidget { override int maxHeight() { return defaultLineHeight + 4; } override int minHeight() { return defaultLineHeight + 4; } - override int minWidth() { return defaultLineHeight * cast(int) label.length + 8; } + override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); } override int maxWidth() { if(cast(MenuBar) parent) { - return defaultLineHeight / 2 * cast(int) label.length + 8; + return minWidth(); } return int.max; } @@ -11417,6 +11458,8 @@ abstract class EditableTextWidget : EditableTextWidgetParent { }); } + private string lastContentBlur; + override void defaultEventHandler_blur(Event ev) { super.defaultEventHandler_blur(ev); if(parentWindow.win.closed) return; @@ -11430,8 +11473,11 @@ abstract class EditableTextWidget : EditableTextWidgetParent { } } - auto evt = new ChangeEvent!string(this, &this.content); - evt.dispatch(); + if(this.content != lastContentBlur) { + auto evt = new ChangeEvent!string(this, &this.content); + evt.dispatch(); + lastContentBlur = this.content; + } } version(custom_widgets) diff --git a/simpledisplay.d b/simpledisplay.d index dcdc905..8bce6c8 100644 --- a/simpledisplay.d +++ b/simpledisplay.d @@ -559,6 +559,10 @@ interface->SetProgressValue(hwnd, 40, 100); $(H2 Platform-specific tips and tricks) + X_tips: + + On X11, if you set an environment variable, `ARSD_SCALING_FACTOR`, you can control the per-monitor DPI scaling returned to the application. The format is `ARSD_SCALING_FACTOR=2;1`, for example, to set 2x scaling on your first monitor and 1x scaling on your second monitor. Support for this was added on March 22, 2022, the dub 10.7 release. + Windows_tips: You can add icons or manifest files to your exe using a resource file. @@ -1506,7 +1510,7 @@ string sdpyWindowClass () { } /++ - Returns the DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. + Returns the logical DPI of the default monitor. [0] is width, [1] is height (they are usually the same though). You may wish to round the numbers off. This isn't necessarily related to the physical side of the screen; it is associated with a user-defined scaling factor. If you want per-monitor dpi values, check [SimpleWindow.actualDpi], but you can fall back to this if it returns 0. +/ @@ -1521,37 +1525,52 @@ float[2] getDpi() { auto screen = DefaultScreen(display); void fallback() { + /+ // 25.4 millimeters in an inch... dpi[0] = cast(float) DisplayWidth(display, screen) / DisplayWidthMM(display, screen) * 25.4; dpi[1] = cast(float) DisplayHeight(display, screen) / DisplayHeightMM(display, screen) * 25.4; + +/ + + // the physical size isn't actually as important as the logical size since this is + // all about scaling really + dpi[0] = 96; + dpi[1] = 96; } - char* resourceString = XResourceManagerString(display); - XrmInitialize(); - - if (resourceString) { - auto db = XrmGetStringDatabase(resourceString); - XrmValue value; - char* type; - if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { - if (value.addr) { - import core.stdc.stdlib; - dpi[0] = atof(cast(char*) value.addr); - dpi[1] = dpi[0]; - } else { - fallback(); - } - } else { - fallback(); - } - } else { + auto xft = getXftDpi(); + if(xft is float.init) fallback(); + else { + dpi[0] = xft; + dpi[1] = xft; } } return dpi; } +version(X11) +float getXftDpi() { + auto display = XDisplayConnection.get; + + char* resourceString = XResourceManagerString(display); + XrmInitialize(); + + if (resourceString) { + auto db = XrmGetStringDatabase(resourceString); + XrmValue value; + char* type; + if (XrmGetResource(db, "Xft.dpi", "String", &type, &value) == true) { + if (value.addr) { + import core.stdc.stdlib; + return atof(cast(char*) value.addr); + } + } + } + + return float.init; +} + /++ Implementation used by [SimpleWindow.takeScreenshot]. @@ -1804,7 +1823,14 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { MonitorInfo.info = MonitorInfo.info[0 .. 0]; MonitorInfo.info.assumeSafeAppend(); - foreach(monitor; monitors[0 .. count]) { + foreach(idx, monitor; monitors[0 .. count]) { + MonitorInfo.info ~= MonitorInfo( + Rectangle(Point(monitor.x, monitor.y), Size(monitor.width, monitor.height)), + Size(monitor.mwidth, monitor.mheight), + cast(int) (customScalingFactorForMonitor(cast(int) idx) * 96) + ); + + /+ if(monitor.mwidth == 0 || monitor.mheight == 0) // unknown physical size, just guess 96 to avoid divide by zero MonitorInfo.info ~= MonitorInfo( @@ -1823,8 +1849,9 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { cast(int)(monitor.height * 25.4 / monitor.mheight + 0.5) ) ); + +/ } - //import std.stdio; writeln("Here", MonitorInfo.info); + // import std.stdio; writeln("Here", MonitorInfo.info); } } @@ -1845,8 +1872,8 @@ class SimpleWindow : CapableOfHandlingNativeEvent, CapableOfBeingDrawnUpon { fallback: // make sure we disable events that aren't coming xrrEventBase = -1; - // best guess... - actualDpi_ = cast(int) getDpi()[0]; + // best guess... respect the custom scaling user command to some extent at least though + actualDpi_ = cast(int) (getDpi()[0] * customScalingFactorForMonitor(0)); } } actualDpiLoadAttempted = true; @@ -9902,6 +9929,7 @@ void sdpyPrintDebugString(string fileOverride = null, T...)(T t) nothrow @truste str ~= "\n"; fwrite(str.ptr, 1, str.length, fp); + fflush(fp); } catch(Exception e) { // sorry no hope } @@ -21491,6 +21519,50 @@ private struct CleanupQueue { } private __gshared CleanupQueue cleanupQueue; +version(X11) +/++ + Returns the custom scaling factor read out of environment["ARSD_SCALING_FACTOR"]. + + $(WARNING + This function is exempted from stability guarantees. + ) ++/ +float customScalingFactorForMonitor(int monitorNumber) { + import core.stdc.stdlib; + auto val = getenv("ARSD_SCALING_FACTOR"); + + if(val is null) + return 1.0; + + char[16] buffer = 0; + int pos; + + const(char)* at = val; + + foreach(item; 0 .. monitorNumber + 1) { + if(*at == 0) + break; // reuse the last number when we at the end of the string + pos = 0; + while(pos + 1 < buffer.length && *at && *at != ';') { + buffer[pos++] = *at; + at++; + } + if(*at) + at++; // skip the semicolon + buffer[pos] = 0; + } + + //sdpyPrintDebugString(buffer[0 .. pos]); + + import core.stdc.math; + auto f = atof(buffer.ptr); + + if(f <= 0.0 || isnan(f) || isinf(f)) + return 1.0; + + return f; +} + void guiAbortProcess(string msg) { import core.stdc.stdlib; version(Windows) { diff --git a/terminal.d b/terminal.d index e8f80ab..1d5f215 100644 --- a/terminal.d +++ b/terminal.d @@ -8245,7 +8245,9 @@ version(TerminalDirectToEmulator) { On January 12, 2022, I changed the font size to be auto-scaled with detected dpi by default. You can undo this by setting - `scaleFontSizeWithDpi` to false. + `scaleFontSizeWithDpi` to false. On March 22, 2022, I tweaked + this slightly to only scale if the font point size is not already + scaled (e.g. by Xft.dpi settings) to avoid double scaling. +/ string fontName = defaultFont; /// ditto @@ -8954,7 +8956,22 @@ version(TerminalDirectToEmulator) { } auto fontSize = integratedTerminalEmulatorConfiguration.fontSize; if(integratedTerminalEmulatorConfiguration.scaleFontSizeWithDpi) { - fontSize = widget.scaleWithDpi(fontSize); + static if(UsingSimpledisplayX11) { + // if it is an xft font and xft is already scaled, we should NOT double scale. + import std.algorithm; + if(integratedTerminalEmulatorConfiguration.fontName.startsWith("core:")) { + // core font doesn't use xft anyway + fontSize = widget.scaleWithDpi(fontSize); + } else { + auto xft = getXftDpi(); + if(xft is float.init) + xft = 96; + fontSize = widget.scaleWithDpi(fontSize, cast(int) xft); + + } + } else { + fontSize = widget.scaleWithDpi(fontSize); + } } if(integratedTerminalEmulatorConfiguration.fontName.length) {