mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-09 23:34:20 +00:00
TextEditor: Fix centred and right alignment when word wrap is disabled
This commit is contained in:
parent
9730cd2808
commit
5f5a247f82
4 changed files with 71 additions and 22 deletions
|
|
@ -108,20 +108,31 @@ static float getMainAxisLineLength (Span<const ShapedGlyph> glyphs, bool trailin
|
|||
|
||||
struct MainAxisLineAlignment
|
||||
{
|
||||
float anchor{}, extraWhitespaceAdvance{}, effectiveLineLength;
|
||||
float anchor{}, extraWhitespaceAdvance{}, effectiveLineLength{};
|
||||
Range<int64> stretchableWhitespaces;
|
||||
};
|
||||
|
||||
static MainAxisLineAlignment getMainAxisLineAlignment (Justification justification,
|
||||
Span<const ShapedGlyph> glyphs,
|
||||
LineLength lineLength,
|
||||
float maxWidth,
|
||||
std::optional<float> maxWidthOpt,
|
||||
std::optional<float> alignmentWidthOpt,
|
||||
bool trailingWhitespacesShouldFit)
|
||||
{
|
||||
const auto effectiveLineLength = (trailingWhitespacesShouldFit ? lineLength.total
|
||||
: lineLength.withoutTrailingWhitespaces);
|
||||
|
||||
const auto tooLong = maxWidth + maxWidthTolerance < effectiveLineLength;
|
||||
const auto alignmentWidth = alignmentWidthOpt.value_or (maxWidthOpt.value_or (0.0f));
|
||||
const auto tooLong = alignmentWidth + maxWidthTolerance < effectiveLineLength;
|
||||
|
||||
MainAxisLineAlignment result;
|
||||
result.effectiveLineLength = effectiveLineLength;
|
||||
|
||||
// The alignment width opt is supporting the TextEditor use-case where all text remains visible
|
||||
// with scrolling, even if longer than the alignment width. We don't need to worry about the
|
||||
// front of and RTL text being visually truncated, because nothing is truncated.
|
||||
if (tooLong && alignmentWidthOpt.has_value())
|
||||
return result;
|
||||
|
||||
const auto mainAxisLineOffset = [&]
|
||||
{
|
||||
|
|
@ -135,19 +146,21 @@ static MainAxisLineAlignment getMainAxisLineAlignment (Justification justificati
|
|||
return glyphs.front().cluster <= glyphs.back().cluster;
|
||||
}();
|
||||
|
||||
// We don't have to align LTR text, but we need to ensure that it's the logical back of
|
||||
// RTL text that falls outside the bounds.
|
||||
if (approximateIsLeftToRight)
|
||||
return 0.0f;
|
||||
|
||||
return maxWidth - effectiveLineLength;
|
||||
return alignmentWidth - effectiveLineLength;
|
||||
}
|
||||
|
||||
if (justification.testFlags (Justification::horizontallyCentred))
|
||||
{
|
||||
return (maxWidth - lineLength.withoutTrailingWhitespaces) / 2.0f;
|
||||
return (alignmentWidth - lineLength.withoutTrailingWhitespaces) / 2.0f;
|
||||
}
|
||||
|
||||
if (justification.testFlags (Justification::right))
|
||||
return maxWidth - effectiveLineLength;
|
||||
return alignmentWidth - effectiveLineLength;
|
||||
|
||||
return 0.0f;
|
||||
}();
|
||||
|
|
@ -171,7 +184,7 @@ static MainAxisLineAlignment getMainAxisLineAlignment (Justification justificati
|
|||
- numWhitespaces.leading
|
||||
- numWhitespaces.trailing;
|
||||
|
||||
return numWhitespacesBetweenWords > 0 ? (maxWidth - effectiveLineLength) / (float) numWhitespacesBetweenWords
|
||||
return numWhitespacesBetweenWords > 0 ? (alignmentWidth - effectiveLineLength) / (float) numWhitespacesBetweenWords
|
||||
: 0.0f;
|
||||
}();
|
||||
|
||||
|
|
@ -246,13 +259,11 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
|
||||
auto m = [&]
|
||||
{
|
||||
if (! options.getMaxWidth().has_value())
|
||||
return MainAxisLineAlignment{};
|
||||
|
||||
return getMainAxisLineAlignment (options.getJustification(),
|
||||
glyphs,
|
||||
lineLength,
|
||||
*options.getMaxWidth(),
|
||||
options.getMaxWidth(),
|
||||
options.getAlignmentWidth(),
|
||||
options.getTrailingWhitespacesShouldFit());
|
||||
}();
|
||||
|
||||
|
|
@ -442,7 +453,6 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
if (cutoffAtFront)
|
||||
pushEllipsisGlyphs();
|
||||
|
||||
|
||||
const auto& range = shapedText.getGlyphs (lastLineVisibleRange);
|
||||
result.insert (result.end(), range.begin(), range.end());
|
||||
|
||||
|
|
@ -454,13 +464,11 @@ JustifiedText::JustifiedText (const SimpleShapedText* t, const ShapedTextOptions
|
|||
|
||||
const auto realign = [&]
|
||||
{
|
||||
if (! options.getMaxWidth().has_value())
|
||||
return MainAxisLineAlignment{};
|
||||
|
||||
return getMainAxisLineAlignment (options.getJustification(),
|
||||
lineWithEllipsisGlyphs,
|
||||
getMainAxisLineLength (lineWithEllipsisGlyphs),
|
||||
*options.getMaxWidth(),
|
||||
options.getMaxWidth(),
|
||||
options.getAlignmentWidth(),
|
||||
options.getTrailingWhitespacesShouldFit());
|
||||
}();
|
||||
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ public:
|
|||
if (std::exchange (lastLine, lineMetrics.lineNumber) != lineMetrics.lineNumber)
|
||||
anchor = lineMetrics.anchor;
|
||||
|
||||
if (range.getStart() != lastGlyph)
|
||||
if (range.getStart() != lastGlyph && drawType != DrawType::ellipsis)
|
||||
{
|
||||
detail::RangedValues<int8_t> glyphMask;
|
||||
Ranges::Operations ops;
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ private:
|
|||
return std::tie (justification,
|
||||
readingDir,
|
||||
maxWidth,
|
||||
alignmentWidth,
|
||||
height,
|
||||
fontsForRange,
|
||||
language,
|
||||
|
|
@ -73,11 +74,45 @@ public:
|
|||
return withMember (*this, &ShapedTextOptions::justification, x);
|
||||
}
|
||||
|
||||
/* This option will use soft wrapping for lines that are longer than the specified value,
|
||||
and it will also align each line to this width, using the Justification provided in
|
||||
withJustification.
|
||||
|
||||
The alignment width can be overriden using withAlignmentWidth, but currently we only need
|
||||
to do this for the TextEditor.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withMaxWidth (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::maxWidth, x);
|
||||
}
|
||||
|
||||
/* With this option each line will be aligned only if it's shorter or equal to the alignment
|
||||
width. Otherwise, the line's x anchor will be 0.0f. This is in contrast to using
|
||||
withMaxWidth only, which will modify the x anchor of RTL lines that are too long, to ensure
|
||||
that it's the logical end of the text that falls outside the visible bounds.
|
||||
|
||||
The alignment width is also a distinct value from the value used for soft wrapping which is
|
||||
specified using withMaxWidth.
|
||||
|
||||
This option is specifically meant to support an existing TextEditor behaviour, where text
|
||||
can be aligned even when word wrapping is off. You probably don't need to use this function,
|
||||
unless you want to reproduce the particular behaviour seen in the TextEditor, and should
|
||||
only use withMaxWidth, if alignment is required.
|
||||
|
||||
With this option off, text is either not aligned, or aligned to the width specified using
|
||||
withMaxWidth.
|
||||
|
||||
When this option is in use, it overrides the width specified in withMaxWidth for alignment
|
||||
purposes, but not for line wrapping purposes.
|
||||
|
||||
It also accommodates the fact that the TextEditor has a scrolling feature and text never
|
||||
becomes unreachable, even if the lines are longer than the viewport's width.
|
||||
*/
|
||||
[[nodiscard]] ShapedTextOptions withAlignmentWidth (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::alignmentWidth, x);
|
||||
}
|
||||
|
||||
[[nodiscard]] ShapedTextOptions withHeight (float x) const
|
||||
{
|
||||
return withMember (*this, &ShapedTextOptions::height, x);
|
||||
|
|
@ -157,6 +192,7 @@ public:
|
|||
const auto& getReadingDirection() const { return readingDir; }
|
||||
const auto& getJustification() const { return justification; }
|
||||
const auto& getMaxWidth() const { return maxWidth; }
|
||||
const auto& getAlignmentWidth() const { return alignmentWidth; }
|
||||
const auto& getHeight() const { return height; }
|
||||
const auto& getFontsForRange() const { return fontsForRange; }
|
||||
const auto& getLanguage() const { return language; }
|
||||
|
|
@ -173,6 +209,7 @@ private:
|
|||
Justification justification { Justification::topLeft };
|
||||
std::optional<TextDirection> readingDir;
|
||||
std::optional<float> maxWidth;
|
||||
std::optional<float> alignmentWidth;
|
||||
std::optional<float> height;
|
||||
|
||||
detail::RangedValues<Font> fontsForRange = std::invoke ([&]
|
||||
|
|
|
|||
|
|
@ -917,11 +917,15 @@ std::pair<Point<float>, float> TextEditor::getTextSelectionEdge (int index, Edge
|
|||
|
||||
void TextEditor::updateBaseShapedTextOptions()
|
||||
{
|
||||
textStorage->setBaseShapedTextOptions (detail::ShapedText::Options{}
|
||||
.withMaxWidth ((float) getWordWrapWidth())
|
||||
.withTrailingWhitespacesShouldFit (true)
|
||||
.withJustification (getJustificationType().getOnlyHorizontalFlags()),
|
||||
passwordCharacter);
|
||||
auto options = detail::ShapedText::Options{}.withTrailingWhitespacesShouldFit (true)
|
||||
.withJustification (getJustificationType().getOnlyHorizontalFlags());
|
||||
|
||||
if (wordWrap)
|
||||
options = options.withMaxWidth ((float) getMaximumTextWidth());
|
||||
else
|
||||
options = options.withAlignmentWidth ((float) getMaximumTextWidth());
|
||||
|
||||
textStorage->setBaseShapedTextOptions (options, passwordCharacter);
|
||||
}
|
||||
|
||||
static auto asInt64Range (Range<int> r)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue