Added context menu on UICodeEditor.

This commit is contained in:
Martín Lucas Golini
2022-03-21 11:01:03 -03:00
parent 375d1e66e8
commit 1b3deaa7d4
16 changed files with 267 additions and 20 deletions

View File

@@ -78,6 +78,7 @@ class EE_API Event {
OnResourceChange,
OnActiveWidgetChange,
OnWindowReady,
OnCreateContextMenu,
UserEvent,
NoEvent = eeINDEX_NOT_FOUND
};

View File

@@ -9,7 +9,7 @@ namespace EE { namespace Scene {
class EE_API MouseEvent : public Event {
public:
MouseEvent( Node* node, const Uint32& EventNum, const Vector2i& Pos, const Uint32& flags );
MouseEvent( Node* node, const Uint32& EventNum, const Vector2i& pos, const Uint32& flags );
~MouseEvent();

View File

@@ -18,6 +18,7 @@
#include <eepp/system/iostream.hpp>
#include <map>
#include <unordered_map>
#include <vector>
#define MAX_KEYNAME 128
@@ -215,6 +216,11 @@ class EE_API IniFile {
std::map<std::string, std::string> getKeyMap( const std::string& keyname ) const;
std::unordered_map<std::string, std::string> getKeyUnorderedMap( const unsigned& keyID ) const;
std::unordered_map<std::string, std::string>
getKeyUnorderedMap( const std::string& keyname ) const;
/** Key comment functions.
** Key comments are those comments within a key. Any comments
** defined within value Names will be added to this list. Therefore,

View File

@@ -402,6 +402,8 @@ class EE_API TextDocument {
void setDeleteOnClose( bool deleteOnClose );
bool hasSyntaxDefinition() const;
protected:
friend class UndoStack;
UndoStack mUndoStack;

View File

@@ -60,11 +60,13 @@ class EE_API KeyBindings {
std::string getCommandFromKeyBind( const Shortcut& keys );
std::string getCommandKeybindString( const std::string& command ) const;
void reset();
const ShortcutMap& getShortcutMap() const;
std::string getShortcutString( Shortcut shortcut );
std::string getShortcutString( Shortcut shortcut ) const;
Uint32 getDefaultModifier() const;
@@ -73,6 +75,7 @@ class EE_API KeyBindings {
protected:
const Window::Input* mInput;
ShortcutMap mShortcuts;
std::map<std::string, Uint64> mKeybindingsInvert;
};
}} // namespace EE::UI

View File

@@ -21,6 +21,7 @@ class UICodeEditor;
class UIWindow;
class UIScrollBar;
class UILoader;
class UIPopUpMenu;
class UICodeEditorModule {
public:
@@ -52,7 +53,9 @@ class UICodeEditorModule {
}
virtual bool onMouseOver( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
virtual bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; }
virtual bool onCreateContextMenu( UICodeEditor*, const Vector2i&, const Uint32& ) {
return false;
}
virtual void drawBeforeLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
const Float& ){};
virtual void drawAfterLineText( UICodeEditor*, const Int64&, Vector2f, const Float&,
@@ -402,6 +405,18 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void setDisplayLoaderIfDocumentLoading( bool newDisplayLoaderIfDocumentLoading );
size_t getMenuIconSize() const;
void setMenuIconSize( size_t menuIconSize );
bool getCreateDefaultContextMenuOptions() const;
void setCreateDefaultContextMenuOptions( bool createDefaultContextMenuOptions );
void openContainingFolder();
void copyFilePath();
protected:
struct LastXOffset {
TextPosition position;
@@ -428,6 +443,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
bool mInteractiveLinks{ true };
bool mHandShown{ false };
bool mDisplayLoaderIfDocumentLoading{ true };
bool mCreateDefaultContextMenuOptions{ true };
TextRange mLinkPosition;
String mLink;
Uint32 mTabWidth;
@@ -465,6 +481,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
std::vector<UICodeEditorModule*> mModules;
UILoader* mLoader{ nullptr };
Float mGlyphWidth{ 0 };
size_t mMenuIconSize{ 16 };
UIPopUpMenu* mCurrentMenu{ nullptr };
UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true,
const bool& autoRegisterBaseKeybindings = true );
@@ -491,6 +509,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
virtual Uint32 onKeyUp( const KeyEvent& event );
virtual bool onCreateContextMenu( const Vector2i& position, const Uint32& flags );
virtual Uint32 onMouseDown( const Vector2i& position, const Uint32& flags );
virtual Uint32 onMouseMove( const Vector2i& position, const Uint32& flags );
@@ -585,6 +605,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
virtual void onFontStyleChanged();
virtual void onDocumentLoaded();
virtual void onDocumentChanged();
virtual Uint32 onMessage( const NodeMessage* msg );
@@ -602,6 +624,13 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
Float getViewportWidth( const bool& forceVScroll = false ) const;
void udpateGlyphWidth();
Drawable* findIcon( const std::string& name );
void createDefaultContextMenuOptions( UIPopUpMenu* menu );
void menuAdd( UIPopUpMenu* menu, const std::string& translateKey, const String& translateString,
const std::string& icon, const std::string& cmd );
};
}} // namespace EE::UI

View File

@@ -25,8 +25,25 @@ class EE_API UIPopUpMenu : public UIMenu {
bool isHiding() const;
bool getCloseOnHide() const;
void setCloseOnHide( bool closeOnHide );
protected:
Action* mHidingAction{nullptr};
Action* mHidingAction{ nullptr };
bool mCloseOnHide{ false };
};
class EE_API ContextMenuEvent : public MouseEvent {
public:
ContextMenuEvent( Node* node, UIPopUpMenu* menu, const Uint32& eventType, const Vector2i& pos,
const Uint32& flags ) :
MouseEvent( node, eventType, pos, flags ), menu( menu ) {}
UIPopUpMenu* getMenu() const { return menu; }
protected:
UIPopUpMenu* menu;
};
}} // namespace EE::UI

View File

@@ -3,9 +3,9 @@
namespace EE { namespace Scene {
MouseEvent::MouseEvent( Node* node, const Uint32& EventNum, const Vector2i& Pos,
MouseEvent::MouseEvent( Node* node, const Uint32& EventNum, const Vector2i& pos,
const Uint32& flags ) :
Event( node, EventNum ), mPos( Pos ), mFlags( flags ) {}
Event( node, EventNum ), mPos( pos ), mFlags( flags ) {}
MouseEvent::~MouseEvent() {}

View File

@@ -474,6 +474,26 @@ std::map<std::string, std::string> IniFile::getKeyMap( const std::string& keynam
return {};
}
std::unordered_map<std::string, std::string>
IniFile::getKeyUnorderedMap( const unsigned& keyID ) const {
std::unordered_map<std::string, std::string> map;
if ( keyID < mKeys.size() ) {
for ( size_t i = 0; i < mKeys[keyID].names.size(); i++ ) {
map[mKeys[keyID].names[i]] = mKeys[keyID].values[i];
}
return map;
}
return {};
}
std::unordered_map<std::string, std::string>
IniFile::getKeyUnorderedMap( const std::string& keyname ) const {
long keyID = findKey( keyname );
if ( keyID != noID )
return getKeyUnorderedMap( keyID );
return {};
}
unsigned IniFile::getNumKeyComments( unsigned const keyID ) const {
if ( keyID < mKeys.size() )
return (unsigned int)mKeys[keyID].comments.size();

View File

@@ -209,6 +209,10 @@ void TextDocument::guessIndentType() {
mIndentWidth = 4;
}
bool TextDocument::hasSyntaxDefinition() const {
return !mSyntaxDefinition.getPatterns().empty();
}
void TextDocument::resetSyntax() {
String header( getText( { { 0, 0 }, positionOffset( { 0, 0 }, 128 ) } ) );
mSyntaxDefinition = SyntaxDefinitionManager::instance()->find( mFilePath, header );

View File

@@ -73,6 +73,7 @@ void KeyBindings::addKeybindString( const std::string& keys, const std::string&
void KeyBindings::addKeybind( const KeyBindings::Shortcut& keys, const std::string& command ) {
mShortcuts[sanitizeShortcut( keys )] = command;
mKeybindingsInvert[command] = sanitizeShortcut( keys );
}
void KeyBindings::replaceKeybindString( const std::string& keys, const std::string& command ) {
@@ -86,10 +87,12 @@ void KeyBindings::replaceKeybind( const KeyBindings::Shortcut& keys, const std::
auto it = mShortcuts.find( sanitizeShortcut( keys ) );
if ( it != mShortcuts.end() ) {
mShortcuts.erase( it );
mKeybindingsInvert.erase( it->second );
erased = true;
}
} while ( erased );
mShortcuts[sanitizeShortcut( keys )] = command;
mKeybindingsInvert[command] = sanitizeShortcut( keys );
}
KeyBindings::Shortcut KeyBindings::getShortcutFromString( const std::string& keys ) {
@@ -130,6 +133,28 @@ std::string KeyBindings::getCommandFromKeyBind( const KeyBindings::Shortcut& key
return "";
}
static std::string keybindFormat( std::string str ) {
if ( !str.empty() ) {
str[0] = std::toupper( str[0] );
size_t found = str.find_first_of( '+' );
while ( found != std::string::npos ) {
if ( found + 1 < str.size() ) {
str[found + 1] = std::toupper( str[found + 1] );
}
found = str.find_first_of( '+', found + 1 );
}
return str;
}
return "";
}
std::string KeyBindings::getCommandKeybindString( const std::string& command ) const {
auto it = mKeybindingsInvert.find( command );
if ( it == mKeybindingsInvert.end() )
return "";
return keybindFormat( getShortcutString( Shortcut( it->second ) ) );
}
void KeyBindings::reset() {
mShortcuts.clear();
}
@@ -138,7 +163,7 @@ const ShortcutMap& KeyBindings::getShortcutMap() const {
return mShortcuts;
}
std::string KeyBindings::getShortcutString( KeyBindings::Shortcut shortcut ) {
std::string KeyBindings::getShortcutString( KeyBindings::Shortcut shortcut ) const {
std::vector<std::string> mods;
std::string keyname( String::toLower( mInput->getKeyName( shortcut.key ) ) );
if ( shortcut.mod & KEYMOD_CTRL )

View File

@@ -231,6 +231,14 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() {
if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) )
splitter->swap();
} );
doc.setCommand( "open-containing-folder", [&] {
if ( mCurEditor )
mCurEditor->openContainingFolder();
} );
doc.setCommand( "copy-file-path", [&] {
if ( mCurEditor )
mCurEditor->copyFilePath();
} );
editor->addEventListener( Event::OnFocus, [&]( const Event* event ) {
setCurrentEditor( event->getNode()->asType<UICodeEditor>() );
} );

View File

@@ -9,7 +9,9 @@
#include <eepp/ui/tools/uicolorpicker.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <eepp/ui/uieventdispatcher.hpp>
#include <eepp/ui/uiicon.hpp>
#include <eepp/ui/uiloader.hpp>
#include <eepp/ui/uipopupmenu.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uiscrollbar.hpp>
#include <eepp/ui/uithememanager.hpp>
@@ -339,8 +341,7 @@ bool UICodeEditor::loadFromFile( const std::string& path ) {
updateLongestLineWidth();
mHighlighter.changeDoc( mDoc.get() );
invalidateDraw();
DocEvent event( this, mDoc.get(), Event::OnDocumentLoaded );
sendEvent( &event );
onDocumentLoaded();
}
return ret;
}
@@ -352,8 +353,7 @@ bool UICodeEditor::loadFromURL( const std::string& url, const Http::Request::Fie
updateLongestLineWidth();
mHighlighter.changeDoc( mDoc.get() );
invalidateDraw();
DocEvent event( this, mDoc.get(), Event::OnDocumentLoaded );
sendEvent( &event );
onDocumentLoaded();
}
return ret;
}
@@ -372,8 +372,7 @@ bool UICodeEditor::loadAsyncFromURL(
updateLongestLineWidth();
mHighlighter.changeDoc( mDoc.get() );
invalidateDraw();
DocEvent event( this, mDoc.get(), Event::OnDocumentLoaded );
sendEvent( &event );
onDocumentLoaded();
if ( !wasLocked )
setLocked( false );
if ( onLoaded )
@@ -431,6 +430,11 @@ void UICodeEditor::onFontStyleChanged() {
udpateGlyphWidth();
}
void UICodeEditor::onDocumentLoaded() {
DocEvent event( this, mDoc.get(), Event::OnDocumentLoaded );
sendEvent( &event );
}
void UICodeEditor::onDocumentChanged() {
DocEvent event( this, mDoc.get(), Event::OnDocumentChanged );
sendEvent( &event );
@@ -785,14 +789,86 @@ Sizef UICodeEditor::getMaxScroll() const {
getLineHeight() );
}
void UICodeEditor::menuAdd( UIPopUpMenu* menu, const std::string& translateKey,
const String& translateString, const std::string& icon,
const std::string& cmd ) {
menu->add( menu->getUISceneNode()->getTranslatorString( "uicodeeditor_" + translateKey,
translateString ),
findIcon( icon ), mKeyBindings.getCommandKeybindString( cmd ) )
->setId( cmd );
}
void UICodeEditor::createDefaultContextMenuOptions( UIPopUpMenu* menu ) {
if ( !mCreateDefaultContextMenuOptions )
return;
menuAdd( menu, "undo", "Undo", "undo", "undo" );
menuAdd( menu, "redo", "Redo", "redo", "redo" );
menu->addSeparator();
menuAdd( menu, "cut", "Cut", "cut", "cut" );
menuAdd( menu, "copy", "Copy", "copy", "copy" );
menuAdd( menu, "cut", "Paste", "paste", "paste" );
menuAdd( menu, "delete", "Delete", "delete", "delete-to-next-char" );
menu->addSeparator();
menuAdd( menu, "select_all", "Select All", "select-all", "select-all" );
if ( mDoc->hasFilepath() ) {
menu->addSeparator();
menuAdd( menu, "open_containing_folder", "Open Containing Folder...", "folder-open",
"open-containing-folder" );
menuAdd( menu, "copy_file_path", "Copy File Path", "copy", "copy-file-path" );
}
}
bool UICodeEditor::onCreateContextMenu( const Vector2i& position, const Uint32& flags ) {
if ( mCurrentMenu )
return false;
UIPopUpMenu* menu = UIPopUpMenu::New();
ContextMenuEvent event( this, menu, Event::OnCreateContextMenu, position, flags );
sendEvent( &event );
createDefaultContextMenuOptions( menu );
for ( auto& module : mModules )
if ( module->onCreateContextMenu( this, position, flags ) )
return false;
if ( menu->getCount() == 0 ) {
menu->close();
return false;
}
menu->setCloseOnHide( true );
menu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) {
if ( !event->getNode()->isType( UI_TYPE_MENUITEM ) )
return;
UIMenuItem* item = event->getNode()->asType<UIMenuItem>();
std::string txt( item->getId() );
mDoc.get()->execute( txt );
} );
Vector2f pos( position.asFloat() );
menu->nodeToWorldTranslation( pos );
UIMenu::findBestMenuPos( pos, menu );
menu->setPixelsPosition( pos );
menu->show();
menu->addEventListener( Event::OnClose, [&]( const Event* ) { mCurrentMenu = nullptr; } );
mCurrentMenu = menu;
return true;
}
Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags ) {
for ( auto& module : mModules )
if ( module->onMouseDown( this, position, flags ) )
return UIWidget::onMouseDown( position, flags );
if ( isTextSelectionEnabled() && !getEventDispatcher()->isNodeDragging() && NULL != mFont &&
!mMouseDown && getEventDispatcher()->getMouseDownNode() == this &&
( flags & EE_BUTTON_LMASK ) ) {
if ( ( flags & EE_BUTTON_LMASK ) && isTextSelectionEnabled() &&
!getEventDispatcher()->isNodeDragging() && NULL != mFont && !mMouseDown &&
getEventDispatcher()->getMouseDownNode() == this ) {
mMouseDown = true;
Input* input = getUISceneNode()->getWindow()->getInput();
input->captureMouse( true );
@@ -852,6 +928,8 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags )
setScrollY( mScroll.y - PixelDensity::dpToPx( mMouseWheelScroll ) );
}
invalidateDraw();
} else if ( ( flags & EE_BUTTON_RMASK ) ) {
onCreateContextMenu( position, flags );
}
return UIWidget::onMouseUp( position, flags );
}
@@ -1071,6 +1149,30 @@ void UICodeEditor::setDisplayLoaderIfDocumentLoading( bool newDisplayLoaderIfDoc
}
}
size_t UICodeEditor::getMenuIconSize() const {
return mMenuIconSize;
}
void UICodeEditor::setMenuIconSize( size_t menuIconSize ) {
mMenuIconSize = menuIconSize;
}
bool UICodeEditor::getCreateDefaultContextMenuOptions() const {
return mCreateDefaultContextMenuOptions;
}
void UICodeEditor::setCreateDefaultContextMenuOptions( bool createDefaultContextMenuOptions ) {
mCreateDefaultContextMenuOptions = createDefaultContextMenuOptions;
}
void UICodeEditor::openContainingFolder() {
Engine::instance()->openURL( mDoc->getFileInfo().getDirectoryPath() );
}
void UICodeEditor::copyFilePath() {
getUISceneNode()->getWindow()->getClipboard()->setText( mDoc->getFilePath() );
}
void UICodeEditor::updateEditor() {
mDoc->setPageSize( getVisibleLinesCount() );
if ( mDoc->getActiveClient() == this )
@@ -1646,6 +1748,13 @@ void UICodeEditor::udpateGlyphWidth() {
mGlyphWidth = mFont->getGlyph( ' ', getCharacterSize(), false ).advance;
}
Drawable* UICodeEditor::findIcon( const std::string& name ) {
UIIcon* icon = getUISceneNode()->findIcon( name );
if ( icon )
return icon->getSize( mMenuIconSize );
return nullptr;
}
const bool& UICodeEditor::getColorPreview() const {
return mColorPreview;
}
@@ -2137,6 +2246,8 @@ void UICodeEditor::registerCommands() {
mDoc->setCommand( "lock", [&] { setLocked( true ); } );
mDoc->setCommand( "unlock", [&] { setLocked( false ); } );
mDoc->setCommand( "lock-toggle", [&] { setLocked( !isLocked() ); } );
mDoc->setCommand( "open-containing-folder", [&] { openContainingFolder(); } );
mDoc->setCommand( "copy-file-path", [&] { copyFilePath(); } );
mUnlockedCmd.insert( { "copy", "select-all" } );
}

View File

@@ -17,7 +17,9 @@ UIPopUpMenu::UIPopUpMenu() : UIMenu() {
applyDefaultTheme();
}
UIPopUpMenu::~UIPopUpMenu() {}
UIPopUpMenu::~UIPopUpMenu() {
onClose();
}
Uint32 UIPopUpMenu::getType() const {
return UI_TYPE_POPUPMENU;
@@ -65,11 +67,17 @@ bool UIPopUpMenu::hide() {
Actions::FadeOut::New(
getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
Actions::Spawn::New( Actions::Disable::New(), Actions::Visible::New( false ),
Actions::Runnable::New( [&] { mHidingAction = nullptr; } ) ) );
Actions::Runnable::New( [&] {
mHidingAction = nullptr;
if ( mCloseOnHide )
close();
} ) ) );
runAction( mHidingAction );
} else {
setEnabled( false );
setVisible( false );
if ( mCloseOnHide )
close();
}
safeHide();
return true;
@@ -81,4 +89,12 @@ bool UIPopUpMenu::isHiding() const {
return mHidingAction != nullptr;
}
bool UIPopUpMenu::getCloseOnHide() const {
return mCloseOnHide;
}
void UIPopUpMenu::setCloseOnHide( bool closeOnHide ) {
mCloseOnHide = closeOnHide;
}
}} // namespace EE::UI

View File

@@ -663,7 +663,7 @@ void UISceneNode::updateDirtyStyleStates() {
if ( mVerbose )
Log::debug( "CSS Style State Invalidated, reapplied state in %.2f ms",
clock.getElapsedTime().asMilliseconds() );
clock.getElapsedTime().asMilliseconds() );
}
}
@@ -795,7 +795,7 @@ void UISceneNode::setInternalPixelsSize( const Sizef& size ) {
}
Uint32 UISceneNode::onKeyDown( const KeyEvent& event ) {
std::string cmd = mKeyBindings.getCommandFromKeyBind( {event.getKeyCode(), event.getMod()} );
std::string cmd = mKeyBindings.getCommandFromKeyBind( { event.getKeyCode(), event.getMod() } );
if ( !cmd.empty() ) {
executeKeyBindingCommand( cmd );
return 0;

View File

@@ -1346,6 +1346,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
editor->setEnableColorPickerOnSelection( config.colorPickerSelection );
editor->setColorPreview( config.colorPreview );
editor->setFont( mFontMono );
editor->setMenuIconSize( mMenuIconSize );
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) );
doc.setAutoDetectIndentType( config.autoDetectIndentType );
@@ -1456,6 +1457,10 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
ed->getKeyBindings().addKeybindsString( mKeybindings );
} );
}
if ( !editor->getDocument().hasSyntaxDefinition() ) {
editor->getDocument().resetSyntax();
editor->setSyntaxDefinition( editor->getDocument().getSyntaxDefinition() );
}
} );
editor->addEventListener(