Here is a shot - introducing 4 methods.
In the public section of class TextEditor:
[code] /** Attempts to scroll the caret to a given position, in pixels relative to the top
left corner of the visible area.
The position will be adjusted to ensure the caret is visible.
*/
void scrollCaretToPosition (const int x, const int y) throw();
/** Attempts to scroll the caret to a given position.
The position is given as a fraction of the visible area.
Both x and y should be between 0.0 and 1.0, inclusive.
The default values will center the caret vertically, which is useful
for text searching.
*/
void scrollCaretToProportionalPosition (const float xProportion = 1.0, const float yProportion = 0.5) throw();
/** Get the graphical position of the caret.
When useScreenCoordinates = false, the rectangle is relative to the TextEditor.
When true, desktop coordinates are used.
Use this with search/replace dialogs to prevent them from hiding the caret.
The width of the rectangle is always 0. The height depends on the
current font.
*/
const Rectangle getCaretRectangle (const bool useDesktopCoordinates = true) throw();
/** Get the caret position as a fraction of the text display area.
The x and y coordinates will be between 0.0 and 1.0, inclusive.
*/
const Point getCaretProportionalPosition () throw();
[/code]
And the implementation:
[code]void TextEditor::scrollCaretToPosition (const int x, const int y) throw()
{
cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn’t set this value)
getCharPosition (caretPosition,
cursorX, cursorY,
cursorHeight);
int vpX = roundFloatToInt (cursorX) - x;
int vpY = roundFloatToInt (cursorY) - y;
if (x < jmax (1, proportionOfWidth (0.05f)))
{
vpX += x - proportionOfWidth (0.2f);
}
else if (x > jmax (0, viewport->getMaximumVisibleWidth() - (wordWrap ? 2 : 10)))
{
vpX += x + (isMultiLine() ? proportionOfWidth (0.2f) : 10) - viewport->getMaximumVisibleWidth();
}
vpX = jlimit (0, jmax (0, textHolder->getWidth() + 8 - viewport->getMaximumVisibleWidth()), vpX);
vpY = jlimit (0, jmax (0, textHolder->getHeight() - viewport->getMaximumVisibleHeight()), vpY);
if (! isMultiLine())
{
vpY = (getHeight() - textHolder->getHeight() - topIndent) / -2;
}
else
{
const int curH = roundFloatToInt (cursorHeight);
if (y < 0)
{
vpY = jmax (0, y + vpY);
}
else if (y > jmax (0, viewport->getMaximumVisibleHeight() - topIndent - curH))
{
vpY += y + 2 + curH + topIndent - viewport->getMaximumVisibleHeight();
}
}
viewport->setViewPosition (vpX, vpY);
}
void TextEditor::scrollCaretToProportionalPosition (const float xRatio, const float yRatio) throw()
{
int width = viewport->getMaximumVisibleWidth();
int height = viewport->getMaximumVisibleHeight() - roundFloatToInt(currentFont.getHeight());
int x = jlimit(0, width, roundFloatToInt(width * xRatio));
int y = jlimit(0, width, roundFloatToInt(height * yRatio));
scrollCaretToPosition (x, y);
}
const Rectangle TextEditor::getCaretRectangle (const bool useScreenCoordinates) throw()
{
cursorHeight = currentFont.getHeight(); // (in case the text is empty and the call below doesn’t set this value)
getCharPosition (caretPosition,
cursorX, cursorY,
cursorHeight);
int x = roundFloatToInt(cursorX) - viewport->getX();
int y = roundFloatToInt(cursorY) - viewport->getY();
int height = roundFloatToInt(cursorHeight);
if (useScreenCoordinates)
{
x += getScreenX();
y += getScreenY();
}
return Rectangle(x, y, 0, height);
}
const Point TextEditor::getCaretProportionalPosition () throw()
{
const Rectangle rect = getCaretRectangle(false);
int width = jmax(1, viewport->getMaximumVisibleWidth());
int height = jmax(1, viewport->getMaximumVisibleHeight() - rect.getHeight());
float x = jlimit(0.0f, 1.0f, rect.getX() * 1.0f / width);
float y = jlimit(0.0f, 1.0f, rect.getY() * 1.0f / height);
return Point (x, y);
}[/code]
The first one - TextEditor::scrollCaretToPosition - is the heavy one, I adapted the code from TextEditor::scrollToMakeSureCursorIsVisible but I don’t fully understand the correction steps, though it works as far as I have tested it. The 2nd is the convenience wrapper I actually use; a single parameterless call centers the caret vertically.
The 3rd and 4th aren’t necessary for what I’m doing but I felt it was natural to add “getter” methods, should someone want finer control.
(The style of returning a Rectangle and a Point is, admittedly, biased by my jucePILS binding generator which doesn’t support out parameters as-is. It won’t spoil my day if you change it or decide these methods aren’t wanted.)
BTW TextEditor::scrollToMakeSureCursorIsVisible uses “Cursor” while other methods use “Caret” - I used “Caret” because it doesn’t get confused with the mouse cursor.
I hope you’ll consider them for inclusion, or, if they don’t feel right, suggest what should be changed or perhaps re-classify the private methods and fields I used as protected, so I can implement the caret scrolling in my own subclass without having to modify the juce source.