mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-04 20:46:29 +03:00
Several fixes and improvements in TextDocument and UICodeEditor.
TextDocument now supports undo/redo (still testing, may have some bugs).
This commit is contained in:
@@ -2,11 +2,16 @@
|
||||
#define EE_UI_DOC_TEXTDOCUMENT
|
||||
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/clock.hpp>
|
||||
#include <eepp/system/time.hpp>
|
||||
#include <eepp/ui/doc/textposition.hpp>
|
||||
#include <eepp/ui/doc/textrange.hpp>
|
||||
#include <eepp/ui/doc/undostack.hpp>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
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<String> 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
|
||||
|
||||
114
include/eepp/ui/doc/undostack.hpp
Normal file
114
include/eepp/ui/doc/undostack.hpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#ifndef EE_UI_DOC_UNDOSTACK_HPP
|
||||
#define EE_UI_DOC_UNDOSTACK_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <eepp/config.hpp>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/time.hpp>
|
||||
#include <eepp/ui/doc/textrange.hpp>
|
||||
#include <memory>
|
||||
|
||||
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<std::unique_ptr<TextUndoCommand>>;
|
||||
|
||||
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<TextUndoCommand>&& 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
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String> 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();
|
||||
}
|
||||
|
||||
141
src/eepp/ui/doc/undostack.cpp
Normal file
141
src/eepp/ui/doc/undostack.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
#include <eepp/ui/doc/textdocument.hpp>
|
||||
#include <eepp/ui/doc/undostack.hpp>
|
||||
|
||||
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<TextUndoCommand>&& 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<TextUndoCommandInsert>( string, position, time ) );
|
||||
}
|
||||
|
||||
void UndoStack::pushRemove( UndoStackContainer& undoStack, const TextRange& range,
|
||||
const Time& time ) {
|
||||
pushUndo( undoStack, std::make_unique<TextUndoCommandRemove>( range, time ) );
|
||||
}
|
||||
|
||||
void UndoStack::pushSelection( UndoStackContainer& undoStack, const TextRange& selection,
|
||||
const Time& time ) {
|
||||
pushUndo( undoStack, std::make_unique<TextUndoCommandSelection>( 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<TextUndoCommandInsert*>( cmd.get() );
|
||||
mDoc->insert( insert->getPosition(), insert->getText(), redoStack,
|
||||
cmd->getTimestamp() );
|
||||
break;
|
||||
}
|
||||
case TextUndoCommandType::Remove: {
|
||||
TextUndoCommandRemove* remove = static_cast<TextUndoCommandRemove*>( cmd.get() );
|
||||
mDoc->remove( remove->getRange(), redoStack, cmd->getTimestamp() );
|
||||
break;
|
||||
}
|
||||
case TextUndoCommandType::Selection: {
|
||||
TextUndoCommandSelection* selection =
|
||||
static_cast<TextUndoCommandSelection*>( 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
|
||||
@@ -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<Float>( 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<Float>( 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<Int64>( 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<Float>( 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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user