diff --git a/include/eepp/ui/uiplacementutils.hpp b/include/eepp/ui/uiplacementutils.hpp index 139e6d4f0..9d941148d 100644 --- a/include/eepp/ui/uiplacementutils.hpp +++ b/include/eepp/ui/uiplacementutils.hpp @@ -31,6 +31,12 @@ struct PopupPlacementConfig { Float minVerticalSpace = 100.f; // Min height needed to trigger Vertical bonus Float minScoreHeight; // Minimum height considered "good" Float maxScoreHeight; // Cap for height in the score calculation + + // Cursor-aware placement: when the popup would sit below the editing cursor + // while the target sits above it, try placing above the target instead. + // Set cursorLineHeight > 0 to enable. + Vector2f cursorScreenPos; + Float cursorLineHeight = 0; }; struct PopupPlacementResult { diff --git a/src/eepp/graphics/systemfontresolver.cpp b/src/eepp/graphics/systemfontresolver.cpp index 96e87cc3a..9430ea33f 100644 --- a/src/eepp/graphics/systemfontresolver.cpp +++ b/src/eepp/graphics/systemfontresolver.cpp @@ -430,25 +430,29 @@ void SystemFontResolver::populateGenericFallbacks() const { }; static const Mapping mappings[] = { - { GenericFamily::Serif, "times new roman" }, - { GenericFamily::Serif, "times" }, + // Prefer native/open desktop families for CSS generics. + // Exact requests such as "Arial" or "Verdana" are still handled by resolve(). + { GenericFamily::Serif, "noto serif" }, { GenericFamily::Serif, "dejavu serif" }, { GenericFamily::Serif, "liberation serif" }, - { GenericFamily::Serif, "noto serif" }, + { GenericFamily::Serif, "times new roman" }, + { GenericFamily::Serif, "times" }, { GenericFamily::Serif, "serif" }, - { GenericFamily::SansSerif, "arial" }, - { GenericFamily::SansSerif, "helvetica" }, + + { GenericFamily::SansSerif, "noto sans" }, { GenericFamily::SansSerif, "dejavu sans" }, { GenericFamily::SansSerif, "liberation sans" }, { GenericFamily::SansSerif, "roboto" }, - { GenericFamily::SansSerif, "noto sans" }, + { GenericFamily::SansSerif, "arial" }, + { GenericFamily::SansSerif, "helvetica" }, { GenericFamily::SansSerif, "sans-serif" }, - { GenericFamily::Monospace, "consolas" }, - { GenericFamily::Monospace, "menlo" }, + + { GenericFamily::Monospace, "noto sans mono" }, { GenericFamily::Monospace, "dejavu sans mono" }, { GenericFamily::Monospace, "liberation mono" }, { GenericFamily::Monospace, "droid sans mono" }, - { GenericFamily::Monospace, "noto sans mono" }, + { GenericFamily::Monospace, "consolas" }, + { GenericFamily::Monospace, "menlo" }, { GenericFamily::Monospace, "monospace" }, { GenericFamily::Cursive, "comic sans ms" }, { GenericFamily::Cursive, "apple chancery" }, diff --git a/src/eepp/ui/uiplacementutils.cpp b/src/eepp/ui/uiplacementutils.cpp index 69ca595f1..aad8a6a1d 100644 --- a/src/eepp/ui/uiplacementutils.cpp +++ b/src/eepp/ui/uiplacementutils.cpp @@ -145,6 +145,37 @@ PopupPlacementResult UIPlacementUtils::findBestPopupPlacement( std::min( pos.x, config.areaRect.Right - boxSize.getWidth() - config.margin ) ); } + // Cursor-aware placement: when the popup would cover the editing cursor + // while the target sits above it, try above the target first. + if ( config.cursorLineHeight > 0 ) { + Rectf popup( pos, boxSize ); + bool belowCursor = pos.y >= config.cursorScreenPos.y; + bool targetAbove = + config.targetRect.Bottom <= config.cursorScreenPos.y + config.cursorLineHeight; + bool hOverlap = ( best.direction == PlacementDirection::Right || + best.direction == PlacementDirection::Left ) && + popup.overlap( config.avoidRect ); + if ( ( belowCursor && targetAbove ) || hOverlap ) { + Float w = popup.getWidth(); + Float h = popup.getHeight(); + PlacementDirection newDir; + Float aboveY = config.targetRect.Top - h - config.margin; + if ( aboveY >= config.areaRect.Top + config.margin ) { + pos.x = config.targetRect.Left; + pos.y = aboveY; + newDir = PlacementDirection::Top; + } else { + pos.y = config.cursorScreenPos.y + config.cursorLineHeight + config.margin; + pos.x = config.targetRect.Left; + Float areaBottom = config.areaRect.Bottom - config.margin; + if ( pos.y + h > areaBottom ) + pos.y = eemax( config.areaRect.Top + config.margin, areaBottom - h ); + newDir = PlacementDirection::Bottom; + } + return { Rectf( pos, { w, h } ).round(), newDir }; + } + } + return { Rectf( pos, boxSize ).round(), best.direction }; } diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index b9b858964..59873f6be 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -1142,7 +1142,7 @@ void AutoCompletePlugin::drawSignatureHelp( UICodeEditor* editor, const Vector2f mBoxPadding.Top + mBoxPadding.Bottom; Float vdiff = drawUp ? -boxHeight : mRowHeight; - auto offset = editor->getTextPositionOffset( mSignatureHelpPosition ); + auto offset = editor->getTextPositionOffset( mSignatureHelpPosition ).asFloat(); Vector2f pos( startScroll.x + offset.x, startScroll.y + offset.y + vdiff ); Rectf boxRect( pos, Sizef( boxWidth, boxHeight ) ); @@ -1189,7 +1189,10 @@ void AutoCompletePlugin::drawSignatureHelp( UICodeEditor* editor, const Vector2f mSignatureHelpText.draw( boxRect.getPosition().x + mBoxPadding.Left, boxRect.getPosition().y + mBoxPadding.Top ); - if ( mSignatureHelpDocumentation && !curSig.documentation.value.empty() ) { + bool drawsSuggestions = + !( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ); + + if ( !drawsSuggestions && mSignatureHelpDocumentation && !curSig.documentation.value.empty() ) { mSuggestionDoc.setFillColor( normalStyle.color ); mSuggestionDoc.setStyle( normalStyle.style ); mSuggestionDoc.setFont( editor->getFont() ); @@ -1197,8 +1200,10 @@ void AutoCompletePlugin::drawSignatureHelp( UICodeEditor* editor, const Vector2f mSuggestionDoc.setLineWrapMode( LineWrapMode::Word ); mSuggestionDoc.setLineWrapKeepIndentation( true ); - Rectf docRect = findBestDocumentationPlacement( editor, curSig.documentation, "", boxRect, - boxRect, drawUp, lineHeight ); + Vector2f cursorScreenPos( startScroll.x + offset.x, startScroll.y + offset.y ); + Rectf docRect = + findBestDocumentationPlacement( editor, curSig.documentation, "", boxRect, boxRect, + cursorScreenPos, drawUp, lineHeight ); if ( docRect.getSize().getWidth() > 0 && docRect.getSize().getHeight() > 0 ) { primitives.setColor( @@ -1339,11 +1344,14 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc mSuggestionDoc.setLineWrapMode( LineWrapMode::Word ); mSuggestionDoc.setLineWrapKeepIndentation( true ); + Vector2f cursorOffset = editor->getTextPositionOffset( cursor ).asFloat(); + Vector2f cursorScreenPos( startScroll.x + cursorOffset.x, + startScroll.y + cursorOffset.y ); Rectf docRect = findBestDocumentationPlacement( editor, suggestion.documentation, suggestion.detail, boxRect, { { cursorPos.x, cursorPos.y + mRowHeight * count }, { mBoxRect.getWidth(), mRowHeight } }, - drawUp, lineHeight ); + cursorScreenPos, drawUp, lineHeight ); if ( docRect.getSize().getWidth() > 0 && docRect.getSize().getHeight() > 0 ) { primitives.setColor( @@ -1384,17 +1392,20 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc Rectf AutoCompletePlugin::findBestDocumentationPlacement( UICodeEditor* editor, const LSPMarkupContent& suggestion, const std::string& detail, - const Rectf& anchorBox, const Rectf& rowRect, bool drawUp, Float lineHeight ) { + const Rectf& anchorBox, const Rectf& rowRect, const Vector2f& cursorScreenPos, bool, + Float lineHeight ) { PopupPlacementConfig config; config.areaRect = editor->getScreenRect(); config.targetRect = anchorBox; config.alignRect = rowRect; - // The avoidRect is the user's cursor line. This ensures Top/Bottom placement skips the line - // being typed. - Float cursorLineTop = - rowRect.Top - lineHeight; // Approximating cursor location based on the suggestion row - config.avoidRect = - Rectf( anchorBox.Left, cursorLineTop, editor->getPixelsSize().getWidth(), lineHeight ); + // Small avoid-rect: just the cursor cell + a few character-widths to the right, + // so the documentation can still sit to the right of the cursor text without being moved. + Float cursorAvoidX = cursorScreenPos.x + editor->getGlyphWidth() * 4; + config.avoidRect = Rectf( Vector2f( cursorScreenPos.x, cursorScreenPos.y ), + Sizef( cursorAvoidX - cursorScreenPos.x + 1, lineHeight ) ); + // Enable cursor-aware placement: when popup would cover the cursor area, try above target. + config.cursorScreenPos = cursorScreenPos; + config.cursorLineHeight = lineHeight; config.userMaxWidth = editor->convertLength( StyleSheetLength( mMaxSuggestionDocumentationWidth ), editor->getPixelsSize().getWidth() ); config.minHorizontalSpace = PixelDensity::dpToPx( 200.f ); diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 85ea1a269..b6863a9e4 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -228,7 +228,8 @@ class AutoCompletePlugin : public Plugin { Rectf findBestDocumentationPlacement( UICodeEditor* editor, const LSPMarkupContent& suggestion, const std::string& detail, const Rectf& anchorBox, - const Rectf& rowRect, bool drawUp, Float lineHeight ); + const Rectf& rowRect, const Vector2f& cursorScreenPos, + bool drawUp, Float lineHeight ); void updateShortcuts(); };