From 098c882a8ddfecd3d80847a53c94d6072e1a004d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 20 May 2020 05:59:46 -0300 Subject: [PATCH] Added UICodeEditor, currently is a WIP. Improvements in TextDocument. --- include/eepp/core/string.hpp | 2 + include/eepp/ui.hpp | 1 + include/eepp/ui/doc/textdocument.hpp | 51 ++- include/eepp/ui/doc/textposition.hpp | 10 +- include/eepp/ui/doc/textrange.hpp | 4 + include/eepp/ui/uicodeedit.hpp | 97 ++++++ include/eepp/ui/uihelper.hpp | 1 + projects/linux/ee.creator.user | 2 +- projects/linux/ee.files | 2 + src/eepp/core/string.cpp | 4 + src/eepp/ui/doc/textdocument.cpp | 198 ++++++++++-- src/eepp/ui/uicodeedit.cpp | 300 ++++++++++++++++++ src/eepp/ui/uiwidgetcreator.cpp | 2 + src/eepp/window/backend/SDL2/inputsdl2.cpp | 5 - .../ui_hello_world/ui_hello_world.cpp | 11 +- 15 files changed, 641 insertions(+), 49 deletions(-) create mode 100644 include/eepp/ui/uicodeedit.hpp create mode 100644 src/eepp/ui/uicodeedit.cpp diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index d443371cf..59314a5d3 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -737,6 +737,8 @@ class EE_API String { std::size_t find_last_not_of( StringBaseType c, std::size_t pos = StringType::npos ) const; + size_t countChar( StringBaseType c ) const; + private: friend EE_API bool operator==( const String& left, const String& right ); friend EE_API bool operator<( const String& left, const String& right ); diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 938bd1253..d45fae173 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 2f95f83bc..3d9ec8028 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -4,19 +4,28 @@ #include #include #include +#include #include namespace EE { namespace UI { namespace Doc { class EE_API TextDocument { public: + class Client { + public: + virtual ~Client(); + virtual void onDocumentTextChanged() = 0; + virtual void onDocumentCursorChange( const TextPosition& ) = 0; + virtual void onDocumentSelectionChange( const TextRange& ) = 0; + }; + static bool isNonWord( String::StringBaseType ch ); TextDocument(); void reset(); - void load( const std::string& path ); + void loadFromPath( const std::string& path ); void save( const std::string& path ); @@ -24,7 +33,7 @@ class EE_API TextDocument { void setSelection( TextPosition position ); - void setSelection( TextPosition pos1, TextPosition pos2, bool swap = false ); + void setSelection( TextPosition start, TextPosition end, bool swap = false ); void setSelection( TextRange range ); @@ -34,6 +43,10 @@ class EE_API TextDocument { String& line( const size_t& index ); + const String& line( const size_t& index ) const; + + size_t lineCount() const; + std::vector& lines(); bool hasSelection() const; @@ -77,6 +90,10 @@ class EE_API TextDocument { TextPosition endOfDoc() const; + TextPosition getAbsolutePosition( TextPosition position ) const; + + Int64 getRelativeColumnOffset( TextPosition position ) const; + void deleteTo( TextPosition offset ); void deleteTo( int offset ); @@ -87,13 +104,41 @@ class EE_API TextDocument { void textInput( const String& text ); + void registerClient( Client& client ); + + void unregisterClient( Client& client ); + + void moveToPreviousChar(); + + void moveToNextChar(); + + void moveToPreviousLine( Int64 lastColIndex = 0 ); + + void moveToNextLine( Int64 lastColIndex = 0 ); + + void moveToPreviousPage( Int64 pageSize ); + + void moveToNextPage( Int64 pageSize ); + + const Uint32& getTabWidth() const; + + void setTabWidth( const Uint32& tabWidth ); + + TextPosition sanitizePosition( const TextPosition& position ) const; + protected: std::string mFilename; std::vector mLines; TextRange mSelection; + std::unordered_set mClients; bool mIsCLRF; + Uint32 mTabWidth{4}; - TextPosition sanitizePosition( const TextPosition& position ) const; + void notifyTextChanged(); + + void notifyCursorChanged(); + + void notifySelectionChanged(); }; }}} // namespace EE::UI::Doc diff --git a/include/eepp/ui/doc/textposition.hpp b/include/eepp/ui/doc/textposition.hpp index 52a48b026..418184552 100644 --- a/include/eepp/ui/doc/textposition.hpp +++ b/include/eepp/ui/doc/textposition.hpp @@ -10,13 +10,13 @@ class EE_API TextPosition { public: TextPosition() {} - TextPosition( size_t line, size_t column ) : mLine( line ), mColumn( column ) {} + TextPosition( Int64 line, Int64 column ) : mLine( line ), mColumn( column ) {} bool isValid() const { return mLine != 0xffffffffu && mColumn != 0xffffffffu; } - size_t line() const { return mLine; } + Int64 line() const { return mLine; } - size_t column() const { return mColumn; } + Int64 column() const { return mColumn; } void setLine( size_t line ) { mLine = line; } @@ -51,8 +51,8 @@ class EE_API TextPosition { } private: - size_t mLine{0xffffffff}; - size_t mColumn{0xffffffff}; + Int64 mLine{0xffffffff}; + Int64 mColumn{0xffffffff}; }; }}} // namespace EE::UI::Doc diff --git a/include/eepp/ui/doc/textrange.hpp b/include/eepp/ui/doc/textrange.hpp index efbcdcab8..42a2de0eb 100644 --- a/include/eepp/ui/doc/textrange.hpp +++ b/include/eepp/ui/doc/textrange.hpp @@ -40,6 +40,10 @@ class EE_API TextRange { return mStart == other.mStart && mEnd == other.mEnd; } + bool operator!=( const TextRange& other ) const { + return mStart != other.mStart || mEnd != other.mEnd; + } + bool contains( const TextPosition& position ) const { if ( !( position.line() > mStart.line() || ( position.line() == mStart.line() && position.column() >= mStart.column() ) ) ) diff --git a/include/eepp/ui/uicodeedit.hpp b/include/eepp/ui/uicodeedit.hpp new file mode 100644 index 000000000..dc7909c4d --- /dev/null +++ b/include/eepp/ui/uicodeedit.hpp @@ -0,0 +1,97 @@ +#ifndef EE_UI_UICODEEDIT_HPP +#define EE_UI_UICODEEDIT_HPP + +#include +#include +#include + +using namespace EE::Graphics; +using namespace EE::UI::Doc; + +namespace EE { namespace Graphics { +class Font; +}} // namespace EE::Graphics + +namespace EE { namespace UI { + +class EE_API UICodeEdit : public UIWidget, public TextDocument::Client { + public: + static UICodeEdit* New(); + + UICodeEdit(); + + virtual ~UICodeEdit(); + + virtual Uint32 getType() const; + + virtual bool isType( const Uint32& type ) const; + + virtual void setTheme( UITheme* Theme ); + + virtual void draw(); + + virtual void scheduledUpdate( const Time& time ); + + void reset(); + + void loadFromFile( const std::string& path ); + + Font* getFont() const; + + void setFont( Font* font ); + + const Uint32& getTabWidth() const; + void setTabWidth( const Uint32& tabWidth ); + + protected: + Font* mFont; + UIFontStyleConfig mFontStyleConfig; + Doc::TextDocument mDoc; + Vector2f mScrollPos; + Clock mBlinkTimer; + bool mDirtyEditor; + bool mCursorVisible; + Uint32 mTabWidth; + Int64 mLastColOffset; + Vector2f mScroll; + + void invalidateEditor(); + + virtual Uint32 onKeyDown( const KeyEvent& Event ); + + virtual Uint32 onMouseDown( const Vector2i& position, const Uint32& flags ); + + virtual void onSizeChange(); + + virtual void onPaddingChange(); + + void updateEditor(); + + void onDocumentTextChanged(); + void onDocumentCursorChange( const TextPosition& ); + void onDocumentSelectionChange( const TextRange& ); + + std::pair getVisibleLineRange(); + + int getVisibleLinesCount(); + + void scrollToMakeVisible( const TextPosition& position ); + + Float getXOffsetCol( const TextPosition& position ) const; + + Int64 getColFromXOffset( Int64 line, const Float& offset ) const; + + Float getLineHeight() const; + + Float getCharacterSize() const; + + Float getGlyphWidth() const; + + void updateLastColumnOffset(); + + TextPosition resolveScreenPosition( const Vector2f& position ) const; +}; + +}} // namespace EE::UI + +#endif // EE_UI_UICODEEDIT_HPP diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp index 774ae86a5..578b50e03 100644 --- a/include/eepp/ui/uihelper.hpp +++ b/include/eepp/ui/uihelper.hpp @@ -83,6 +83,7 @@ enum UINodeType { UI_TYPE_LAYOUT, UI_TYPE_VIEWPAGER, UI_TYPE_ITEMCONTAINER, + UI_TYPE_CODEEDIT, UI_TYPE_USER = 10000 }; diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index b39fdf1f5..f931a4694 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/projects/linux/ee.files b/projects/linux/ee.files index a38a67ec2..84929ac8c 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -325,6 +325,7 @@ ../../include/eepp/ui/uibackgrounddrawable.hpp ../../include/eepp/ui/uiborderdrawable.hpp ../../include/eepp/ui/uicheckbox.hpp +../../include/eepp/ui/uicodeedit.hpp ../../include/eepp/ui/uicombobox.hpp ../../include/eepp/ui/uicommondialog.hpp ../../include/eepp/ui/uidropdownlist.hpp @@ -765,6 +766,7 @@ ../../src/eepp/ui/uibackgrounddrawable.cpp ../../src/eepp/ui/uiborderdrawable.cpp ../../src/eepp/ui/uicheckbox.cpp +../../src/eepp/ui/uicodeedit.cpp ../../src/eepp/ui/uicombobox.cpp ../../src/eepp/ui/uicommondialog.cpp ../../src/eepp/ui/uidropdownlist.cpp diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 02f8f0329..9cc797763 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -956,6 +956,10 @@ std::size_t String::find_last_not_of( StringBaseType c, std::size_t pos ) const return mString.find_last_not_of( c, pos ); } +size_t String::countChar( String::StringBaseType c ) const { + return std::count( begin(), end(), c ); +} + bool operator==( const String& left, const String& right ) { return left.mString == right.mString; } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 6bf02a0ad..b0eb0b31d 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -22,14 +22,17 @@ void TextDocument::reset() { mFilename = "unsaved"; mSelection.set( {0, 0}, {0, 0} ); mLines.clear(); + notifyTextChanged(); + notifyCursorChanged(); + notifySelectionChanged(); } -void TextDocument::load( const std::string& path ) { +void TextDocument::loadFromPath( const std::string& path ) { reset(); mFilename = path; std::string line; - std::ifstream infile( path ); - while ( std::getline( infile, line ) ) { + std::ifstream file( path ); + while ( std::getline( file, line ) ) { std::istringstream iss( line ); if ( mLines.empty() && line.size() >= 3 ) { // Check UTF-8 BOM header @@ -46,6 +49,8 @@ void TextDocument::load( const std::string& path ) { } if ( mLines.empty() ) { mLines.emplace_back( String( "\n" ) ); + } else if ( mLines[mLines.size() - 1].at( mLines[mLines.size() - 1].size() - 1 ) == '\n' ) { + mLines.emplace_back( String( "\n" ) ); } } @@ -59,15 +64,25 @@ void TextDocument::setSelection( TextPosition position ) { setSelection( position, position ); } -void TextDocument::setSelection( TextPosition pos1, TextPosition pos2, bool swap ) { +void TextDocument::setSelection( TextPosition start, TextPosition end, bool swap ) { if ( swap ) { - auto posT = pos1; - pos1 = pos2; - pos2 = posT; + auto posT = start; + start = end; + end = posT; + } + + if ( start == end ) { + start = end = sanitizePosition( start ); + } else { + start = sanitizePosition( start ); + end = sanitizePosition( end ); + } + + if ( mSelection != TextRange( start, end ) ) { + mSelection.set( start, end ); + notifyCursorChanged(); + notifySelectionChanged(); } - pos1 = sanitizePosition( pos1 ); - pos2 = sanitizePosition( pos2 ); - mSelection.set( pos1, pos2 ); } void TextDocument::setSelection( TextRange range ) { @@ -86,6 +101,14 @@ String& TextDocument::line( const size_t& index ) { return mLines[index]; } +const String& TextDocument::line( const size_t& index ) const { + return mLines[index]; +} + +size_t TextDocument::lineCount() const { + return mLines.size(); +} + std::vector& TextDocument::lines() { return mLines; } @@ -101,7 +124,7 @@ String TextDocument::getText( const TextRange& range ) const { nrange.end().column() ); } std::vector lines = {mLines[nrange.start().line()].substr( nrange.start().column() )}; - for ( size_t i = nrange.start().line() + 1; i < nrange.end().line() - 1; i++ ) { + for ( auto i = nrange.start().line() + 1; i < nrange.end().line() - 1; i++ ) { lines.emplace_back( mLines[i] ); } lines.emplace_back( mLines[nrange.end().line()].substr( 0, nrange.end().column() ) ); @@ -122,38 +145,37 @@ TextPosition TextDocument::insert( const TextPosition& position, const String& t TextPosition TextDocument::insert( TextPosition position, const String::StringBaseType& ch ) { position = sanitizePosition( position ); - size_t m_soft_tab_width = 4; - bool at_head = position.column() == 0; - bool at_tail = position.column() == line( position.line() ).length(); + size_t tabWidth = mTabWidth; + bool atHead = position.column() == 0; + bool atTail = position.column() == (Int64)line( position.line() ).length(); if ( ch == '\n' ) { - if ( at_tail || at_head ) { + if ( atTail || atHead ) { String new_line_contents; size_t row = position.line(); String line_content; for ( size_t i = position.column(); i < line( row ).length(); i++ ) line_content.append( line( row )[i] ); - mLines.insert( mLines.begin() + position.line() + ( at_tail ? 1 : 0 ), + mLines.insert( mLines.begin() + position.line() + ( atTail ? 1 : 0 ), new_line_contents ); - return {position.line() + 1, line( position.line() + 1 ).length()}; + return {position.line() + 1, (Int64)line( position.line() + 1 ).length()}; } - String new_line; - new_line += + String newLine; + newLine += line( position.line() ) .substr( position.column(), line( position.line() ).length() - position.column() ); - String line_content( new_line ); + String lineContent( newLine ); line( position.line() ).substr( 0, position.column() ); - mLines.insert( mLines.begin() + position.line() + 1, std::move( new_line ) ); + mLines.insert( mLines.begin() + position.line() + 1, std::move( newLine ) ); return {position.line() + 1, 0}; } else if ( ch == '\t' ) { - size_t next_soft_tab_stop = - ( ( position.column() + m_soft_tab_width ) / m_soft_tab_width ) * m_soft_tab_width; - size_t spaces_to_insert = next_soft_tab_stop - position.column(); + Int64 nextSoftTabStop = ( ( position.column() + tabWidth ) / tabWidth ) * tabWidth; + size_t spaces_to_insert = nextSoftTabStop - position.column(); for ( size_t i = 0; i < spaces_to_insert; ++i ) { line( position.line() ) .insert( line( position.line() ).begin() + position.column(), ' ' ); } - return {position.line(), next_soft_tab_stop}; + return {position.line(), nextSoftTabStop}; } line( position.line() ).insert( line( position.line() ).begin() + position.column(), ch ); return {position.line(), position.column() + 1}; @@ -172,7 +194,7 @@ void TextDocument::remove( TextRange range ) { range.setEnd( sanitizePosition( range.end() ) ); // First delete all the lines in between the first and last one. - for ( size_t i = range.start().line() + 1; i < range.end().line(); ) { + for ( auto i = range.start().line() + 1; i < range.end().line(); ) { mLines.erase( mLines.begin() + i ); range.end().setLine( range.end().line() - 1 ); } @@ -181,7 +203,7 @@ void TextDocument::remove( TextRange range ) { // Delete within same line. auto& line = this->line( range.start().line() ); bool whole_line_is_selected = - range.start().column() == 0 && range.end().column() == line.length(); + range.start().column() == 0 && range.end().column() == (Int64)line.length(); if ( whole_line_is_selected ) { line.clear(); @@ -215,8 +237,8 @@ TextPosition TextDocument::positionOffset( TextPosition position, int columnOffs position.setLine( position.line() - 1 ); position.setColumn( position.column() + mLines[position.line()].size() ); } - while ( position.line() < mLines.size() && - position.column() > mLines[position.line()].size() ) { + while ( position.line() < (Int64)mLines.size() && + position.column() > (Int64)mLines[position.line()].size() - 1 ) { position.setColumn( position.column() - mLines[position.line()].size() ); position.setLine( position.line() + 1 ); } @@ -228,11 +250,11 @@ TextPosition TextDocument::positionOffset( TextPosition position, TextPosition o } TextPosition TextDocument::nextChar( TextPosition position ) const { - return positionOffset( position, 1 ); + return positionOffset( position, TextPosition( 0, 1 ) ); } TextPosition TextDocument::previousChar( TextPosition position ) const { - return positionOffset( position, -1 ); + return positionOffset( position, TextPosition( 0, -1 ) ); } TextPosition TextDocument::previousWordBoundary( TextPosition position ) const { @@ -307,6 +329,30 @@ TextPosition TextDocument::endOfDoc() const { return TextPosition( mLines.size() - 1, mLines[mLines.size() - 1].size() - 1 ); } +TextPosition TextDocument::getAbsolutePosition( TextPosition position ) const { + position = sanitizePosition( position ); + const String& string = line( position.line() ); + size_t tabCount = string.substr( 0, position.column() ).countChar( '\t' ); + return TextPosition( position.line(), position.column() - tabCount + tabCount * getTabWidth() ); +} + +Int64 TextDocument::getRelativeColumnOffset( TextPosition position ) const { + const String& line = mLines[position.line()]; + Int64 length = eemin( position.column(), line.size() - 1 ); + Int64 offset = 0; + for ( Int64 i = 0; i <= length; ++i ) { + if ( offset >= position.column() ) { + return i; + } + if ( line[i] == '\t' ) { + offset += getTabWidth(); + } else if ( line[i] != '\n' && line[i] != '\r' ) { + offset += 1; + } + } + return length; +} + void TextDocument::deleteTo( int offset ) { TextPosition cursorPos = getSelection( true ).start(); if ( hasSelection() ) { @@ -340,6 +386,72 @@ void TextDocument::textInput( const String& text ) { moveTo( TextPosition( 0, text.size() ) ); } +void TextDocument::registerClient( TextDocument::Client& client ) { + mClients.insert( &client ); +} + +void TextDocument::unregisterClient( TextDocument::Client& client ) { + mClients.erase( &client ); +} + +void TextDocument::moveToPreviousChar() { + if ( hasSelection() ) { + TextRange selection = getSelection( true ); + setSelection( selection.end() ); + } else { + setSelection( positionOffset( getSelection().start(), -1 ) ); + } +} + +void TextDocument::moveToNextChar() { + if ( hasSelection() ) { + TextRange selection = getSelection( true ); + setSelection( selection.start() ); + } else { + setSelection( positionOffset( getSelection().start(), 1 ) ); + } +} + +void TextDocument::moveToPreviousLine( Int64 lastColIndex ) { + TextPosition pos = getSelection().start(); + pos.setLine( pos.line() - 1 ); + if ( pos.line() >= 0 ) { + lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) ); + } + pos.setColumn( lastColIndex ); + setSelection( pos ); +} + +void TextDocument::moveToNextLine( Int64 lastColIndex ) { + TextPosition pos = getSelection().start(); + pos.setLine( pos.line() + 1 ); + if ( pos.line() < (Int64)mLines.size() ) { + lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) ); + } + pos.setColumn( lastColIndex ); + setSelection( pos ); +} + +void TextDocument::moveToPreviousPage( Int64 pageSize ) { + TextPosition pos = getSelection().start(); + pos.setLine( pos.line() - pageSize ); + setSelection( pos ); +} + +void TextDocument::moveToNextPage( Int64 pageSize ) { + TextPosition pos = getSelection().start(); + pos.setLine( pos.line() + pageSize ); + setSelection( pos ); +} + +const Uint32& TextDocument::getTabWidth() const { + return mTabWidth; +} + +void TextDocument::setTabWidth( const Uint32& tabWidth ) { + mTabWidth = tabWidth; +} + void TextDocument::deleteTo( TextPosition offset ) { TextPosition cursorPos = getSelection( true ).start(); if ( hasSelection() ) { @@ -360,9 +472,29 @@ void TextDocument::print() const { } TextPosition TextDocument::sanitizePosition( const TextPosition& position ) const { - size_t line = eeclamp( position.line(), 0UL, mLines.size() - 1 ); - size_t col = eeclamp( position.column(), 0UL, mLines[line].size() - 1 ); + Int64 line = eeclamp( position.line(), 0UL, mLines.size() - 1 ); + Int64 col = eeclamp( position.column(), 0UL, mLines[line].size() - 1 ); return {line, col}; } +void TextDocument::notifyTextChanged() { + for ( auto& client : mClients ) { + client->onDocumentTextChanged(); + } +} + +void TextDocument::notifyCursorChanged() { + for ( auto& client : mClients ) { + client->onDocumentCursorChange( getSelection().start() ); + } +} + +void TextDocument::notifySelectionChanged() { + for ( auto& client : mClients ) { + client->onDocumentSelectionChange( getSelection() ); + } +} + +TextDocument::Client::~Client() {} + }}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uicodeedit.cpp b/src/eepp/ui/uicodeedit.cpp new file mode 100644 index 000000000..b054698e3 --- /dev/null +++ b/src/eepp/ui/uicodeedit.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EE { namespace UI { + +UICodeEdit* UICodeEdit::New() { + return eeNew( UICodeEdit, () ); +} + +UICodeEdit::UICodeEdit() : + UIWidget( "codeedit" ), + mFont( FontManager::instance()->getByName( "monospace" ) ), + mDirtyEditor( false ), + mCursorVisible( false ), + mTabWidth( 4 ), + mLastColOffset( 0 ) { + clipEnable(); + if ( NULL == mFont ) { + mFont = FontTrueType::New( "monospace", "assets/fonts/DejaVuSansMono.ttf" ); + } + mDoc.registerClient( *this ); + subscribeScheduledUpdate(); +} + +UICodeEdit::~UICodeEdit() { + mDoc.unregisterClient( *this ); +} + +Uint32 UICodeEdit::getType() const { + return UI_TYPE_CODEEDIT; +} + +bool UICodeEdit::isType( const Uint32& type ) const { + return type == getType() || UIWidget::isType( type ); +} + +void UICodeEdit::setTheme( UITheme* Theme ) { + UIWidget::setTheme( Theme ); + setThemeSkin( Theme, "textedit" ); +} + +void UICodeEdit::draw() { + if ( mDirtyEditor ) { + updateEditor(); + } + + if ( mFont == NULL ) + return; + + Float charSize = getCharacterSize(); + Float lineHeight = getLineHeight(); + Vector2f start( mScreenPos.x + mRealPadding.Left, mScreenPos.y + mRealPadding.Top ); + Vector2f startScroll( start - mScroll ); + std::pair lineRange = getVisibleLineRange(); + for ( int i = lineRange.first; i <= lineRange.second; i++ ) { + Text line( mDoc.line( i ), mFont, charSize ); + line.draw( startScroll.x, startScroll.y + lineHeight * i ); + } + + Primitives primitives; + TextPosition cursor( mDoc.getSelection().start() ); + + primitives.setColor( Color( 255, 255, 255, 50 ) ); + primitives.drawRectangle( + Rectf( Vector2f( startScroll.x, startScroll.y + cursor.line() * lineHeight ), + Sizef( mSize.getWidth(), lineHeight ) ) ); + + if ( mDoc.hasSelection() ) { + } + + if ( mCursorVisible ) { + Vector2f cursorPos( startScroll.x + getXOffsetCol( cursor ), + startScroll.y + cursor.line() * lineHeight ); + + primitives.setColor( Color::White ); + primitives.drawRectangle( + Rectf( cursorPos, Sizef( PixelDensity::dpToPx( 2 ), lineHeight ) ) ); + } +} + +void UICodeEdit::scheduledUpdate( const Time& ) { + if ( hasFocus() ) { + if ( mBlinkTimer.getElapsedTime().asSeconds() > 0.5f ) { + mCursorVisible = !mCursorVisible; + mBlinkTimer.restart(); + invalidateDraw(); + } + } +} + +void UICodeEdit::reset() {} + +void UICodeEdit::loadFromFile( const std::string& path ) { + mDoc.loadFromPath( path ); + invalidateEditor(); +} + +Font* UICodeEdit::getFont() const { + return mFont; +} + +void UICodeEdit::setFont( Font* font ) { + if ( mFont != font ) { + mFont = font; + invalidateDraw(); + invalidateEditor(); + } +} + +const Uint32& UICodeEdit::getTabWidth() const { + return mTabWidth; +} + +void UICodeEdit::setTabWidth( const Uint32& tabWidth ) { + if ( mTabWidth != tabWidth ) { + mTabWidth = tabWidth; + mDoc.setTabWidth( mTabWidth ); + } +} + +void UICodeEdit::invalidateEditor() { + mDirtyEditor = true; +} + +Uint32 UICodeEdit::onKeyDown( const KeyEvent& event ) { + switch ( event.getKeyCode() ) { + case KEY_UP: { + mDoc.moveToPreviousLine( mLastColOffset ); + break; + } + case KEY_DOWN: { + mDoc.moveToNextLine( mLastColOffset ); + break; + } + case KEY_LEFT: { + mDoc.moveToPreviousChar(); + updateLastColumnOffset(); + break; + } + case KEY_RIGHT: { + mDoc.moveToNextChar(); + updateLastColumnOffset(); + break; + } + case KEY_HOME: { + mDoc.setSelection( mDoc.startOfLine( mDoc.getSelection().start() ) ); + updateLastColumnOffset(); + break; + } + case KEY_END: { + mDoc.setSelection( mDoc.endOfLine( mDoc.getSelection().start() ) ); + updateLastColumnOffset(); + break; + } + case KEY_PAGEUP: { + mDoc.moveToPreviousPage( getVisibleLinesCount() ); + updateLastColumnOffset(); + break; + } + case KEY_PAGEDOWN: { + mDoc.moveToNextPage( getVisibleLinesCount() ); + updateLastColumnOffset(); + break; + } + default: + break; + } + + return 1; +} + +TextPosition UICodeEdit::resolveScreenPosition( const Vector2f& position ) const { + Vector2f localPos( position ); + worldToNode( localPos ); + localPos = PixelDensity::dpToPx( localPos ); + localPos += mScroll; + localPos.x -= mRealPadding.Left; + localPos.y -= mRealPadding.Top; + Int64 line = + eeclamp( (Int64)eefloor( localPos.y / getLineHeight() ), 0, mDoc.lineCount() - 1 ); + return TextPosition( line, getColFromXOffset( line, localPos.x ) ); +} + +Uint32 UICodeEdit::onMouseDown( const Vector2i& position, const Uint32& flags ) { + if ( flags & EE_BUTTON_LMASK ) { + mDoc.setSelection( resolveScreenPosition( position.asFloat() ) ); + } + return UIWidget::onMouseDown( position, flags ); +} + +void UICodeEdit::onSizeChange() { + UIWidget::onSizeChange(); + invalidateEditor(); +} + +void UICodeEdit::onPaddingChange() { + UIWidget::onPaddingChange(); + invalidateEditor(); +} + +void UICodeEdit::updateEditor() { + scrollToMakeVisible( mDoc.getSelection().start() ); +} + +void UICodeEdit::onDocumentTextChanged() { + invalidateDraw(); +} + +void UICodeEdit::onDocumentCursorChange( const Doc::TextPosition& ) { + mCursorVisible = true; + mBlinkTimer.restart(); + invalidateEditor(); + invalidateDraw(); +} + +void UICodeEdit::onDocumentSelectionChange( const Doc::TextRange& ) { + mCursorVisible = true; + mBlinkTimer.restart(); + invalidateDraw(); +} + +std::pair UICodeEdit::getVisibleLineRange() { + Float lineHeight = getLineHeight(); + Float minLine = eemax( 0.f, eefloor( mScroll.y / lineHeight ) ); + Float maxLine = eemin( mDoc.lineCount() - 1.f, + eefloor( ( mSize.getHeight() + mScroll.y ) / lineHeight ) + 1 ); + return std::make_pair( (int)minLine, (int)maxLine ); +} + +int UICodeEdit::getVisibleLinesCount() { + auto lines = getVisibleLineRange(); + return lines.second - lines.first; +} + +void UICodeEdit::scrollToMakeVisible( const TextPosition& position ) { + Float lineHeight = getLineHeight(); + Float min = lineHeight * ( eemax( 0, position.line() - 1 ) ); + Float max = lineHeight * ( position.line() + 2 ) - mSize.getHeight(); + mScroll.y = eemin( mScroll.y, min ); + mScroll.y = eemax( mScroll.y, max ); + invalidateDraw(); +} + +Float UICodeEdit::getXOffsetCol( const TextPosition& position ) const { + const String& line = mDoc.line( position.line() ); + Float glyphWidth = getGlyphWidth(); + Float x = 0; + for ( auto i = 0; i < position.column(); i++ ) { + if ( line[i] == '\t' ) { + x += glyphWidth * mTabWidth; + } else if ( line[i] != '\n' && line[i] != '\r' ) { + x += glyphWidth; + } + } + return x; +} + +Int64 UICodeEdit::getColFromXOffset( Int64 _line, const Float& offset ) const { + if ( offset <= 0 ) + return 0; + TextPosition pos = mDoc.sanitizePosition( TextPosition( _line, 0 ) ); + const String& line = mDoc.line( pos.line() ); + Float glyphWidth = getGlyphWidth(); + Float x = 0; + for ( size_t i = 0; i < line.size(); i++ ) { + if ( line[i] == '\t' ) { + x += glyphWidth * mTabWidth; + } else if ( line[i] != '\n' && line[i] != '\r' ) { + x += glyphWidth; + } + if ( x >= offset ) { + return i; + } + } + return line.size() - 1; +} + +Float UICodeEdit::getLineHeight() const { + return mFont->getLineSpacing( getCharacterSize() ); +} + +Float UICodeEdit::getCharacterSize() const { + return PixelDensity::pxToDp( mFontStyleConfig.getFontCharacterSize() ); +} + +Float UICodeEdit::getGlyphWidth() const { + return mFont->getGlyph( KEY_SPACE, getCharacterSize(), false ).advance; +} + +void UICodeEdit::updateLastColumnOffset() { + mLastColOffset = mDoc.getAbsolutePosition( mDoc.getSelection().start() ).column(); +} + +}} // namespace EE::UI diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp index fda9dfa28..94a767dd8 100644 --- a/src/eepp/ui/uiwidgetcreator.cpp +++ b/src/eepp/ui/uiwidgetcreator.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -76,6 +77,7 @@ void UIWidgetCreator::createBaseWidgetList() { registeredWidget["gridlayout"] = UIGridLayout::New; registeredWidget["layout"] = UILayout::New; registeredWidget["viewpager"] = UIViewPager::New; + registeredWidget["codeedit"] = UICodeEdit ::New; registeredWidget["hbox"] = UILinearLayout::NewHorizontal; registeredWidget["vbox"] = UILinearLayout::NewVertical; diff --git a/src/eepp/window/backend/SDL2/inputsdl2.cpp b/src/eepp/window/backend/SDL2/inputsdl2.cpp index a6495c697..cfa606df5 100644 --- a/src/eepp/window/backend/SDL2/inputsdl2.cpp +++ b/src/eepp/window/backend/SDL2/inputsdl2.cpp @@ -160,11 +160,6 @@ void InputSDL::update() { EEEvent.key.state = SDLEvent.key.state; EEEvent.key.which = SDLEvent.key.windowID; EEEvent.key.keysym.sym = KeyCodesTable[SDLEvent.key.keysym.scancode]; - - if ( SDLEvent.key.keysym.scancode == SDL_SCANCODE_1 ) { - EEEvent.key.state = SDLEvent.key.state; - } - EEEvent.key.keysym.mod = SDLEvent.key.keysym.mod; EEEvent.key.keysym.unicode = 0; break; diff --git a/src/examples/ui_hello_world/ui_hello_world.cpp b/src/examples/ui_hello_world/ui_hello_world.cpp index 29b587861..dbca0188a 100644 --- a/src/examples/ui_hello_world/ui_hello_world.cpp +++ b/src/examples/ui_hello_world/ui_hello_world.cpp @@ -29,14 +29,14 @@ void mainLoop() { EE_MAIN_FUNC int main( int, char** ) { Doc::TextDocument doc; - doc.load( "/home/downloads/textposition.hpp" ); + doc.loadFromPath( "/home/downloads/textposition.hpp" ); doc.remove( TextRange( TextPosition( 0, 0 ), TextPosition( 1, 40 ) ) ); doc.insert( TextPosition( 0, 0 ), String( "#ifndef EE_UI_DOC_TEXPOS\n#define EE_UI_DOC_TEXPOS" ) ); doc.remove( TextRange( TextPosition( 31, 2 ), TextPosition( 33, 15 ) ) ); doc.insert( TextPosition( 31, 2 ), String( "hexNumber" ) ); doc.print(); - return EXIT_SUCCESS; + // return EXIT_SUCCESS; win = Engine::instance()->createWindow( WindowSettings( 640, 480, "eepp - UI Hello World" ), ContextSettings( true ) ); @@ -68,6 +68,10 @@ EE_MAIN_FUNC int main( int, char** ) { layout_width="match_parent" layout_height="wrap_content" text="Hello, I am a PushButton" /> + )xml" ); @@ -89,6 +93,9 @@ EE_MAIN_FUNC int main( int, char** ) { } )css" ); + uiSceneNode->find( "code_edit" ) + ->loadFromFile( "/home/downloads/textposition.hpp" ); + win->runMainLoop( &mainLoop ); }