1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

iOS: Add proper UITextInput implementation

This provides an improved user experience, allowing cursor movement
directly from the keyboard.
This commit is contained in:
reuk 2022-07-05 20:13:07 +01:00
parent 09c107698b
commit 5cf1a964fc
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
8 changed files with 1022 additions and 255 deletions

View file

@ -71,21 +71,7 @@ private:
void setSelection (Range<int> r) override
{
if (r == codeEditorComponent.getHighlightedRegion())
return;
if (r.isEmpty())
{
codeEditorComponent.caretPos.setPosition (r.getStart());
return;
}
auto& doc = codeEditorComponent.document;
const auto cursorAtStart = r.getEnd() == codeEditorComponent.getHighlightedRegion().getStart()
|| r.getEnd() == codeEditorComponent.getHighlightedRegion().getEnd();
codeEditorComponent.selectRegion (CodeDocument::Position (doc, cursorAtStart ? r.getEnd() : r.getStart()),
CodeDocument::Position (doc, cursorAtStart ? r.getStart() : r.getEnd()));
codeEditorComponent.setHighlightedRegion (r);
}
String getText (Range<int> r) const override
@ -108,32 +94,7 @@ private:
RectangleList<int> getTextBounds (Range<int> textRange) const override
{
auto& doc = codeEditorComponent.document;
RectangleList<int> localRects;
CodeDocument::Position startPosition (doc, textRange.getStart());
CodeDocument::Position endPosition (doc, textRange.getEnd());
for (int line = startPosition.getLineNumber(); line <= endPosition.getLineNumber(); ++line)
{
CodeDocument::Position lineStart (doc, line, 0);
CodeDocument::Position lineEnd (doc, line, doc.getLine (line).length());
if (line == startPosition.getLineNumber())
lineStart = lineStart.movedBy (startPosition.getIndexInLine());
if (line == endPosition.getLineNumber())
lineEnd = { doc, line, endPosition.getIndexInLine() };
auto startPos = codeEditorComponent.getCharacterBounds (lineStart).getTopLeft();
auto endPos = codeEditorComponent.getCharacterBounds (lineEnd).getTopLeft();
localRects.add (startPos.x,
startPos.y,
jmax (1, endPos.x - startPos.x),
codeEditorComponent.getLineHeight());
}
const auto localRects = codeEditorComponent.getTextBounds (textRange);
RectangleList<int> globalRects;
@ -213,11 +174,23 @@ public:
return true;
}
void getHighlightArea (RectangleList<float>& area, float x, int y, int lineH, float characterWidth) const
Optional<Rectangle<float>> getHighlightArea (float x, int y, int lineH, float characterWidth) const
{
if (highlightColumnStart < highlightColumnEnd)
area.add (Rectangle<float> (x + (float) highlightColumnStart * characterWidth - 1.0f, (float) y - 0.5f,
(float) (highlightColumnEnd - highlightColumnStart) * characterWidth + 1.5f, (float) lineH + 1.0f));
return getHighlightArea (x, y, lineH, characterWidth, { highlightColumnStart, highlightColumnEnd });
}
Optional<Rectangle<float>> getHighlightArea (float x,
int y,
int lineH,
float characterWidth,
Range<int> highlightColumns) const
{
if (highlightColumns.isEmpty())
return {};
return Rectangle<float> (x + (float) highlightColumns.getStart() * characterWidth - 1.0f, (float) y - 0.5f,
(float) (highlightColumns.getEnd() - highlightColumns.getStart()) * characterWidth + 1.5f, (float) lineH + 1.0f);
}
void draw (CodeEditorComponent& owner, Graphics& g, const Font& fontToUse,
@ -533,14 +506,6 @@ void CodeEditorComponent::setTemporaryUnderlining (const Array<Range<int>>&)
jassertfalse; // TODO Windows IME not yet supported for this comp..
}
Rectangle<int> CodeEditorComponent::getCaretRectangle()
{
if (caret != nullptr)
return getLocalArea (caret.get(), caret->getLocalBounds());
return {};
}
void CodeEditorComponent::setLineNumbersShown (const bool shouldBeShown)
{
if (showLineNumbers != shouldBeShown)
@ -598,25 +563,26 @@ void CodeEditorComponent::paint (Graphics& g)
{
g.fillAll (findColour (CodeEditorComponent::backgroundColourId));
auto gutterSize = getGutterSize();
auto bottom = horizontalScrollBar.isVisible() ? horizontalScrollBar.getY() : getHeight();
auto right = verticalScrollBar.isVisible() ? verticalScrollBar.getX() : getWidth();
const auto gutterSize = getGutterSize();
const auto bottom = horizontalScrollBar.isVisible() ? horizontalScrollBar.getY() : getHeight();
const auto right = verticalScrollBar.isVisible() ? verticalScrollBar.getX() : getWidth();
g.reduceClipRegion (gutterSize, 0, right - gutterSize, bottom);
g.setFont (font);
auto clip = g.getClipBounds();
auto firstLineToDraw = jmax (0, clip.getY() / lineHeight);
auto lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1);
auto x = (float) (gutterSize - xOffset * charWidth);
auto rightClip = (float) clip.getRight();
const auto clip = g.getClipBounds();
const auto firstLineToDraw = jmax (0, clip.getY() / lineHeight);
const auto lastLineToDraw = jmin (lines.size(), clip.getBottom() / lineHeight + 1);
const auto x = (float) (gutterSize - xOffset * charWidth);
const auto rightClip = (float) clip.getRight();
{
RectangleList<float> highlightArea;
for (int i = firstLineToDraw; i < lastLineToDraw; ++i)
lines.getUnchecked(i)->getHighlightArea (highlightArea, x, lineHeight * i, lineHeight, charWidth);
if (const auto area = lines.getUnchecked(i)->getHighlightArea (x, lineHeight * i, lineHeight, charWidth))
highlightArea.add (*area);
g.setColour (findColour (CodeEditorComponent::highlightColourId));
g.fillRectList (highlightArea);
@ -893,6 +859,37 @@ CodeDocument::Position CodeEditorComponent::getPositionAt (int x, int y) const
return CodeDocument::Position (document, line, index);
}
int CodeEditorComponent::getCharIndexForPoint (Point<int> point) const
{
return getPositionAt (point.x, point.y).getPosition();
}
RectangleList<int> CodeEditorComponent::getTextBounds (Range<int> textRange) const
{
RectangleList<int> localRects;
const CodeDocument::Position startPosition (document, textRange.getStart());
const CodeDocument::Position endPosition (document, textRange.getEnd());
for (int line = startPosition.getLineNumber(); line <= endPosition.getLineNumber(); ++line)
{
const CodeDocument::Position lineStartColumn0 { document, line, 0 };
const auto lineStart = line == startPosition.getLineNumber() ? lineStartColumn0.movedBy (startPosition.getIndexInLine())
: lineStartColumn0;
const CodeDocument::Position lineEnd { document, line, line == endPosition.getLineNumber() ? endPosition.getIndexInLine()
: document.getLine (line).length() };
const auto startPos = getCharacterBounds (lineStart).getTopLeft();
const auto endPos = getCharacterBounds (lineEnd) .getTopLeft();
localRects.add (startPos.x, startPos.y, jmax (1, endPos.x - startPos.x), getLineHeight());
}
return localRects;
}
//==============================================================================
void CodeEditorComponent::insertTextAtCaret (const String& newText)
{
@ -1336,8 +1333,13 @@ bool CodeEditorComponent::isHighlightActive() const noexcept
void CodeEditorComponent::setHighlightedRegion (const Range<int>& newRange)
{
selectRegion (CodeDocument::Position (document, newRange.getStart()),
CodeDocument::Position (document, newRange.getEnd()));
if (newRange == getHighlightedRegion())
return;
const auto cursorAtStart = newRange.getEnd() == getHighlightedRegion().getStart()
|| newRange.getEnd() == getHighlightedRegion().getEnd();
selectRegion (CodeDocument::Position (document, cursorAtStart ? newRange.getEnd() : newRange.getStart()),
CodeDocument::Position (document, cursorAtStart ? newRange.getStart() : newRange.getEnd()));
}
String CodeEditorComponent::getTextInRange (const Range<int>& range) const

View file

@ -92,8 +92,8 @@ public:
/** Returns the current caret position. */
CodeDocument::Position getCaretPos() const { return caretPos; }
/** Returns the position of the caret, relative to the editor's origin. */
Rectangle<int> getCaretRectangle() override;
/** Returns the total number of codepoints in the string. */
int getTotalNumChars() const override { return document.getNumCharacters(); }
/** Moves the caret.
If selecting is true, the section of the document between the current
@ -121,6 +121,26 @@ public:
/** Enables or disables the line-number display in the gutter. */
void setLineNumbersShown (bool shouldBeShown);
/** Returns the number of characters from the beginning of the document to the caret. */
int getCaretPosition() const override { return getCaretPos().getPosition(); }
/** @see getPositionAt */
int getCharIndexForPoint (Point<int> point) const override;
/** Returns the bounds of the caret at a particular location in the text. */
Rectangle<int> getCaretRectangleForCharIndex (int index) const override
{
return getCharacterBounds ({ document, index });
}
/** Returns the bounding box for a range of text in the editor. As the range may span
multiple lines, this method returns a RectangleList.
The bounds are relative to the component's top-left and may extend beyond the bounds
of the component if the text is long and word wrapping is disabled.
*/
RectangleList<int> getTextBounds (Range<int> textRange) const override;
//==============================================================================
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting);
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting);