diff --git a/examples/example1/src/main.d b/examples/example1/src/main.d index 8e9cecbf..bb8f316c 100644 --- a/examples/example1/src/main.d +++ b/examples/example1/src/main.d @@ -186,14 +186,14 @@ extern (C) int UIAppMain(string[] args) { text ~= " The quick brown fox jumps over the lazy dog."; } editBox.text = text; - editBox.minFontSize(12).maxFontSize(75); + editBox.minFontSize(12).maxFontSize(75); // allow font zoom with Ctrl + MouseWheel editors.addChild(createEditorSettingsControl(editBox)); editors.addChild(editBox); editors.addChild(new TextWidget(null, "EditBox: additional view for the same content (split view testing)"d)); EditBox editBox2 = new EditBox("editbox2", ""d); editBox2.content = editBox.content; // view the same content as first editbox - editBox2.minFontSize(12).maxFontSize(75); + editBox2.minFontSize(12).maxFontSize(75); // allow font zoom with Ctrl + MouseWheel editors.addChild(editBox2); editors.layoutHeight(FILL_PARENT).layoutWidth(FILL_PARENT); diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 3e2045ac..e7a0d414 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -331,7 +331,30 @@ class EditableContent { /// returns text position for end of line lineIndex TextPosition lineEnd(int lineIndex) { - return TextPosition(lineIndex, lineIndex >= 0 && lineIndex < _lines.length ? cast(int)_lines[lineIndex].length : 0); + return TextPosition(lineIndex, lineLength(lineIndex)); + } + + /// returns position before first non-space character of line, returns 0 position if no non-space chars + TextPosition firstNonSpace(int lineIndex) { + dstring s = line(lineIndex); + for (int i = 0; i < s.length; i++) + if (s[i] != ' ' && s[i] != '\t') + return TextPosition(lineIndex, i); + return TextPosition(lineIndex, 0); + } + + /// returns position after last non-space character of line, returns 0 position if no non-space chars on line + TextPosition lastNonSpace(int lineIndex) { + dstring s = line(lineIndex); + for (int i = cast(int)s.length - 1; i >= 0; i--) + if (s[i] != ' ' && s[i] != '\t') + return TextPosition(lineIndex, i + 1); + return TextPosition(lineIndex, 0); + } + + /// returns text position for end of line lineIndex + int lineLength(int lineIndex) { + return lineIndex >= 0 && lineIndex < _lines.length ? cast(int)_lines[lineIndex].length : 0; } /// returns maximum length of line @@ -375,6 +398,29 @@ class EditableContent { return res; } + /// when position is out of content bounds, fix it to nearest valid position + void correctPosition(ref TextPosition position) { + if (position.line >= length) { + position.line = length - 1; + position.pos = lineLength(position.line); + } + if (position.line < 0) { + position.line = 0; + position.pos = 0; + } + int currentLineLength = lineLength(position.line); + if (position.pos > currentLineLength) + position.pos = currentLineLength; + if (position.pos < 0) + position.pos = 0; + } + + /// when range positions is out of content bounds, fix it to nearest valid position + void correctRange(ref TextRange range) { + correctPosition(range.start); + correctPosition(range.end); + } + /// removes removedCount lines starting from start protected void removeLines(int start, int removedCount) { int end = start + removedCount; @@ -427,12 +473,82 @@ class EditableContent { } } + static bool isDigit(dchar ch) pure nothrow { + return ch >= '0' && ch <= '9'; + } + static bool isAlpha(dchar ch) pure nothrow { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_'); + } + static bool isLowerAlpha(dchar ch) pure nothrow { + return (ch >= 'a' && ch <= 'z') || (ch == '_'); + } + static bool isUpperAlpha(dchar ch) pure nothrow { + return (ch >= 'A' && ch <= 'Z'); + } + + /// change text position to nearest word bound (direction < 0 - back, > 0 - forward) + TextPosition moveByWord(TextPosition p, int direction, bool camelCasePartsAsWords) { + correctPosition(p); + TextPosition firstns = firstNonSpace(p.line); // before first non space + TextPosition lastns = lastNonSpace(p.line); // after last non space + int linelen = lineLength(p.line); // line length + if (direction < 0) { + // back + if (p.pos <= 0) { + // beginning of line - move to prev line + if (p.line > 0) + p = lastNonSpace(p.line - 1); + } else if (p.pos <= firstns.pos) { // before first nonspace + // to beginning of line + p.pos = 0; + } else { + } + } else if (direction > 0) { + // forward + if (p.pos >= linelen) { + // last position of line + if (p.line < length) + p = firstNonSpace(p.line + 1); + } else if (p.pos >= lastns.pos) { // before first nonspace + // to beginning of line + p.pos = linelen; + } else { + dstring txt = line(p.line); + int found = -1; + for (int i = p.pos; i < linelen; i++) { + // check if position i + 1 is after word end + dchar thischar = txt[i]; + if (thischar == '\t') + thischar = ' '; + dchar nextchar = i < linelen ? txt[i + 1] : ' '; + if (nextchar == '\t') + nextchar = ' '; + bool thisalpha = isAlpha(thischar); + bool nextalpha = isAlpha(nextchar); + if ((thisalpha && !nextalpha) + || (camelCasePartsAsWords && isLowerAlpha(thischar) && isUpperAlpha(nextchar)) + || (thischar != ' ' && nextchar == ' ') + ) { + found = i + 1; + break; + } + } + if (found >= 0) + p.pos = found; + else + p.pos = linelen; + } + } + return p; + } + /// edit content bool performOperation(EditOperation op, Object source) { if (_readOnly) throw new Exception("content is readonly"); if (op.action == EditAction.Replace) { TextRange rangeBefore = op.range; + correctRange(rangeBefore); dstring[] oldcontent = rangeText(rangeBefore); dstring[] newcontent = op.content; if (newcontent.length == 0) @@ -903,24 +1019,11 @@ class EditWidgetBase : WidgetGroup, EditableContentListener { protected void updateScrollbars() { } - /// when position is out of content bounds, fix it to nearest valid position - protected void correctPosition(ref TextPosition position) { - if (position.line >= _content.length) - position.line = _content.length - 1; - if (position.line < 0) - position.line = 0; - dstring currentLine = _content[position.line]; - if (position.pos > currentLine.length) - position.pos = cast(int)currentLine.length; - if (position.pos < 0) - position.pos = 0; - } - /// when cursor position or selection is out of content bounds, fix it to nearest valid position protected void correctCaretPos() { - correctPosition(_caretPos); - correctPosition(_selectionRange.start); - correctPosition(_selectionRange.end); + _content.correctPosition(_caretPos); + _content.correctPosition(_selectionRange.start); + _content.correctPosition(_selectionRange.end); if (_selectionRange.empty) _selectionRange = TextRange(_caretPos, _caretPos); }