catchup changes

This commit is contained in:
Adam D. Ruppe 2017-04-19 19:24:42 -04:00
parent d7c19998c5
commit 5f7dcb8e44
4 changed files with 1040 additions and 189 deletions

960
minigui.d

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,69 @@
--- ---
import arsd.minigui_xml; import arsd.minigui_xml;
Window window = createWindow(` Window window = createWindowFromXml(`
<MainWindow> <MainWindow>
<Button label="Hi!" /> <Button label="Hi!" />
</MainWindow> </MainWindow>
`); `);
--- ---
To add custom widgets to the minigui_xml factory, you need
to register them with FIXME.
You can attach some events right in the XML using attributes.
The attribute names are `onEVENTNAME` or `ondirectEVENTNAME`
and the values are one of the following three value types:
$(LIST
* If it starts with `&`, it is a delegate you need
to register using the FIXME function.
* If it starts with `(`, it is a string passed to
the [arsd.dom.querySelector] function to get an
element reference
* Otherwise, it tries to call a script function (if
scripting is available).
)
Keep in mind
For example, to make a page widget that changes based on a
drop down selection, you may:
```xml
<DropDownSelection onchange="$(+PageWidget).setCurrentTab">
<option>Foo</option>
<option>Bar</option>
</DropDownSelection>
<PageWidget name="mypage">
<!-- contents elided -->
</PageWidget>
```
That will create a select widget that when it changes, it will
look for the next PageWidget sibling (that's the meaning of `+PageWidget`,
see css selector syntax for more) and call its `setCurrentTab`
method.
Since the function knows `setCurrentTab` takes an integer, it will
automatically pull the `intValue` member out of the event and pass
it to the method.
The given XML is the same as the following D:
---
auto select = new DropDownSelection(parent);
select.addOption("Foo");
select.addOption("Bar");
auto page = new PageWidget(parent);
page.name = "mypage";
select.addEventListener("change", (Event event) {
page.setCurrentTab(event.intValue);
});
---
+/ +/
module arsd.minigui_xml; module arsd.minigui_xml;
@ -41,7 +98,7 @@ void loadMiniguiPublicClasses() {
foreach(memberName; __traits(allMembers, mixin("arsd.minigui"))) static if(__traits(compiles, __traits(getMember, mixin("arsd.minigui"), memberName))) { foreach(memberName; __traits(allMembers, mixin("arsd.minigui"))) static if(__traits(compiles, __traits(getMember, mixin("arsd.minigui"), memberName))) {
alias Member = ident!(__traits(getMember, mixin("arsd.minigui"), memberName)); alias Member = ident!(__traits(getMember, mixin("arsd.minigui"), memberName));
static if(is(Member == class) && !isAbstractClass!Member && is(Member : Widget)) { static if(is(Member == class) && !isAbstractClass!Member && is(Member : Widget) && __traits(getProtection, Member) != "private") {
widgetFactoryFunctions[memberName] = (string[string] args, Widget parent) { widgetFactoryFunctions[memberName] = (string[string] args, Widget parent) {
static if(is(Member : Dialog)) { static if(is(Member : Dialog)) {
return new Member(); return new Member();

View File

@ -406,7 +406,7 @@ class TokenStream(TextStream) {
token.lineNumber = lineNumber; token.lineNumber = lineNumber;
token.scriptFilename = scriptFilename; token.scriptFilename = scriptFilename;
if(text[0] == ' ' || text[0] == '\t' || text[0] == '\n') { if(text[0] == ' ' || text[0] == '\t' || text[0] == '\n' || text[0] == '\r') {
advance(1); advance(1);
continue; continue;
} else if(text[0] >= '0' && text[0] <= '9') { } else if(text[0] >= '0' && text[0] <= '9') {
@ -600,7 +600,7 @@ class TokenStream(TextStream) {
if(symbol == "//") { if(symbol == "//") {
// one line comment // one line comment
int pos = 0; int pos = 0;
while(pos < text.length && text[pos] != '\n') while(pos < text.length && text[pos] != '\n' && text[0] != '\r')
pos++; pos++;
advance(pos); advance(pos);
continue mainLoop; continue mainLoop;

View File

@ -942,6 +942,7 @@ enum WindowFlags : int {
alwaysOnTop = 2, /// alwaysOnTop = 2, ///
alwaysOnBottom = 4, /// alwaysOnBottom = 4, ///
cannotBeActivated = 8, /// cannotBeActivated = 8, ///
alwaysRequestMouseMotionEvents = 16, /// By default, simpledisplay will attempt to optimize mouse motion event reporting when it detects a remote connection, causing them to only be issued if input is grabbed (see: [SimpleWindow.grabInput]). This means doing hover effects and mouse game control on a remote X connection may not work right. Include this flag to override this optimization and always request the motion events. However btw, if you are doing mouse game control, you probably want to grab input anyway, and hover events are usually expendable! So think before you use this flag.
dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually. dontAutoShow = 0x1000_0000, /// Don't automatically show window after creation; you will have to call `show()` manually.
} }
@ -3036,7 +3037,7 @@ char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
if(str.length == 0) if(str.length == 0)
return null; return null;
auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, str.length, buffer.ptr, buffer.length, null, null); auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null);
if(got == 0) { if(got == 0) {
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
throw new Exception("not enough buffer"); throw new Exception("not enough buffer");
@ -3049,7 +3050,7 @@ char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) {
version(Windows) version(Windows)
string makeUtf8StringFromWindowsString(in wchar[] str) { string makeUtf8StringFromWindowsString(in wchar[] str) {
char[] buffer; char[] buffer;
auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, str.length, null, 0, null, null); auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null);
buffer.length = got; buffer.length = got;
// it is unique because we just allocated it above! // it is unique because we just allocated it above!
@ -3062,7 +3063,7 @@ string makeUtf8StringFromWindowsString(wchar* str) {
auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null);
buffer.length = got; buffer.length = got;
got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, buffer.length, null, null); got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null);
if(got == 0) { if(got == 0) {
if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
throw new Exception("not enough buffer"); throw new Exception("not enough buffer");
@ -4306,9 +4307,9 @@ class OperatingSystemFont {
struct ScreenPainter { struct ScreenPainter {
CapableOfBeingDrawnUpon window; CapableOfBeingDrawnUpon window;
this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) { this(CapableOfBeingDrawnUpon window, NativeWindowHandle handle) {
if(window.closed)
throw new Exception("cannot draw on a closed window");
this.window = window; this.window = window;
if(window.closed)
return; // null painter is now allowed so no need to throw anymore, this likely happens at the end of a program anyway
currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height); currentClipRectangle = arsd.color.Rectangle(0, 0, window.width, window.height);
if(window.activeScreenPainter !is null) { if(window.activeScreenPainter !is null) {
impl = window.activeScreenPainter; impl = window.activeScreenPainter;
@ -4405,6 +4406,8 @@ struct ScreenPainter {
/// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping. /// Sets the clipping region for drawing. If width == 0 && height == 0, disabled clipping.
void setClipRectangle(Point pt, int width, int height) { void setClipRectangle(Point pt, int width, int height) {
if(impl is null) return; if(impl is null) return;
if(pt == currentClipRectangle.upperLeft && width == currentClipRectangle.width && height == currentClipRectangle.height)
return; // no need to do anything
currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height)); currentClipRectangle = arsd.color.Rectangle(pt, Size(width, height));
transform(pt); transform(pt);
@ -6680,12 +6683,17 @@ version(X11) {
bool backgroundIsNotTransparent = true; bool backgroundIsNotTransparent = true;
bool foregroundIsNotTransparent = true; bool foregroundIsNotTransparent = true;
bool _penInitialized = false;
Pen _activePen; Pen _activePen;
Color _outlineColor; Color _outlineColor;
Color _fillColor; Color _fillColor;
@property void pen(Pen p) { @property void pen(Pen p) {
if(_penInitialized && p == _activePen) {
return;
}
_penInitialized = true;
_activePen = p; _activePen = p;
_outlineColor = p.color; _outlineColor = p.color;
@ -6724,7 +6732,13 @@ version(X11) {
cast(uint) p.color.b); cast(uint) p.color.b);
} }
RasterOp _currentRasterOp;
bool _currentRasterOpInitialized = false;
@property void rasterOp(RasterOp op) { @property void rasterOp(RasterOp op) {
if(_currentRasterOpInitialized && _currentRasterOp == op)
return;
_currentRasterOp = op;
_currentRasterOpInitialized = true;
int mode; int mode;
final switch(op) { final switch(op) {
case RasterOp.normal: case RasterOp.normal:
@ -6738,11 +6752,13 @@ version(X11) {
} }
bool _fillColorInitialized = false;
@property void fillColor(Color c) { @property void fillColor(Color c) {
if(_fillColor == c) if(_fillColorInitialized && _fillColor == c)
return; // already good, no need to waste time calling it return; // already good, no need to waste time calling it
_fillColor = c; _fillColor = c;
_fillColorInitialized = true;
if(c.a == 0) { if(c.a == 0) {
backgroundIsNotTransparent = false; backgroundIsNotTransparent = false;
return; return;
@ -6760,8 +6776,9 @@ version(X11) {
void swapColors() { void swapColors() {
auto tmp = _fillColor; auto tmp = _fillColor;
fillColor = _outlineColor; fillColor = _outlineColor;
_activePen.color = tmp; auto newPen = _activePen;
pen(_activePen); newPen.color = tmp;
pen(newPen);
} }
void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) { void drawImage(int x, int y, Image i, int ix, int iy, int w, int h) {
@ -7728,18 +7745,8 @@ version(X11) {
// What would be ideal here is if they only were // What would be ideal here is if they only were
// selected if there was actually an event handler // selected if there was actually an event handler
// for them... // for them...
XSelectInput(display, window,
EventMask.ExposureMask | selectDefaultInput((customizationFlags & WindowFlags.alwaysRequestMouseMotionEvents)?true:false);
EventMask.KeyPressMask |
EventMask.KeyReleaseMask |
EventMask.PropertyChangeMask |
EventMask.FocusChangeMask |
EventMask.StructureNotifyMask |
EventMask.VisibilityChangeMask
| EventMask.PointerMotionMask // FIXME: not efficient
| EventMask.ButtonPressMask
| EventMask.ButtonReleaseMask
);
hiddenWindow: hiddenWindow:
@ -7766,6 +7773,26 @@ version(X11) {
} }
} }
void selectDefaultInput(bool forceIncludeMouseMotion) {
auto mask = EventMask.ExposureMask |
EventMask.KeyPressMask |
EventMask.KeyReleaseMask |
EventMask.PropertyChangeMask |
EventMask.FocusChangeMask |
EventMask.StructureNotifyMask |
EventMask.VisibilityChangeMask
| EventMask.ButtonPressMask
| EventMask.ButtonReleaseMask
;
// xshm is our shortcut for local connections
if(Image.impl.xshmAvailable || forceIncludeMouseMotion)
mask |= EventMask.PointerMotionMask;
XSelectInput(display, window, mask);
}
void setNetWMWindowType(Atom type) { void setNetWMWindowType(Atom type) {
Atom[2] atoms; Atom[2] atoms;
@ -8218,6 +8245,11 @@ version(X11) {
break; break;
case EventType.Expose: case EventType.Expose:
if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) { if(auto win = e.xexpose.window in SimpleWindow.nativeMapping) {
// if it is closing from a popup menu, it can get
// an Expose event right by the end and trigger a
// BadDrawable error ... we'll just check
// closed to handle that.
if((*win).closed) break;
if((*win).openglMode == OpenGlOptions.no) { if((*win).openglMode == OpenGlOptions.no) {
bool doCopy = true; bool doCopy = true;
if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count); if (win.handleExpose !is null) doCopy = !win.handleExpose(e.xexpose.x, e.xexpose.y, e.xexpose.width, e.xexpose.height, e.xexpose.count);
@ -11273,7 +11305,7 @@ mixin template ExperimentalTextComponent() {
struct TextIdentifyResult { struct TextIdentifyResult {
InlineElement element; InlineElement element;
size_t offset; int offset;
private TextIdentifyResult fixupNewline() { private TextIdentifyResult fixupNewline() {
if(element !is null && offset < element.text.length && element.text[offset] == '\n') { if(element !is null && offset < element.text.length && element.text[offset] == '\n') {
@ -11388,7 +11420,7 @@ mixin template ExperimentalTextComponent() {
ie.text = arg[lastLineIndex .. $]; ie.text = arg[lastLineIndex .. $];
ie.containingBlock = blocks[$-1]; ie.containingBlock = blocks[$-1];
blocks[$-1].parts ~= ie.clone; blocks[$-1].parts ~= ie.clone;
caret = Caret(this, blocks[$-1].parts[$-1], blocks[$-1].parts[$-1].text.length); caret = Caret(this, blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length);
} }
} }
@ -11424,9 +11456,10 @@ mixin template ExperimentalTextComponent() {
if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) { if(x >= part.boundingBox.left && x < part.boundingBox.right && y >= part.boundingBox.top && y < part.boundingBox.bottom) {
// FIXME binary search // FIXME binary search
size_t tidx; int tidx;
int lastX; int lastX;
foreach_reverse(idx, lx; part.letterXs) { foreach_reverse(idxo, lx; part.letterXs) {
int idx = cast(int) idxo;
if(lx <= x) { if(lx <= x) {
if(lastX && lastX - x < x - lx) if(lastX && lastX - x < x - lx)
tidx = idx + 1; tidx = idx + 1;
@ -11441,13 +11474,13 @@ mixin template ExperimentalTextComponent() {
} else if(!exact) { } else if(!exact) {
// we're not in the box, but are we on the same line? // we're not in the box, but are we on the same line?
if(y >= part.boundingBox.top && y < part.boundingBox.bottom) if(y >= part.boundingBox.top && y < part.boundingBox.bottom)
inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : part.text.length); inexactMatch = TextIdentifyResult(part, x == 0 ? 0 : cast(int) part.text.length);
} }
} }
} }
if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length) if(!exact && inexactMatch is TextIdentifyResult.init && blocks.length && blocks[$-1].parts.length)
return TextIdentifyResult(blocks[$-1].parts[$-1], blocks[$-1].parts[$-1].text.length).fixupNewline; return TextIdentifyResult(blocks[$-1].parts[$-1], cast(int) blocks[$-1].parts[$-1].text.length).fixupNewline;
return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline; return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
} }
@ -11458,6 +11491,33 @@ mixin template ExperimentalTextComponent() {
caret.offset = result.offset; caret.offset = result.offset;
} }
void selectToPixelCoordinates(int x, int y) {
auto result = identify(x, y);
if(y < caretLastDrawnY1) {
// on a previous line, carat is selectionEnd
selectionEnd = caret;
selectionStart = Caret(this, result.element, result.offset);
} else if(y > caretLastDrawnY2) {
// on a later line
selectionStart = caret;
selectionEnd = Caret(this, result.element, result.offset);
} else {
// on the same line...
if(x <= caretLastDrawnX) {
selectionEnd = caret;
selectionStart = Caret(this, result.element, result.offset);
} else {
selectionStart = caret;
selectionEnd = Caret(this, result.element, result.offset);
}
}
}
/// Call this if the inputs change. It will reflow everything /// Call this if the inputs change. It will reflow everything
void redoLayout(ScreenPainter painter) { void redoLayout(ScreenPainter painter) {
//painter.setClipRectangle(boundingBox); //painter.setClipRectangle(boundingBox);
@ -11637,7 +11697,7 @@ mixin template ExperimentalTextComponent() {
/// Caret movement api /// Caret movement api
/// These should give the user a logical result based on what they see on screen... /// These should give the user a logical result based on what they see on screen...
/// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!) /// thus they locate predominately by *pixels* not char index. (These will generally coincide with monospace fonts tho!)
void moveUp(ref Caret caret) { void moveUp() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
auto x = caret.inlineElement.xOfIndex(caret.offset); auto x = caret.inlineElement.xOfIndex(caret.offset);
auto y = caret.inlineElement.boundingBox.top + 2; auto y = caret.inlineElement.boundingBox.top + 2;
@ -11653,7 +11713,7 @@ mixin template ExperimentalTextComponent() {
caret.offset = i.offset; caret.offset = i.offset;
} }
} }
void moveDown(ref Caret caret) { void moveDown() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
auto x = caret.inlineElement.xOfIndex(caret.offset); auto x = caret.inlineElement.xOfIndex(caret.offset);
auto y = caret.inlineElement.boundingBox.bottom - 2; auto y = caret.inlineElement.boundingBox.bottom - 2;
@ -11666,7 +11726,7 @@ mixin template ExperimentalTextComponent() {
caret.offset = i.offset; caret.offset = i.offset;
} }
} }
void moveLeft(ref Caret caret) { void moveLeft() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
if(caret.offset) if(caret.offset)
caret.offset--; caret.offset--;
@ -11675,13 +11735,13 @@ mixin template ExperimentalTextComponent() {
if(p) { if(p) {
caret.inlineElement = p; caret.inlineElement = p;
if(p.text.length && p.text[$-1] == '\n') if(p.text.length && p.text[$-1] == '\n')
caret.offset = p.text.length - 1; caret.offset = cast(int) p.text.length - 1;
else else
caret.offset = p.text.length; caret.offset = cast(int) p.text.length;
} }
} }
} }
void moveRight(ref Caret caret) { void moveRight() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') { if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
caret.offset++; caret.offset++;
@ -11693,7 +11753,7 @@ mixin template ExperimentalTextComponent() {
} }
} }
} }
void moveHome(ref Caret caret) { void moveHome() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
auto x = 0; auto x = 0;
auto y = caret.inlineElement.boundingBox.top + 2; auto y = caret.inlineElement.boundingBox.top + 2;
@ -11705,7 +11765,7 @@ mixin template ExperimentalTextComponent() {
caret.offset = i.offset; caret.offset = i.offset;
} }
} }
void moveEnd(ref Caret caret) { void moveEnd() {
if(caret.inlineElement is null) return; if(caret.inlineElement is null) return;
auto x = int.max; auto x = int.max;
auto y = caret.inlineElement.boundingBox.top + 2; auto y = caret.inlineElement.boundingBox.top + 2;
@ -11732,7 +11792,7 @@ mixin template ExperimentalTextComponent() {
if(blocks.length) { if(blocks.length) {
auto parts = blocks[$-1].parts; auto parts = blocks[$-1].parts;
if(parts.length) { if(parts.length) {
caret = Caret(this, parts[$-1], parts[$-1].text.length); caret = Caret(this, parts[$-1], cast(int) parts[$-1].text.length);
} else { } else {
caret = Caret.init; caret = Caret.init;
} }
@ -11748,33 +11808,42 @@ mixin template ExperimentalTextComponent() {
assert(selectionEnd.inlineElement !is null); assert(selectionEnd.inlineElement !is null);
auto at = selectionStart.inlineElement; auto at = selectionStart.inlineElement;
auto atOffset = selectionStart.offset;
while(at) {
at.text = at.text[atOffset .. $];
if(atOffset < at.letterXs.length)
at.letterXs = at.letterXs[atOffset .. $];
if(at is selectionEnd.inlineElement) { if(selectionEnd.inlineElement is at) {
selectionEnd.offset -= atOffset; // same element, need to chop out
at.text = at.text[selectionEnd.offset .. $]; at.text = at.text[0 .. selectionStart.offset] ~ at.text[selectionEnd.offset .. $];
if(selectionEnd.offset < at.letterXs.length) at.letterXs = at.letterXs[0 .. selectionStart.offset] ~ at.letterXs[selectionEnd.offset .. $];
at.letterXs = at.letterXs[selectionEnd.offset .. $]; selectionEnd.offset -= selectionEnd.offset - selectionStart.offset;
selectionEnd.offset = 0; } else {
break; // different elements, we can do it with slicing
} else { at.text = at.text[0 .. selectionStart.offset];
auto cfd = at; if(selectionStart.offset < at.letterXs.length)
at.letterXs = at.letterXs[0 .. selectionStart.offset];
at = at.getNextInlineElement(); at = at.getNextInlineElement();
if(at)
atOffset = at.text.length;
if(cfd.text.length == 0) {
// and remove cfd
for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
if(cfd.containingBlock.parts[a] is cfd) {
for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
while(at) {
if(at is selectionEnd.inlineElement) {
at.text = at.text[selectionEnd.offset .. $];
if(selectionEnd.offset < at.letterXs.length)
at.letterXs = at.letterXs[selectionEnd.offset .. $];
selectionEnd.offset = 0;
break;
} else {
auto cfd = at;
cfd.text = null; // delete the whole thing
at = at.getNextInlineElement();
if(cfd.text.length == 0) {
// and remove cfd
for(size_t a = 0; a < cfd.containingBlock.parts.length; a++) {
if(cfd.containingBlock.parts[a] is cfd) {
for(size_t i = a; i < cfd.containingBlock.parts.length - 1; i++)
cfd.containingBlock.parts[i] = cfd.containingBlock.parts[i + 1];
cfd.containingBlock.parts = cfd.containingBlock.parts[0 .. $-1];
}
} }
} }
} }
@ -11886,7 +11955,7 @@ mixin template ExperimentalTextComponent() {
auto prev = e.getPreviousInlineElement(); auto prev = e.getPreviousInlineElement();
if(prev is null) if(prev is null)
return; return;
auto newOffset = prev.text.length; auto newOffset = cast(int) prev.text.length;
tryMerge(prev, e); tryMerge(prev, e);
caret.inlineElement = prev; caret.inlineElement = prev;
caret.offset = prev is null ? 0 : newOffset; caret.offset = prev is null ? 0 : newOffset;
@ -11904,11 +11973,14 @@ mixin template ExperimentalTextComponent() {
invalidateLayout(); invalidateLayout();
} }
void delete_() { void delete_() {
auto after = caret; if(selectionStart !is selectionEnd)
moveRight(after); deleteSelection();
if(caret != after) { else {
caret = after; auto before = caret;
backspace(); moveRight();
if(caret != before) {
backspace();
}
} }
invalidateLayout(); invalidateLayout();
@ -11963,7 +12035,7 @@ mixin template ExperimentalTextComponent() {
struct Caret { struct Caret {
TextLayout layout; TextLayout layout;
InlineElement inlineElement; InlineElement inlineElement;
size_t offset; int offset;
} }
enum TextFormat : ushort { enum TextFormat : ushort {