From 7dd21fa77cd90e7b592e3ff12bad909a704f989b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 29 Aug 2026 20:28:27 -0300 Subject: [PATCH] Improve tag creation and add UITextEdit hint support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add native hint text support to UITextEdit, matching UITextInput behavior and exposing configurable hint text, color, font, size, style, shadow, outline, and HintDisplay modes. Render hints only for empty documents and preserve the correct drawing order beneath the caret. Expose the corresponding hint CSS properties through UITextEdit and apply the theme hint color to TextEdit and TextArea widgets. Add an “Add Tag & Push” action alongside local-only tag creation. Push only the newly created tag using an explicit refspec and the current branch’s tracked remote, falling back to origin. --- bin/assets/i18n/de.xml | 3 + bin/assets/i18n/en.xml | 3 + bin/assets/i18n/fr.xml | 3 + bin/assets/i18n/zh.xml | 3 + bin/assets/ui/breeze.css | 1 + include/eepp/ui/uitextedit.hpp | 55 +++++ src/eepp/ui/uitextedit.cpp | 235 ++++++++++++++++++++++ src/tools/ecode/plugins/git/git.cpp | 8 + src/tools/ecode/plugins/git/git.hpp | 3 + src/tools/ecode/plugins/git/gitplugin.cpp | 52 ++++- 10 files changed, 357 insertions(+), 9 deletions(-) diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index 23afaa892..6dd25df34 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -1271,8 +1271,11 @@ Für sichtbare Änderung ecode neu starten. Nachricht kopieren Branch hinzufügen... Tag hinzufügen + Tag hinzufügen & pushen Tag hinzufügen... Tag zu Commit %s hinzufügen + Tag-Name + Nachricht für annotiertes Tag (optional) Branch bei Commit %s erstellen. Zusammenführen... Cherry-Pick... diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index 7a809bcd2..97081f4e4 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -1256,8 +1256,11 @@ Restart ecode to see the changes. Copy Message Add Branch... Add Tag + Add Tag & Push Add Tag... Add tag to commit %s + Tag name + Annotated tag message (optional) Create a branch at commit %s. Merge... Cherry-Pick... diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index c53b4e7b3..8c53a8b53 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -1255,8 +1255,11 @@ Redémarrer ecode pour voir les changements. Copier le message Ajouter une branche... Ajouter une étiquette + Ajouter et pousser l’étiquette Ajouter une étiquette... Ajouter une étiquette au commit %s + Nom de l’étiquette + Message de l’étiquette annotée (facultatif) Créer une branche au commit %s. Fusionner... Cherry-pick... diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml index 38f0be4c5..2614e7a91 100644 --- a/bin/assets/i18n/zh.xml +++ b/bin/assets/i18n/zh.xml @@ -1060,8 +1060,11 @@ file in the directory tree. 复制提交消息 添加分支... 添加标签 + 添加并推送标签 添加标签... 为提交 %s 添加标签 + 标签名称 + 附注标签消息(可选) 在提交 %s 创建分支。 合并... 拣选提交... diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index f8fe0654e..d88c4d628 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -467,6 +467,7 @@ TextArea { padding-right: var(--base-horizontal-padding); padding-top: var(--base-vertical-padding); padding-bottom: var(--base-vertical-padding); + hint-color: var(--font-hint); transition: all 0.15; } diff --git a/include/eepp/ui/uitextedit.hpp b/include/eepp/ui/uitextedit.hpp index 1ad77ab5a..085352eeb 100644 --- a/include/eepp/ui/uitextedit.hpp +++ b/include/eepp/ui/uitextedit.hpp @@ -26,13 +26,68 @@ class EE_API UITextEdit : public UICodeEditor { void setWordWrap( bool enabled ); + const String& getHint() const; + + UITextEdit* setHint( const String& hint ); + + const Color& getHintColor() const; + + UITextEdit* setHintColor( const Color& hintColor ); + + const Color& getHintShadowColor() const; + + UITextEdit* setHintShadowColor( const Color& shadowColor ); + + const Vector2f& getHintShadowOffset() const; + + UITextEdit* setHintShadowOffset( const Vector2f& shadowOffset ); + + Font* getHintFont() const; + + UITextEdit* setHintFont( Font* font ); + + Uint32 getHintFontSize() const; + + UITextEdit* setHintFontSize( const Uint32& characterSize ); + + const Uint32& getHintFontStyle() const; + + UITextEdit* setHintFontStyle( const Uint32& fontStyle ); + + const Float& getHintOutlineThickness() const; + + UITextEdit* setHintOutlineThickness( const Float& outlineThickness ); + + const Color& getHintOutlineColor() const; + + UITextEdit* setHintOutlineColor( const Color& outlineColor ); + + void setHintDisplay( HintDisplay display ); + + HintDisplay getHintDisplay() const; + virtual bool applyProperty( const StyleSheetProperty& attribute ); + virtual std::string getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex = 0 ) const; + + virtual std::vector getPropertiesImplemented() const; + protected: UITextEdit( const std::string& tag ); + Text mHintCache; + FontStyleConfig mHintStyleConfig; + HintDisplay mHintDisplay{ HintDisplay::Always }; + + virtual void drawLineText( const Int64& line, Vector2f position, const Float& fontSize, + const Float& lineHeight, + const DocumentViewLineRange& visibleLineRange ); + virtual void drawCursor( const Vector2f& startScroll, const Float& lineHeight, const TextPosition& cursor ); + + virtual void onTextHintsChanged(); }; }} // namespace EE::UI diff --git a/src/eepp/ui/uitextedit.cpp b/src/eepp/ui/uitextedit.cpp index bfc4af68e..dd1b97c41 100644 --- a/src/eepp/ui/uitextedit.cpp +++ b/src/eepp/ui/uitextedit.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -45,6 +46,11 @@ UITextEdit::UITextEdit( const std::string& tag ) : UICodeEditor( tag, true, true } } + mHintCache.setTextHints( getWidgetTextDrawHints() ); + setHintFont( theme && theme->getDefaultFont() ? theme->getDefaultFont() : getFont() ); + setHintFontSize( theme ? theme->getDefaultFontSize() + : getUISceneNode()->getUIThemeManager()->getDefaultFontSize() ); + disableEditorFeatures(); applyDefaultTheme(); } @@ -90,6 +96,135 @@ void UITextEdit::setWordWrap( bool enabled ) { setLineWrapMode( enabled ? LineWrapMode::Word : LineWrapMode::NoWrap ); } +const String& UITextEdit::getHint() const { + return mHintCache.getString(); +} + +UITextEdit* UITextEdit::setHint( const String& hint ) { + if ( hint != mHintCache.getString() ) { + mHintCache.setString( hint ); + invalidateDraw(); + } + return this; +} + +const Color& UITextEdit::getHintColor() const { + return mHintStyleConfig.getFontColor(); +} + +UITextEdit* UITextEdit::setHintColor( const Color& hintColor ) { + if ( hintColor != mHintStyleConfig.getFontColor() ) { + mHintCache.setFillColor( hintColor ); + mHintStyleConfig.FontColor = hintColor; + invalidateDraw(); + } + return this; +} + +const Color& UITextEdit::getHintShadowColor() const { + return mHintStyleConfig.getFontShadowColor(); +} + +UITextEdit* UITextEdit::setHintShadowColor( const Color& shadowColor ) { + if ( shadowColor != mHintStyleConfig.getFontShadowColor() ) { + mHintCache.setShadowColor( shadowColor ); + mHintStyleConfig.ShadowColor = shadowColor; + invalidateDraw(); + } + return this; +} + +const Vector2f& UITextEdit::getHintShadowOffset() const { + return mHintStyleConfig.getFontShadowOffset(); +} + +UITextEdit* UITextEdit::setHintShadowOffset( const Vector2f& shadowOffset ) { + if ( shadowOffset != mHintStyleConfig.getFontShadowOffset() ) { + mHintCache.setShadowOffset( shadowOffset ); + mHintStyleConfig.ShadowOffset = shadowOffset; + invalidateDraw(); + } + return this; +} + +Font* UITextEdit::getHintFont() const { + return mHintStyleConfig.getFont(); +} + +UITextEdit* UITextEdit::setHintFont( Font* font ) { + if ( font != mHintStyleConfig.getFont() ) { + mHintCache.setFont( font ); + mHintStyleConfig.Font = font; + invalidateDraw(); + } + return this; +} + +Uint32 UITextEdit::getHintFontSize() const { + return mHintCache.getCharacterSize(); +} + +UITextEdit* UITextEdit::setHintFontSize( const Uint32& characterSize ) { + if ( characterSize != mHintCache.getCharacterSize() ) { + mHintCache.setFontSize( characterSize ); + mHintStyleConfig.CharacterSize = characterSize; + invalidateDraw(); + } + return this; +} + +const Uint32& UITextEdit::getHintFontStyle() const { + return mHintStyleConfig.Style; +} + +UITextEdit* UITextEdit::setHintFontStyle( const Uint32& fontStyle ) { + if ( fontStyle != mHintStyleConfig.Style ) { + mHintCache.setStyle( fontStyle ); + mHintStyleConfig.Style = fontStyle; + invalidateDraw(); + } + return this; +} + +const Float& UITextEdit::getHintOutlineThickness() const { + return mHintStyleConfig.OutlineThickness; +} + +UITextEdit* UITextEdit::setHintOutlineThickness( const Float& outlineThickness ) { + if ( outlineThickness != mHintStyleConfig.OutlineThickness ) { + mHintCache.setOutlineThickness( outlineThickness ); + mHintStyleConfig.OutlineThickness = outlineThickness; + invalidateDraw(); + } + return this; +} + +const Color& UITextEdit::getHintOutlineColor() const { + return mHintStyleConfig.OutlineColor; +} + +UITextEdit* UITextEdit::setHintOutlineColor( const Color& outlineColor ) { + if ( outlineColor != mHintStyleConfig.OutlineColor ) { + mHintStyleConfig.OutlineColor = outlineColor; + Color color( outlineColor.r, outlineColor.g, outlineColor.b, + outlineColor.a * mAlpha / 255.f ); + mHintCache.setOutlineColor( color ); + invalidateDraw(); + } + return this; +} + +void UITextEdit::setHintDisplay( HintDisplay display ) { + if ( display != mHintDisplay ) { + mHintDisplay = display; + invalidateDraw(); + } +} + +HintDisplay UITextEdit::getHintDisplay() const { + return mHintDisplay; +} + bool UITextEdit::applyProperty( const StyleSheetProperty& attribute ) { if ( !checkPropertyDefinition( attribute ) ) return false; @@ -101,6 +236,42 @@ bool UITextEdit::applyProperty( const StyleSheetProperty& attribute ) { break; case PropertyId::Wordwrap: setWordWrap( attribute.asBool() ); + break; + case PropertyId::Hint: + setHint( getTranslatorString( attribute.value() ) ); + break; + case PropertyId::HintColor: + setHintColor( attribute.asColor() ); + break; + case PropertyId::HintShadowColor: + setHintShadowColor( attribute.asColor() ); + break; + case PropertyId::HintShadowOffset: + setHintShadowOffset( attribute.asVector2f() ); + break; + case PropertyId::HintFontSize: + setHintFontSize( lengthFromValue( attribute ) ); + break; + case PropertyId::HintFontFamily: + setHintFont( + getUISceneNode() + ? getUISceneNode()->getResourceScope()->findFont( attribute.value() ).get() + : nullptr ); + break; + case PropertyId::HintFontStyle: + setHintFontStyle( attribute.asFontStyle() ); + break; + case PropertyId::HintStrokeWidth: + setHintOutlineThickness( PixelDensity::dpToPx( attribute.asDpDimension() ) ); + break; + case PropertyId::HintStrokeColor: + setHintOutlineColor( attribute.asColor() ); + break; + case PropertyId::HintDisplay: + setHintDisplay( String::toLower( attribute.asString() ) == "focus" + ? HintDisplay::Focus + : HintDisplay::Always ); + break; default: return UICodeEditor::applyProperty( attribute ); } @@ -108,6 +279,65 @@ bool UITextEdit::applyProperty( const StyleSheetProperty& attribute ) { return true; } +std::string UITextEdit::getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex ) const { + if ( NULL == propertyDef ) + return ""; + + switch ( propertyDef->getPropertyId() ) { + case PropertyId::Hint: + return getHint().toUtf8(); + case PropertyId::HintColor: + return getHintColor().toHexString(); + case PropertyId::HintShadowColor: + return getHintShadowColor().toHexString(); + case PropertyId::HintShadowOffset: + return String::fromFloat( getHintShadowOffset().x ) + " " + + String::fromFloat( getHintShadowOffset().y ); + case PropertyId::HintFontSize: + return String::format( "%ddp", getHintFontSize() ); + case PropertyId::HintFontFamily: + return getHintFont() ? getUISceneNode()->getFontFamilyName( getHintFont() ) : ""; + case PropertyId::HintFontStyle: + return Text::styleFlagToString( getHintFontStyle() ); + case PropertyId::HintStrokeWidth: + return String::fromFloat( PixelDensity::dpToPx( getHintOutlineThickness() ), "px" ); + case PropertyId::HintStrokeColor: + return getHintOutlineColor().toHexString(); + case PropertyId::HintDisplay: + return mHintDisplay == HintDisplay::Always ? "always" : "focus"; + default: + return UICodeEditor::getPropertyString( propertyDef, propertyIndex ); + } +} + +std::vector UITextEdit::getPropertiesImplemented() const { + auto props = UICodeEditor::getPropertiesImplemented(); + auto local = { PropertyId::Hint, + PropertyId::HintColor, + PropertyId::HintShadowColor, + PropertyId::HintShadowOffset, + PropertyId::HintFontSize, + PropertyId::HintFontFamily, + PropertyId::HintFontStyle, + PropertyId::HintStrokeWidth, + PropertyId::HintStrokeColor, + PropertyId::HintDisplay }; + props.insert( props.end(), local.begin(), local.end() ); + return props; +} + +void UITextEdit::drawLineText( const Int64& line, Vector2f position, const Float& fontSize, + const Float& lineHeight, + const DocumentViewLineRange& visibleLineRange ) { + UICodeEditor::drawLineText( line, position, fontSize, lineHeight, visibleLineRange ); + if ( line == 0 && mDoc->isEmpty() && !mHintCache.getString().empty() && + ( mHintDisplay == HintDisplay::Always || hasFocus() ) ) { + mHintCache.draw( std::trunc( position.x ), std::trunc( position.y ), Vector2f::One, 0.f, + getBlendMode() ); + } +} + void UITextEdit::drawCursor( const Vector2f& startScroll, const Float& lineHeight, const TextPosition& cursor ) { if ( mCursorVisible && !mLocked && isTextSelectionEnabled() ) { @@ -121,4 +351,9 @@ void UITextEdit::drawCursor( const Vector2f& startScroll, const Float& lineHeigh } } +void UITextEdit::onTextHintsChanged() { + UICodeEditor::onTextHintsChanged(); + mHintCache.setTextHints( getWidgetTextDrawHints() ); +} + }} // namespace EE::UI diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index 2349878a5..f4917a4cb 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -950,6 +950,14 @@ Git::Result Git::createTag( const std::string& name, const std::string& revision return result; } +Git::Result Git::pushTag( const std::string& name, const std::string& remote, + const std::string& projectDir ) { + Result result; + result.returnCode = git( { "push", remote, "refs/tags/" + name + ":refs/tags/" + name }, + projectDir, result.result ); + return result; +} + Git::Result Git::deleteTag( const std::string& name, const std::string& projectDir ) { Result result; result.returnCode = git( { "tag", "-d", name }, projectDir, result.result ); diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 96a6bbab6..423315afb 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -380,6 +380,9 @@ class Git { Result createTag( const std::string& name, const std::string& revision, const std::string& message = "", const std::string& projectDir = "" ); + Result pushTag( const std::string& name, const std::string& remote, + const std::string& projectDir = "" ); + Result deleteTag( const std::string& name, const std::string& projectDir = "" ); Result deleteRemoteBranch( const std::string& remote, const std::string& branch, diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index 290e7d3f4..bb9178eee 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -2736,19 +2736,53 @@ void GitPlugin::addTag( const Git::Commit& commit ) { name->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent ) ->setLayoutMargin( Rectf( 0, 4, 0, 4 ) ) ->setParent( box->getTextEdit()->getParent() ); + name->setHint( i18n( "git_tag_name_hint", "Tag name" ) ); name->toPosition( 1 ); - box->getTextEdit()->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed ); - box->getButtonOK()->setText( i18n( "git_add_tag", "Add Tag" ) ); - box->on( Event::OnConfirm, [this, box, name, commit]( const Event* ) { - const std::string tag = name->getText().toUtf8(); + auto* message = box->getTextEdit(); + message->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::Fixed ); + message->setHint( i18n( "git_tag_message_hint", "Annotated tag message (optional)" ) ); + auto addTagBtn = box->getButtonOK(); + addTagBtn->setText( i18n( "git_add_tag", "Add Tag" ) ); + auto* addTagAndPushBtn = UIPushButton::New(); + addTagAndPushBtn->setText( i18n( "git_add_tag_and_push", "Add Tag & Push" ) ) + ->setLayoutMargin( Rectf( 8, 0, 0, 0 ) ) + ->setParent( addTagBtn->getParent() ); + box->getButtonCancel()->setLayoutMargin( {} )->toPosition( 0 ); + addTagBtn->setLayoutMargin( Rectf( 8, 0, 0, 0 ) )->toPosition( 1 ); + addTagAndPushBtn->toPosition( 2 ); + addTagBtn->setEnabled( false ); + addTagAndPushBtn->setEnabled( false ); + name->on( Event::OnTextChanged, [name, addTagBtn, addTagAndPushBtn]( const Event* ) { + const bool enabled = !name->getText().empty(); + addTagBtn->setEnabled( enabled ); + addTagAndPushBtn->setEnabled( enabled ); + } ); + auto createTag = [this, box, name, message, commit]( bool push ) { + std::string tag = name->getText().toUtf8(); if ( tag.empty() ) return; - const std::string message = box->getTextEdit()->getText().toUtf8(); + std::string tagMessage = message->getText().toUtf8(); + std::string repo = repoSelected(); + std::string remote{ "origin" }; + if ( const auto branch = getBranchFromRepoPath( repo ); + branch && !branch->remote.empty() ) { + const size_t separator = branch->remote.find( '/' ); + if ( separator != std::string::npos ) + remote = branch->remote.substr( 0, separator ); + } box->closeWindow(); - runAsync( [this, tag, message, - commit] { return mGit->createTag( tag, commit.hash, message, repoSelected() ); }, - false, true ); - } ); + runAsync( + [git = mGit, tag = std::move( tag ), tagMessage = std::move( tagMessage ), + repo = std::move( repo ), remote = std::move( remote ), hash = commit.hash, push]() { + auto result = git->createTag( tag, hash, tagMessage, repo ); + if ( result.success() && push ) + return git->pushTag( tag, remote, repo ); + return result; + }, + false, true ); + }; + box->on( Event::OnConfirm, [createTag]( const Event* ) mutable { createTag( false ); } ); + addTagAndPushBtn->onClick( [createTag]( const Event* ) mutable { createTag( true ); } ); box->setTitle( i18n( "git_add_tag", "Add Tag" ) ); box->setCloseShortcut( { KEY_ESCAPE, KEYMOD_NONE } )->center(); box->showWhenReady();