From 825626a9d2744f634960ad47c941d9d33c041237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Fri, 22 May 2020 04:36:17 -0300 Subject: [PATCH] Several fixes and improvements in TextDocument and UICodeEditor. TextDocument now supports undo/redo (still testing, may have some bugs). --- include/eepp/ui/doc/textdocument.hpp | 29 +++++- include/eepp/ui/doc/undostack.hpp | 114 ++++++++++++++++++++++ include/eepp/ui/uicodeeditor.hpp | 4 + projects/linux/ee.files | 2 + projects/linux/ee.includes | 3 - src/eepp/ui/doc/textdocument.cpp | 86 ++++++++++++---- src/eepp/ui/doc/undostack.cpp | 141 +++++++++++++++++++++++++++ src/eepp/ui/uicodeeditor.cpp | 109 +++++++++++++++------ src/eepp/window/input.cpp | 3 + 9 files changed, 434 insertions(+), 57 deletions(-) create mode 100644 include/eepp/ui/doc/undostack.hpp create mode 100644 src/eepp/ui/doc/undostack.cpp diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index f1ea323b2..d735888e6 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -2,11 +2,16 @@ #define EE_UI_DOC_TEXTDOCUMENT #include +#include +#include #include #include +#include #include #include +using namespace EE::System; + namespace EE { namespace UI { namespace Doc { class EE_API TextDocument { @@ -61,8 +66,6 @@ class EE_API TextDocument { TextPosition insert( const TextPosition& position, const String& text ); - TextPosition insert( TextPosition position, const String::StringBaseType& text ); - void remove( TextPosition position ); void remove( TextRange range ); @@ -98,13 +101,13 @@ class EE_API TextDocument { Int64 getRelativeColumnOffset( TextPosition position ) const; - void deleteTo( TextPosition offset ); + void deleteTo( TextPosition position ); void deleteTo( int offset ); void deleteSelection(); - void selectTo( TextPosition offset ); + void selectTo( TextPosition position ); void selectTo( int offset ); @@ -138,6 +141,10 @@ class EE_API TextDocument { void deleteToNextChar(); + void deleteToPreviousWord(); + + void deleteToNextWord(); + void selectToPreviousChar(); void selectToNextChar(); @@ -180,7 +187,13 @@ class EE_API TextDocument { void setIndentType( const IndentType& indentType ); + void undo(); + + void redo(); + protected: + friend class UndoStack; + UndoStack mUndoStack; std::string mFilename; std::vector mLines; TextRange mSelection; @@ -188,6 +201,7 @@ class EE_API TextDocument { bool mIsCLRF; Uint32 mTabWidth{4}; IndentType mIndentType{IndentTabs}; + Clock mTimer; void notifyTextChanged(); @@ -198,6 +212,13 @@ class EE_API TextDocument { void insertAtStartOfSelectedLines( String text, bool skipEmpty ); void removeFromStartOfSelectedLines( String text, bool skipEmpty ); + + void remove( TextRange range, UndoStackContainer& undoStack, const Time& time ); + + TextPosition insert( const TextPosition& position, const String& text, + UndoStackContainer& undoStack, const Time& time ); + + TextPosition insert( TextPosition position, const String::StringBaseType& text ); }; }}} // namespace EE::UI::Doc diff --git a/include/eepp/ui/doc/undostack.hpp b/include/eepp/ui/doc/undostack.hpp new file mode 100644 index 000000000..691e1f602 --- /dev/null +++ b/include/eepp/ui/doc/undostack.hpp @@ -0,0 +1,114 @@ +#ifndef EE_UI_DOC_UNDOSTACK_HPP +#define EE_UI_DOC_UNDOSTACK_HPP + +#include +#include +#include +#include +#include +#include + +using namespace EE::System; + +namespace EE { namespace UI { namespace Doc { + +class TextDocument; + +enum class TextUndoCommandType { Insert, Remove, Selection }; + +class EE_API TextUndoCommand { + public: + TextUndoCommand( const TextUndoCommandType& type, const Time& timestamp ); + + virtual ~TextUndoCommand(); + + const TextUndoCommandType& getType() const; + + const Time& getTimestamp() const; + + protected: + TextUndoCommandType mType; + Time mTimestamp; +}; + +class EE_API TextUndoCommandInsert : public TextUndoCommand { + public: + TextUndoCommandInsert( const String& text, const TextPosition& position, + const Time& timestamp ); + + const String& getText() const; + + const TextPosition& getPosition() const; + + protected: + String mText; + TextPosition mPosition; +}; + +class EE_API TextUndoCommandRemove : public TextUndoCommand { + public: + TextUndoCommandRemove( const TextRange& range, const Time& timestamp ); + + const TextRange& getRange() const; + + protected: + TextRange mRange; +}; + +class EE_API TextUndoCommandSelection : public TextUndoCommand { + public: + TextUndoCommandSelection( const TextRange& selection, const Time& timestamp ); + + const TextRange& getSelection() const; + + protected: + TextRange mSelection; +}; + +using UndoStackContainer = std::deque>; + +class EE_API UndoStack { + public: + UndoStack( TextDocument* owner, const Uint32& maxStackSize = 1000 ); + + void clearRedoStack(); + + void undo(); + + void redo(); + + const Uint32& getMaxStackSize() const; + + const Time& getMergeTimeout() const; + + void setMergeTimeout( const Time& mergeTimeout ); + + protected: + friend class TextDocument; + + TextDocument* mDoc; + Uint32 mMaxStackSize; + UndoStackContainer mUndoStack; + UndoStackContainer mRedoStack; + Time mMergeTimeout; + + void pushUndo( UndoStackContainer& undoStack, std::unique_ptr&& cmd ); + + void pushInsert( UndoStackContainer& undoStack, const String& string, + const TextPosition& position, const Time& time ); + + void pushRemove( UndoStackContainer& undoStack, const TextRange& range, const Time& time ); + + void pushSelection( UndoStackContainer& undoStack, const TextRange& selection, + const Time& time ); + + UndoStackContainer& getUndoStackContainer(); + + UndoStackContainer& getRedoStackContainer(); + + void popUndo( UndoStackContainer& undoStack, UndoStackContainer& redoStack ); +}; + +}}} // namespace EE::UI::Doc + +#endif // EE_UI_DOC_UNDOSTACK_HPP diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 3c86ec274..0edfe3ba4 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -88,6 +88,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual Uint32 onMouseDoubleClick( const Vector2i& position, const Uint32& flags ); + virtual Uint32 onMouseOver( const Vector2i& position, const Uint32& flags ); + + virtual Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags ); + virtual void onSizeChange(); virtual void onPaddingChange(); diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 0acdd54b4..5a8e2a21c 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -318,6 +318,7 @@ ../../include/eepp/ui/doc/textdocument.hpp ../../include/eepp/ui/doc/textposition.hpp ../../include/eepp/ui/doc/textrange.hpp +../../include/eepp/ui/doc/undostack.hpp ../../include/eepp/ui/keyboardshortcut.hpp ../../include/eepp/ui/marginmove/scale.hpp ../../include/eepp/ui/tools/textureatlaseditor.hpp @@ -757,6 +758,7 @@ ../../src/eepp/ui/css/stylesheetvariable.cpp ../../src/eepp/ui/css/transitiondefinition.cpp ../../src/eepp/ui/doc/textdocument.cpp +../../src/eepp/ui/doc/undostack.cpp ../../src/eepp/ui/tools/textureatlaseditor.cpp ../../src/eepp/ui/tools/textureatlasnew.cpp ../../src/eepp/ui/tools/textureatlasnew.hpp diff --git a/projects/linux/ee.includes b/projects/linux/ee.includes index 8b895b62f..0018824a1 100644 --- a/projects/linux/ee.includes +++ b/projects/linux/ee.includes @@ -5,8 +5,5 @@ ../../src/thirdparty/efsw/include ../../src/thirdparty/libvorbis/include /usr/include/freetype2/ -../../include/eepp/ui/css -../../src/eepp/ui/css ../../src/thirdparty/mbedtls/include -../../include/eepp/ui ../../docs/articles diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index b896d3ea5..394aa331f 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -16,7 +16,7 @@ bool TextDocument::isNonWord( String::StringBaseType ch ) { return false; } -TextDocument::TextDocument() {} +TextDocument::TextDocument() : mUndoStack( this ) {} void TextDocument::reset() { mFilename = "unsaved"; @@ -124,8 +124,8 @@ bool TextDocument::hasSelection() const { String TextDocument::getText( const TextRange& range ) const { TextRange nrange = range.normalized(); if ( nrange.start().line() == nrange.end().line() ) { - return mLines[nrange.start().line()].substr( nrange.start().column(), - nrange.end().column() ); + return mLines[nrange.start().line()].substr( + nrange.start().column(), nrange.end().column() - nrange.start().column() ); } std::vector lines = {mLines[nrange.start().line()].substr( nrange.start().column() )}; for ( auto i = nrange.start().line() + 1; i <= nrange.end().line() - 1; i++ ) { @@ -145,9 +145,23 @@ String::StringBaseType TextDocument::getChar( const TextPosition& position ) con } TextPosition TextDocument::insert( const TextPosition& position, const String& text ) { + mUndoStack.clearRedoStack(); + return insert( position, text, mUndoStack.getUndoStackContainer(), mTimer.getElapsedTime() ); +} + +TextPosition TextDocument::insert( const TextPosition& position, const String& text, + UndoStackContainer& undoStack, const Time& time ) { TextPosition cursor = position; - for ( size_t i = 0; i < text.length(); ++i ) + + for ( size_t i = 0; i < text.length(); ++i ) { cursor = insert( cursor, text[i] ); + } + + mUndoStack.pushSelection( undoStack, getSelection(), time ); + mUndoStack.pushRemove( undoStack, {position, cursor}, time ); + + notifyTextChanged(); + return cursor; } @@ -157,27 +171,26 @@ TextPosition TextDocument::insert( TextPosition position, const String::StringBa bool atTail = position.column() == (Int64)line( position.line() ).length() - 1; if ( ch == '\n' ) { if ( atTail || atHead ) { - String newLineContents( "\n" ); 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() + ( atTail ? 1 : 0 ), newLineContents ); - notifyTextChanged(); - if ( atTail ) - return {position.line() + 1, (Int64)line( position.line() + 1 ).length()}; - return {position.line() + 1, 0}; + mLines.insert( mLines.begin() + position.line() + ( atTail ? 1 : 0 ), String( "\n" ) ); + return atTail + ? TextPosition( position.line() + 1, line( position.line() + 1 ).length() ) + : TextPosition( position.line() + 1, 0 ); } String newLine = line( position.line() ) .substr( position.column(), line( position.line() ).length() - position.column() ); line( position.line() ) = line( position.line() ).substr( 0, position.column() ); + if ( newLine.empty() ) { + eePRINTL( "wtf" ); + } mLines.insert( mLines.begin() + position.line() + 1, std::move( newLine ) ); - notifyTextChanged(); return {position.line() + 1, 0}; } line( position.line() ).insert( line( position.line() ).begin() + position.column(), ch ); - notifyTextChanged(); return {position.line(), position.column() + 1}; } @@ -186,12 +199,19 @@ void TextDocument::remove( TextPosition position ) { } void TextDocument::remove( TextRange range ) { - if ( !range.isValid() ) - return; - + mUndoStack.clearRedoStack(); range = range.normalized(); range.setStart( sanitizePosition( range.start() ) ); range.setEnd( sanitizePosition( range.end() ) ); + remove( range, mUndoStack.getUndoStackContainer(), mTimer.getElapsedTime() ); +} + +void TextDocument::remove( TextRange range, UndoStackContainer& undoStack, const Time& time ) { + if ( !range.isValid() ) + return; + + mUndoStack.pushSelection( undoStack, getSelection(), time ); + mUndoStack.pushInsert( undoStack, getText( range ), range.start(), time ); // First delete all the lines in between the first and last one. for ( auto i = range.start().line() + 1; i < range.end().line(); ) { @@ -221,12 +241,15 @@ void TextDocument::remove( TextRange range ) { auto beforeSelection = firstLine.substr( 0, range.start().column() ); auto afterSelection = secondLine.substr( range.end().column(), secondLine.length() - range.end().column() ); + if ( beforeSelection.empty() && afterSelection.empty() ) { + beforeSelection += '\n'; + } firstLine.assign( beforeSelection + afterSelection ); mLines.erase( mLines.begin() + range.end().line() ); } if ( lines().empty() ) { - mLines.emplace_back( String() ); + mLines.emplace_back( String( "\n" ) ); } notifyTextChanged(); } @@ -374,8 +397,8 @@ void TextDocument::deleteSelection() { setSelection( cursorPos ); } -void TextDocument::selectTo( TextPosition offset ) { - setSelection( TextRange( sanitizePosition( offset ), getSelection().end() ) ); +void TextDocument::selectTo( TextPosition position ) { + setSelection( TextRange( sanitizePosition( position ), getSelection().end() ) ); } void TextDocument::selectTo( int offset ) { @@ -483,6 +506,14 @@ void TextDocument::deleteToNextChar() { deleteTo( 1 ); } +void TextDocument::deleteToPreviousWord() { + deleteTo( previousWordBoundary( getSelection().start() ) ); +} + +void TextDocument::deleteToNextWord() { + deleteTo( nextWordBoundary( getSelection().start() ) ); +} + void TextDocument::selectToPreviousChar() { selectTo( -1 ); } @@ -626,13 +657,12 @@ void TextDocument::setTabWidth( const Uint32& tabWidth ) { mTabWidth = tabWidth; } -void TextDocument::deleteTo( TextPosition offset ) { +void TextDocument::deleteTo( TextPosition position ) { TextPosition cursorPos = getSelection( true ).start(); if ( hasSelection() ) { remove( getSelection() ); } else { - TextPosition delPos = positionOffset( cursorPos, offset ); - TextRange range( cursorPos, delPos ); + TextRange range( cursorPos, position ); remove( range ); range = range.normalized(); cursorPos = range.start(); @@ -660,7 +690,21 @@ void TextDocument::setIndentType( const IndentType& indentType ) { mIndentType = indentType; } +void TextDocument::undo() { + mUndoStack.undo(); +} + +void TextDocument::redo() { + mUndoStack.redo(); +} + void TextDocument::notifyTextChanged() { + for ( size_t i = 0; i < mLines.size(); i++ ) { + if ( mLines[i].empty() ) { + eePRINTL( "wtf" ); + } + } + for ( auto& client : mClients ) { client->onDocumentTextChanged(); } diff --git a/src/eepp/ui/doc/undostack.cpp b/src/eepp/ui/doc/undostack.cpp new file mode 100644 index 000000000..97e7c90bd --- /dev/null +++ b/src/eepp/ui/doc/undostack.cpp @@ -0,0 +1,141 @@ +#include +#include + +using namespace EE::System; + +namespace EE { namespace UI { namespace Doc { + +TextUndoCommand::TextUndoCommand( const TextUndoCommandType& type, const Time& timestamp ) : + mType( type ), mTimestamp( timestamp ) {} + +TextUndoCommand::~TextUndoCommand() {} + +const TextUndoCommandType& TextUndoCommand::getType() const { + return mType; +} + +const Time& TextUndoCommand::getTimestamp() const { + return mTimestamp; +} + +TextUndoCommandInsert::TextUndoCommandInsert( const String& text, const TextPosition& position, + const Time& timestamp ) : + TextUndoCommand( TextUndoCommandType::Insert, timestamp ), + mText( text ), + mPosition( position ) {} + +const String& TextUndoCommandInsert::getText() const { + return mText; +} + +const TextPosition& TextUndoCommandInsert::getPosition() const { + return mPosition; +} + +TextUndoCommandRemove::TextUndoCommandRemove( const TextRange& range, const Time& timestamp ) : + TextUndoCommand( TextUndoCommandType::Remove, timestamp ), mRange( range ) {} + +const TextRange& TextUndoCommandRemove::getRange() const { + return mRange; +} + +TextUndoCommandSelection::TextUndoCommandSelection( const TextRange& selection, + const Time& timestamp ) : + TextUndoCommand( TextUndoCommandType::Selection, timestamp ), mSelection( selection ) {} + +const TextRange& TextUndoCommandSelection::getSelection() const { + return mSelection; +} + +UndoStack::UndoStack( TextDocument* owner, const Uint32& maxStackSize ) : + mDoc( owner ), mMaxStackSize( maxStackSize ), mMergeTimeout( Milliseconds( 300.f ) ) {} + +void UndoStack::clearRedoStack() { + mRedoStack.clear(); +} + +void UndoStack::pushUndo( UndoStackContainer& undoStack, std::unique_ptr&& cmd ) { + undoStack.push_back( std::move( cmd ) ); + while ( undoStack.size() > mMaxStackSize ) { + undoStack.pop_front(); + } +} + +void UndoStack::pushInsert( UndoStackContainer& undoStack, const String& string, + const TextPosition& position, const Time& time ) { + pushUndo( undoStack, std::make_unique( string, position, time ) ); +} + +void UndoStack::pushRemove( UndoStackContainer& undoStack, const TextRange& range, + const Time& time ) { + pushUndo( undoStack, std::make_unique( range, time ) ); +} + +void UndoStack::pushSelection( UndoStackContainer& undoStack, const TextRange& selection, + const Time& time ) { + pushUndo( undoStack, std::make_unique( selection, time ) ); +} + +void UndoStack::popUndo( UndoStackContainer& undoStack, UndoStackContainer& redoStack ) { + if ( undoStack.empty() ) + return; + + auto cmd = std::move( undoStack.back() ); + undoStack.pop_back(); + + switch ( cmd->getType() ) { + case TextUndoCommandType::Insert: { + TextUndoCommandInsert* insert = static_cast( cmd.get() ); + mDoc->insert( insert->getPosition(), insert->getText(), redoStack, + cmd->getTimestamp() ); + break; + } + case TextUndoCommandType::Remove: { + TextUndoCommandRemove* remove = static_cast( cmd.get() ); + mDoc->remove( remove->getRange(), redoStack, cmd->getTimestamp() ); + break; + } + case TextUndoCommandType::Selection: { + TextUndoCommandSelection* selection = + static_cast( cmd.get() ); + mDoc->setSelection( selection->getSelection() ); + break; + } + } + + if ( !undoStack.empty() && + eeabs( ( cmd->getTimestamp() - undoStack.back()->getTimestamp() ).asMilliseconds() ) < + mMergeTimeout.asMilliseconds() ) { + popUndo( undoStack, redoStack ); + } +} + +void UndoStack::undo() { + popUndo( mUndoStack, mRedoStack ); +} + +void UndoStack::redo() { + popUndo( mRedoStack, mUndoStack ); +} + +const Uint32& UndoStack::getMaxStackSize() const { + return mMaxStackSize; +} + +const Time& UndoStack::getMergeTimeout() const { + return mMergeTimeout; +} + +void UndoStack::setMergeTimeout( const Time& mergeTimeout ) { + mMergeTimeout = mergeTimeout; +} + +UndoStackContainer& UndoStack::getUndoStackContainer() { + return mUndoStack; +} + +UndoStackContainer& UndoStack::getRedoStackContainer() { + return mRedoStack; +} + +}}} // namespace EE::UI::Doc diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 27e8aea4b..18d4ccf01 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -25,6 +25,7 @@ UICodeEditor::UICodeEditor() : mMouseWheelScroll( 50 ) { clipEnable(); if ( NULL == mFont ) { + // TODO: Remove this. mFont = FontTrueType::New( "monospace", "assets/fonts/DejaVuSansMono.ttf" ); } mDoc.registerClient( *this ); @@ -45,7 +46,7 @@ bool UICodeEditor::isType( const Uint32& type ) const { void UICodeEditor::setTheme( UITheme* Theme ) { UIWidget::setTheme( Theme ); - setThemeSkin( Theme, "textedit" ); + setThemeSkin( Theme, "codeeditor" ); } void UICodeEditor::draw() { @@ -230,12 +231,23 @@ Uint32 UICodeEditor::onTextInput( const TextInputEvent& event ) { Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { switch ( event.getKeyCode() ) { case KEY_BACKSPACE: { - mDoc.deleteToPreviousChar(); - updateLastColumnOffset(); + if ( event.getMod() & KEYMOD_CTRL ) { + mDoc.deleteToPreviousWord(); + updateLastColumnOffset(); + } else { + mDoc.deleteToPreviousChar(); + updateLastColumnOffset(); + } break; } case KEY_DELETE: { - mDoc.deleteToNextChar(); + if ( event.getMod() & KEYMOD_CTRL ) { + mDoc.deleteToNextWord(); + updateLastColumnOffset(); + } else { + mDoc.deleteToNextChar(); + updateLastColumnOffset(); + } break; } case KEY_KP_ENTER: @@ -245,7 +257,10 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_UP: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_CTRL ) { + mScroll.y = eefloor( eemax( 0, mScroll.y - getLineHeight() ) ); + invalidateDraw(); + } else if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToPreviousLine( mLastColOffset ); } else { mDoc.moveToPreviousLine( mLastColOffset ); @@ -253,7 +268,11 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_DOWN: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_CTRL ) { + mScroll.y = + eefloor( eemin( getMaxScroll().y, mScroll.y + getLineHeight() ) ); + invalidateDraw(); + } else if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToNextLine( mLastColOffset ); } else { mDoc.moveToNextLine( mLastColOffset ); @@ -261,11 +280,11 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_LEFT: { - if ( ( event.getMod() & KEYMOD_LSHIFT ) && ( event.getMod() & KEYMOD_LCTRL ) ) { + if ( ( event.getMod() & KEYMOD_SHIFT ) && ( event.getMod() & KEYMOD_CTRL ) ) { mDoc.selectToPreviousWord(); - } else if ( event.getMod() & KEYMOD_LSHIFT ) { + } else if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToPreviousChar(); - } else if ( event.getMod() & KEYMOD_LCTRL ) { + } else if ( event.getMod() & KEYMOD_CTRL ) { mDoc.moveToPreviousWord(); } else { mDoc.moveToPreviousChar(); @@ -274,11 +293,11 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_RIGHT: { - if ( ( event.getMod() & KEYMOD_LSHIFT ) && ( event.getMod() & KEYMOD_LCTRL ) ) { + if ( ( event.getMod() & KEYMOD_SHIFT ) && ( event.getMod() & KEYMOD_CTRL ) ) { mDoc.selectToNextWord(); - } else if ( event.getMod() & KEYMOD_LSHIFT ) { + } else if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToNextChar(); - } else if ( event.getMod() & KEYMOD_LCTRL ) { + } else if ( event.getMod() & KEYMOD_CTRL ) { mDoc.moveToNextWord(); } else { mDoc.moveToNextChar(); @@ -287,10 +306,10 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_HOME: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToStartOfLine(); updateLastColumnOffset(); - } else if ( event.getMod() & KEYMOD_LCTRL ) { + } else if ( event.getMod() & KEYMOD_CTRL ) { mScroll.y = 0; mDoc.setSelection( {0, 0} ); invalidateDraw(); @@ -301,10 +320,10 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_END: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToEndOfLine(); updateLastColumnOffset(); - } else if ( event.getMod() & KEYMOD_LCTRL ) { + } else if ( event.getMod() & KEYMOD_CTRL ) { mScroll.y = getMaxScroll().y; mDoc.setSelection( {static_cast( mDoc.lineCount() - 1 ), @@ -317,7 +336,7 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_PAGEUP: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToPreviousPage( getVisibleLinesCount() ); updateLastColumnOffset(); } else { @@ -327,7 +346,7 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_PAGEDOWN: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.selectToNextPage( getVisibleLinesCount() ); updateLastColumnOffset(); } else { @@ -337,7 +356,7 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_TAB: { - if ( event.getMod() & KEYMOD_LSHIFT ) { + if ( event.getMod() & KEYMOD_SHIFT ) { mDoc.unindent(); updateLastColumnOffset(); } else if ( !event.getMod() ) { @@ -347,30 +366,47 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { break; } case KEY_V: { - if ( event.getMod() & KEYMOD_LCTRL ) { + if ( event.getMod() & KEYMOD_CTRL ) { mDoc.textInput( getUISceneNode()->getWindow()->getClipboard()->getText() ); } break; } case KEY_C: { - if ( event.getMod() & KEYMOD_LCTRL ) { + if ( event.getMod() & KEYMOD_CTRL ) { getUISceneNode()->getWindow()->getClipboard()->setText( mDoc.getSelectedText() ); } break; } case KEY_X: { - if ( event.getMod() & KEYMOD_LCTRL ) { + if ( event.getMod() & KEYMOD_CTRL ) { getUISceneNode()->getWindow()->getClipboard()->setText( mDoc.getSelectedText() ); mDoc.deleteSelection(); } break; } case KEY_A: { - if ( event.getMod() & KEYMOD_LCTRL ) { + if ( event.getMod() & KEYMOD_CTRL ) { mDoc.selectAll(); } break; } + case KEY_Z: { + if ( ( event.getMod() & KEYMOD_CTRL ) && ( event.getMod() & KEYMOD_SHIFT ) ) { + mDoc.redo(); + updateLastColumnOffset(); + } else if ( event.getMod() & KEYMOD_CTRL ) { + mDoc.undo(); + updateLastColumnOffset(); + } + break; + } + case KEY_Y: { + if ( event.getMod() & KEYMOD_CTRL ) { + mDoc.redo(); + updateLastColumnOffset(); + } + break; + } default: break; } @@ -401,13 +437,18 @@ Sizef UICodeEditor::getMaxScroll() const { eefloor( ( mSize.getWidth() - mRealPadding.Left - mRealPadding.Right ) / getGlyphWidth() ), vplc.y > mDoc.lineCount() - 1 ? 0.f - : ( mDoc.lineCount() - getViewPortLineCount().y ) * getLineHeight() ); + : eefloor( mDoc.lineCount() - getViewPortLineCount().y ) * getLineHeight() ); } Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags ) { if ( !mMouseDown && ( flags & EE_BUTTON_LMASK ) ) { mMouseDown = true; - mDoc.setSelection( resolveScreenPosition( position.asFloat() ) ); + Input* input = getUISceneNode()->getWindow()->getInput(); + if ( input->isShiftPressed() ) { + mDoc.selectTo( resolveScreenPosition( position.asFloat() ) ); + } else { + mDoc.setSelection( resolveScreenPosition( position.asFloat() ) ); + } } return UIWidget::onMouseDown( position, flags ); } @@ -426,11 +467,11 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags ) mMouseDown = false; } else if ( flags & EE_BUTTON_WDMASK ) { mScroll.y += PixelDensity::dpToPx( mMouseWheelScroll ); - mScroll.y = eemin( mScroll.y, getMaxScroll().y ); + mScroll.y = eefloor( eemin( mScroll.y, getMaxScroll().y ) ); invalidateDraw(); } else if ( flags & EE_BUTTON_WUMASK ) { mScroll.y -= PixelDensity::dpToPx( mMouseWheelScroll ); - mScroll.y = eemax( mScroll.y, 0.f ); + mScroll.y = eefloor( eemax( mScroll.y, 0.f ) ); invalidateDraw(); } return UIWidget::onMouseUp( position, flags ); @@ -443,6 +484,16 @@ Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i&, const Uint32& flags ) return 1; } +Uint32 UICodeEditor::onMouseOver( const Vector2i& position, const Uint32& flags ) { + getUISceneNode()->setCursor( Cursor::IBeam ); + return UIWidget::onMouseOver( position, flags ); +} + +Uint32 UICodeEditor::onMouseLeave( const Vector2i& Pos, const Uint32& Flags ) { + getUISceneNode()->setCursor( Cursor::Arrow ); + return UIWidget::onMouseLeave( Pos, Flags ); +} + void UICodeEditor::onSizeChange() { UIWidget::onSizeChange(); mScroll = {0, 0}; @@ -492,7 +543,7 @@ void UICodeEditor::scrollToMakeVisible( const TextPosition& position ) { 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 ); + mScroll.y = eefloor( eemax( mScroll.y, max ) ); invalidateDraw(); } @@ -539,7 +590,7 @@ Float UICodeEditor::getCharacterSize() const { } Float UICodeEditor::getGlyphWidth() const { - return mFont->getGlyph( KEY_SPACE, getCharacterSize(), false ).advance; + return mFont->getGlyph( ' ', getCharacterSize(), false ).advance; } void UICodeEditor::updateLastColumnOffset() { diff --git a/src/eepp/window/input.cpp b/src/eepp/window/input.cpp index 11a3f4ee3..ae35a8033 100644 --- a/src/eepp/window/input.cpp +++ b/src/eepp/window/input.cpp @@ -68,6 +68,9 @@ void Input::processEvent( InputEvent* Event ) { break; } case InputEvent::KeyUp: { + if ( Event->key.keysym.mod != eeINDEX_NOT_FOUND ) + mInputMod = Event->key.keysym.mod; + if ( Event->key.keysym.sym > EE_KEYS_NUM ) break;