Fixes in UICodeEditor mouse click in gutter.

Added some basic culling in UICodeEditor.
Remember the Chat UI input text.
This commit is contained in:
Martín Lucas Golini
2025-03-29 02:17:56 -03:00
parent 584dfdfc59
commit c7c45a87cf
8 changed files with 79 additions and 17 deletions

View File

@@ -118,6 +118,7 @@ class EE_API Event {
OnWindowToFront,
OnVisibleLinesCountChange,
OnDataChanged,
OnFoldUnfoldRange,
NoEvent = eeINDEX_NOT_FOUND
};

View File

@@ -157,6 +157,8 @@ class EE_API DocumentView {
void setOnVisibleLineCountChange( std::function<void()> onVisibleLinesCountChangeCb );
void setOnFoldUnfoldCb( std::function<void(Int64 docIdx, bool unfolded)> onFoldUnfoldCb );
protected:
std::shared_ptr<TextDocument> mDoc;
FontStyleConfig mFontStyle;
@@ -171,6 +173,7 @@ class EE_API DocumentView {
bool mUnderConstruction{ false };
bool mUpdatingFoldRegions{ false };
std::function<void()> mOnVisibleLineCountChange;
std::function<void(Int64 docIdx, bool unfolded)> mOnFoldUnfoldCb;
void changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visible,
bool recomputeOffset = true, bool recomputeLineToVisibleIndex = true );

View File

@@ -792,6 +792,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
bool isCursorBlinkingAfterAMinuteOfInactivityDisabled() const;
void setAllowSelectingTextFromGutter( bool allow );
bool allowSelectingTextFromGutter() const;
protected:
struct LastXOffset {
TextPosition position{ 0, 0 };
@@ -841,6 +845,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
bool mFoldsIsFirst{ true };
bool mEnableFlashCursor{ false };
bool mDisableCursorBlinkingAfterAMinuteOfInactivity{ true };
bool mAllowSelectingTextFromGutter{ true };
Uint32 mTabWidth;
std::atomic<size_t> mHighlightWordProcessing{ false };
TextRange mLinkPosition;

View File

@@ -814,4 +814,9 @@ void DocumentView::setOnVisibleLineCountChange(
mOnVisibleLineCountChange = std::move( onVisibleLinesCountChangeCb );
}
void DocumentView::setOnFoldUnfoldCb(
std::function<void( Int64 docIdx, bool unfolded )> onFoldUnfoldCb ) {
mOnFoldUnfoldCb = std::move( onFoldUnfoldCb );
}
}}} // namespace EE::UI::Doc

View File

@@ -140,6 +140,8 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis
refreshTag();
mDocView.setOnVisibleLineCountChange(
[this] { sendCommonEvent( Event::OnVisibleLinesCountChange ); } );
mDocView.setOnFoldUnfoldCb(
[this]( auto, auto ) { sendCommonEvent( Event::OnFoldUnfoldRange ); } );
mVScrollBar = UIScrollBar::NewVertical();
mVScrollBar->setParent( this );
mVScrollBar->addEventListener( Event::OnSizeChange,
@@ -338,6 +340,13 @@ void UICodeEditor::draw() {
drawLineEndings( lineRange, startScroll, lineHeight );
}
const bool canCull = !isMeOrParentTreeScaledOrRotated();
const auto maxScreenY = isClipped()
? eemin( getUISceneNode()->getPixelsSize().getHeight(),
mScreenPos.y + mSize.getHeight() ) +
lineHeight
: getUISceneNode()->getPixelsSize().getHeight() + lineHeight;
for ( auto i = lineRange.first; i <= lineRange.second; i++ ) {
if ( !mDocView.isLineVisible( i ) )
continue;
@@ -346,6 +355,14 @@ void UICodeEditor::draw() {
{ startScroll.x,
static_cast<Float>( startScroll.y + mDocView.getLineYOffset( i, lineHeight ) ) } );
if ( canCull ) {
if ( curScroll.y < -lineHeight && !mDocView.isWrappedLine( i ) )
continue;
if ( curScroll.y > maxScreenY )
break;
}
for ( auto& plugin : mPlugins )
plugin->drawBeforeLineText( this, i, curScroll, charSize, lineHeight );
@@ -1597,8 +1614,9 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags
mLastDoubleClick.getElapsedTime() < Milliseconds( 300.f ) ) {
mDoc->selectLine();
} else {
mDoc->setSelection( textScreenPos );
if ( downOverGutter )
if ( !downOverGutter || mAllowSelectingTextFromGutter )
mDoc->setSelection( textScreenPos );
if ( downOverGutter && mAllowSelectingTextFromGutter )
mDoc->selectLine();
}
} else if ( !mDoc->hasSelection() ) {
@@ -1662,10 +1680,11 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags
}
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
bool downOverGutter = localPos.x < mPaddingPx.Left + getGutterWidth();
if ( !mMouseDownMinimap && isTextSelectionEnabled() &&
!getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont &&
mMouseDown ) {
!getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont && mMouseDown &&
( !downOverGutter || mAllowSelectingTextFromGutter ) ) {
TextRange selection = mDoc->getSelection();
selection.setStart( resolveScreenPosition( position.asFloat() ) );
mDoc->setSelection( selection );
@@ -1794,7 +1813,9 @@ Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i& position, const Uint32&
return 1;
}
if ( isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) ) {
Vector2f localPos( convertToNodeSpace( position.asFloat() ) );
if ( isTextSelectionEnabled() && ( flags & EE_BUTTON_LMASK ) &&
localPos.x >= mPaddingPx.Left + getGutterWidth() && localPos.y >= mPluginsTopSpace ) {
mDoc->selectWord( false );
mLastDoubleClick.restart();
checkColorPickerAction();
@@ -5254,7 +5275,8 @@ bool UICodeEditor::isNotMonospace() const {
void UICodeEditor::updateMouseCursor( const Vector2f& position ) {
if ( getScreenBounds().contains( position ) ) {
auto localPos( convertToNodeSpace( position ) );
bool overGutterOrTop = localPos.x < getGutterWidth() || localPos.y < mPluginsTopSpace;
bool overGutterOrTop =
localPos.x < mPaddingPx.Left + getGutterWidth() || localPos.y < mPluginsTopSpace;
getUISceneNode()->setCursor(
mHandShown ? Cursor::Hand
: ( !overGutterOrTop && !mLocked ? Cursor::IBeam : Cursor::Arrow ) );
@@ -5315,4 +5337,12 @@ bool UICodeEditor::isCursorBlinkingAfterAMinuteOfInactivityDisabled() const {
return mDisableCursorBlinkingAfterAMinuteOfInactivity;
}
void UICodeEditor::setAllowSelectingTextFromGutter( bool allow ) {
mAllowSelectingTextFromGutter = allow;
}
bool UICodeEditor::allowSelectingTextFromGutter() const {
return mAllowSelectingTextFromGutter;
}
}} // namespace EE::UI

View File

@@ -186,17 +186,23 @@ void AIAssistantPlugin::load( PluginManager* pluginManager ) {
filePath = conversationsPath + *foundIt;
}
std::string inputText;
if ( !filePath.empty() ) {
std::string data;
FileSystem::fileGet( filePath, data );
if ( !data.empty() ) {
auto j = nlohmann::json::parse( data, nullptr, false );
if ( !j.empty() )
chatUI->unserialize( j );
if ( !j.empty() ){
inputText = chatUI->unserialize( j );
}
}
}
chatUI->on( Event::OnDataChanged, [chatUI]( auto ) { chatUI->updateTabTitle(); } );
chatUI->on( Event::OnDataChanged, [chatUI, inputText = std::move( inputText )]( auto ) {
chatUI->updateTabTitle();
if ( chatUI->getChatInput() )
chatUI->getChatInput()->getDocument().textInput( inputText );
} );
}
return TabWidgetData{ chatUI, getPluginContext()->findIcon( "code-ai" ),

View File

@@ -138,7 +138,7 @@ DropDownList.role_ui {
<TextView text="@string(ai_llm_presentation, What can I help with?)" font-size="24dp" />
</vbox>
</RelativeLayout>
<RelativeLayout lw="mp" class="llm_controls">
<RelativeLayout lw="mp" class="llm_controls" clip="true">
<CodeEditor class="llm_chat_input" lw="mp" lh="mp" />
<hbox lw="mp" lh="wc" layout_gravity="bottom|left" layout_margin="8dp" clip="false">
<PushButton id="llm_user" class="llm_button" text="@string(user, User)" min-width="60dp" margin-right="8dp" />
@@ -220,6 +220,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatInput->getDocument().getFoldRangeService().setEnabled( true );
mChatInput->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) );
mChatInput->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) );
mChatInput->setAllowSelectingTextFromGutter( false );
mChatInput->setSyntaxDefinition( markdown );
@@ -311,10 +312,12 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
return;
}
auto* chatUI = getPlugin()->newAIAssistant();
chatUI->unserialize( serialize() );
auto input = chatUI->unserialize( serialize() );
chatUI->mUUID = UUID();
chatUI->mSummary += i18n( "chat_cloned", " (cloned)" );
updateTabTitle();
if ( !input.empty() )
chatUI->mChatInput->getDocument().textInput( input );
chatUI->updateTabTitle();
chatUI->setFocus();
} );
@@ -517,7 +520,9 @@ void LLMChatUI::showChatHistory() {
std::string data;
FileSystem::fileGet( path.toString(), data );
nlohmann::json j = nlohmann::json::parse( data, nullptr, false );
chatUI->unserialize( j );
auto input = chatUI->unserialize( j );
if ( !input.empty() )
chatUI->mChatInput->getDocument().textInput( input );
chatUI->setFocus();
};
@@ -737,10 +742,12 @@ nlohmann::json LLMChatUI::serialize() {
j["provider"] = mCurModel.provider;
j["timestamp"] = mTimestamp;
j["summary"] = mSummary;
std::string inputText( mChatInput->getDocument().getText().toUtf8() );
j["input"] = std::move( inputText );
return j;
}
void LLMChatUI::unserialize( const nlohmann::json& payload ) {
std::string LLMChatUI::unserialize( const nlohmann::json& payload ) {
auto uuid = UUID::fromString( payload.value( "uuid", "" ) );
if ( uuid )
mUUID = *uuid;
@@ -755,7 +762,7 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) {
}
if ( mCurModel.name.empty() )
return;
return payload.value( "input", "" );
if ( !selectModel( mModelDDL, mCurModel ) )
fillModelDropDownList( mModelDDL );
@@ -770,6 +777,8 @@ void LLMChatUI::unserialize( const nlohmann::json& payload ) {
}
updateTabTitle();
return payload.value( "input", "" );
}
LLMModel LLMChatUI::findModel( const std::string& provider, const std::string& model ) {
@@ -843,7 +852,7 @@ void LLMChatUI::doRequest() {
auto* thinking = editor->findByClass<UIImage>( "thinking" );
auto thinkingID = String::hash( String::format( "thinking-%p", thinking ) );
thinking->setVisible( true );
thinking->setPosition( { PixelDensity::dpToPx( 16 ), PixelDensity::dpToPx( 4 ) } );
thinking->setPosition( { PixelDensity::dpToPx( 8 ), PixelDensity::dpToPx( 4 ) } );
thinking->setInterval( [thinking] { thinking->rotate( 360 / 32 ); }, Seconds( 0.125 ),
thinkingID );
@@ -1035,6 +1044,7 @@ void LLMChatUI::addChat( LLMChat::Role role, std::string conversation ) {
auto* editor = chat->findByClass<UICodeEditor>( "data_ui" );
editor->getDocument().textInput( String::fromUtf8( conversation ) );
editor->setCursorVisible( false );
editor->setAllowSelectingTextFromGutter( false );
resizeToFit( editor );
}

View File

@@ -52,7 +52,7 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
nlohmann::json serialize();
void unserialize( const nlohmann::json& payload );
std::string unserialize( const nlohmann::json& payload ); // returns the input value
UISplitter* getSplitter() const;
@@ -78,6 +78,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
return WidgetCommandExecuter::onKeyDown( event );
}
UICodeEditor* getChatInput() const { return mChatInput; }
protected:
UUID mUUID;
std::string mSummary;