mirror of https://github.com/adamdruppe/arsd.git
more text improvements
This commit is contained in:
parent
9deb9d0801
commit
11a5c1e43f
79
minigui.d
79
minigui.d
|
@ -306,6 +306,8 @@ class FreeEntrySelection : ComboboxBase {
|
|||
|
||||
tabStop = false;
|
||||
|
||||
lineEdit.addEventListener("focus", &lineEdit.selectAll);
|
||||
|
||||
auto btn = new class ArrowButton {
|
||||
this() {
|
||||
super(ArrowDirection.down, hl);
|
||||
|
@ -365,6 +367,8 @@ class ComboBox : ComboboxBase {
|
|||
redraw();
|
||||
});
|
||||
|
||||
lineEdit.addEventListener("focus", &lineEdit.selectAll);
|
||||
|
||||
listWidget.addDirectEventListener(EventType.change, {
|
||||
int set = -1;
|
||||
foreach(idx, opt; listWidget.options)
|
||||
|
@ -515,13 +519,13 @@ class DataView : Widget {
|
|||
/*
|
||||
TextEdit needs:
|
||||
|
||||
* carat manipulation
|
||||
* caret manipulation
|
||||
* selection control
|
||||
* convenience functions for appendText, insertText, insertTextAtCarat, etc.
|
||||
* convenience functions for appendText, insertText, insertTextAtCaret, etc.
|
||||
|
||||
For example:
|
||||
|
||||
connect(paste, &textEdit.insertTextAtCarat);
|
||||
connect(paste, &textEdit.insertTextAtCaret);
|
||||
|
||||
would be nice.
|
||||
|
||||
|
@ -1895,6 +1899,8 @@ class HorizontalLayout : Layout {
|
|||
int lastMargin = 0;
|
||||
foreach(child; children) {
|
||||
auto mh = child.maxHeight();
|
||||
if(mh == int.max)
|
||||
return int.max;
|
||||
if(mh > largest)
|
||||
largest = mh;
|
||||
margins += mymax(lastMargin, child.marginTop());
|
||||
|
@ -1908,7 +1914,7 @@ class HorizontalLayout : Layout {
|
|||
foreach(child; children) {
|
||||
auto c = child.heightStretchiness;
|
||||
if(c > max)
|
||||
c = max;
|
||||
max = c;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
@ -2384,6 +2390,10 @@ class LabeledLineEdit : Widget {
|
|||
void content(string c) {
|
||||
return lineEdit.content(c);
|
||||
}
|
||||
|
||||
void selectAll() {
|
||||
lineEdit.selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -3563,6 +3573,15 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
override int minHeight() { return Window.lineHeight + 0; } // the +0 is to leave room for the padding
|
||||
override int widthStretchiness() { return 7; }
|
||||
|
||||
void selectAll() {
|
||||
version(win32_widgets)
|
||||
SendMessage(hwnd, EM_SETSEL, 0, -1);
|
||||
else version(custom_widgets) {
|
||||
textLayout.selectAll();
|
||||
redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@property string content() {
|
||||
version(win32_widgets) {
|
||||
char[4096] buffer;
|
||||
|
@ -3602,7 +3621,7 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
else version(custom_widgets) {
|
||||
// FIXME
|
||||
|
||||
Timer caratTimer;
|
||||
Timer caretTimer;
|
||||
TextLayout textLayout;
|
||||
|
||||
void setupCustomTextEditing() {
|
||||
|
@ -3616,38 +3635,38 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
painter.outlineColor = Color.black;
|
||||
// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
|
||||
|
||||
textLayout.caratShowingOnScreen = false;
|
||||
textLayout.caretShowingOnScreen = false;
|
||||
|
||||
textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
|
||||
};
|
||||
|
||||
defaultEventHandlers["click"] = delegate (Widget _this, Event ev) {
|
||||
if(parentWindow.win.closed) return;
|
||||
textLayout.moveCaratToPixelCoordinates(ev.clientX, ev.clientY);
|
||||
textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
|
||||
this.focus();
|
||||
};
|
||||
|
||||
defaultEventHandlers["focus"] = delegate (Widget _this, Event ev) {
|
||||
if(parentWindow.win.closed) return;
|
||||
auto painter = this.draw();
|
||||
textLayout.drawCarat(painter);
|
||||
textLayout.drawCaret(painter);
|
||||
|
||||
if(caratTimer) {
|
||||
caratTimer.destroy();
|
||||
caratTimer = null;
|
||||
if(caretTimer) {
|
||||
caretTimer.destroy();
|
||||
caretTimer = null;
|
||||
}
|
||||
|
||||
caratTimer = new Timer(500, {
|
||||
caretTimer = new Timer(500, {
|
||||
if(parentWindow.win.closed) {
|
||||
caratTimer.destroy();
|
||||
caretTimer.destroy();
|
||||
return;
|
||||
}
|
||||
if(isFocused()) {
|
||||
auto painter = this.draw();
|
||||
textLayout.drawCarat(painter);
|
||||
} else if(textLayout.caratShowingOnScreen) {
|
||||
textLayout.drawCaret(painter);
|
||||
} else if(textLayout.caretShowingOnScreen) {
|
||||
auto painter = this.draw();
|
||||
textLayout.eraseCarat(painter);
|
||||
textLayout.eraseCaret(painter);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3655,10 +3674,10 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
defaultEventHandlers["blur"] = delegate (Widget _this, Event ev) {
|
||||
if(parentWindow.win.closed) return;
|
||||
auto painter = this.draw();
|
||||
textLayout.eraseCarat(painter);
|
||||
if(caratTimer) {
|
||||
caratTimer.destroy();
|
||||
caratTimer = null;
|
||||
textLayout.eraseCaret(painter);
|
||||
if(caretTimer) {
|
||||
caretTimer.destroy();
|
||||
caretTimer = null;
|
||||
}
|
||||
|
||||
auto evt = new Event(EventType.change, this);
|
||||
|
@ -3680,27 +3699,27 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
redraw();
|
||||
break;
|
||||
case Key.Left:
|
||||
textLayout.moveLeft(textLayout.carat);
|
||||
textLayout.moveLeft(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
case Key.Right:
|
||||
textLayout.moveRight(textLayout.carat);
|
||||
textLayout.moveRight(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
case Key.Up:
|
||||
textLayout.moveUp(textLayout.carat);
|
||||
textLayout.moveUp(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
case Key.Down:
|
||||
textLayout.moveDown(textLayout.carat);
|
||||
textLayout.moveDown(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
case Key.Home:
|
||||
textLayout.moveHome(textLayout.carat);
|
||||
textLayout.moveHome(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
case Key.End:
|
||||
textLayout.moveEnd(textLayout.carat);
|
||||
textLayout.moveEnd(textLayout.caret);
|
||||
redraw();
|
||||
break;
|
||||
default:
|
||||
|
@ -3724,8 +3743,10 @@ abstract class EditableTextWidget : ScrollableWidget {
|
|||
///
|
||||
class LineEdit : EditableTextWidget {
|
||||
// FIXME: hack
|
||||
version(custom_widgets) {
|
||||
override bool showingVerticalScroll() { return false; }
|
||||
override bool showingHorizontalScroll() { return false; }
|
||||
}
|
||||
|
||||
this(Widget parent = null) {
|
||||
super(parent);
|
||||
|
@ -4050,7 +4071,6 @@ version(win32_widgets) {
|
|||
// import win32.winuser;
|
||||
|
||||
pragma(lib, "comctl32");
|
||||
|
||||
shared static this() {
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
|
||||
INITCOMMONCONTROLSEX ic;
|
||||
|
@ -4306,6 +4326,9 @@ enum GenericIcons : ushort {
|
|||
Print,
|
||||
}
|
||||
|
||||
version(win32_widgets)
|
||||
pragma(lib, "comdlg32");
|
||||
|
||||
void getOpenFileName(
|
||||
void delegate(string) onOK = null,
|
||||
string prefilledName = null,
|
||||
|
@ -4398,7 +4421,7 @@ class FilePicker : Dialog {
|
|||
okButton.addEventListener(EventType.triggered, &OK);
|
||||
|
||||
this.addEventListener("keydown", (Event event) {
|
||||
if(event.key == Key.Enter)
|
||||
if(event.key == Key.Enter || event.key == Key.PadEnter)
|
||||
OK();
|
||||
if(event.key == Key.Escape)
|
||||
Cancel();
|
||||
|
|
|
@ -124,6 +124,11 @@ class ColorPickerDialog : Dialog {
|
|||
b.content = to!string(current.b);
|
||||
a.content = to!string(current.a);
|
||||
|
||||
r.addEventListener("focus", &r.selectAll);
|
||||
g.addEventListener("focus", &g.selectAll);
|
||||
b.addEventListener("focus", &b.selectAll);
|
||||
a.addEventListener("focus", &a.selectAll);
|
||||
|
||||
|
||||
if(hslImage !is null)
|
||||
wid.addEventListener("mousedown", (Event event) {
|
||||
|
@ -149,7 +154,7 @@ class ColorPickerDialog : Dialog {
|
|||
}
|
||||
|
||||
this.addEventListener("keydown", (Event event) {
|
||||
if(event.key == Key.Enter)
|
||||
if(event.key == Key.Enter || event.key == Key.PadEnter)
|
||||
OK();
|
||||
if(event.key == Key.Escape)
|
||||
Cancel();
|
||||
|
@ -185,6 +190,8 @@ class ColorPickerDialog : Dialog {
|
|||
|
||||
cancelButton.addEventListener(EventType.triggered, &Cancel);
|
||||
okButton.addEventListener(EventType.triggered, &OK);
|
||||
|
||||
r.focus();
|
||||
}
|
||||
|
||||
LabeledLineEdit r;
|
||||
|
|
308
simpledisplay.d
308
simpledisplay.d
|
@ -8,7 +8,7 @@
|
|||
changed on the painter i guess) like font, color, size, background,
|
||||
etc.
|
||||
|
||||
We can also fetch the carat location from it somehow.
|
||||
We can also fetch the caret location from it somehow.
|
||||
|
||||
Should prolly be an overload of drawText
|
||||
|
||||
|
@ -10922,7 +10922,7 @@ mixin template ExperimentalTextComponent() {
|
|||
|
||||
void clear() {
|
||||
blocks = null;
|
||||
carat = Carat.init;
|
||||
caret = Caret.init;
|
||||
}
|
||||
|
||||
void addText(Args...)(Args args) {
|
||||
|
@ -10957,7 +10957,7 @@ mixin template ExperimentalTextComponent() {
|
|||
ie.text = arg[lastLineIndex .. $];
|
||||
ie.containingBlock = blocks[$-1];
|
||||
blocks[$-1].parts ~= ie.clone;
|
||||
carat = Carat(this, blocks[$-1].parts[$-1], blocks[$-1].parts[$-1].text.length);
|
||||
caret = Caret(this, blocks[$-1].parts[$-1], blocks[$-1].parts[$-1].text.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10980,7 +10980,7 @@ mixin template ExperimentalTextComponent() {
|
|||
}
|
||||
}
|
||||
|
||||
// FIXME: ensure no other carats have a reference to it
|
||||
// FIXME: ensure no other carets have a reference to it
|
||||
}
|
||||
|
||||
/// Call this if the inputs change. It will reflow everything
|
||||
|
@ -11024,13 +11024,13 @@ mixin template ExperimentalTextComponent() {
|
|||
return exact ? TextIdentifyResult.init : inexactMatch.fixupNewline;
|
||||
}
|
||||
|
||||
void moveCaratToPixelCoordinates(int x, int y) {
|
||||
void moveCaretToPixelCoordinates(int x, int y) {
|
||||
auto result = identify(x, y);
|
||||
carat.inlineElement = result.element;
|
||||
carat.offset = result.offset;
|
||||
caret.inlineElement = result.element;
|
||||
caret.offset = result.offset;
|
||||
}
|
||||
|
||||
// FIXME: carat can remain sometimes when inserting
|
||||
// FIXME: caret can remain sometimes when inserting
|
||||
// FIXME: inserting at the beginning once you already have something can eff it up.
|
||||
void drawInto(ScreenPainter painter, bool focused = false) {
|
||||
//painter.setClipRectangle(boundingBox);
|
||||
|
@ -11077,38 +11077,69 @@ mixin template ExperimentalTextComponent() {
|
|||
}
|
||||
}
|
||||
|
||||
// on every redraw, I will force the carat to be
|
||||
// on every redraw, I will force the caret to be
|
||||
// redrawn too, in order to eliminate perceived lag
|
||||
// when moving around with the mouse.
|
||||
eraseCarat(painter);
|
||||
eraseCaret(painter);
|
||||
|
||||
if(focused) {
|
||||
highlightSelection(painter);
|
||||
drawCarat(painter);
|
||||
drawCaret(painter);
|
||||
}
|
||||
}
|
||||
|
||||
void highlightSelection(ScreenPainter painter) {
|
||||
if(selectionStart is selectionEnd)
|
||||
return; // no selection
|
||||
|
||||
assert(selectionStart.inlineElement !is null);
|
||||
assert(selectionEnd.inlineElement !is null);
|
||||
|
||||
painter.rasterOp = RasterOp.xor;
|
||||
painter.outlineColor = Color.transparent;
|
||||
painter.fillColor = Color(255, 255, 127);
|
||||
|
||||
auto at = selectionStart.inlineElement;
|
||||
auto atOffset = selectionStart.offset;
|
||||
bool done;
|
||||
while(at) {
|
||||
auto box = at.boundingBox;
|
||||
if(atOffset < at.letterXs.length)
|
||||
box.left = at.letterXs[atOffset];
|
||||
|
||||
if(at is selectionEnd.inlineElement) {
|
||||
if(selectionEnd.offset < at.letterXs.length)
|
||||
box.right = at.letterXs[selectionEnd.offset];
|
||||
done = true;
|
||||
}
|
||||
|
||||
painter.drawRectangle(box.upperLeft, box.width, box.height);
|
||||
|
||||
if(done)
|
||||
break;
|
||||
|
||||
at = at.getNextInlineElement();
|
||||
atOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int caratLastDrawnX, caratLastDrawnY1, caratLastDrawnY2;
|
||||
bool caratShowingOnScreen = false;
|
||||
void drawCarat(ScreenPainter painter) {
|
||||
int caretLastDrawnX, caretLastDrawnY1, caretLastDrawnY2;
|
||||
bool caretShowingOnScreen = false;
|
||||
void drawCaret(ScreenPainter painter) {
|
||||
//painter.setClipRectangle(boundingBox);
|
||||
int x, y1, y2;
|
||||
if(carat.inlineElement is null) {
|
||||
if(caret.inlineElement is null) {
|
||||
x = boundingBox.left;
|
||||
y1 = boundingBox.top + 2;
|
||||
y2 = boundingBox.top + painter.fontHeight;
|
||||
} else {
|
||||
x = carat.inlineElement.xOfIndex(carat.offset);
|
||||
y1 = carat.inlineElement.boundingBox.top + 2;
|
||||
y2 = carat.inlineElement.boundingBox.bottom - 2;
|
||||
x = caret.inlineElement.xOfIndex(caret.offset);
|
||||
y1 = caret.inlineElement.boundingBox.top + 2;
|
||||
y2 = caret.inlineElement.boundingBox.bottom - 2;
|
||||
}
|
||||
|
||||
if(caratShowingOnScreen && (x != caratLastDrawnX || y1 != caratLastDrawnY1 || y2 != caratLastDrawnY2))
|
||||
eraseCarat(painter);
|
||||
if(caretShowingOnScreen && (x != caretLastDrawnX || y1 != caretLastDrawnY1 || y2 != caretLastDrawnY2))
|
||||
eraseCaret(painter);
|
||||
|
||||
painter.pen = Pen(Color.white, 1);
|
||||
painter.rasterOp = RasterOp.xor;
|
||||
|
@ -11117,117 +11148,181 @@ mixin template ExperimentalTextComponent() {
|
|||
Point(x, y2)
|
||||
);
|
||||
painter.rasterOp = RasterOp.normal;
|
||||
caratShowingOnScreen = !caratShowingOnScreen;
|
||||
caretShowingOnScreen = !caretShowingOnScreen;
|
||||
|
||||
if(caratShowingOnScreen) {
|
||||
caratLastDrawnX = x;
|
||||
caratLastDrawnY1 = y1;
|
||||
caratLastDrawnY2 = y2;
|
||||
if(caretShowingOnScreen) {
|
||||
caretLastDrawnX = x;
|
||||
caretLastDrawnY1 = y1;
|
||||
caretLastDrawnY2 = y2;
|
||||
}
|
||||
}
|
||||
|
||||
void eraseCarat(ScreenPainter painter) {
|
||||
void eraseCaret(ScreenPainter painter) {
|
||||
//painter.setClipRectangle(boundingBox);
|
||||
if(!caratShowingOnScreen) return;
|
||||
if(!caretShowingOnScreen) return;
|
||||
painter.pen = Pen(Color.white, 1);
|
||||
painter.rasterOp = RasterOp.xor;
|
||||
painter.drawLine(
|
||||
Point(caratLastDrawnX, caratLastDrawnY1),
|
||||
Point(caratLastDrawnX, caratLastDrawnY2)
|
||||
Point(caretLastDrawnX, caretLastDrawnY1),
|
||||
Point(caretLastDrawnX, caretLastDrawnY2)
|
||||
);
|
||||
|
||||
caratShowingOnScreen = false;
|
||||
caretShowingOnScreen = false;
|
||||
painter.rasterOp = RasterOp.normal;
|
||||
}
|
||||
|
||||
/// Carat movement api
|
||||
/// Caret movement api
|
||||
/// 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!)
|
||||
void moveUp(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
auto x = carat.inlineElement.xOfIndex(carat.offset);
|
||||
auto y = carat.inlineElement.boundingBox.top + 2;
|
||||
void moveUp(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
auto x = caret.inlineElement.xOfIndex(caret.offset);
|
||||
auto y = caret.inlineElement.boundingBox.top + 2;
|
||||
|
||||
y -= carat.inlineElement.boundingBox.bottom - carat.inlineElement.boundingBox.top;
|
||||
y -= caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
|
||||
|
||||
auto i = identify(x, y);
|
||||
|
||||
if(i.element) {
|
||||
carat.inlineElement = i.element;
|
||||
carat.offset = i.offset;
|
||||
caret.inlineElement = i.element;
|
||||
caret.offset = i.offset;
|
||||
}
|
||||
}
|
||||
void moveDown(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
auto x = carat.inlineElement.xOfIndex(carat.offset);
|
||||
auto y = carat.inlineElement.boundingBox.bottom - 2;
|
||||
void moveDown(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
auto x = caret.inlineElement.xOfIndex(caret.offset);
|
||||
auto y = caret.inlineElement.boundingBox.bottom - 2;
|
||||
|
||||
y += carat.inlineElement.boundingBox.bottom - carat.inlineElement.boundingBox.top;
|
||||
y += caret.inlineElement.boundingBox.bottom - caret.inlineElement.boundingBox.top;
|
||||
|
||||
auto i = identify(x, y);
|
||||
if(i.element) {
|
||||
carat.inlineElement = i.element;
|
||||
carat.offset = i.offset;
|
||||
caret.inlineElement = i.element;
|
||||
caret.offset = i.offset;
|
||||
}
|
||||
}
|
||||
void moveLeft(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
if(carat.offset)
|
||||
carat.offset--;
|
||||
void moveLeft(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
if(caret.offset)
|
||||
caret.offset--;
|
||||
else {
|
||||
auto p = carat.inlineElement.getPreviousInlineElement();
|
||||
auto p = caret.inlineElement.getPreviousInlineElement();
|
||||
if(p) {
|
||||
carat.inlineElement = p;
|
||||
caret.inlineElement = p;
|
||||
if(p.text.length && p.text[$-1] == '\n')
|
||||
carat.offset = p.text.length - 1;
|
||||
caret.offset = p.text.length - 1;
|
||||
else
|
||||
carat.offset = p.text.length;
|
||||
caret.offset = p.text.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
void moveRight(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
if(carat.offset < carat.inlineElement.text.length && carat.inlineElement.text[carat.offset] != '\n') {
|
||||
carat.offset++;
|
||||
void moveRight(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
if(caret.offset < caret.inlineElement.text.length && caret.inlineElement.text[caret.offset] != '\n') {
|
||||
caret.offset++;
|
||||
} else {
|
||||
auto p = carat.inlineElement.getNextInlineElement();
|
||||
auto p = caret.inlineElement.getNextInlineElement();
|
||||
if(p) {
|
||||
carat.inlineElement = p;
|
||||
carat.offset = 0;
|
||||
caret.inlineElement = p;
|
||||
caret.offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
void moveHome(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
void moveHome(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
auto x = 0;
|
||||
auto y = carat.inlineElement.boundingBox.top + 2;
|
||||
auto y = caret.inlineElement.boundingBox.top + 2;
|
||||
|
||||
auto i = identify(x, y);
|
||||
|
||||
if(i.element) {
|
||||
carat.inlineElement = i.element;
|
||||
carat.offset = i.offset;
|
||||
caret.inlineElement = i.element;
|
||||
caret.offset = i.offset;
|
||||
}
|
||||
}
|
||||
void moveEnd(ref Carat carat) {
|
||||
if(carat.inlineElement is null) return;
|
||||
void moveEnd(ref Caret caret) {
|
||||
if(caret.inlineElement is null) return;
|
||||
auto x = int.max;
|
||||
auto y = carat.inlineElement.boundingBox.top + 2;
|
||||
auto y = caret.inlineElement.boundingBox.top + 2;
|
||||
|
||||
auto i = identify(x, y);
|
||||
|
||||
if(i.element) {
|
||||
carat.inlineElement = i.element;
|
||||
carat.offset = i.offset;
|
||||
caret.inlineElement = i.element;
|
||||
caret.offset = i.offset;
|
||||
}
|
||||
|
||||
}
|
||||
void movePageUp(ref Carat carat) {}
|
||||
void movePageDown(ref Carat carat) {}
|
||||
void movePageUp(ref Caret caret) {}
|
||||
void movePageDown(ref Caret caret) {}
|
||||
|
||||
/// Plain text editing api. These work at the current carat inside the selected inline element.
|
||||
void moveDocumentStart(ref Caret caret) {
|
||||
if(blocks.length && blocks[0].parts.length)
|
||||
caret = Caret(this, blocks[0].parts[0], 0);
|
||||
else
|
||||
caret = Caret.init;
|
||||
}
|
||||
|
||||
void moveDocumentEnd(ref Caret caret) {
|
||||
if(blocks.length) {
|
||||
auto parts = blocks[$-1].parts;
|
||||
if(parts.length) {
|
||||
caret = Caret(this, parts[$-1], parts[$-1].text.length);
|
||||
} else {
|
||||
caret = Caret.init;
|
||||
}
|
||||
} else
|
||||
caret = Caret.init;
|
||||
}
|
||||
|
||||
void deleteSelection() {
|
||||
if(selectionStart is selectionEnd)
|
||||
return;
|
||||
|
||||
assert(selectionStart.inlineElement !is null);
|
||||
assert(selectionEnd.inlineElement !is null);
|
||||
|
||||
auto at = selectionStart.inlineElement;
|
||||
auto atOffset = selectionStart.offset;
|
||||
while(at) {
|
||||
at.text = at.text[atOffset .. $];
|
||||
|
||||
if(at is selectionEnd.inlineElement) {
|
||||
selectionEnd.offset -= atOffset;
|
||||
at.text = at.text[selectionEnd.offset .. $];
|
||||
selectionEnd.offset = 0;
|
||||
break;
|
||||
} else {
|
||||
auto cfd = at;
|
||||
|
||||
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];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
caret = selectionEnd;
|
||||
selectNone();
|
||||
|
||||
}
|
||||
|
||||
/// Plain text editing api. These work at the current caret inside the selected inline element.
|
||||
void insert(string text) {}
|
||||
void insert(dchar ch) {
|
||||
|
||||
deleteSelection();
|
||||
|
||||
if(ch == 127) {
|
||||
delete_();
|
||||
return;
|
||||
|
@ -11237,32 +11332,32 @@ mixin template ExperimentalTextComponent() {
|
|||
return;
|
||||
}
|
||||
if(ch == 13) ch = 10;
|
||||
auto e = carat.inlineElement;
|
||||
auto e = caret.inlineElement;
|
||||
if(e is null) {
|
||||
addText("" ~ cast(char) ch) ; // FIXME
|
||||
return;
|
||||
}
|
||||
|
||||
if(carat.offset == e.text.length) {
|
||||
if(caret.offset == e.text.length) {
|
||||
e.text ~= cast(char) ch; // FIXME
|
||||
carat.offset++;
|
||||
caret.offset++;
|
||||
if(ch == 10) {
|
||||
auto c = carat.inlineElement.clone;
|
||||
auto c = caret.inlineElement.clone;
|
||||
c.text = null;
|
||||
insertPartAfter(c,e);
|
||||
carat = Carat(this, c, 0);
|
||||
caret = Caret(this, c, 0);
|
||||
}
|
||||
} else {
|
||||
// FIXME cast char sucks
|
||||
if(ch == 10) {
|
||||
auto c = carat.inlineElement.clone;
|
||||
c.text = e.text[carat.offset .. $];
|
||||
e.text = e.text[0 .. carat.offset] ~ cast(char) ch;
|
||||
auto c = caret.inlineElement.clone;
|
||||
c.text = e.text[caret.offset .. $];
|
||||
e.text = e.text[0 .. caret.offset] ~ cast(char) ch;
|
||||
insertPartAfter(c,e);
|
||||
carat = Carat(this, c, 0);
|
||||
caret = Caret(this, c, 0);
|
||||
} else {
|
||||
e.text = e.text[0 .. carat.offset] ~ cast(char) ch ~ e.text[carat.offset .. $];
|
||||
carat.offset++;
|
||||
e.text = e.text[0 .. caret.offset] ~ cast(char) ch ~ e.text[caret.offset .. $];
|
||||
caret.offset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11300,48 +11395,53 @@ mixin template ExperimentalTextComponent() {
|
|||
|
||||
void backspace() {
|
||||
try_again:
|
||||
auto e = carat.inlineElement;
|
||||
auto e = caret.inlineElement;
|
||||
if(e is null)
|
||||
return;
|
||||
if(carat.offset == 0) {
|
||||
if(caret.offset == 0) {
|
||||
auto prev = e.getPreviousInlineElement();
|
||||
if(prev is null)
|
||||
return;
|
||||
auto newOffset = prev.text.length;
|
||||
tryMerge(prev, e);
|
||||
carat.inlineElement = prev;
|
||||
carat.offset = prev is null ? 0 : newOffset;
|
||||
caret.inlineElement = prev;
|
||||
caret.offset = prev is null ? 0 : newOffset;
|
||||
|
||||
goto try_again;
|
||||
} else if(carat.offset == e.text.length) {
|
||||
} else if(caret.offset == e.text.length) {
|
||||
e.text = e.text[0 .. $-1];
|
||||
carat.offset--;
|
||||
caret.offset--;
|
||||
} else {
|
||||
e.text = e.text[0 .. carat.offset - 1] ~ e.text[carat.offset .. $];
|
||||
carat.offset--;
|
||||
e.text = e.text[0 .. caret.offset - 1] ~ e.text[caret.offset .. $];
|
||||
caret.offset--;
|
||||
}
|
||||
//cleanupStructures();
|
||||
}
|
||||
void delete_() {
|
||||
auto after = carat;
|
||||
auto after = caret;
|
||||
moveRight(after);
|
||||
if(carat != after) {
|
||||
carat = after;
|
||||
if(caret != after) {
|
||||
caret = after;
|
||||
backspace();
|
||||
}
|
||||
}
|
||||
void overstrike() {}
|
||||
|
||||
/// Selection API. See also: carat movement.
|
||||
void selectAll() {}
|
||||
void selectNone() {}
|
||||
/// Selection API. See also: caret movement.
|
||||
void selectAll() {
|
||||
moveDocumentStart(selectionStart);
|
||||
moveDocumentEnd(selectionEnd);
|
||||
}
|
||||
void selectNone() {
|
||||
selectionStart = selectionEnd = Caret.init;
|
||||
}
|
||||
|
||||
/// Rich text editing api. These allow you to manipulate the meta data of the current element and add new elements.
|
||||
/// They will modify the current selection if there is one and will splice one in if needed.
|
||||
void changeAttributes() {}
|
||||
|
||||
|
||||
/// Text search api. They manipulate the selection and/or carat.
|
||||
/// Text search api. They manipulate the selection and/or caret.
|
||||
void findText(string text) {}
|
||||
void findIndex(size_t textIndex) {}
|
||||
|
||||
|
@ -11362,17 +11462,17 @@ mixin template ExperimentalTextComponent() {
|
|||
}
|
||||
|
||||
bool contentEditable; // can it be edited?
|
||||
bool contentCaratable; // is there a carat/cursor that moves around in there?
|
||||
bool contentCaretable; // is there a caret/cursor that moves around in there?
|
||||
bool contentSelectable; // selectable?
|
||||
|
||||
Carat carat;
|
||||
Carat selectionStart;
|
||||
Carat selectionEnd;
|
||||
Caret caret;
|
||||
Caret selectionStart;
|
||||
Caret selectionEnd;
|
||||
|
||||
bool insertMode;
|
||||
}
|
||||
|
||||
struct Carat {
|
||||
struct Caret {
|
||||
TextLayout layout;
|
||||
InlineElement inlineElement;
|
||||
size_t offset;
|
||||
|
|
Loading…
Reference in New Issue