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();