more stuff im too lazy to figure out details

This commit is contained in:
Adam D. Ruppe 2025-03-29 21:05:17 -04:00
parent d1cb09bdaa
commit f1a259ecac
10 changed files with 1936 additions and 93 deletions

View File

@ -289,3 +289,13 @@ immutable monthNames = [
"November", "November",
"December" "December"
]; ];
immutable daysOfWeekNames = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
];

28
core.d
View File

@ -129,7 +129,8 @@ version(OSXCocoa) {
enum bool UseCocoa = false; enum bool UseCocoa = false;
else else
enum bool UseCocoa = true; enum bool UseCocoa = true;
} } else
enum bool UseCocoa = false;
import core.attribute; import core.attribute;
static if(!__traits(hasMember, core.attribute, "mustuse")) static if(!__traits(hasMember, core.attribute, "mustuse"))
@ -199,18 +200,22 @@ version(Emscripten) {
} }
// FIXME: pragma(linkerDirective, "-framework", "Cocoa") works in ldc // FIXME: pragma(linkerDirective, "-framework", "Cocoa") works in ldc
version(OSXCocoa) static if(UseCocoa)
enum CocoaAvailable = true; enum CocoaAvailable = true;
else else
enum CocoaAvailable = false; enum CocoaAvailable = false;
version(D_OpenD) { version(D_OpenD) {
version(OSXCocoa) static if(UseCocoa) {
pragma(linkerDirective, "-framework", "Cocoa"); pragma(linkerDirective, "-framework", "Cocoa");
pragma(linkerDirective, "-framework", "QuartzCore");
}
} else { } else {
version(OSXCocoa) static if(UseCocoa)
version(LDC) version(LDC) {
pragma(linkerDirective, "-framework", "Cocoa"); pragma(linkerDirective, "-framework", "Cocoa");
pragma(linkerDirective, "-framework", "QuartzCore");
}
} }
version(Posix) { version(Posix) {
@ -1897,7 +1902,7 @@ unittest {
assert(decodeUriComponent("+", true) == " "); assert(decodeUriComponent("+", true) == " ");
} }
private auto toDelegate(T)(T t) { public auto toDelegate(T)(T t) {
// static assert(is(T == function)); // lol idk how to do what i actually want here // static assert(is(T == function)); // lol idk how to do what i actually want here
static if(is(T Return == return)) static if(is(T Return == return))
@ -1908,8 +1913,8 @@ private auto toDelegate(T)(T t) {
} }
} }
return &((cast(Wrapper*) t).call); return &((cast(Wrapper*) t).call);
} else static assert(0, "could not get params"); } else static assert(0, "could not get params; is it already a delegate you can pass directly?");
else static assert(0, "could not get return value"); else static assert(0, "could not get return value, if it is a functor maybe try getting a delegate with `&yourobj.opCall` instead of toDelegate(yourobj)");
} }
@system unittest { @system unittest {
@ -4519,7 +4524,8 @@ class Timer {
version(Windows) {} else version(Windows) {} else
static void unregister(arsd.core.ICoreEventLoop.UnregisterToken urt) { static void unregister(arsd.core.ICoreEventLoop.UnregisterToken urt) {
urt.unregister(); if(urt.impl !is null)
urt.unregister();
} }
@ -4576,7 +4582,7 @@ class Timer {
CallbackHelper cbh; CallbackHelper cbh;
} else version(linux) { } else version(linux) {
int fd = -1; int fd = -1;
} else version(OSXCocoa) { } else static if(UseCocoa) {
} else static assert(0, "timer not supported"); } else static assert(0, "timer not supported");
} }
@ -9242,7 +9248,7 @@ package(arsd) version(Windows) extern(Windows) {
int WSARecvFrom(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, sockaddr*, LPINT, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); int WSARecvFrom(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, sockaddr*, LPINT, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE);
} }
package(arsd) version(OSXCocoa) { package(arsd) static if(UseCocoa) {
/* Copy/paste chunk from Jacob Carlborg { */ /* Copy/paste chunk from Jacob Carlborg { */
// from https://raw.githubusercontent.com/jacob-carlborg/druntime/550edd0a64f0eb2c4f35d3ec3d88e26b40ac779e/src/core/stdc/clang_block.d // from https://raw.githubusercontent.com/jacob-carlborg/druntime/550edd0a64f0eb2c4f35d3ec3d88e26b40ac779e/src/core/stdc/clang_block.d

View File

@ -22,6 +22,8 @@
+/ +/
module arsd.discord; module arsd.discord;
// FIXME: it thought it was still alive but showed as not online and idk why. maybe setPulseCallback stopped triggering?
// FIXME: Secure Connect Failed sometimes on trying to reconnect, should prolly just try again after a short period, or ditch the whole thing if reconnectAndResume and try fresh // FIXME: Secure Connect Failed sometimes on trying to reconnect, should prolly just try again after a short period, or ditch the whole thing if reconnectAndResume and try fresh
// FIXME: User-Agent: DiscordBot ($url, $versionNumber) // FIXME: User-Agent: DiscordBot ($url, $versionNumber)
@ -672,6 +674,7 @@ class DiscordGatewayConnection {
} else { } else {
// otherwise, unless we were asked by the api user to close, let's try reconnecting // otherwise, unless we were asked by the api user to close, let's try reconnecting
// since discord just does discord things. // since discord just does discord things.
websocket_ = null;
connect(); connect();
} }
} }
@ -947,6 +950,8 @@ class DiscordGatewayConnection {
auto d = 1.seconds; auto d = 1.seconds;
int count = 0; int count = 0;
try_again:
try { try {
this.websocket_.connect(); this.websocket_.connect();
} catch(Exception e) { } catch(Exception e) {
@ -956,6 +961,8 @@ class DiscordGatewayConnection {
count++; count++;
if(count == 10) if(count == 10)
throw e; throw e;
goto try_again;
} }
} }

13
dom.d
View File

@ -4825,6 +4825,19 @@ struct AttributesHolder {
} }
return 0; return 0;
} }
string toString() {
string ret;
foreach(k, v; this) {
if(ret.length)
ret ~= " ";
ret ~= k;
ret ~= `="`;
ret ~= v;
ret ~= `"`;
}
return ret;
}
} }
unittest { unittest {

View File

@ -5620,6 +5620,7 @@ class WebSocket {
activeSockets ~= s; activeSockets ~= s;
s.registered = true; s.registered = true;
version(use_arsd_core) { version(use_arsd_core) {
version(Posix)
s.unregisterToken = arsd.core.getThisThreadEventLoop().addCallbackOnFdReadable(s.socket.handle, new arsd.core.CallbackHelper(() { s.readyToRead(s); })); s.unregisterToken = arsd.core.getThisThreadEventLoop().addCallbackOnFdReadable(s.socket.handle, new arsd.core.CallbackHelper(() { s.readyToRead(s); }));
} }
} }

700
minigui.d
View File

@ -21,6 +21,8 @@
// FIXME: add menu checkbox and menu icon eventually // FIXME: add menu checkbox and menu icon eventually
// FIXME: checkbox menus and submenus and stuff
// FOXME: look at Windows rebar control too // FOXME: look at Windows rebar control too
/* /*
@ -668,11 +670,23 @@ version(Windows) {
} }
version(Windows) { version(Windows) {
version(minigui_manifest) {} else version=minigui_no_manifest; // to swap the default
// version(minigui_manifest) {} else version=minigui_no_manifest;
version(minigui_no_manifest) {} else version(minigui_no_manifest) {} else {
static if(__VERSION__ >= 2_083) version(D_OpenD) {
version(CRuntime_Microsoft) { // FIXME: mingw? // OpenD always supports it
version=UseManifestMinigui;
} else {
static if(__VERSION__ >= 2_083)
version(CRuntime_Microsoft) // FIXME: mingw?
version=UseManifestMinigui;
}
}
version(UseManifestMinigui) {
// assume we want commctrl6 whenever possible since there's really no reason not to // assume we want commctrl6 whenever possible since there's really no reason not to
// and this avoids some of the manifest hassle // and this avoids some of the manifest hassle
pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\""); pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"");
@ -1482,16 +1496,21 @@ class Widget : ReflectableProperties {
Menu contextMenu(int x, int y) { return null; } Menu contextMenu(int x, int y) { return null; }
/++ /++
Shows the widget's context menu, as if the user right clicked at the x, y position. You should rarely, if ever, have to call this, since default event handlers will do it for you automatically. To control what menu shows up, override [contextMenu] instead. Shows the widget's context menu, as if the user right clicked at the x, y position. You should rarely, if ever, have to call this, since default event handlers will do it for you automatically. To control what menu shows up, you can pass one as `menuToShow`, but if you don't, it will call [contextMenu], which you can override on a per-widget basis.
History:
The `menuToShow` parameter was added on March 19, 2025.
+/ +/
final bool showContextMenu(int x, int y) { final bool showContextMenu(int x, int y, Menu menuToShow = null) {
return showContextMenu(x, y, -2, -2); return showContextMenu(x, y, -2, -2, menuToShow);
} }
private final bool showContextMenu(int x, int y, int screenX, int screenY) { private final bool showContextMenu(int x, int y, int screenX, int screenY, Menu menu = null) {
if(parentWindow is null || parentWindow.win is null) return false; if(parentWindow is null || parentWindow.win is null) return false;
auto menu = this.contextMenu(x, y); if(menu is null)
menu = this.contextMenu(x, y);
if(menu is null) if(menu is null)
return false; return false;
@ -2312,7 +2331,7 @@ class Widget : ReflectableProperties {
bool invalidateChildren = invalidate; bool invalidateChildren = invalidate;
if(redrawRequested || force) { if(redrawRequested || force) {
painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height); painter.setClipRectangleForWidget(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
painter.drawingUpon = this; painter.drawingUpon = this;
@ -4224,6 +4243,10 @@ struct WidgetPainter {
this(ScreenPainter screenPainter, Widget drawingUpon) { this(ScreenPainter screenPainter, Widget drawingUpon) {
this.drawingUpon = drawingUpon; this.drawingUpon = drawingUpon;
this.screenPainter = screenPainter; this.screenPainter = screenPainter;
this.widgetClipRectangle = screenPainter.currentClipRectangle;
// this.screenPainter.impl.enableXftDraw();
if(auto font = visualTheme.defaultFontCached(drawingUpon.currentDpi)) if(auto font = visualTheme.defaultFontCached(drawingUpon.currentDpi))
this.screenPainter.setFont(font); this.screenPainter.setFont(font);
} }
@ -4241,10 +4264,37 @@ struct WidgetPainter {
} }
} }
private Rectangle widgetClipRectangle;
private Rectangle setClipRectangleForWidget(Point upperLeft, int width, int height) {
widgetClipRectangle = Rectangle(upperLeft, Size(width, height));
return screenPainter.setClipRectangle(widgetClipRectangle);
}
/++
Sets the clip rectangle to the given settings. It will automatically calculate the intersection
of your widget's content boundaries and your requested clip rectangle.
History:
Before February 26, 2025, you could sometimes exceed widget boundaries, as this forwarded
directly to the underlying `ScreenPainter`. It now wraps it to calculate the intersection.
+/
Rectangle setClipRectangle(Rectangle rectangle) {
return screenPainter.setClipRectangle(rectangle.intersectionOf(widgetClipRectangle));
}
/// ditto
Rectangle setClipRectangle(Point upperLeft, int width, int height) {
return setClipRectangle(Rectangle(upperLeft, Size(width, height)));
}
/// ditto
Rectangle setClipRectangle(Point upperLeft, Size size) {
return setClipRectangle(Rectangle(upperLeft, size));
}
/// ///
ScreenPainter screenPainter; ScreenPainter screenPainter;
/// Forward to the screen painter for other methods /// Forward to the screen painter for all other methods, see [arsd.simpledisplay.ScreenPainter] for more information
alias screenPainter this; alias screenPainter this;
private Widget drawingUpon; private Widget drawingUpon;
@ -4718,6 +4768,10 @@ private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void del
update = () { le.content = *valptr; }; update = () { le.content = *valptr; };
update(); update();
return le; return le;
} else static if(is(typeof(tt) == E[], E)) {
auto w = new ArrayEditingWidget!E(parent);
// FIXME update
return w;
} else static if(is(typeof(tt) == function)) { } else static if(is(typeof(tt) == function)) {
auto w = new Button(displayName, parent); auto w = new Button(displayName, parent);
return w; return w;
@ -4727,6 +4781,300 @@ private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void del
} else static assert(0, "multiple controllers not yet supported"); } else static assert(0, "multiple controllers not yet supported");
} }
class ArrayEditingWidget(T) : ArrayEditingWidgetBase {
this(Widget parent) {
super(parent);
}
}
class ArrayEditingWidgetBase : Widget {
this(Widget parent) {
super(parent);
// FIXME: a trash can to move items into to delete them?
static class MyListViewItem : GenericListViewItem {
this(Widget parent) {
super(parent);
/+
drag handle
left click lets you move the whole selection. if the current element is not selected, it changes the selection to it.
right click here gives you the movement controls too
index/key view zone
left click here selects/unselects
element view/edit zone
delete button
+/
// FIXME: make sure the index is viewable
auto hl = new HorizontalLayout(this);
button = new CommandButton("d", hl);
label = new TextLabel("unloaded", TextAlignment.Left, hl);
// if member editable, have edit view... get from the subclass.
// or a "..." menu?
button = new CommandButton("Up", hl); // shift+click is move to top
button = new CommandButton("Down", hl); // shift+click is move to bottom
button = new CommandButton("Move to", hl); // move before, after, or swap
button = new CommandButton("Delete", hl);
button.addEventListener("triggered", delegate(){
//messageBox(text("clicked ", currentIndexLoaded()));
});
}
override void showItem(int idx) {
label.label = "Item ";// ~ to!string(idx);
}
TextLabel label;
Button button;
}
auto outer_this = this;
// FIXME: make sure item count is easy to see
glvw = new class GenericListViewWidget {
this() {
super(outer_this);
}
override GenericListViewItem itemFactory(Widget parent) {
return new MyListViewItem(parent);
}
override Size itemSize() {
return Size(0, scaleWithDpi(80));
}
override Menu contextMenu(int x, int y) {
return createContextMenuFromAnnotatedCode(this);
}
@context_menu {
void Select_All() {
}
void Undo() {
}
void Redo() {
}
void Cut() {
}
void Copy() {
}
void Paste() {
}
void Delete() {
}
void Find() {
}
}
};
glvw.setItemCount(400);
auto hl = new HorizontalLayout(this);
add = new FreeEntrySelection(hl);
addButton = new Button("Add", hl);
}
GenericListViewWidget glvw;
ComboboxBase add;
Button addButton;
/+
Controls:
clear (select all / delete)
reset (confirmation blocked button, maybe only on the whole form? or hit undo so many times to get back there)
add item
palette of options to add to the array (add prolly a combo box)
rearrange - move up/down, drag and drop a selection? right click can always do, left click only drags when on a selection handle.
edit/input/view items (GLVW? or it could be a table view in a way.)
undo/redo
select whole elements (even if a struct)
cut/copy/paste elements
could have an element picker, a details pane, and an add bare?
put a handle on the elements for left click dragging. allow right click drag anywhere but pretty big wiggle until it enables.
left click and drag should never work for plain text, i more want to change selection there and there no room to put a handle on it.
the handle should let dragging w/o changing the selection, or if part of the selection, drag the whole selection i think.
make it textured and use the grabby hand mouse cursor.
+/
}
/++
A button that pops up a menu on click for working on a particular item or selection.
History:
Added March 23, 2025
+/
class MenuPopupButton : Button {
/++
You might consider using [createContextMenuFromAnnotatedCode] to populate the `menu` argument.
You also may want to set the [prepare] delegate after construction.
+/
this(Menu menu, Widget parent) {
assert(menu !is null);
this.menu = menu;
super("...", parent);
}
private Menu menu;
/++
If set, this delegate is called before popping up the window. This gives you a chance
to prepare your dynamic data structures for the element(s) selected.
For example, if your `MenuPopupButton` is attached to a [GenericListViewItem], you can call
[GenericListViewItem.currentIndexLoaded] in here and set it to a variable in the object you
called [createContextMenuFromAnnotatedCode] to apply the operation to the right object.
(The api could probably be simpler...)
+/
void delegate() prepare;
override void defaultEventHandler_triggered(scope Event e) {
if(prepare)
prepare();
showContextMenu(this.x, this.y + this.height, -2, -2, menu);
}
override int maxHeight() {
return defaultLineHeight;
}
override int maxWidth() {
return defaultLineHeight;
}
}
/++
A button that pops up an information box, similar to a tooltip, but explicitly triggered.
FIXME: i want to be able to easily embed these in other things too.
+/
class TipPopupButton : Button {
/++
+/
this(Widget delegate(Widget p) factory, Widget parent) {
this.factory = factory;
super("?", parent);
}
private Widget delegate(Widget p) factory;
override void defaultEventHandler_triggered(scope Event e) {
auto window = new TooltipWindow(factory, this);
window.popup(this);
}
}
/++
History:
Added March 23, 2025
+/
class TooltipWindow : Window {
void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
/+
this.menuParent = parent;
previouslyFocusedWidget = parent.parentWindow.focusedWidget;
previouslyFocusedWidgetBelongsIn = &parent.parentWindow.focusedWidget;
parent.parentWindow.focusedWidget = this;
int w = 150;
int h = paddingTop + paddingBottom;
if(this.children.length) {
// hacking it to get the ideal height out of recomputeChildLayout
this.width = w;
this.height = h;
this.recomputeChildLayoutEntry();
h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
h += paddingBottom;
h -= 2; // total hack, i just like the way it looks a bit tighter even though technically MenuItem reserves some space to center in normal circumstances
}
+/
if(offsetY == int.min)
offsetY = parent.defaultLineHeight;
int w = 150;
int h = 50;
auto coord = parent.globalCoordinates();
dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
static if(UsingSimpledisplayX11)
XSync(XDisplayConnection.get, 0);
dropDown.visibilityChanged = (bool visible) {
if(visible) {
this.redraw();
dropDown.grabInput();
} else {
dropDown.releaseInputGrab();
}
};
dropDown.show();
clickListener = this.addEventListener((scope ClickEvent ev) {
unpopup();
// need to unlock asap just in case other user handlers block...
static if(UsingSimpledisplayX11)
flushGui();
}, true /* again for asap action */);
}
private EventListener clickListener;
void unpopup() {
mouseLastOver = mouseLastDownOn = null;
dropDown.hide();
clickListener.disconnect();
}
private SimpleWindow dropDown;
private Widget child;
///
this(Widget delegate(Widget p) factory, Widget parent) {
assert(parent);
assert(parent.parentWindow);
assert(parent.parentWindow.win);
dropDown = new SimpleWindow(
250, 40,
null, OpenGlOptions.no, Resizability.fixedSize,
WindowTypes.tooltip,
WindowFlags.dontAutoShow,
parent ? parent.parentWindow.win : null
);
super(dropDown);
child = factory(this);
}
}
private template controlledByCount(alias tt) { private template controlledByCount(alias tt) {
static int helper() { static int helper() {
int count; int count;
@ -5653,7 +6001,7 @@ enum ScrollBarShowPolicy {
+/ +/
// FIXME ScrollBarShowPolicy // FIXME ScrollBarShowPolicy
// FIXME: use the ScrollMessageWidget in here now that it exists // FIXME: use the ScrollMessageWidget in here now that it exists
// deprecated("Use ScrollMessageWidget or ScrollableContainerWidget instead") // ugh compiler won't let me do it deprecated("Use ScrollMessageWidget or ScrollableContainerWidget instead") // ugh compiler won't let me do it
class ScrollableWidget : Widget { class ScrollableWidget : Widget {
// FIXME: make line size configurable // FIXME: make line size configurable
// FIXME: add keyboard controls // FIXME: add keyboard controls
@ -6014,12 +6362,19 @@ class ScrollableWidget : Widget {
return WidgetPainter(painter, this); return WidgetPainter(painter, this);
} }
override void addScrollPosition(ref int x, ref int y) {
x += scrollOrigin.x;
y += scrollOrigin.y;
}
mixin ScrollableChildren; mixin ScrollableChildren;
} }
// you need to have a Point scrollOrigin in the class somewhere // you need to have a Point scrollOrigin in the class somewhere
// and a paintFrameAndBackground // and a paintFrameAndBackground
private mixin template ScrollableChildren() { private mixin template ScrollableChildren() {
static assert(!__traits(isSame, this.addScrollPosition, Widget.addScrollPosition), "Your widget should provide `Point scrollOrigin()` and `override void addScrollPosition`");
override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) { override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
if(hidden) if(hidden)
return; return;
@ -6038,7 +6393,7 @@ private mixin template ScrollableChildren() {
if(force || redrawRequested) { if(force || redrawRequested) {
//painter.setClipRectangle(scrollOrigin, width, height); //painter.setClipRectangle(scrollOrigin, width, height);
painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height); painter.setClipRectangleForWidget(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
paintFrameAndBackground(painter); paintFrameAndBackground(painter);
} }
@ -6051,7 +6406,7 @@ private mixin template ScrollableChildren() {
painter.originX = painter.originX - scrollOrigin.x; painter.originX = painter.originX - scrollOrigin.x;
painter.originY = painter.originY - scrollOrigin.y; painter.originY = painter.originY - scrollOrigin.y;
if(force || redrawRequested) { if(force || redrawRequested) {
painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4); painter.setClipRectangleForWidget(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4); //painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
//erase(painter); // we paintFrameAndBackground above so no need //erase(painter); // we paintFrameAndBackground above so no need
@ -6104,7 +6459,7 @@ private class InternalScrollableContainerInsideWidget : ContainerWidget {
painter.originX = lox + x - scrollOrigin.x; painter.originX = lox + x - scrollOrigin.x;
painter.originY = loy + y - scrollOrigin.y; painter.originY = loy + y - scrollOrigin.y;
if(force || redrawRequested) { if(force || redrawRequested) {
painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height); painter.setClipRectangleForWidget(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
erase(painter); erase(painter);
if(painter.visualTheme) if(painter.visualTheme)
@ -6326,7 +6681,7 @@ class ScrollableContainerWidget : ContainerWidget {
version(custom_widgets) version(custom_widgets)
// deprecated // i can't deprecate it w/o stupid messages ugh deprecated
private class InternalScrollableContainerWidget : Widget { private class InternalScrollableContainerWidget : Widget {
ScrollableWidget sw; ScrollableWidget sw;
@ -9315,10 +9670,18 @@ class Window : Widget {
eleR.x = ev.x; eleR.x = ev.x;
eleR.y = ev.y; eleR.y = ev.y;
auto pain = captureEle; auto pain = captureEle;
auto vpx = eleR.x;
auto vpy = eleR.y;
while(pain) { while(pain) {
eleR.x -= pain.x; eleR.x -= pain.x;
eleR.y -= pain.y; eleR.y -= pain.y;
pain.addScrollPosition(eleR.x, eleR.y); pain.addScrollPosition(eleR.x, eleR.y);
vpx -= pain.x;
vpy -= pain.y;
pain = pain.parent; pain = pain.parent;
} }
@ -9329,6 +9692,9 @@ class Window : Widget {
event.clientX = eleR.x; event.clientX = eleR.x;
event.clientY = eleR.y; event.clientY = eleR.y;
event.viewportX = vpx;
event.viewportY = vpy;
event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false; event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
event.altKey = (ev.modifierState & ModifierState.alt) ? true : false; event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false; event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
@ -9696,7 +10062,9 @@ class TableView : Widget {
super(parent); super(parent);
version(win32_widgets) { version(win32_widgets) {
createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED); // LVS_EX_LABELTIP might be worth too
// LVS_OWNERDRAWFIXED
createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//, LVS_EX_TRACKSELECT); // ex style for for LVN_HOTTRACK
} else version(custom_widgets) { } else version(custom_widgets) {
auto smw = new ScrollMessageWidget(this); auto smw = new ScrollMessageWidget(this);
smw.addDefaultKeyboardListeners(); smw.addDefaultKeyboardListeners();
@ -9836,6 +10204,54 @@ class TableView : Widget {
} }
} }
version(custom_widgets)
private int getColumnSizeForContent(size_t columnIndex) {
// FIXME: idk where the problem is but with a 2x scale the horizontal scroll is insuffiicent. i think the SMW is doing it wrong.
// might also want a user-defined max size too
int padding = scaleWithDpi(6);
int m = this.defaultTextWidth(this.columns[columnIndex].name) + padding;
if(getData !is null)
foreach(row; 0 .. itemCount)
getData(row, cast(int) columnIndex, (txt) {
m = mymax(m, this.defaultTextWidth(txt) + padding);
});
if(m < 32)
m = 32;
return m;
}
/++
History:
Added February 26, 2025
+/
void autoSizeColumnsToContent() {
version(custom_widgets) {
foreach(idx, ref c; columns) {
c.width = getColumnSizeForContent(idx);
}
updateCalculatedWidth(false);
tvwi.updateScrolls();
} else version(win32_widgets) {
foreach(i, c; columns)
SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, LVSCW_AUTOSIZE); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
}
}
/++
History:
Added March 1, 2025
+/
bool supportsPerCellAlignment() {
version(custom_widgets)
return true;
else version(win32_widgets)
return false;
return false;
}
private int getActualSetSize(size_t i, bool askWindows) { private int getActualSetSize(size_t i, bool askWindows) {
version(win32_widgets) version(win32_widgets)
if(askWindows) if(askWindows)
@ -10017,12 +10433,26 @@ class TableView : Widget {
auto info = cast(LPNMLISTVIEW) hdr; auto info = cast(LPNMLISTVIEW) hdr;
this.emit!HeaderClickedEvent(info.iSubItem); this.emit!HeaderClickedEvent(info.iSubItem);
break; break;
case (LVN_FIRST-21) /* LVN_HOTTRACK */:
// requires LVS_EX_TRACKSELECT
// sdpyPrintDebugString("here");
mustReturn = 1; // override Windows' auto selection
break;
case NM_CLICK: case NM_CLICK:
NMITEMACTIVATE* info = cast(NMITEMACTIVATE*) hdr;
this.emit!CellClickedEvent(info.iItem, info.iSubItem, MouseButton.left, MouseButtonLinear.left, info.ptAction.x, info.ptAction.y, !!(info.uKeyFlags & LVKF_ALT), !!(info.uKeyFlags & LVKF_CONTROL), !!(info.uKeyFlags & LVKF_SHIFT), false);
break;
case NM_DBLCLK: case NM_DBLCLK:
NMITEMACTIVATE* info = cast(NMITEMACTIVATE*) hdr;
this.emit!CellClickedEvent(info.iItem, info.iSubItem, MouseButton.left, MouseButtonLinear.left, info.ptAction.x, info.ptAction.y, !!(info.uKeyFlags & LVKF_ALT), !!(info.uKeyFlags & LVKF_CONTROL), !!(info.uKeyFlags & LVKF_SHIFT), true);
break;
case NM_RCLICK: case NM_RCLICK:
NMITEMACTIVATE* info = cast(NMITEMACTIVATE*) hdr;
this.emit!CellClickedEvent(info.iItem, info.iSubItem, MouseButton.right, MouseButtonLinear.left, info.ptAction.x, info.ptAction.y, !!(info.uKeyFlags & LVKF_ALT), !!(info.uKeyFlags & LVKF_CONTROL), !!(info.uKeyFlags & LVKF_SHIFT), false);
break;
case NM_RDBLCLK: case NM_RDBLCLK:
// the item/subitem is set here and that can be a useful notification NMITEMACTIVATE* info = cast(NMITEMACTIVATE*) hdr;
// even beyond the normal click notification this.emit!CellClickedEvent(info.iItem, info.iSubItem, MouseButton.right, MouseButtonLinear.left, info.ptAction.x, info.ptAction.y, !!(info.uKeyFlags & LVKF_ALT), !!(info.uKeyFlags & LVKF_CONTROL), !!(info.uKeyFlags & LVKF_SHIFT), true);
break; break;
case LVN_GETDISPINFO: case LVN_GETDISPINFO:
LV_DISPINFO* info = cast(LV_DISPINFO*) hdr; LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
@ -10048,6 +10478,7 @@ class TableView : Widget {
return 0; return 0;
} }
// FIXME: this throws off mouse calculations, it should only happen when we're at the top level or something idk
override bool encapsulatedChildren() { override bool encapsulatedChildren() {
return true; return true;
} }
@ -10120,7 +10551,28 @@ class TableView : Widget {
this.backgroundColor = backgroundColor; this.backgroundColor = backgroundColor;
this.flags |= Flags.textColorSet | Flags.backgroundColorSet; this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
} }
/++
Alignment is only supported on some platforms.
+/
this(TextAlignment alignment) {
this.alignment = alignment;
this.flags |= Flags.alignmentSet;
}
/// ditto
this(TextAlignment alignment, Color textColor) {
this.alignment = alignment;
this.textColor = textColor;
this.flags |= Flags.alignmentSet | Flags.textColorSet;
}
/// ditto
this(TextAlignment alignment, Color textColor, Color backgroundColor) {
this.alignment = alignment;
this.textColor = textColor;
this.backgroundColor = backgroundColor;
this.flags |= Flags.alignmentSet | Flags.textColorSet | Flags.backgroundColorSet;
}
TextAlignment alignment;
Color textColor; Color textColor;
Color backgroundColor; Color backgroundColor;
int flags; /// bitmask of [Flags] int flags; /// bitmask of [Flags]
@ -10128,6 +10580,7 @@ class TableView : Widget {
enum Flags { enum Flags {
textColorSet = 1 << 0, textColorSet = 1 << 0,
backgroundColorSet = 1 << 1, backgroundColorSet = 1 << 1,
alignmentSet = 1 << 2,
} }
} }
/++ /++
@ -10148,9 +10601,15 @@ class TableView : Widget {
// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell; // void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
/++ /++
When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked. When the user clicks on a header, this event is emitted. It has a member to identify which header (by index) was clicked.
+/ +/
mixin Emits!HeaderClickedEvent; mixin Emits!HeaderClickedEvent;
/++
History:
Added March 2, 2025
+/
mixin Emits!CellClickedEvent;
} }
/++ /++
@ -10181,6 +10640,54 @@ final class HeaderClickedEvent : Event {
} }
} }
/++
History:
Added March 2, 2025
+/
final class CellClickedEvent : MouseEventBase {
enum EventString = "CellClicked";
this(Widget target, int rowIndex, int columnIndex, MouseButton button, MouseButtonLinear mouseButtonLinear, int x, int y, bool altKey, bool ctrlKey, bool shiftKey, bool isDoubleClick) {
this.rowIndex = rowIndex;
this.columnIndex = columnIndex;
this.button = button;
this.buttonLinear = mouseButtonLinear;
this.isDoubleClick = isDoubleClick;
this.clientX = x;
this.clientY = y;
this.altKey = altKey;
this.ctrlKey = ctrlKey;
this.shiftKey = shiftKey;
// import std.stdio; std.stdio.writeln(rowIndex, "x", columnIndex, " @ ", x, ",", y, " ", button, " ", isDoubleClick, " ", altKey, " ", ctrlKey, " ", shiftKey);
// FIXME: x, y, state, altButton etc?
super(EventString, target);
}
/++
See also: [button] inherited from the base class.
clientX and clientY are irrespective of scrolling - FIXME is that sane?
+/
int columnIndex;
/// ditto
int rowIndex;
/// ditto
bool isDoubleClick;
/+
// i could do intValue as a linear index if we know the width
// and a stringValue with the string in the cell. but idk if worth.
override @property int intValue() {
return columnIndex;
}
+/
}
version(custom_widgets) version(custom_widgets)
private class TableViewWidgetInner : Widget { private class TableViewWidgetInner : Widget {
@ -10214,8 +10721,7 @@ private class TableViewWidgetInner : Widget {
void updateScrolls() { void updateScrolls() {
int w; int w;
foreach(idx, column; tvw.columns) { foreach(idx, column; tvw.columns) {
if(column.width == 0) continue; w += column.calculatedWidth;
w += tvw.getActualSetSize(idx, false);// + padding;
} }
smw.setTotalArea(w, tvw.itemCount); smw.setTotalArea(w, tvw.itemCount);
columnsWidth = w; columnsWidth = w;
@ -10256,10 +10762,13 @@ private class TableViewWidgetInner : Widget {
} }
if(column.width != 0) // no point drawing an invisible column if(column.width != 0) // no point drawing an invisible column
tvw.getData(row, cast(int) columnNumber, (in char[] info) { tvw.getData(row, cast(int) columnNumber, (in char[] info) {
auto clip = painter.setClipRectangle(Rectangle(Point(startX - smw.position.x, y), Point(endX - smw.position.x, y + lh))); auto endClip = endX - smw.position.x;
if(endClip > this.width - padding)
endClip = this.width - padding;
auto clip = painter.setClipRectangle(Rectangle(Point(startX - smw.position.x, y), Point(endClip, y + lh)));
void dotext(WidgetPainter painter) { void dotext(WidgetPainter painter, TextAlignment alignment) {
painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment); painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x - padding, y + lh), alignment);
} }
if(tvw.getCellStyle !is null) { if(tvw.getCellStyle !is null) {
@ -10277,9 +10786,12 @@ private class TableViewWidgetInner : Widget {
if(style.flags & TableView.CellStyle.Flags.textColorSet) if(style.flags & TableView.CellStyle.Flags.textColorSet)
tempPainter.outlineColor = style.textColor; tempPainter.outlineColor = style.textColor;
dotext(tempPainter); auto alignment = column.alignment;
if(style.flags & TableView.CellStyle.Flags.alignmentSet)
alignment = style.alignment;
dotext(tempPainter, alignment);
} else { } else {
dotext(painter); dotext(painter, column.alignment);
} }
}); });
} }
@ -10327,6 +10839,10 @@ private class TableViewWidgetInner : Widget {
}); });
} }
override int minHeight() {
return defaultLineHeight + 4; // same as Button
}
void updateHeaders() { void updateHeaders() {
foreach(child; children[1 .. $]) foreach(child; children[1 .. $])
child.removeWidget(); child.removeWidget();
@ -10376,8 +10892,55 @@ private class TableViewWidgetInner : Widget {
} }
void paintFrameAndBackground(WidgetPainter painter) { } void paintFrameAndBackground(WidgetPainter painter) { }
// for mouse event dispatching
override protected void addScrollPosition(ref int x, ref int y) {
x += scrollOrigin.x;
y += scrollOrigin.y;
}
mixin ScrollableChildren; mixin ScrollableChildren;
} }
private void emitCellClickedEvent(scope MouseEventBase event, bool isDoubleClick) {
int mx = event.clientX + smw.position.x;
int my = event.clientY;
Widget par = this;
while(par && !par.encapsulatedChildren) {
my -= par.y; // to undo the encapsulatedChildren adjustClientCoordinates effect
par = par.parent;
}
if(par is null)
my = event.clientY; // encapsulatedChildren not present?
int row = my / lh + smw.position.y; // scrolling here is done per-item, not per pixel
if(row > tvw.itemCount)
row = -1;
int column = -1;
if(row != -1) {
int pos;
foreach(idx, col; tvw.columns) {
pos += col.calculatedWidth;
if(mx < pos) {
column = cast(int) idx;
break;
}
}
}
// wtf are these casts about?
tvw.emit!CellClickedEvent(row, column, cast(MouseButton) event.button, cast(MouseButtonLinear) event.buttonLinear, event.clientX, event.clientY, event.altKey, event.ctrlKey, event.shiftKey, isDoubleClick);
}
override void defaultEventHandler_click(scope ClickEvent ce) {
// FIXME: should i filter mouse wheel events? Windows doesn't send them but i can.
emitCellClickedEvent(ce, false);
}
override void defaultEventHandler_dblclick(scope DoubleClickEvent ce) {
emitCellClickedEvent(ce, true);
}
} }
/+ /+
@ -11410,6 +11973,7 @@ class MenuBar : Widget {
sb.parts[0].content = "Status bar text!"; sb.parts[0].content = "Status bar text!";
*/ */
// https://learn.microsoft.com/en-us/windows/win32/controls/status-bars#owner-drawn-status-bars
class StatusBar : Widget { class StatusBar : Widget {
private Part[] partsArray; private Part[] partsArray;
/// ///
@ -12106,6 +12670,7 @@ class Menu : Window {
} }
dropDown = new SimpleWindow( dropDown = new SimpleWindow(
150, 4, 150, 4,
// FIXME: what if it is a popupMenu ?
null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null); null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
this.label = label; this.label = label;
@ -13924,8 +14489,8 @@ class TextDisplayHelper : Widget {
auto cs = getComputedStyle(); auto cs = getComputedStyle();
auto defaultColor = cs.foregroundColor; auto defaultColor = cs.foregroundColor;
auto old = painter.setClipRectangle(bounds); auto old = painter.setClipRectangleForWidget(bounds.upperLeft, bounds.width, bounds.height);
scope(exit) painter.setClipRectangle(old); scope(exit) painter.setClipRectangleForWidget(old.upperLeft, old.width, old.height);
l.getDrawableText(delegate bool(txt, style, info, carets...) { l.getDrawableText(delegate bool(txt, style, info, carets...) {
//writeln("Segment: ", txt); //writeln("Segment: ", txt);
@ -13990,6 +14555,17 @@ class TextDisplayHelper : Widget {
override OperatingSystemFont font() { override OperatingSystemFont font() {
return font_; return font_;
} }
bool foregroundColorOverridden;
bool backgroundColorOverridden;
Color foregroundColor;
Color backgroundColor; // should this be inline segment or the whole paragraph block?
bool italic;
bool bold;
bool underline;
bool strikeout;
bool subscript;
bool superscript;
} }
static class MyImageStyle : TextStyle, MeasurableFont { static class MyImageStyle : TextStyle, MeasurableFont {
@ -15207,7 +15783,7 @@ alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
This is an opaque type you can use to disconnect an event handler when you're no longer interested. This is an opaque type you can use to disconnect an event handler when you're no longer interested.
History: History:
The data members were `public` (albiet undocumented and not intended for use) prior to May 13, 2021. They are now `private`, reflecting the single intended use of this object. The data members were `public` (albeit undocumented and not intended for use) prior to May 13, 2021. They are now `private`, reflecting the single intended use of this object.
+/ +/
struct EventListener { struct EventListener {
private Widget widget; private Widget widget;
@ -15217,7 +15793,8 @@ struct EventListener {
/// ///
void disconnect() { void disconnect() {
widget.removeEventListener(this); if(widget !is null && handler !is null)
widget.removeEventListener(this);
} }
} }
@ -15569,8 +16146,6 @@ class Event : ReflectableProperties {
private bool isBubbling; private bool isBubbling;
/// This is an internal implementation detail you should not use. It would be private if the language allowed it and it may be removed without notice. /// This is an internal implementation detail you should not use. It would be private if the language allowed it and it may be removed without notice.
protected void adjustScrolling() { }
/// ditto
protected void adjustClientCoordinates(int deltaX, int deltaY) { } protected void adjustClientCoordinates(int deltaX, int deltaY) { }
/++ /++
@ -15588,8 +16163,6 @@ class Event : ReflectableProperties {
//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools) //debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement); //target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
adjustScrolling();
if(auto e = target.parentWindow) { if(auto e = target.parentWindow) {
if(auto handlers = "*" in e.capturingEventHandlers) if(auto handlers = "*" in e.capturingEventHandlers)
foreach(handler; *handlers) foreach(handler; *handlers)
@ -15628,7 +16201,6 @@ class Event : ReflectableProperties {
//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools) //debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement); //target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
adjustScrolling();
// first capture, then bubble // first capture, then bubble
Widget[] chain; Widget[] chain;
@ -16153,20 +16725,6 @@ abstract class MouseEventBase : Event {
clientY += deltaY; clientY += deltaY;
} }
override void adjustScrolling() {
version(custom_widgets) { // TEMP
viewportX = clientX;
viewportY = clientY;
if(auto se = cast(ScrollableWidget) srcElement) {
clientX += se.scrollOrigin.x;
clientY += se.scrollOrigin.y;
} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
//clientX += se.scrollX_;
//clientY += se.scrollY_;
}
}
}
mixin Register; mixin Register;
} }
@ -17510,6 +18068,33 @@ struct tip { string tip; }
/// ///
/// Group: generating_from_code /// Group: generating_from_code
enum context_menu = menu.init; enum context_menu = menu.init;
/++
// FIXME: the options should have both a label and a value
if label is null, it will try to just stringify value.
if type is int or size_t and it returns a string array, we can use the index but this will implicitly not allow custom, even if allowCustom is set.
+/
/// Group: generating_from_code
Choices!T choices(T)(T[] options, bool allowCustom = false, bool allowReordering = true, bool allowDuplicates = true) {
return Choices!T(() => options, allowCustom, allowReordering, allowDuplicates);
}
/// ditto
Choices!T choices(T)(T[] delegate() options, bool allowCustom = false, bool allowReordering = true, bool allowDuplicates = true) {
return Choices!T(options, allowCustom, allowReordering, allowDuplicates);
}
/// ditto
struct Choices(T) {
///
T[] delegate() options;
bool allowCustom = false;
/// only relevant if attached to an array
bool allowReordering = true;
/// ditto
bool allowDuplicates = true;
/// makes no sense on a set
bool requireAll = false;
}
/++ /++
@ -18334,23 +18919,28 @@ void addWhenTriggered(Widget w, void delegate() dg) {
} }
/++ /++
Observable varables can be added to widgets and when they are changed, it fires Observable variables can be added to widgets and when they are changed, it fires
off a [StateChanged] event so you can react to it. off a [StateChanged] event so you can react to it.
It is implemented as a getter and setter property, along with another helper you It is implemented as a getter and setter property, along with another helper you
can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged] can use to subscribe with is `name_changed`. You can also subscribe to the [StateChanged]
event through the usual means. Just give the name of the variable. See [StateChanged] for an event through the usual means. Just give the name of the variable. See [StateChanged] for an
example. example.
To get an `ObservableReference` to the observable, use `&yourname_changed`.
History: History:
Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4) Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
As of March 5, 2025, the changed function now returns an [EventListener] handle, which
you can use to disconnect the observer.
+/ +/
mixin template Observable(T, string name) { mixin template Observable(T, string name) {
private T backing; private T backing;
mixin(q{ mixin(q{
void } ~ name ~ q{_changed (void delegate(T) dg) { EventListener } ~ name ~ q{_changed (void delegate(T) dg) {
this.addEventListener((StateChanged!this_thing ev) { return this.addEventListener((StateChanged!this_thing ev) {
dg(ev.newValue); dg(ev.newValue);
}); });
} }
@ -18369,6 +18959,8 @@ mixin template Observable(T, string name) {
mixin("private alias this_thing = " ~ name ~ ";"); mixin("private alias this_thing = " ~ name ~ ";");
} }
/// ditto
alias ObservableReference(T) = EventListener delegate(void delegate(T));
private bool startsWith(string test, string thing) { private bool startsWith(string test, string thing) {
if(test.length < thing.length) if(test.length < thing.length)

12
rtf.d
View File

@ -96,6 +96,9 @@ unittest {
// writeln(toPlainText(document)); // writeln(toPlainText(document));
} }
/++
Returns a plan text string that represents the jist of the document's content.
+/
string toPlainText(RtfDocument document) { string toPlainText(RtfDocument document) {
string ret; string ret;
document.process((piece, ref state) { document.process((piece, ref state) {
@ -243,6 +246,9 @@ private string parseRtfText(ref const(ubyte)[] s) {
// \t is read but you should use \tab generally // \t is read but you should use \tab generally
// when reading, ima translate the ascii tab to \tab control word // when reading, ima translate the ascii tab to \tab control word
// and ignore // and ignore
/++
A union of entities you can see while parsing a RTF file.
+/
struct RtfPiece { struct RtfPiece {
/++ /++
+/ +/
@ -301,6 +307,9 @@ struct RtfPiece {
} }
// a \word thing // a \word thing
/++
A control word directly from the RTF file format.
+/
struct RtfControlWord { struct RtfControlWord {
bool hadSpaceAtEnd; bool hadSpaceAtEnd;
bool hadNumber; bool hadNumber;
@ -371,6 +380,9 @@ private bool isAlpha(char c) {
} }
// a { ... } thing // a { ... } thing
/++
A group directly from the RTF file.
+/
struct RtfGroup { struct RtfGroup {
RtfPiece[] pieces; RtfPiece[] pieces;

View File

@ -1162,7 +1162,16 @@ unittest {
import arsd.core; import arsd.core;
version(OSX) version(DigitalMars) version=OSXCocoa; version(D_OpenD) {
version(OSX)
version=OSXCocoa;
version(iOS)
version=OSXCocoa;
} else {
version(OSX) version(DigitalMars) version=OSXCocoa;
}
version(Emscripten) { version(Emscripten) {
version=allow_unimplemented_features; version=allow_unimplemented_features;
@ -18686,10 +18695,10 @@ struct Visual
SimpleWindow simpleWindow; SimpleWindow simpleWindow;
override static SDGraphicsView alloc() @selector("alloc"); override static SDGraphicsView alloc() @selector("alloc");
override SDGraphicsView init() @selector("init") { override SDGraphicsView init() @selector("init");/* {
super.init(); super.init();
return this; return this;
} }*/
override void drawRect(NSRect rect) @selector("drawRect:") { override void drawRect(NSRect rect) @selector("drawRect:") {
auto curCtx = NSGraphicsContext.currentContext.graphicsPort; auto curCtx = NSGraphicsContext.currentContext.graphicsPort;

View File

@ -36,6 +36,9 @@ Each cell ends with a tab character. A column block is a run of uninterrupted ve
// want to support PS (new paragraph), LS (forced line break), FF (next page) // want to support PS (new paragraph), LS (forced line break), FF (next page)
// and GS = <table> RS = <tr> US = <td> FS = </table> maybe. // and GS = <table> RS = <tr> US = <td> FS = </table> maybe.
// use \a bell for bookmarks in the text?
// note: ctrl+c == ascii 3 and ctrl+d == ascii 4 == end of text
// FIXME: maybe i need another overlay of block style not just text style. list, alignment, heading, paragraph spacing, etc. should it nest? // FIXME: maybe i need another overlay of block style not just text style. list, alignment, heading, paragraph spacing, etc. should it nest?
@ -130,6 +133,8 @@ import arsd.simpledisplay;
// You can do the caret by any time it gets drawn, you set the flag that it is on, then you can xor it to turn it off and keep track of that at top level. // You can do the caret by any time it gets drawn, you set the flag that it is on, then you can xor it to turn it off and keep track of that at top level.
// FIXME: might want to be able to swap out all styles at once and trigger whole relayout, as if a document theme changed wholesale, without changing the saved style handles
// FIXME: line and paragrpah numbering options while drawing
/++ /++
Represents the style of a span of text. Represents the style of a span of text.
@ -143,6 +148,39 @@ interface TextStyle {
+/ +/
MeasurableFont font(); MeasurableFont font();
/++
History:
Added February 24, 2025
+/
//ParagraphMetrics paragraphMetrics();
// FIXME: list styles?
// FIXME: table styles?
/// ditto
static struct ParagraphMetrics {
/++
Extra spacing between each line, given in physical pixels.
+/
int lineSpacing;
/++
Spacing between each paragraph, given in physical pixels.
+/
int paragraphSpacing;
/++
Extra indentation on the first line of each paragraph, given in physical pixels.
+/
int paragraphIndentation;
// margin left and right?
/++
Note that TextAlignment.Left might be redefined to mean "Forward", meaning left if left-to-right, right if right-to-left,
but right now it ignores bidi anyway.
+/
TextAlignment alignment = TextAlignment.Left;
}
// FIXME: I might also want a duplicate function for saving state. // FIXME: I might also want a duplicate function for saving state.
// verticalAlign? // verticalAlign?
@ -167,6 +205,13 @@ interface TextStyle {
return TerminalFontRepresentation.instance; return TerminalFontRepresentation.instance;
} }
/++
The default returns reasonable values, you might want to call this to get the defaults,
then change some values and return the rest.
+/
ParagraphMetrics paragraphMetrics() {
return ParagraphMetrics.init;
}
} }
} }

1198
xlsx.d

File diff suppressed because it is too large Load Diff