Implemented tab-stops support (SpartanJ/ecode#55). Not enabled by default until I'm sure I did not break anything.

This commit is contained in:
Martín Lucas Golini
2025-04-09 21:22:17 -03:00
parent 8df0793590
commit 4146bfef6f
10 changed files with 299 additions and 149 deletions

View File

@@ -156,6 +156,18 @@ shapeAndRun( const String& string, const FontStyleConfig& config,
} // namespace
Float Text::tabAdvance( Float hspace, Uint32 tabWidth, std::optional<Float> tabOffset ) {
Float advance = hspace * tabWidth;
if ( tabOffset ) {
Float offset = fmodf( *tabOffset, advance );
advance = advance - offset;
// If there is not enough space until the next stop, skip it
if ( advance < hspace )
advance += hspace * tabWidth;
}
return advance;
}
bool Text::TextShaperEnabled = false;
std::string Text::styleFlagToString( const Uint32& flags ) {
@@ -216,28 +228,33 @@ Uint32 Text::stringToStyleFlag( const std::string& str ) {
Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String& string,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness, Uint32 textDrawHints ) {
const Float& outlineThickness, Uint32 textDrawHints,
std::optional<Float> tabOffset ) {
return getTextWidth<String>( font, fontSize, string, style, tabWidth, outlineThickness,
textDrawHints );
textDrawHints, tabOffset );
}
Float Text::getTextWidth( Font* font, const Uint32& fontSize, const String::View& string,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness, Uint32 textDrawHints ) {
const Float& outlineThickness, Uint32 textDrawHints,
std::optional<Float> tabOffset ) {
return getTextWidth<String::View>( font, fontSize, string, style, tabWidth, outlineThickness,
textDrawHints );
textDrawHints, tabOffset );
}
Float Text::getTextWidth( const String& string, const FontStyleConfig& config,
const Uint32& tabWidth, Uint32 textDrawHints ) {
const Uint32& tabWidth, Uint32 textDrawHints,
std::optional<Float> tabOffset ) {
return getTextWidth<String>( config.Font, config.CharacterSize, string, config.Style, tabWidth,
config.OutlineThickness, textDrawHints );
config.OutlineThickness, textDrawHints, tabOffset );
}
Float Text::getTextWidth( const String::View& string, const FontStyleConfig& config,
const Uint32& tabWidth, Uint32 textDrawHints ) {
const Uint32& tabWidth, Uint32 textDrawHints,
std::optional<Float> tabOffset ) {
return getTextWidth<String::View>( config.Font, config.CharacterSize, string, config.Style,
tabWidth, config.OutlineThickness, textDrawHints );
tabWidth, config.OutlineThickness, textDrawHints,
tabOffset );
}
Sizef Text::draw( const String& string, const Vector2f& pos, Font* font, Float fontSize,
@@ -412,19 +429,9 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo
GlyphDrawable* spaceGlyph = nullptr;
GlyphDrawable* tabGlyph = nullptr;
Float hspace = font->getGlyph( ' ', fontSize, isBold, isItalic ).advance;
if ( whitespaceDisplayConfig.tabDisplayCharacter ) {
std::optional<Float> tabOffset{ whitespaceDisplayConfig.tabOffset };
if ( whitespaceDisplayConfig.tabDisplayCharacter )
tabGlyph = font->getGlyphDrawable( whitespaceDisplayConfig.tabDisplayCharacter, fontSize );
switch ( whitespaceDisplayConfig.tabAlign ) {
case CharacterAlignment::Center:
tabAlign = ( ( tabWidth * hspace ) - tabGlyph->getPixelsSize().getWidth() ) * 0.5f;
break;
case CharacterAlignment::Right:
tabAlign = ( tabWidth * hspace ) - tabGlyph->getPixelsSize().getWidth();
break;
case CharacterAlignment::Left:
break;
}
}
BR->setBlendMode( BlendMode::Alpha() );
BR->quadsBegin();
@@ -445,12 +452,30 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo
cluster = curGlyph.cluster;
ch = string[cluster];
if ( ch == '\t' ) {
Float advance = tabAdvance( hspace, tabWidth,
tabOffset ? cpos.x - pos.x + *tabOffset
: std::optional<Float>{} );
if ( whitespaceDisplayConfig.tabDisplayCharacter ) {
switch ( whitespaceDisplayConfig.tabAlign ) {
case CharacterAlignment::Center:
tabAlign =
( advance - tabGlyph->getPixelsSize().getWidth() ) * 0.5f;
break;
case CharacterAlignment::Right:
tabAlign = advance - tabGlyph->getPixelsSize().getWidth();
break;
case CharacterAlignment::Left:
break;
}
}
if ( tabGlyph ) {
drawGlyph( BR, tabGlyph, { cpos.x + tabAlign, cpos.y },
whitespaceDisplayConfig.color, isItalic );
}
width += hspace * tabWidth;
cpos.x += hspace * tabWidth;
width += advance;
cpos.x += advance;
} else {
if ( style & Text::Shadow ) {
auto* gds = font->getGlyphDrawableFromGlyphIndex(
@@ -552,7 +577,23 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, Font* font, Flo
case '\r':
continue;
case '\t': {
Float advance = hspace * tabWidth;
Float advance =
tabAdvance( hspace, tabWidth,
tabOffset ? cpos.x - pos.x + *tabOffset : std::optional<Float>{} );
if ( whitespaceDisplayConfig.tabDisplayCharacter ) {
switch ( whitespaceDisplayConfig.tabAlign ) {
case CharacterAlignment::Center:
tabAlign = ( advance - tabGlyph->getPixelsSize().getWidth() ) * 0.5f;
break;
case CharacterAlignment::Right:
tabAlign = advance - tabGlyph->getPixelsSize().getWidth();
break;
case CharacterAlignment::Left:
break;
}
}
if ( tabGlyph ) {
drawGlyph( BR, tabGlyph, { cpos.x + tabAlign, cpos.y },
whitespaceDisplayConfig.color, isItalic );
@@ -657,10 +698,12 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyle
template <typename StringType>
bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, const Float& maxWidth,
const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness ) {
const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness,
std::optional<Float> tabOffset ) {
if ( string.empty() || NULL == font )
return false;
Float x = 0.f;
Float tCurWidth = 0.f;
Float tWordWidth = 0.f;
auto tChar = &string[0];
@@ -680,12 +723,13 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con
Float fCharWidth = (Float)pChar.advance;
if ( ( *tChar ) == '\t' )
fCharWidth = hspace * tabWidth;
fCharWidth = tabAdvance( hspace, tabWidth, tabOffset ? ( *tabOffset + x ) : tabOffset );
else if ( ( *tChar ) == '\r' )
fCharWidth = 0;
// Add the new char width to the current word width
tWordWidth += fCharWidth;
x += fCharWidth;
if ( *tChar != '\r' ) {
tWordWidth +=
@@ -707,9 +751,11 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con
*tLastSpace = '\n';
tChar = tLastSpace + 1;
wrapped = true;
x = 0.f;
} else { // The word is larger than the current possible width
*tChar = '\n';
wrapped = true;
x = 0.f;
}
if ( tChar == tLastChar )
@@ -727,6 +773,7 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con
} else if ( '\n' == *tChar ) {
tWordWidth = 0.f;
tCurWidth = 0.f;
x = 0.f;
tLastSpace = NULL;
tChar++;
} else {
@@ -739,19 +786,21 @@ bool Text::wrapText( Font* font, const Uint32& fontSize, StringType& string, con
template <typename StringType>
bool Text::wrapText( StringType& string, const Float& maxWidth, const FontStyleConfig& config,
const Uint32& tabWidth ) {
const Uint32& tabWidth, std::optional<Float> tabOffset ) {
return wrapText<StringType>( config.Font, config.CharacterSize, string, maxWidth, config.Style,
tabWidth, config.OutlineThickness );
tabWidth, config.OutlineThickness, tabOffset );
}
bool Text::wrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth,
const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness ) {
return wrapText<String>( font, fontSize, string, maxWidth, style, tabWidth, outlineThickness );
const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness,
std::optional<Float> tabOffset ) {
return wrapText<String>( font, fontSize, string, maxWidth, style, tabWidth, outlineThickness,
tabOffset );
}
bool Text::wrapText( String& string, const Float& maxWidth, const FontStyleConfig& config,
const Uint32& tabWidth ) {
return wrapText<String>( string, maxWidth, config, tabWidth );
const Uint32& tabWidth, std::optional<Float> tabOffset ) {
return wrapText<String>( string, maxWidth, config, tabWidth, tabOffset );
}
Text::Text() {}
@@ -1004,7 +1053,8 @@ void Text::findWordFromCharacterIndex( Int32 characterIndex, Int32& initCur, Int
template <typename StringType>
Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType& string,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness, Uint32 textDrawHints ) {
const Float& outlineThickness, Uint32 textDrawHints,
std::optional<Float> tabOffset ) {
if ( NULL == font || string.empty() )
return 0;
Float width = 0;
@@ -1028,7 +1078,10 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType&
if ( string[i] == '\n' ) {
width = 0;
} else {
width += ( string[i] == '\t' ) ? hspace * tabWidth : hspace;
width +=
( string[i] == '\t' )
? tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset )
: hspace;
}
maxWidth = eemax( width, maxWidth );
}
@@ -1047,7 +1100,8 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType&
hb_glyph_info_t curGlyph = glyphInfo[i];
auto curChar = string[curGlyph.cluster];
if ( curChar == '\t' ) {
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth,
tabOffset ? *tabOffset + width : tabOffset );
} else {
const Glyph& glyph = font->getGlyphByIndex(
curGlyph.codepoint, fontSize, bold, italic, outlineThickness,
@@ -1081,7 +1135,7 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType&
codepoint = string.at( i );
Glyph glyph = font->getGlyph( codepoint, fontSize, bold, italic, outlineThickness );
if ( codepoint == '\t' ) {
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset );
} else if ( codepoint == '\n' ) {
width = 0;
} else if ( codepoint != '\r' ) {
@@ -1096,10 +1150,10 @@ Float Text::getTextWidth( Font* font, const Uint32& fontSize, const StringType&
}
template <typename StringType>
std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize,
const StringType& string, Float maxWidth,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness ) {
std::size_t
Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const StringType& string,
Float maxWidth, const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness, std::optional<Float> tabOffset ) {
if ( NULL == font || string.empty() )
return 0;
String::StringBaseType codepoint;
@@ -1127,7 +1181,8 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz
auto curChar = string[curGlyph.cluster];
if ( curChar == '\t' ) {
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth,
tabOffset ? *tabOffset + width : tabOffset );
} else {
const Glyph& glyph = font->getGlyphByIndex(
curGlyph.codepoint, fontSize, bold, italic, outlineThickness,
@@ -1170,7 +1225,7 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz
font->getKerning( prevChar, codepoint, fontSize, bold, italic, outlineThickness );
width += glyph.advance;
} else if ( codepoint == '\t' )
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset );
if ( width > maxWidth )
return i > 0 ? i - 1 : 0;
@@ -1183,7 +1238,8 @@ std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSiz
Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize,
const String& string, const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness, bool allowNewLine ) {
const Float& outlineThickness, std::optional<Float> tabOffset,
bool allowNewLine ) {
// Make sure that we have a valid font
if ( !font )
return Vector2f();
@@ -1227,7 +1283,9 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo
auto curChar = string[curGlyph.cluster];
if ( curChar == '\t' ) {
position.x += hspace * tabWidth;
position.x +=
tabAdvance( hspace, tabWidth,
tabOffset ? *tabOffset + position.x : tabOffset );
} else {
const Glyph& glyph = font->getGlyphByIndex(
curGlyph.codepoint, fontSize, bold, italic, outlineThickness,
@@ -1281,7 +1339,8 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo
position.x += hspace;
continue;
case '\t':
position.x += hspace * tabWidth;
position.x +=
tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + position.x : tabOffset );
continue;
case '\n':
if ( allowNewLine ) {
@@ -1306,7 +1365,8 @@ Vector2f Text::findCharacterPos( std::size_t index, Font* font, const Uint32& fo
Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font,
const Uint32& fontSize, const String& string, const Uint32& style,
const Uint32& tabWidth, const Float& outlineThickness ) {
const Uint32& tabWidth, const Float& outlineThickness,
std::optional<Float> tabOffset ) {
if ( NULL == font )
return 0;
@@ -1341,7 +1401,8 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
lWidth = width;
if ( curChar == '\t' ) {
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth,
tabOffset ? *tabOffset + width : tabOffset );
} else {
const Glyph& glyph = font->getGlyphByIndex(
curGlyph.codepoint, fontSize, bold, italic, outlineThickness,
@@ -1419,7 +1480,7 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
prevChar = codepoint;
width += glyph.advance;
} else if ( codepoint == '\t' ) {
width += hspace * tabWidth;
width += tabAdvance( hspace, tabWidth, tabOffset ? *tabOffset + width : tabOffset );
}
if ( codepoint == '\n' ) {
lWidth = 0;
@@ -1462,32 +1523,37 @@ Int32 Text::findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font*
std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize,
const String& string, Float maxWidth,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness ) {
const Float& outlineThickness,
std::optional<Float> tabOffset ) {
return findLastCharPosWithinLength<String>( font, fontSize, string, maxWidth, style, tabWidth,
outlineThickness );
outlineThickness, tabOffset );
}
std::size_t Text::findLastCharPosWithinLength( Font* font, const Uint32& fontSize,
const String::View& string, Float maxWidth,
const Uint32& style, const Uint32& tabWidth,
const Float& outlineThickness ) {
const Float& outlineThickness,
std::optional<Float> tabOffset ) {
return findLastCharPosWithinLength<String::View>( font, fontSize, string, maxWidth, style,
tabWidth, outlineThickness );
tabWidth, outlineThickness, tabOffset );
}
std::size_t Text::findLastCharPosWithinLength( const String& string, Float maxWidth,
const FontStyleConfig& config,
const Uint32& tabWidth ) {
const Uint32& tabWidth,
std::optional<Float> tabOffset ) {
return findLastCharPosWithinLength<String>( config.Font, config.CharacterSize, string, maxWidth,
config.Style, tabWidth, config.OutlineThickness );
config.Style, tabWidth, config.OutlineThickness,
tabOffset );
}
std::size_t Text::findLastCharPosWithinLength( const String::View& string, Float maxWidth,
const FontStyleConfig& config,
const Uint32& tabWidth ) {
const Uint32& tabWidth,
std::optional<Float> tabOffset ) {
return findLastCharPosWithinLength<String::View>( config.Font, config.CharacterSize, string,
maxWidth, config.Style, tabWidth,
config.OutlineThickness );
config.OutlineThickness, tabOffset );
}
void Text::updateWidthCache() {

View File

@@ -50,13 +50,12 @@ std::string DocumentView::fromLineWrapType( LineWrapType type ) {
}
Float DocumentView::computeOffsets( const String::View& string, const FontStyleConfig& fontStyle,
Uint32 tabWidth, Float maxWidth ) {
Uint32 tabWidth, Float maxWidth, bool tabStops ) {
static const String sepSpaces = " \t\n\v\f\r";
auto nonIndentPos = string.find_first_not_of( sepSpaces.data() );
if ( nonIndentPos != String::View::npos ) {
Float w = Text::getTextWidth( string.substr( 0, nonIndentPos ), fontStyle, tabWidth,
TextHints::AllAscii );
TextHints::AllAscii, tabStops ? 0 : std::optional<Float>{} );
return maxWidth != 0.f ? ( w > maxWidth ? 0.f : w ) : w;
}
return 0.f;
@@ -67,7 +66,7 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View&
Float maxWidth, LineWrapMode mode,
bool keepIndentation, Uint32 tabWidth,
Float whiteSpaceWidth, Uint32 textHints,
Float initialXOffset ) {
bool tabStops, Float initialXOffset ) {
LineWrapInfo info;
info.wraps.push_back( 0 );
if ( string.empty() || nullptr == fontStyle.Font || mode == LineWrapMode::NoWrap )
@@ -83,8 +82,8 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View&
: whiteSpaceWidth;
if ( keepIndentation ) {
info.paddingStart =
computeOffsets( string, fontStyle, tabWidth, eemax( maxWidth - hspace, hspace ) );
info.paddingStart = computeOffsets( string, fontStyle, tabWidth,
eemax( maxWidth - hspace, hspace ), tabStops );
}
Float xoffset = initialXOffset;
@@ -108,7 +107,7 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String::View&
: hspace;
if ( curChar == '\t' )
w = hspace * tabWidth;
w = Text::tabAdvance( hspace, tabWidth, tabStops ? w : std::optional<Float>{} );
if ( !isMonospace && curChar != '\r' ) {
w += fontStyle.Font->getKerning( prevChar, curChar, fontStyle.CharacterSize, bold,
@@ -143,22 +142,22 @@ DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const String& string
Float maxWidth, LineWrapMode mode,
bool keepIndentation, Uint32 tabWidth,
Float whiteSpaceWidth, Uint32 textHints,
Float initialXOffset ) {
bool tabStops, Float initialXOffset ) {
return computeLineBreaks( string.view(), fontStyle, maxWidth, mode, keepIndentation, tabWidth,
whiteSpaceWidth, textHints, initialXOffset );
whiteSpaceWidth, textHints, tabStops, initialXOffset );
}
DocumentView::LineWrapInfo DocumentView::computeLineBreaks( const TextDocument& doc, size_t line,
const FontStyleConfig& fontStyle,
Float maxWidth, LineWrapMode mode,
bool keepIndentation, Uint32 tabWidth,
Float whiteSpaceWidth,
Float whiteSpaceWidth, bool tabStops,
Float initialXOffset ) {
const auto& docLine = doc.line( line );
const auto& text = docLine.getText();
return computeLineBreaks( text.view().substr( 0, text.size() - 1 ), fontStyle, maxWidth, mode,
keepIndentation, tabWidth, whiteSpaceWidth, docLine.getTextHints(),
initialXOffset );
tabStops, initialXOffset );
}
DocumentView::DocumentView( std::shared_ptr<TextDocument> doc, FontStyleConfig fontStyle,
@@ -260,7 +259,7 @@ void DocumentView::invalidateCache() {
} else {
auto lb = wrap ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
mConfig.keepIndentation, mConfig.tabWidth,
mWhiteSpaceWidth )
mWhiteSpaceWidth, mConfig.tabStops )
: LineWrapInfo{ { 0 }, 0.f };
mVisibleLinesOffset.emplace_back( lb.paddingStart );
bool first = true;
@@ -526,9 +525,9 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
eemax( mMaxWidth - mWhiteSpaceWidth, mWhiteSpaceWidth ) ) );
mDocLineToVisibleIndex[i] = static_cast<Int64>( VisibleIndex::invalid );
} else {
auto lb =
computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
mConfig.keepIndentation, mConfig.tabWidth, mWhiteSpaceWidth );
auto lb = computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
mConfig.keepIndentation, mConfig.tabWidth,
mWhiteSpaceWidth, mConfig.tabStops );
mVisibleLinesOffset.insert( mVisibleLinesOffset.begin() + i, lb.paddingStart );
@@ -680,10 +679,11 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
}
continue;
}
auto lb = isWrapEnabled() ? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth,
mConfig.mode, mConfig.keepIndentation,
mConfig.tabWidth, mWhiteSpaceWidth )
: LineWrapInfo{ { 0 }, 0 };
auto lb = isWrapEnabled()
? computeLineBreaks( *mDoc, i, mFontStyle, mMaxWidth, mConfig.mode,
mConfig.keepIndentation, mConfig.tabWidth,
mWhiteSpaceWidth, mConfig.tabStops )
: LineWrapInfo{ { 0 }, 0 };
if ( recomputeOffset )
mVisibleLinesOffset[i] = lb.paddingStart;
for ( const auto& col : lb.wraps ) {
@@ -824,4 +824,11 @@ void DocumentView::setOnFoldUnfoldCb(
mOnFoldUnfoldCb = std::move( onFoldUnfoldCb );
}
void DocumentView::setTabStops( bool enabled ) {
if ( enabled != mConfig.tabStops ) {
mConfig.tabStops = enabled;
invalidateCache();
}
}
}}} // namespace EE::UI::Doc

View File

@@ -122,7 +122,7 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
UIWidget( elementTag ),
mFont( FontManager::instance()->getByName( "monospace" ) ),
mDoc( std::make_shared<TextDocument>() ),
mDocView( mDoc, mFontStyleConfig, {} ),
mDocView( mDoc, mFontStyleConfig, { .tabStops = mTabStops } ),
mBlinkTime( Seconds( 0.5f ) ),
mFoldsRefreshTime( Seconds( 2.f ) ),
mTabWidth( 4 ),
@@ -2019,12 +2019,13 @@ Float UICodeEditor::getLineWidth( const Int64& docLine ) {
auto found = mLinesWidthCache.find( docLine );
if ( found != mLinesWidthCache.end() && line.getHash() == found->second.first )
return found->second.second;
Float width = getTextWidth( line.getText() );
Float width = getTextWidth( line.getText(), {}, mTabStops ? 0 : std::optional<Float>{} );
mLinesWidthCache[docLine] = { line.getHash(), width };
return width;
}
return getTextWidth( mDoc->line( docLine ).getText(), isMonospaceLine );
return getTextWidth( mDoc->line( docLine ).getText(), isMonospaceLine,
mTabStops ? 0 : std::optional<Float>{} );
}
void UICodeEditor::updateScrollBar() {
@@ -2455,7 +2456,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position,
Float x =
Text::findCharacterPos( position.column() - info.range.start().column(), mFont,
getCharacterSize(), partialLine, mFontStyleConfig.Style,
mTabWidth, mFontStyleConfig.OutlineThickness, false )
mTabWidth, mFontStyleConfig.OutlineThickness,
mTabStops ? 0 : std::optional<Float>(), false )
.x;
if ( visualizeNewLine && allowVisualLineEnd &&
position.column() == (Int64)mDoc->line( position.line() ).getText().size() - 1 )
@@ -2468,7 +2470,8 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position,
Int64 maxCol = eemin( position.column(), info.range.end().column() );
for ( auto i = info.range.start().column(); i < maxCol; i++ ) {
if ( line[i] == '\t' ) {
x += glyphWidth * mTabWidth;
x += Text::tabAdvance( glyphWidth, mTabWidth,
mTabStops ? x : std::optional<Float>{} );
} else if ( visualizeNewLine || ( line[i] != '\n' && line[i] != '\r' ) ) {
x += glyphWidth;
}
@@ -2483,11 +2486,12 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position,
if ( !isMonospaceLine( position.line() ) ) {
bool isLastChar =
position.column() == (Int64)mDoc->line( position.line() ).getText().size();
Float x = Text::findCharacterPos(
isLastChar ? position.column() - 1 : position.column(), mFont,
getCharacterSize(), mDoc->line( position.line() ).getText(),
mFontStyleConfig.Style, mTabWidth, mFontStyleConfig.OutlineThickness, false )
.x;
Float x =
Text::findCharacterPos(
isLastChar ? position.column() - 1 : position.column(), mFont, getCharacterSize(),
mDoc->line( position.line() ).getText(), mFontStyleConfig.Style, mTabWidth,
mFontStyleConfig.OutlineThickness, mTabStops ? 0 : std::optional<Float>(), false )
.x;
if ( visualizeNewLine && isLastChar )
x += getGlyphWidth();
return { x, offsetY };
@@ -2499,7 +2503,7 @@ Vector2d UICodeEditor::getTextPositionOffset( const TextPosition& position,
Int64 maxCol = eemin( (Int64)line.size(), position.column() );
for ( auto i = 0; i < maxCol; i++ ) {
if ( line[i] == '\t' ) {
x += glyphWidth * mTabWidth;
x += Text::tabAdvance( glyphWidth, mTabWidth, mTabStops ? x : std::optional<Float>{} );
} else if ( visualizeNewLine || ( line[i] != '\n' && line[i] != '\r' ) ) {
x += glyphWidth;
}
@@ -2519,31 +2523,34 @@ size_t UICodeEditor::characterWidth( const String& str ) const {
return characterWidth<String>( str );
}
Float UICodeEditor::getTextWidth( const String& text ) const {
return getTextWidth<String>( text, false );
Float UICodeEditor::getTextWidth( const String& text, std::optional<Float> tabOffset ) const {
return getTextWidth<String>( text, false, tabOffset );
}
size_t UICodeEditor::characterWidth( const String::View& str ) const {
return characterWidth<String::View>( str );
}
Float UICodeEditor::getTextWidth( const String::View& text ) const {
return getTextWidth<String::View>( text, false );
Float UICodeEditor::getTextWidth( const String::View& text, std::optional<Float> tabOffset ) const {
return getTextWidth<String::View>( text, false, tabOffset );
}
Float UICodeEditor::getTextWidth( const String& text, bool fromMonospaceLine ) const {
return getTextWidth<String>( text, fromMonospaceLine );
Float UICodeEditor::getTextWidth( const String& text, bool fromMonospaceLine,
std::optional<Float> tabOffset ) const {
return getTextWidth<String>( text, fromMonospaceLine, tabOffset );
}
Float UICodeEditor::getTextWidth( const String::View& text, bool fromMonospaceLine ) const {
return getTextWidth<String::View>( text, fromMonospaceLine );
Float UICodeEditor::getTextWidth( const String::View& text, bool fromMonospaceLine,
std::optional<Float> tabOffset ) const {
return getTextWidth<String::View>( text, fromMonospaceLine, tabOffset );
}
template <typename StringType>
Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine ) const {
Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine,
std::optional<Float> tabOffset ) const {
if ( !fromMonospaceLine && isNotMonospace() ) {
return Text::getTextWidth( mFont, getCharacterSize(), line, mFontStyleConfig.Style,
mTabWidth );
mTabWidth, 0.f, 0, tabOffset );
}
Float glyphWidth = getGlyphWidth();
@@ -2551,7 +2558,11 @@ Float UICodeEditor::getTextWidth( const StringType& line, bool fromMonospaceLine
line.length() ? ( line[line.length() - 1] == '\n' ? line.length() - 1 : line.length() ) : 0;
Float x = 0;
for ( size_t i = 0; i < len; i++ )
x += ( line[i] == '\t' ) ? glyphWidth * mTabWidth : glyphWidth;
x +=
( line[i] == '\t' )
? Text::tabAdvance( glyphWidth, mTabWidth,
mTabStops ? ( x + ( tabOffset ? *tabOffset : 0 ) ) : tabOffset )
: glyphWidth;
return x;
}
@@ -3110,50 +3121,51 @@ Int64 UICodeEditor::getColFromXOffset( VisibleIndex visibleIndex, const Float& x
return visibleIndexRange.start().column() +
Text::findCharacterFromPos( Vector2i( eemax( -xOffset + x, 0.f ), 0 ), true,
mFont, getCharacterSize(), line,
mFontStyleConfig.Style, mTabWidth );
mFontStyleConfig.Style, mTabWidth, 0.f,
mTabStops ? 0 : std::optional<Float>() );
}
Int64 len = line.length();
Float glyphWidth = getGlyphWidth();
Float tabWidth = glyphWidth * mTabWidth;
Float hTab = tabWidth * 0.5f;
for ( int i = 0; i < len; i++ ) {
bool isTab = ( line[i] == '\t' );
Float advance = isTab ? Text::tabAdvance( glyphWidth, mTabWidth,
mTabStops ? xOffset : std::optional<Float>{} )
: glyphWidth;
if ( xOffset >= x ) {
auto col = xOffset - x > ( isTab ? hTab : glyphWidth * 0.5f )
? eemax<Int64>( 0, i - 1 )
: i;
auto col = xOffset - x > advance * 0.5f ? eemax<Int64>( 0, i - 1 ) : i;
return visibleIndexRange.start().column() + col;
} else if ( isTab && ( xOffset + tabWidth > x ) ) {
auto col = x - xOffset > hTab ? eemin<Int64>( i + 1, line.size() - 1 ) : i;
} else if ( isTab && ( xOffset + advance > x ) ) {
auto col =
x - xOffset > ( advance * 0.5f ) ? eemin<Int64>( i + 1, line.size() - 1 ) : i;
return visibleIndexRange.start().column() + col;
}
xOffset += isTab ? tabWidth : glyphWidth;
xOffset += advance;
}
return visibleIndexRange.start().column() + static_cast<Int64>( line.size() ) - 1;
}
if ( !isMonospaceLine( pos.line() ) ) {
return Text::findCharacterFromPos( Vector2i( x, 0 ), true, mFont, getCharacterSize(),
mDoc->line( pos.line() ).getText(),
mFontStyleConfig.Style, mTabWidth );
return Text::findCharacterFromPos(
Vector2i( x, 0 ), true, mFont, getCharacterSize(), mDoc->line( pos.line() ).getText(),
mFontStyleConfig.Style, mTabWidth, 0.f, mTabStops ? 0 : std::optional<Float>() );
}
const String& line = mDoc->line( pos.line() ).getText();
Int64 len = line.length();
Float glyphWidth = getGlyphWidth();
Float xOffset = 0;
Float tabWidth = glyphWidth * mTabWidth;
Float hTab = tabWidth * 0.5f;
for ( int i = 0; i < len; i++ ) {
bool isTab = ( line[i] == '\t' );
Float advance = isTab ? Text::tabAdvance( glyphWidth, mTabWidth,
mTabStops ? xOffset : std::optional<Float>{} )
: glyphWidth;
if ( xOffset >= x ) {
return xOffset - x > ( isTab ? hTab : glyphWidth * 0.5f ) ? eemax<Int64>( 0, i - 1 )
: i;
} else if ( isTab && ( xOffset + tabWidth > x ) ) {
return x - xOffset > hTab ? eemin<Int64>( i + 1, line.size() - 1 ) : i;
return xOffset - x > ( advance * 0.5f ) ? eemax<Int64>( 0, i - 1 ) : i;
} else if ( isTab && ( xOffset + advance > x ) ) {
return x - xOffset > ( advance * 0.5f ) ? eemin<Int64>( i + 1, line.size() - 1 ) : i;
}
xOffset += isTab ? tabWidth : glyphWidth;
xOffset += advance;
}
return static_cast<Int64>( line.size() ) - 1;
}
@@ -3822,6 +3834,7 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
bool ended = false;
Float lineOffset = getLineOffset();
size_t pos = 0;
Float initX = position.x;
Uint32 drawHints = docLine.getTextHints();
if ( mDoc->mightBeBinary() && mFont->getType() == FontType::TTF ) {
FontTrueType* ttf = static_cast<FontTrueType*>( mFont );
@@ -3832,7 +3845,8 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
}
WhitespaceDisplayConfig whitespaceDisplayConfig =
mShowWhitespaces ? WhitespaceDisplayConfig{ L'·', mTabIndentCharacter, mTabIndentAlignment,
Color( mWhitespaceColor ).blendAlpha( mAlpha ) }
Color( mWhitespaceColor ).blendAlpha( mAlpha ),
std::optional<Float>{} }
: WhitespaceDisplayConfig{};
String::View buff;
@@ -3869,12 +3883,17 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
if ( style.background != Color::Transparent ) {
primitives.setColor( Color( style.background ).blendAlpha( mAlpha ) );
primitives.drawRectangle(
Rectf( position,
Sizef( getTextWidth( text, isMonospace ), lineHeight ) ) );
primitives.drawRectangle( Rectf(
position, Sizef( getTextWidth( text, isMonospace,
mTabStops ? position.x - initX
: std::optional<Float>{} ),
lineHeight ) ) );
}
}
if ( mTabStops )
whitespaceDisplayConfig.tabOffset = position.x - initX;
position.x +=
Text::draw( text, { position.x, position.y + lineOffset }, fontStyle,
mTabWidth, drawHints, whitespaceDisplayConfig )
@@ -3913,7 +3932,12 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
}
pos += token.len;
Float textWidth = isMonospace ? getTextWidth( text, isMonospace ) : 0;
if ( mTabStops )
whitespaceDisplayConfig.tabOffset = position.x - initX;
Float textWidth =
isMonospace ? getTextWidth( text, isMonospace, whitespaceDisplayConfig.tabOffset )
: 0;
if ( !isMonospace || ( position.x + textWidth >= mScreenPos.x &&
position.x <= mScreenPos.x + mSize.getWidth() ) ) {
Int64 curCharsWidth = text.size();
@@ -4399,7 +4423,7 @@ void UICodeEditor::drawIndentationGuides( const DocumentLineRange& lineRange,
p.setForceDraw( false );
Float w = eefloor( PixelDensity::dpToPx( 1 ) );
String idt( mDoc->getIndentString() );
auto spaceW = getTextWidth( String( " " ) );
auto spaceW = getTextWidth( String( " " ), {} );
p.setColor( Color( mWhitespaceColor ).blendAlpha( mAlpha ) );
auto indentSize = mDoc->getIndentType() == TextDocument::IndentType::IndentTabs
? getTabWidth()
@@ -5352,4 +5376,11 @@ bool UICodeEditor::allowSelectingTextFromGutter() const {
return mAllowSelectingTextFromGutter;
}
void UICodeEditor::setTabStops( bool enabled ) {
if ( mTabStops != enabled ) {
mTabStops = enabled;
mDocView.setTabStops( enabled );
}
}
}} // namespace EE::UI

View File

@@ -141,6 +141,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
doc.writeUnicodeBOM = ini.getValueB( "document", "write_bom", false );
doc.indentWidth = ini.getValueI( "document", "indent_width", 4 );
doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false );
doc.tabStops = ini.getValueB( "document", "tab_stops", false );
doc.lineEndings =
TextFormat::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) );
// Migrate old data
@@ -303,6 +304,7 @@ void AppConfig::save( const std::vector<std::string>& recentFiles,
ini.setValueB( "document", "auto_detect_indent_type", doc.autoDetectIndentType );
ini.setValueB( "document", "write_bom", doc.writeUnicodeBOM );
ini.setValueI( "document", "indent_width", doc.indentWidth );
ini.setValueB( "document", "tab_stops", doc.tabStops );
ini.setValueB( "document", "indent_spaces", doc.indentSpaces );
ini.setValue( "document", "line_endings", TextFormat::lineEndingToString( doc.lineEndings ) );
ini.setValueI( "document", "tab_width", doc.tabWidth );

View File

@@ -98,6 +98,7 @@ struct DocumentConfig {
bool autoDetectIndentType{ true };
bool writeUnicodeBOM{ false };
bool indentSpaces{ false };
bool tabStops{ false };
TextFormat::LineEnding lineEndings{ TextFormat::LineEnding::LF };
int indentWidth{ 4 };
int tabWidth{ 4 };

View File

@@ -2368,6 +2368,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) );
editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) );
editor->setEnableFlashCursor( config.flashCursor );
editor->setTabStops( mConfig.doc.tabStops );
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) );

View File

@@ -634,6 +634,9 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->getConfig().doc.trimTrailingWhitespaces )
->setId( "trim_whitespaces" );
mGlobalMenu->addCheckBox( i18n( "tab_stops", "Tab Stops" ), mApp->getConfig().doc.tabStops )
->setId( "tab_stops" );
mGlobalMenu
->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ),
mApp->getConfig().doc.forceNewLineAtEndOfFile )
@@ -730,6 +733,11 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->getConfig().workspace.sessionSnapshot = item->isActive();
} else if ( "allow_flash_cursor" == id ) {
mApp->getConfig().editor.flashCursor = item->isActive();
} else if ( "tab_stops" == id ) {
mApp->getConfig().doc.tabStops = item->isActive();
mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setTabStops( mApp->getConfig().doc.tabStops );
} );
}
} else if ( "line_breaking_column" == id ) {
mApp->getSettingsActions()->setLineBreakingColumn();