diff --git a/frameworks/base/utils/measure_util.cpp b/frameworks/base/utils/measure_util.cpp index da13135daf1caea7a10460f38f2fa12aa9314eea..e61ed8ec0062548ea3266ac97305a0b30148825c 100644 --- a/frameworks/base/utils/measure_util.cpp +++ b/frameworks/base/utils/measure_util.cpp @@ -15,6 +15,7 @@ #include "base/utils/measure_util.h" #include "core/components/custom_paint/render_custom_paint.h" +#include "frameworks/core/components/custom_paint/rosen_render_custom_paint.h" namespace OHOS::Ace { double MeasureUtil::MeasureText(const MeasureContext& context) @@ -26,4 +27,26 @@ Size MeasureUtil::MeasureTextSize(const MeasureContext& context) { return RenderCustomPaint::MeasureTextSize(context); } + +Size MeasureUtil::MeasureTextSize(const TextStyle& textStyle, const std::string& text) +{ +#ifdef ENABLE_ROSEN_BACKEND + MeasureContext content; + content.textContent = text; + content.fontSize = textStyle.GetFontSize(); + auto fontweight = StringUtils::FontWeightToString(textStyle.GetFontWeight()); + content.fontWeight = fontweight; + content.isReturnActualWidth = true; + content.maxlines = 1; + return RosenRenderCustomPaint::MeasureTextSizeInner(content); +#else + return 0.0f; +#endif +} + +float MeasureUtil::MeasureTextWidth(const TextStyle& textStyle, const std::string& text) +{ + auto buttonWidth = MeasureTextSize(textStyle, text).Width(); + return std::max(static_cast(buttonWidth), 0.0f); +} } // namespace OHOS::Ace diff --git a/frameworks/base/utils/measure_util.h b/frameworks/base/utils/measure_util.h index 989f3aaeb0364dc0a3ab32b349088390cebe24aa..6edf6c4bf0b74921deb408c5f1b3b52437e2b19f 100644 --- a/frameworks/base/utils/measure_util.h +++ b/frameworks/base/utils/measure_util.h @@ -49,6 +49,8 @@ class ACE_FORCE_EXPORT MeasureUtil { public: static double MeasureText(const MeasureContext& context); static Size MeasureTextSize(const MeasureContext& context); + static Size MeasureTextSize(const TextStyle& textStyle, const std::string& text); + static float MeasureTextWidth(const TextStyle& textStyle, const std::string& text); }; } // namespace OHOS::Ace diff --git a/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js b/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js index 77028b1d7bb75537c201d98419f78797e66dd803..9c1095385e3df9a46e77ac1879e29c3f3ae7fd04 100644 --- a/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js +++ b/frameworks/bridge/declarative_frontend/engine/jsEnumStyle.js @@ -2231,6 +2231,26 @@ class TextMenuItemId { static get AI_WRITER() { return new TextMenuItemId('OH_DEFAULT_AI_WRITE'); } + + static get AI_MENU_PHONE() { + return new TextMenuItemId('OH_DEFAULT_AI_MENU_PHONE'); + } + + static get AI_MENU_URL() { + return new TextMenuItemId('OH_DEFAULT_AI_MENU_URL'); + } + + static get AI_MENU_EMAIL() { + return new TextMenuItemId('OH_DEFAULT_AI_MENU_EMAIL'); + } + + static get AI_MENU_ADDRESS() { + return new TextMenuItemId('OH_DEFAULT_AI_MENU_ADDRESS'); + } + + static get AI_MENU_DATETIME() { + return new TextMenuItemId('OH_DEFAULT_AI_MENU_DATETIME'); + } } globalThis.TextMenuItemId = TextMenuItemId; diff --git a/frameworks/core/common/ai/data_detector_adapter.h b/frameworks/core/common/ai/data_detector_adapter.h index e1afb1b9fb791feb010d5a6113a5f73213458048..b1efb565881fa6643c5cfca410e77b40696ebf21 100644 --- a/frameworks/core/common/ai/data_detector_adapter.h +++ b/frameworks/core/common/ai/data_detector_adapter.h @@ -33,6 +33,8 @@ class WantParams; namespace OHOS::Ace { +extern const std::unordered_map TEXT_DETECT_MAP; + namespace NG { class TextPattern; class RichEditorPattern; @@ -43,7 +45,7 @@ struct AISpan { int32_t start = 0; int32_t end = 0; std::string content = ""; - TextDataDetectType type = TextDataDetectType::PHONE_NUMBER; + TextDataDetectType type = TextDataDetectType::INVALID; bool operator==(const AISpan& span) const { return start == span.start && end == span.end && content == span.content && type == span.type; diff --git a/frameworks/core/components/common/layout/constants.h b/frameworks/core/components/common/layout/constants.h index d385bbb9bc63a6a559fefc2371853191f347d1a9..c6b2fbb22d7891b2d67c8c28179763c6e3e59005 100644 --- a/frameworks/core/components/common/layout/constants.h +++ b/frameworks/core/components/common/layout/constants.h @@ -361,6 +361,7 @@ enum class MarqueeStartPolicy { }; enum class TextDataDetectType { + INVALID = -1, PHONE_NUMBER = 0, URL, EMAIL, diff --git a/frameworks/core/components/common/properties/text_style.h b/frameworks/core/components/common/properties/text_style.h index c7d313df4d98dd8fee61d4fb5e1847decf453925..cdf27c5abae8419c4785ce22f764304853469f85 100644 --- a/frameworks/core/components/common/properties/text_style.h +++ b/frameworks/core/components/common/properties/text_style.h @@ -85,6 +85,31 @@ enum class FontStyle { NONE }; +struct FontForegroudGradiantColor { + std::vector points; + std::vector scalars; + std::vector colors; + + bool IsValid() const + { + // 2 points including begin and end + return points.size() == 2 && scalars.size() == colors.size(); + } + + bool operator==(const FontForegroudGradiantColor& fontForegroudGradiantColor) const + { + auto isScalarEq = true; + if (scalars.size() != fontForegroudGradiantColor.scalars.size()) { + isScalarEq = false; + } + for (size_t i = 0; i < scalars.size() && isScalarEq; i++) { + isScalarEq = NearEqual(scalars[i], fontForegroudGradiantColor.scalars[i]); + } + return isScalarEq && (points == fontForegroudGradiantColor.points) && + (colors == fontForegroudGradiantColor.colors); + } +}; + namespace StringUtils { inline std::string ToString(const FontStyle& fontStyle) { @@ -610,6 +635,10 @@ public: ACE_DEFINE_PARAGRAPH_STYLE_WITH_DEFAULT_VALUE( OptimizeTrailingSpace, bool, false, ParagraphStyleAttribute::RE_CREATE); + // used for gradiant color + ACE_DEFINE_PARAGRAPH_STYLE(FontForegroudGradiantColor, FontForegroudGradiantColor, + TextStyleAttribute::RE_CREATE); + // for Symbol ACE_DEFINE_SYMBOL_STYLE(RenderColors, std::vector, SymbolStyleAttribute::COLOR_LIST); ACE_DEFINE_SYMBOL_STYLE_WITH_DEFAULT_VALUE(RenderStrategy, int32_t, 0, SymbolStyleAttribute::RENDER_MODE); diff --git a/frameworks/core/components/font/constants_converter.cpp b/frameworks/core/components/font/constants_converter.cpp index bc518b8f98cfe8f317d00479d23c82e4b2f391a6..6c55df8bc98e5e42be4f6df01a8bbc9908714599 100644 --- a/frameworks/core/components/font/constants_converter.cpp +++ b/frameworks/core/components/font/constants_converter.cpp @@ -21,7 +21,9 @@ #include "rosen_text/typography_style.h" #include "base/i18n/localization.h" +#include "base/utils/measure_util.h" #include "core/components_ng/render/drawing.h" +#include "frameworks/core/components/custom_paint/rosen_render_custom_paint.h" namespace OHOS::Ace::Constants { namespace { @@ -35,6 +37,12 @@ constexpr int32_t NONE_EFFECT = 0; constexpr float ORIGINAL_LINE_HEIGHT_SCALE = 1.0f; constexpr float DEFAULT_STROKE_WIDTH = 0.0f; const std::string DEFAULT_SYMBOL_FONTFAMILY = "HM Symbol"; +struct LineSpaceAndHeightInfo { + double lineHeightScale = 0.0; + double lineSpacingScale = 0.0; + bool lineHeightOnly = false; + bool lineSpacingOnly = false; +}; } // namespace Rosen::FontWeight ConvertTxtFontWeight(FontWeight fontWeight) @@ -364,6 +372,84 @@ void ConvertTxtStyle(const TextStyle& textStyle, Rosen::TextStyle& txtStyle) txtStyle.backgroundRect.rightBottomRadius = radiusConverter(radius->radiusBottomRight); } +// ConvertTxtStyle helper for LineSpacing and LineHeight etc +void ConvertSpacingAndHeigh( + const TextStyle& textStyle, const WeakPtr& context, Rosen::TextStyle& txtStyle, + LineSpaceAndHeightInfo& info) +{ + auto pipelineContext = context.Upgrade(); + if (textStyle.GetLineHeight().Unit() == DimensionUnit::PERCENT) { + info.lineHeightOnly = true; + info.lineHeightScale = textStyle.GetLineHeight().Value(); + } else { + double fontSize = txtStyle.fontSize; + double lineHeight = textStyle.GetLineHeight().Value(); + if (pipelineContext) { + lineHeight = textStyle.GetLineHeight().ConvertToPxDistribute( + textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()); + } + info.lineHeightOnly = textStyle.HasHeightOverride(); + if (!NearEqual(lineHeight, fontSize) && (lineHeight > 0.0) && (!NearZero(fontSize))) { + info.lineHeightScale = lineHeight / fontSize; + } else { + info.lineHeightScale = 1; + static const int32_t BEGIN_VERSION = 6; + auto isBeginVersion = pipelineContext && pipelineContext->GetMinPlatformVersion() >= BEGIN_VERSION; + if (NearZero(lineHeight) || (!isBeginVersion && NearEqual(lineHeight, fontSize))) { + info.lineHeightOnly = false; + } + } + } + if (textStyle.GetLineSpacing().Unit() == DimensionUnit::PERCENT) { + info.lineSpacingOnly = true; + info.lineSpacingScale = textStyle.GetLineSpacing().Value(); + } else { + double fontSize = txtStyle.fontSize; + double lineSpacing = textStyle.GetLineSpacing().Value(); + if (pipelineContext) { + lineSpacing = textStyle.GetLineSpacing().ConvertToPxDistribute( + textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()); + } + info.lineSpacingOnly = true; + if (!NearEqual(lineSpacing, fontSize) && (lineSpacing > 0.0) && (!NearZero(fontSize))) { + info.lineSpacingScale = lineSpacing / fontSize; + } else { + info.lineSpacingScale = 1; + if (NearZero(lineSpacing)) { + info.lineSpacingOnly = false; + } + } + } +} + +// ConvertTxtStyle helper for LineSpacing and LineHeight etc +void ConvertGradiantColor( + const TextStyle& textStyle, const WeakPtr& context, Rosen::TextStyle& txtStyle, + OHOS::Ace::FontForegroudGradiantColor & gradiantColor) +{ + RSBrush brush; + std::vector points = { + Rosen::Drawing::PointF(gradiantColor.points[0].GetX(), gradiantColor.points[0].GetY()), + Rosen::Drawing::PointF(gradiantColor.points[1].GetX(), gradiantColor.points[1].GetY()) + // Rosen::Drawing::PointF(textSize.Width(), textSize.Height()) }; + }; + std::vector colors; + std::vector pos; + for (size_t i = 0; i < gradiantColor.colors.size(); i++) { + colors.push_back(ConvertSkColor(gradiantColor.colors[i])); + // IsValid ensures colors and scalars are same size + pos.push_back(gradiantColor.scalars[i]); + } + brush.SetShaderEffect( + RSShaderEffect::CreateLinearGradient(points.at(0), points.at(1), colors, pos, RSTileMode::CLAMP)); + if (txtStyle.foregroundBrush) { + txtStyle.foregroundBrush->SetShaderEffect( + RSShaderEffect::CreateLinearGradient(points.at(0), points.at(1), colors, pos, RSTileMode::CLAMP)); + } else { + txtStyle.foregroundBrush = brush; + } +} + void ConvertTxtStyle(const TextStyle& textStyle, const WeakPtr& context, Rosen::TextStyle& txtStyle) { txtStyle.relayoutChangeBitmap = textStyle.GetReLayoutTextStyleBitmap(); @@ -438,60 +524,15 @@ void ConvertTxtStyle(const TextStyle& textStyle, const WeakPtr& co txtStyle.shadows.emplace_back(txtShadow); } - double lineHeightScale = 0.0; - double lineSpacingScale = 0.0; - bool lineHeightOnly = false; - bool lineSpacingOnly = false; - if (textStyle.GetLineHeight().Unit() == DimensionUnit::PERCENT) { - lineHeightOnly = true; - lineHeightScale = textStyle.GetLineHeight().Value(); - } else { - double fontSize = txtStyle.fontSize; - double lineHeight = textStyle.GetLineHeight().Value(); - if (pipelineContext) { - lineHeight = textStyle.GetLineHeight().ConvertToPxDistribute( - textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()); - } - lineHeightOnly = textStyle.HasHeightOverride(); - if (!NearEqual(lineHeight, fontSize) && (lineHeight > 0.0) && (!NearZero(fontSize))) { - lineHeightScale = lineHeight / fontSize; - } else { - lineHeightScale = 1; - static const int32_t beginVersion = 6; - auto isBeginVersion = pipelineContext && pipelineContext->GetMinPlatformVersion() >= beginVersion; - if (NearZero(lineHeight) || (!isBeginVersion && NearEqual(lineHeight, fontSize))) { - lineHeightOnly = false; - } - } - } - if (textStyle.GetLineSpacing().Unit() == DimensionUnit::PERCENT) { - lineSpacingOnly = true; - lineSpacingScale = textStyle.GetLineSpacing().Value(); - } else { - double fontSize = txtStyle.fontSize; - double lineSpacing = textStyle.GetLineSpacing().Value(); - if (pipelineContext) { - lineSpacing = textStyle.GetLineSpacing().ConvertToPxDistribute( - textStyle.GetMinFontScale(), textStyle.GetMaxFontScale(), textStyle.IsAllowScale()); - } - lineSpacingOnly = true; - if (!NearEqual(lineSpacing, fontSize) && (lineSpacing > 0.0) && (!NearZero(fontSize))) { - lineSpacingScale = lineSpacing / fontSize; - } else { - lineSpacingScale = 1; - if (NearZero(lineSpacing)) { - lineSpacingOnly = false; - } - } - } - - txtStyle.heightOnly = lineHeightOnly || lineSpacingOnly; - if (lineHeightOnly && lineSpacingOnly) { - txtStyle.heightScale = lineHeightScale + lineSpacingScale; - } else if (lineHeightOnly && !lineSpacingOnly) { - txtStyle.heightScale = lineHeightScale; - } else if (!lineHeightOnly && lineSpacingOnly) { - txtStyle.heightScale = ORIGINAL_LINE_HEIGHT_SCALE + lineSpacingScale; + LineSpaceAndHeightInfo info; + ConvertSpacingAndHeigh(textStyle, context, txtStyle, info); + txtStyle.heightOnly = info.lineHeightOnly || info.lineSpacingOnly; + if (info.lineHeightOnly && info.lineSpacingOnly) { + txtStyle.heightScale = info.lineHeightScale + info.lineSpacingScale; + } else if (info.lineHeightOnly && !info.lineSpacingOnly) { + txtStyle.heightScale = info.lineHeightScale; + } else if (!info.lineHeightOnly && info.lineSpacingOnly) { + txtStyle.heightScale = ORIGINAL_LINE_HEIGHT_SCALE + info.lineSpacingScale; } else { txtStyle.heightScale = 1; } @@ -505,6 +546,12 @@ void ConvertTxtStyle(const TextStyle& textStyle, const WeakPtr& co } txtStyle.fontFeatures = features; } + + auto gradiantColor = textStyle.GetFontForegroudGradiantColor(); + if (gradiantColor.IsValid()) { + ConvertGradiantColor(textStyle, context, txtStyle, gradiantColor); + } + auto textBackgroundStyle = textStyle.GetTextBackgroundStyle(); CHECK_NULL_VOID(textBackgroundStyle.has_value()); txtStyle.styleId = textBackgroundStyle->groupId; diff --git a/frameworks/core/components/text/text_theme.h b/frameworks/core/components/text/text_theme.h index 7b92aad4e04770968e38d7f5dd6b0ce25dfb3391..05b3f14c92fc8dafac2b7827b2ebc9746df70c06 100644 --- a/frameworks/core/components/text/text_theme.h +++ b/frameworks/core/components/text/text_theme.h @@ -75,6 +75,7 @@ public: theme->textStyle_.SetLineSpacing(pattern->GetAttr("text_line_spacing", 0.0_vp)); theme->textStyle_.SetFontWeight(static_cast(pattern->GetAttr("text_font_weight", 0.0))); theme->textStyle_.SetTextAlign(static_cast(pattern->GetAttr("text_align", 0.0))); + theme->textStyle_.SetFontForegroudGradiantColor(FontForegroudGradiantColor()); theme->selectedColor_ = pattern->GetAttr(PATTERN_BG_COLOR_SELECTED, Color(0x33007dff)); auto draggable = pattern->GetAttr("draggable", "0"); theme->draggable_ = StringUtils::StringToInt(draggable); diff --git a/frameworks/core/components/text_overlay/text_overlay_theme.h b/frameworks/core/components/text_overlay/text_overlay_theme.h index 685e135a924baa70e7a882685821b2df613d7ec9..44dc9364d02149648c19727ddb85a7c49190bdfd 100644 --- a/frameworks/core/components/text_overlay/text_overlay_theme.h +++ b/frameworks/core/components/text_overlay/text_overlay_theme.h @@ -16,6 +16,7 @@ #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_TEXT_OVERLAY_TEXT_OVERLAY_THEME_H #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_TEXT_OVERLAY_TEXT_OVERLAY_THEME_H +#include #include "base/geometry/dimension.h" #include "core/components/common/properties/border.h" #include "core/components/common/properties/border_edge.h" @@ -62,6 +63,7 @@ public: theme->translateSymbolId_ = themeConstants->GetSymbolByName("sys.symbol.translate_c2e"); ParsePattern(themeConstants->GetThemeStyle(), theme); ParseMenuPattern(themeConstants->GetThemeStyle(), theme); + ParseAIMenu(themeConstants->GetThemeStyle(), theme); return theme; } @@ -138,6 +140,18 @@ public: theme->shareLabel_ = pattern->GetAttr("text_overlay_menu_share_label", "Share"); theme->searchLabel_ = pattern->GetAttr("text_overlay_menu_search_label", "Search"); } + void ParseAIMenu(const RefPtr& themeStyle, const RefPtr& theme) const + { + // Get value from global resource after value exists in resource + theme->aiMenuTypeOptionNames_[OHOS::Ace::TextDataDetectType::ADDRESS] = "导航至该位置"; + theme->aiMenuTypeOptionNames_[OHOS::Ace::TextDataDetectType::DATE_TIME] = "新建日程提醒"; + theme->aiMenuTypeOptionNames_[OHOS::Ace::TextDataDetectType::EMAIL] = "新建邮件"; + theme->aiMenuTypeOptionNames_[OHOS::Ace::TextDataDetectType::PHONE_NUMBER] = "呼叫"; + theme->aiMenuTypeOptionNames_[OHOS::Ace::TextDataDetectType::URL] = "打开链接"; + theme->aiMenuFontGradientColors_ = { Color(0xff3A73DE), Color(0xff13C0C0), + Color(0xffFFB000), Color(0xffED692E) }; + theme->aiMenuFontGradientScalars_ = { 0, 0.33, 0.66, 1.0 }; + } }; ~TextOverlayTheme() override = default; @@ -407,6 +421,29 @@ public: return searchLabel_; } + const std::string GetAiMenuOptionName(OHOS::Ace::TextDataDetectType type) const + { + if (type == TextDataDetectType::INVALID) { + return "invalid"; + } + auto findIter = aiMenuTypeOptionNames_.find(type); + if (findIter != aiMenuTypeOptionNames_.end()) { + return findIter->second; + } else { + return "No such type"; + } + } + + const std::vector& GetAiMenuFontGradientColors() const + { + return aiMenuFontGradientColors_; + } + + const std::vector& GetAiMenuFontGradientScalars() const + { + return aiMenuFontGradientScalars_; + } + protected: TextOverlayTheme() = default; @@ -461,6 +498,10 @@ private: uint32_t aiWriteSymbolId_ = 0; uint32_t searchSymbolId_ = 0; uint32_t translateSymbolId_ = 0; + + std::unordered_map aiMenuTypeOptionNames_; + std::vector aiMenuFontGradientColors_; + std::vector aiMenuFontGradientScalars_; }; } // namespace OHOS::Ace diff --git a/frameworks/core/components_ng/base/view_abstract.h b/frameworks/core/components_ng/base/view_abstract.h index 335a4d6f97560a9d7811fcd9d9175a6a91a05255..675524284fec8db0e414faf420521f63d6081b2e 100644 --- a/frameworks/core/components_ng/base/view_abstract.h +++ b/frameworks/core/components_ng/base/view_abstract.h @@ -80,6 +80,8 @@ struct OptionParam { // Used for security controls. bool isPasteOption = false; + bool isAIMenuOption = false; + OptionParam() = default; OptionParam(const std::string &valueParam, const std::string &iconParam, const std::function &actionParam) : value(valueParam), icon(iconParam), enabled(true), action(actionParam) diff --git a/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.cpp b/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.cpp index 2f4664ad01f320725e0256ca000aeba77a5301b5..7b8f7599860d4bd5eae95f41da932adccc9445e3 100644 --- a/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.cpp +++ b/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.cpp @@ -200,6 +200,8 @@ SelectOverlayInfo SelectContentOverlayManager::BuildSelectOverlayInfo(int32_t re overlayInfo.menuCallback.onAIWrite = MakeMenuCallback(OptionMenuActionId::AI_WRITE, overlayInfo); overlayInfo.menuCallback.onAppear = MakeMenuCallback(OptionMenuActionId::APPEAR, overlayInfo); overlayInfo.menuCallback.onDisappear = MakeMenuCallback(OptionMenuActionId::DISAPPEAR, overlayInfo); + overlayInfo.menuCallback.onAIMenuOption = + MakeMenuCallbackWithInfo(OptionMenuActionId::AI_MENU_OPTION, overlayInfo); overlayInfo.isUseOverlayNG = true; RegisterTouchCallback(overlayInfo); RegisterHandleCallback(overlayInfo); @@ -333,6 +335,23 @@ std::function SelectContentOverlayManager::MakeMenuCallback( }; } +// return callback funtion with label information as input +std::function SelectContentOverlayManager::MakeMenuCallbackWithInfo( + OptionMenuActionId id, const SelectOverlayInfo& info) +{ + auto callback = selectOverlayHolder_->GetCallback(); + CHECK_NULL_RETURN(callback, nullptr); + return [actionId = id, weakCallback = WeakClaim(AceType::RawPtr(callback)), + menuType = info.menuInfo.menuType, logInfo = GetOwnerDebugInfo()](const std::string& labelInfo = "") { + auto callback = weakCallback.Upgrade(); + CHECK_NULL_VOID(callback); + TAG_LOGI(AceLogTag::ACE_SELECT_OVERLAY, + "MakeMenuCallbackWithInfo called, menu id %{public}d, menu type %{public}d, consumer %{public}s", actionId, + menuType, logInfo.c_str()); + callback->OnMenuItemAction(actionId, menuType, labelInfo); + }; +} + void SelectContentOverlayManager::UpdateExistOverlay(const SelectOverlayInfo& info, bool animation, int32_t requestCode) { TAG_LOGI(AceLogTag::ACE_SELECT_OVERLAY, "UpdateExistOverlay called by %{public}s", GetOwnerDebugInfo().c_str()); @@ -417,6 +436,21 @@ void SelectContentOverlayManager::SwitchToHandleMode(HandleLevelMode mode, bool } } +void SelectContentOverlayManager::HandleDirtyViewPort(RefPtr& menuPattern) +{ + auto viewPort = selectOverlayHolder_->GetAncestorNodeViewPort(); + if (viewPort) { + ConvertRectRelativeToParent(*viewPort); + } + if (menuPattern) { + menuPattern->UpdateViewPort(viewPort); + } + auto handlePattern = GetSelectHandlePattern(WeakClaim(this)); + if (handlePattern) { + handlePattern->UpdateViewPort(viewPort); + } +} + void SelectContentOverlayManager::MarkInfoChange(SelectOverlayDirtyFlag dirty) { CHECK_NULL_VOID(selectOverlayHolder_); @@ -434,16 +468,22 @@ void SelectContentOverlayManager::MarkInfoChange(SelectOverlayDirtyFlag dirty) TAG_LOGI(AceLogTag::ACE_SELECT_OVERLAY, "Update all menu item: %{public}s - %{public}s", menuInfo.ToString().c_str(), GetOwnerDebugInfo().c_str()); menuPattern->UpdateSelectMenuInfo(menuInfo); - } - if ((dirty & DIRTY_COPY_ALL_ITEM) == DIRTY_COPY_ALL_ITEM) { - auto oldMenuInfo = menuPattern->GetSelectMenuInfo(); - SelectMenuInfo menuInfo = { .showCopy = oldMenuInfo.showCopy, .showCopyAll = oldMenuInfo.showCopyAll }; - selectOverlayHolder_->OnUpdateMenuInfo(menuInfo, DIRTY_COPY_ALL_ITEM); - oldMenuInfo.showCopyAll = menuInfo.showCopyAll; - oldMenuInfo.showCopy = menuInfo.showCopy; + } else if ( + (dirty & DIRTY_COPY_ALL_ITEM) == DIRTY_COPY_ALL_ITEM || + (dirty & DIRTY_AI_MENU_ITEM) == DIRTY_AI_MENU_ITEM) { // Diff specified flags + auto localReplacedMenuInfo = menuPattern->GetSelectMenuInfo(); + SelectMenuInfo menuInfo; + selectOverlayHolder_->OnUpdateMenuInfo(menuInfo, DIRTY_ALL_MENU_ITEM); + if ((dirty & DIRTY_COPY_ALL_ITEM) == DIRTY_COPY_ALL_ITEM) { // can extract to function + localReplacedMenuInfo.showCopyAll = menuInfo.showCopyAll; + localReplacedMenuInfo.showCopy = menuInfo.showCopy; + } + if ((dirty & DIRTY_AI_MENU_ITEM) == DIRTY_AI_MENU_ITEM) { + localReplacedMenuInfo.aiMenuOptionType = menuInfo.aiMenuOptionType; + } TAG_LOGI(AceLogTag::ACE_SELECT_OVERLAY, "Update select all menu: %{public}s - %{public}s", - oldMenuInfo.ToString().c_str(), GetOwnerDebugInfo().c_str()); - menuPattern->UpdateSelectMenuInfo(oldMenuInfo); + localReplacedMenuInfo.ToString().c_str(), GetOwnerDebugInfo().c_str()); + menuPattern->UpdateSelectMenuInfo(localReplacedMenuInfo); } if ((dirty & DIRTY_SELECT_TEXT) == DIRTY_SELECT_TEXT) { auto selectedInfo = selectOverlayHolder_->GetSelectedText(); @@ -451,17 +491,7 @@ void SelectContentOverlayManager::MarkInfoChange(SelectOverlayDirtyFlag dirty) } } if ((dirty & DIRTY_VIEWPORT) == DIRTY_VIEWPORT) { - auto viewPort = selectOverlayHolder_->GetAncestorNodeViewPort(); - if (viewPort) { - ConvertRectRelativeToParent(*viewPort); - } - if (menuPattern) { - menuPattern->UpdateViewPort(viewPort); - } - auto handlePattern = GetSelectHandlePattern(WeakClaim(this)); - if (handlePattern) { - handlePattern->UpdateViewPort(viewPort); - } + HandleDirtyViewPort(menuPattern); } UpdateHandleInfosWithFlag(dirty); shareOverlayInfo_->containerModalOffset = GetContainerModalOffset(); diff --git a/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.h b/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.h index a65d063361f2ee5d463be53b98089b27f0a2a98b..c4d7ce5d4005b7c86562f94788a142a1c3f1e446 100644 --- a/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.h +++ b/frameworks/core/components_ng/manager/select_content_overlay/select_content_overlay_manager.h @@ -27,6 +27,7 @@ #include "core/event/touch_event.h" namespace OHOS::Ace::NG { +class SelectContentOverlayPattern; struct LegacyManagerCallbacks { std::function closeCallback; @@ -141,6 +142,9 @@ private: void MountMenuNodeToSubWindow(const RefPtr& overlayNode, bool animation, NodeType nodeType); bool IsEnableSubWindowMenu(); void UpdateRightClickSubWindowMenuProps(const RefPtr& overlayNode); + std::function MakeMenuCallbackWithInfo + (OptionMenuActionId actionId, const SelectOverlayInfo& info); + void HandleDirtyViewPort(RefPtr& menuPattern); RefPtr selectOverlayHolder_; WeakPtr selectOverlayNode_; diff --git a/frameworks/core/components_ng/manager/select_content_overlay/select_overlay_callback.h b/frameworks/core/components_ng/manager/select_content_overlay/select_overlay_callback.h index 5910b2d3bd765559fbe836dfefc5a9e02ff81e7d..4073afa3c7275a89b3b9070e40e9c0d512cc16af 100644 --- a/frameworks/core/components_ng/manager/select_content_overlay/select_overlay_callback.h +++ b/frameworks/core/components_ng/manager/select_content_overlay/select_overlay_callback.h @@ -73,6 +73,7 @@ public: ~SelectOverlayCallback() override = default; virtual void OnAfterSelectOverlayShow(bool isCreate) {} virtual void OnMenuItemAction(OptionMenuActionId id, OptionMenuType type) {} + virtual void OnMenuItemAction(OptionMenuActionId id, OptionMenuType type, const std::string& labelInfo) {} virtual void OnOverlayTouchDown(const TouchEventInfo& event) {} virtual void OnOverlayTouchUp(const TouchEventInfo& event) {} diff --git a/frameworks/core/components_ng/pattern/menu/menu_view.cpp b/frameworks/core/components_ng/pattern/menu/menu_view.cpp index 2eb59e6e505d606f905fd0e367adc0d870af5727..e5355722d423fb82f8186505231df728083eeb23 100644 --- a/frameworks/core/components_ng/pattern/menu/menu_view.cpp +++ b/frameworks/core/components_ng/pattern/menu/menu_view.cpp @@ -1620,7 +1620,7 @@ void MenuView::CreateOption(bool optionsHasIcon, std::vector& param auto iconNode = CreateSymbol(params[index].symbol, row, nullptr, params[index].symbolUserDefinedIdealFontSize); pattern->SetIconNode(iconNode); } - auto textNode = CreateText(params[index].value, row); + auto textNode = CreateText(params[index].value, row, false, params[index].isAIMenuOption); row->MountToParent(option); row->MarkModifyDone(); pattern->SetTextNode(textNode); @@ -1631,17 +1631,17 @@ void MenuView::CreateOption(bool optionsHasIcon, std::vector& param eventHub->SetMenuOnClick(params[index].action); } -void MenuView::CreateOption(bool optionsHasIcon, const std::string& value, const std::string& icon, +void MenuView::CreateOption(const OptionValueInfo& value, const std::string& icon, const RefPtr& row, const RefPtr& option, const std::function& onClickFunc) { auto pattern = option->GetPattern(); CHECK_NULL_VOID(pattern); - if (optionsHasIcon) { + if (value.optionsHasIcon) { auto iconNode = CreateIcon(icon, row); pattern->SetIconNode(iconNode); pattern->SetIcon(icon); } - auto textNode = CreateText(value, row); + auto textNode = CreateText(value.content, row, false, value.isAIMenuOption); row->MountToParent(option); row->MarkModifyDone(); pattern->SetTextNode(textNode); @@ -1671,7 +1671,7 @@ RefPtr MenuView::CreateMenuOption(bool optionsHasIcon, std::vector MenuView::CreateMenuOption(bool optionsHasIcon, const OptionValueInfo& value, +RefPtr MenuView::CreateMenuOption(const OptionValueInfo& value, const std::function& onClickFunc, int32_t index, const std::string& icon) { auto option = Create(index); @@ -1681,13 +1681,15 @@ RefPtr MenuView::CreateMenuOption(bool optionsHasIcon, const OptionVa #ifdef OHOS_PLATFORM constexpr char BUTTON_PASTE[] = "textoverlay.paste"; - if (value.value == Localization::GetInstance()->GetEntryLetters(BUTTON_PASTE)) { - CreatePasteButton(optionsHasIcon, option, row, onClickFunc, icon); + if (value.content == Localization::GetInstance()->GetEntryLetters(BUTTON_PASTE)) { + CreatePasteButton(value.optionsHasIcon, option, row, onClickFunc, icon); } else { - CreateOption(optionsHasIcon, value.value, icon, row, option, onClickFunc); + CreateOption({ .optionsHasIcon = value.optionsHasIcon, + .content = value.content, .isAIMenuOption = value.isAIMenuOption }, + icon, row, option, onClickFunc); } #else - CreateOption(optionsHasIcon, value.value, icon, row, option, onClickFunc); + CreateOption({optionsHasIcon, value.content, value.isAIMenuOption}, icon, row, option, onClickFunc); #endif return option; } @@ -1704,7 +1706,9 @@ void MenuView::MountOptionToColumn(std::vector& params, const RefPt optionNode = CreateMenuOption(optionsHasSymbol, params, i); } else { optionNode = CreateMenuOption( - optionsHasIcon, { params[i].value, params[i].isPasteOption }, params[i].action, i, params[i].icon); + { .optionsHasIcon = optionsHasIcon, .content = params[i].value, + .isPasteOption = params[i].isPasteOption, .isAIMenuOption = params[i].isAIMenuOption }, + params[i].action, i, params[i].icon); if (optionNode) { auto optionPattern = optionNode->GetPattern(); optionPattern->SetBlockClick(params[i].disableSystemClick); @@ -1832,7 +1836,8 @@ RefPtr MenuView::CreateSymbol(const std::function MenuView::CreateText(const std::string& value, const RefPtr& parent, bool autoWrapFlag) +RefPtr MenuView::CreateText(const std::string& value, const RefPtr& parent, + bool autoWrapFlag, bool isAIMenuOption) { // create child text node auto textId = ElementRegister::GetInstance()->MakeUniqueId(); @@ -1871,6 +1876,17 @@ RefPtr MenuView::CreateText(const std::string& value, const RefPtr(theme->GetOptionContentNormalAlign()); auto convertValue = ConvertTxtTextAlign(IsRightToLeft, textAlign); textProperty->UpdateAlignment(convertValue); + auto textOverlayTheme = pipeline->GetTheme(); + if (isAIMenuOption && textOverlayTheme) { + auto textStyle = theme->GetOptionTextStyle(); + auto textSize = MeasureUtil::MeasureTextSize(textStyle, value); + FontForegroudGradiantColor colorInfo; + colorInfo.points = { NG::PointF(0, 0), + NG::PointF(static_cast(textSize.Width()), static_cast(textSize.Height())) }; + colorInfo.colors = textOverlayTheme->GetAiMenuFontGradientColors(); + colorInfo.scalars = textOverlayTheme->GetAiMenuFontGradientScalars(); + textProperty->UpdateFontForegroudGradiantColor(colorInfo); + } textProperty->UpdateWordBreak(theme->GetWordBreak()); textNode->MountToParent(parent); textNode->MarkModifyDone(); diff --git a/frameworks/core/components_ng/pattern/menu/menu_view.h b/frameworks/core/components_ng/pattern/menu/menu_view.h index 0afcd031a6e02a0b5f6b251c4adfc13268ec559f..13bc062677c2b00ede018e3383630bb321f79cf9 100644 --- a/frameworks/core/components_ng/pattern/menu/menu_view.h +++ b/frameworks/core/components_ng/pattern/menu/menu_view.h @@ -29,8 +29,11 @@ namespace OHOS::Ace::NG { class ACE_EXPORT MenuView { struct OptionValueInfo { - std::string value; + bool optionsHasIcon = false; + + std::string content; bool isPasteOption = false; + bool isAIMenuOption = false; }; public: @@ -62,8 +65,8 @@ public: static void CalcHoverScaleInfo(const RefPtr& menuNode); static RefPtr CreateIcon(const std::string& icon, const RefPtr& parent, const RefPtr& child = nullptr); - static RefPtr CreateText( - const std::string& value, const RefPtr& parent, bool autoWrapFlag = false); + static RefPtr CreateText(const std::string& value, const RefPtr& parent, + bool autoWrapFlag = false, bool isAIMenuOption = false); static void CreatePasteButton(bool optionsHasIcon, const RefPtr& option, const RefPtr& row, const std::function& onClickFunc, const std::string& icon = ""); static RefPtr CreateSelectOption(const SelectParam& param, int32_t index, bool autoWrapFlag = false); @@ -88,10 +91,10 @@ private: static void CustomPreviewParentNodeCreate(const RefPtr& stackNode, const RefPtr& posNode, const RefPtr& wrapperNode, const RefPtr& previewNode); static RefPtr Create(int32_t index); - static RefPtr CreateMenuOption(bool optionsHasIcon, const OptionValueInfo& value, + static RefPtr CreateMenuOption(const OptionValueInfo& value, const std::function& onClickFunc, int32_t index, const std::string& icon = ""); static RefPtr CreateMenuOption(bool optionsHasIcon, std::vector& params, int32_t index); - static void CreateOption(bool optionsHasIcon, const std::string& value, const std::string& icon, + static void CreateOption(const OptionValueInfo& value, const std::string& icon, const RefPtr& row, const RefPtr& option, const std::function& onClickFunc); static void CreateOption(bool optionsHasIcon, std::vector& params, int32_t index, const RefPtr& row, const RefPtr& option); diff --git a/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.cpp b/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.cpp index 3de6e2232be5fded57e4b23fbc73e5bc61ec3673..f9b1078597d5ad9ef1397745055fd7175b7f87b6 100644 --- a/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.cpp +++ b/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.cpp @@ -9950,7 +9950,7 @@ void RichEditorPattern::HandleOnCameraInput() #endif } -bool RichEditorPattern::CanStartAITask() +bool RichEditorPattern::CanStartAITask() const { return TextPattern::CanStartAITask() && !isEditing_ && !spans_.empty(); } diff --git a/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.h b/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.h index 7523fa4fbf497b66ae4f3b8ff9055a4298bbfe6c..c4b4e589994e23ae8e60999df32e0be109bbd009 100644 --- a/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.h +++ b/frameworks/core/components_ng/pattern/rich_editor/rich_editor_pattern.h @@ -1247,7 +1247,7 @@ public: } protected: - bool CanStartAITask() override; + bool CanStartAITask() const override; std::vector GetSelectedRects(int32_t start, int32_t end) override; PointF GetTextOffset(const Offset& localLocation, const RectF& contentRect) override; std::pair GetStartAndEnd(int32_t start, const RefPtr& spanItem) override; diff --git a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.cpp b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.cpp index b772b5c165edffee6c0baf536fdb95a4afb58268..736f433cbe18473c3265ca7cb6a2f1d476520a01 100644 --- a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.cpp +++ b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include "ui/base/utils/utils.h" #include "base/geometry/dimension.h" #include "base/geometry/ng/offset_t.h" @@ -56,6 +58,7 @@ #ifdef ENABLE_ROSEN_BACKEND #include "core/components/custom_paint/rosen_render_custom_paint.h" #endif +#include "frameworks/core/components_ng/pattern/text/span/mutable_span_string.h" namespace OHOS::Ace::NG { namespace { @@ -68,6 +71,7 @@ constexpr int32_t OPTION_INDEX_SEARCH = 5; constexpr int32_t OPTION_INDEX_SHARE = 6; constexpr int32_t OPTION_INDEX_CAMERA_INPUT = 7; constexpr int32_t OPTION_INDEX_AI_WRITE = 8; +constexpr int32_t OPTION_INDEX_AI_MENU = 9; constexpr int32_t ANIMATION_DURATION1 = 350; constexpr int32_t ANIMATION_DURATION2 = 150; constexpr int32_t SYMBOL_ANIMATION_DELAY = 50; @@ -90,8 +94,28 @@ const std::string OH_DEFAULT_SEARCH = "OH_DEFAULT_SEARCH"; const std::string OH_DEFAULT_SHARE = "OH_DEFAULT_SHARE"; const std::string OH_DEFAULT_CAMERA_INPUT = "OH_DEFAULT_CAMERA_INPUT"; const std::string OH_DEFAULT_AI_WRITE = "OH_DEFAULT_AI_WRITE"; +const std::string OH_DEFAULT_AI_MENU_PHONE = "OH_DEFAULT_AI_MENU_PHONE"; +const std::string OH_DEFAULT_AI_MENU_URL = "OH_DEFAULT_AI_MENU_URL"; +const std::string OH_DEFAULT_AI_MENU_EMAIL = "OH_DEFAULT_AI_MENU_EMAIL"; +const std::string OH_DEFAULT_AI_MENU_ADDRESS = "OH_DEFAULT_AI_MENU_ADDRESS"; +const std::string OH_DEFAULT_AI_MENU_DATETIME = "OH_DEFAULT_AI_MENU_DATETIME"; + const std::string OH_DEFAULT_COLLABORATION_SERVICE = "OH_DEFAULT_COLLABORATION_SERVICE"; +std::unordered_map AI_TYPE_ID_MAP = { + { TextDataDetectType::PHONE_NUMBER, OH_DEFAULT_AI_MENU_PHONE }, + { TextDataDetectType::URL, OH_DEFAULT_AI_MENU_URL }, + { TextDataDetectType::EMAIL, OH_DEFAULT_AI_MENU_EMAIL }, + { TextDataDetectType::ADDRESS, OH_DEFAULT_AI_MENU_ADDRESS }, + { TextDataDetectType::DATE_TIME, OH_DEFAULT_AI_MENU_DATETIME } +}; + +bool IsAIMenuOption(const std::string& id) +{ + return id == OH_DEFAULT_AI_MENU_PHONE || id == OH_DEFAULT_AI_MENU_URL || id == OH_DEFAULT_AI_MENU_EMAIL || + id == OH_DEFAULT_AI_MENU_ADDRESS || id == OH_DEFAULT_AI_MENU_DATETIME; +} + const std::unordered_map> isMenuItemEnabledFuncMap = { { OH_DEFAULT_CUT, [](const SelectMenuInfo& info){ return info.showCut; } }, { OH_DEFAULT_COPY, [](const SelectMenuInfo& info){ return info.showCopy; } }, @@ -127,9 +151,29 @@ const std::unordered_map& textOverlayTheme) { return textOverlayTheme->GetTranslateSymbolId();} + }, + { OH_DEFAULT_AI_MENU_PHONE, [](const RefPtr& textOverlayTheme) + { return textOverlayTheme->GetAIWriteSymbolId();} // todo: replace after resource ready + }, + { OH_DEFAULT_AI_MENU_URL, [](const RefPtr& textOverlayTheme) + { return textOverlayTheme->GetAIWriteSymbolId();} // todo: replace after resource ready + }, + { OH_DEFAULT_AI_MENU_EMAIL, [](const RefPtr& textOverlayTheme) + { return textOverlayTheme->GetAIWriteSymbolId();} // todo: replace after resource ready + }, + { OH_DEFAULT_AI_MENU_ADDRESS, [](const RefPtr& textOverlayTheme) + { return textOverlayTheme->GetAIWriteSymbolId();} // todo: replace after resource ready + }, + { OH_DEFAULT_AI_MENU_DATETIME, [](const RefPtr& textOverlayTheme) + { return textOverlayTheme->GetAIWriteSymbolId();} // todo: replace after resource ready } }; +enum class SelectOverlayMenuButtonType { + NORMAL, + AIBUTTON +}; + void SetResponseRegion(RefPtr& node) { auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck(); @@ -151,22 +195,6 @@ void SetResponseRegion(RefPtr& node) gestureHub->SetResponseRegion(vector); } -float MeasureTextWidth(const TextStyle& textStyle, const std::string& text) -{ -#ifdef ENABLE_ROSEN_BACKEND - MeasureContext content; - content.textContent = text; - content.fontSize = textStyle.GetFontSize(); - auto fontweight = StringUtils::FontWeightToString(textStyle.GetFontWeight()); - content.fontWeight = fontweight; - content.isReturnActualWidth = true; - content.maxlines = 1; - return std::max(static_cast(RosenRenderCustomPaint::MeasureTextSizeInner(content).Width()), 0.0f); -#else - return 0.0f; -#endif -} - #ifdef OHOS_PLATFORM RefPtr BuildPasteButton( const std::function& callback, int32_t overlayId, float& buttonWidth, bool isSelectAll = false) @@ -192,7 +220,7 @@ RefPtr BuildPasteButton( buttonLayoutProperty->UpdateBackgroundRightPadding(padding.Right()); std::string buttonContent; PasteButtonModelNG::GetInstance()->GetTextResource(descriptionId, buttonContent); - buttonWidth = MeasureTextWidth(textStyle, buttonContent); + buttonWidth = MeasureUtil::MeasureTextWidth(textStyle, buttonContent); buttonWidth = buttonWidth + padding.Left().ConvertToPx() + padding.Right().ConvertToPx(); if (GreatOrEqual(pipeline->GetFontScale(), AGING_MIN_SCALE)) { buttonLayoutProperty->UpdateUserDefinedIdealSize({ CalcLength(buttonWidth), std::nullopt }); @@ -254,36 +282,45 @@ RefPtr CreatePasteButtonForCreateMenu( } #endif -RefPtr BuildButton(const std::string& data, const std::function& callback, int32_t overlayId, - float& buttonWidth, bool isSelectAll = false) +bool PrepareButtonTextProp(RefPtr& textLayoutProperty, + bool hasCallback, float& buttonWidth, const std::string& data, + SelectOverlayMenuButtonType buttonType = SelectOverlayMenuButtonType::NORMAL) { - auto button = FrameNode::GetOrCreateFrameNode("SelectMenuButton", ElementRegister::GetInstance()->MakeUniqueId(), - []() { return AceType::MakeRefPtr(); }); - auto text = FrameNode::GetOrCreateFrameNode("SelectMenuButtonText", ElementRegister::GetInstance()->MakeUniqueId(), - []() { return AceType::MakeRefPtr(); }); - auto textLayoutProperty = text->GetLayoutProperty(); - CHECK_NULL_RETURN(textLayoutProperty, button); - textLayoutProperty->UpdateContent(data); - text->MountToParent(button); auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck(); - CHECK_NULL_RETURN(pipeline, button); + CHECK_NULL_RETURN(pipeline, false); auto textOverlayTheme = pipeline->GetTheme(); - CHECK_NULL_RETURN(textOverlayTheme, button); + CHECK_NULL_RETURN(textOverlayTheme, false); auto textStyle = textOverlayTheme->GetMenuButtonTextStyle(); textLayoutProperty->UpdateFontSize(textStyle.GetFontSize()); textLayoutProperty->UpdateFontWeight(textStyle.GetFontWeight()); textLayoutProperty->UpdateMaxLines(1); - if (callback) { + if (hasCallback) { textLayoutProperty->UpdateTextColor(textStyle.GetTextColor()); } else { textLayoutProperty->UpdateTextColor( textStyle.GetTextColor().BlendOpacity(textOverlayTheme->GetAlphaDisabled())); } - text->MarkModifyDone(); + auto textSize = MeasureUtil::MeasureTextSize(textStyle, data); + buttonWidth = std::max(static_cast(textSize.Width()), 0.0f); + if (buttonType == SelectOverlayMenuButtonType::AIBUTTON) { + FontForegroudGradiantColor colorInfo; + colorInfo.points = { NG::PointF(0, 0), + NG::PointF(static_cast(textSize.Width()), static_cast(textSize.Height())) }; + colorInfo.colors = textOverlayTheme->GetAiMenuFontGradientColors(); + colorInfo.scalars = textOverlayTheme->GetAiMenuFontGradientScalars(); + textLayoutProperty->UpdateFontForegroudGradiantColor(colorInfo); + } + return true; +} - auto buttonLayoutProperty = button->GetLayoutProperty(); - CHECK_NULL_RETURN(buttonLayoutProperty, button); - if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN)) { +bool PrepareButtonProp(RefPtr& buttonLayoutProperty, + float& buttonWidth, const RefPtr& buttonNode) +{ + auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck(); + CHECK_NULL_RETURN(pipeline, false); + auto textOverlayTheme = pipeline->GetTheme(); + CHECK_NULL_RETURN(textOverlayTheme, false); + if (buttonNode->GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN)) { buttonLayoutProperty->UpdateType(ButtonType::ROUNDED_RECTANGLE); } else { buttonLayoutProperty->UpdateType(ButtonType::CAPSULE); @@ -294,7 +331,6 @@ RefPtr BuildButton(const std::string& data, const std::functionUpdatePadding({ left, right, top, bottom, std::nullopt, std::nullopt }); - buttonWidth = MeasureTextWidth(textStyle, data); // Calculate the width of default option include button padding. buttonWidth = buttonWidth + padding.Left().ConvertToPx() + padding.Right().ConvertToPx(); if (GreatOrEqual(pipeline->GetFontScale(), AGING_MIN_SCALE)) { @@ -304,11 +340,41 @@ RefPtr BuildButton(const std::string& data, const std::functionGetMenuButtonHeight()) }); } buttonLayoutProperty->UpdateFlexShrink(0); + return true; +} + +RefPtr BuildButton(const std::string& data, std::variant, + const std::function> callbackVariant, int32_t overlayId, float& buttonWidth, + SelectOverlayMenuButtonType buttonType = SelectOverlayMenuButtonType::NORMAL) +{ + auto button = FrameNode::GetOrCreateFrameNode("SelectMenuButton", ElementRegister::GetInstance()->MakeUniqueId(), + []() { return AceType::MakeRefPtr(); }); + auto text = FrameNode::GetOrCreateFrameNode("SelectMenuButtonText", ElementRegister::GetInstance()->MakeUniqueId(), + []() { return AceType::MakeRefPtr(); }); + auto textLayoutProperty = text->GetLayoutProperty(); + CHECK_NULL_RETURN(textLayoutProperty, button); + textLayoutProperty->UpdateContent(data); + text->MountToParent(button); + auto hasCallback = std::holds_alternative>(callbackVariant) || + std::holds_alternative>(callbackVariant); + CHECK_NULL_RETURN(hasCallback, button); + + auto retPrepare = PrepareButtonTextProp(textLayoutProperty, hasCallback, buttonWidth, data, buttonType); + CHECK_NE_RETURN(retPrepare, true, button); + text->MarkModifyDone(); + + auto buttonLayoutProperty = button->GetLayoutProperty(); + CHECK_NULL_RETURN(buttonLayoutProperty, button); + retPrepare = PrepareButtonProp(buttonLayoutProperty, buttonWidth, button); + CHECK_NE_RETURN(retPrepare, true, button); + button->GetRenderContext()->UpdateBackgroundColor(Color::TRANSPARENT); - if (callback) { + if (hasCallback) { button->GetOrCreateGestureEventHub()->SetUserOnClick( - [callback, overlayId, id = Container::CurrentIdSafelyWithCheck()](GestureEvent& /*info*/) { + [lableInfo = data, callbackVariant, overlayId, + id = Container::CurrentIdSafelyWithCheck(), buttonType = buttonType] + (GestureEvent& /* info */) { ContainerScope scope(id); auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck(); CHECK_NULL_VOID(pipeline); @@ -320,8 +386,10 @@ RefPtr BuildButton(const std::string& data, const std::functionGetIsExtensionMenu(); CHECK_NULL_VOID(!isExtensionMenu); - if (callback) { - callback(); + if (std::holds_alternative>(callbackVariant)) { + std::get>(callbackVariant)(); + } else if (std::holds_alternative>(callbackVariant)) { + std::get>(callbackVariant)(lableInfo); } }); } else { @@ -378,7 +446,7 @@ RefPtr BuildButton(const MenuOptionsParam& menuOption, int32_t overla textLayoutProperty->UpdateFontWeight(textStyle.GetFontWeight()); text->MarkModifyDone(); // Calculate the width of entension option include button padding. - contentWidth = MeasureTextWidth(textStyle, data); + contentWidth = MeasureUtil::MeasureTextWidth(textStyle, data); const auto& padding = textOverlayTheme->GetMenuButtonPadding(); auto left = CalcLength(padding.Left().ConvertToPx()); auto right = CalcLength(padding.Right().ConvertToPx()); @@ -439,15 +507,6 @@ void BindCreateMenuItemClickEvent(const RefPtr& button, const MenuOpt }); } -void UpdateTextProperty(const TextStyle& textStyle, const RefPtr& textLayoutProperty) -{ - textLayoutProperty->UpdateFontSize(textStyle.GetFontSize()); - textLayoutProperty->UpdateTextColor(textStyle.GetTextColor()); - textLayoutProperty->UpdateFontWeight(textStyle.GetFontWeight()); - textLayoutProperty->UpdateMaxLines(1); - textLayoutProperty->UpdateWordBreak(WordBreak::BREAK_ALL); -} - RefPtr BuildCreateMenuItemButton(const MenuOptionsParam& menuOptionsParam, const std::function& systemCallback, const OnMenuItemCallback& menuItemCallback, int32_t overlayId, float& remainderWidth) @@ -458,17 +517,7 @@ RefPtr BuildCreateMenuItemButton(const MenuOptionsParam& menuOptionsP CHECK_NULL_RETURN(textOverlayTheme, nullptr); auto textStyle = textOverlayTheme->GetMenuButtonTextStyle(); auto data = menuOptionsParam.content.value_or(""); - auto contentWidth = MeasureTextWidth(textStyle, data); - // Calculate the width of entension option include button padding. - const auto& padding = textOverlayTheme->GetMenuButtonPadding(); - auto left = CalcLength(padding.Left().ConvertToPx()); - auto right = CalcLength(padding.Right().ConvertToPx()); - auto top = CalcLength(padding.Top().ConvertToPx()); - auto bottom = CalcLength(padding.Bottom().ConvertToPx()); - contentWidth = contentWidth + padding.Left().ConvertToPx() + padding.Right().ConvertToPx(); - auto isOverWidth = GreatOrEqual(remainderWidth, contentWidth); - CHECK_NULL_RETURN(isOverWidth || menuOptionsParam.isFirstOption, nullptr); - contentWidth = std::min(contentWidth, remainderWidth); + auto contentWidth = 0.0f; auto button = FrameNode::GetOrCreateFrameNode("SelectMenuButton", ElementRegister::GetInstance()->MakeUniqueId(), []() { return AceType::MakeRefPtr(); }); @@ -478,9 +527,23 @@ RefPtr BuildCreateMenuItemButton(const MenuOptionsParam& menuOptionsP auto textLayoutProperty = text->GetLayoutProperty(); CHECK_NULL_RETURN(textLayoutProperty, button); textLayoutProperty->UpdateContent(data); - UpdateTextProperty(textStyle, textLayoutProperty); + auto buttonType = IsAIMenuOption(menuOptionsParam.id) ? + SelectOverlayMenuButtonType::AIBUTTON : SelectOverlayMenuButtonType::NORMAL; + PrepareButtonTextProp(textLayoutProperty, true, contentWidth, data, buttonType); + textLayoutProperty->UpdateWordBreak(WordBreak::BREAK_ALL); text->MountToParent(button); + // Calculate the width of entension option include button padding. + const auto& padding = textOverlayTheme->GetMenuButtonPadding(); + auto left = CalcLength(padding.Left().ConvertToPx()); + auto right = CalcLength(padding.Right().ConvertToPx()); + auto top = CalcLength(padding.Top().ConvertToPx()); + auto bottom = CalcLength(padding.Bottom().ConvertToPx()); + contentWidth = contentWidth + padding.Left().ConvertToPx() + padding.Right().ConvertToPx(); + auto isOverWidth = GreatOrEqual(remainderWidth, contentWidth); + CHECK_NULL_RETURN(isOverWidth || menuOptionsParam.isFirstOption, nullptr); + contentWidth = std::min(contentWidth, remainderWidth); + if (!isOverWidth && menuOptionsParam.isFirstOption) { textLayoutProperty->UpdateTextOverflow(TextOverflow::ELLIPSIS); } @@ -639,6 +702,23 @@ std::function GetMenuCallbackWithContainerId(std::function callb return optionCallback; } +std::function GetMenuCallbackWithContainerId( + std::function callback, const std::string& info) +{ + auto optionCallback = [func = std::move(callback), mainId = Container::CurrentIdSafelyWithCheck(), info]() { + ContainerScope scope(mainId); + func(info); + }; + return optionCallback; +} + +std::function ConvertToVoidFunction(std::function funcWithArg, const std::string& arg) +{ + return [funcWithArg, arg]() { + funcWithArg(arg); + }; +} + std::vector GetOptionsParams(const std::shared_ptr& info) { std::vector params; @@ -666,10 +746,18 @@ std::vector GetOptionsParams(const std::shared_ptrGetSearchLabel(), GetMenuCallbackWithContainerId(info->menuCallback.onSearch), "", info->menuInfo.showSearch); } + if (info->menuInfo.aiMenuOptionType != TextDataDetectType::INVALID) { + auto inheritFunc = ConvertToVoidFunction(info->menuCallback.onAIMenuOption, "From_Right_Click"); + params.emplace_back(theme->GetAiMenuOptionName(info->menuInfo.aiMenuOptionType), + GetMenuCallbackWithContainerId(inheritFunc), "", true); + params.back().isAIMenuOption = true; + } + return params; } -std::unordered_map> GetSystemCallback(const std::shared_ptr& info) +std::unordered_map> GetSystemCallback( + const std::shared_ptr& info, bool isCallbackWithParam = false) { CHECK_NULL_RETURN(info, {}); std::unordered_map> systemCallback = { @@ -679,7 +767,17 @@ std::unordered_map> GetSystemCallback(const s { OH_DEFAULT_SEARCH, info->menuCallback.onSearch }, { OH_DEFAULT_SHARE, info->menuCallback.onShare }, { OH_DEFAULT_CAMERA_INPUT, info->menuCallback.onCameraInput }, - { OH_DEFAULT_AI_WRITE, info->menuCallback.onAIWrite } + { OH_DEFAULT_AI_WRITE, info->menuCallback.onAIWrite }, + { OH_DEFAULT_AI_MENU_PHONE, ConvertToVoidFunction( + info->menuCallback.onAIMenuOption, OH_DEFAULT_AI_MENU_PHONE) }, + { OH_DEFAULT_AI_MENU_URL, ConvertToVoidFunction( + info->menuCallback.onAIMenuOption, OH_DEFAULT_AI_MENU_URL) }, + { OH_DEFAULT_AI_MENU_EMAIL, ConvertToVoidFunction( + info->menuCallback.onAIMenuOption, OH_DEFAULT_AI_MENU_EMAIL) }, + { OH_DEFAULT_AI_MENU_ADDRESS, ConvertToVoidFunction( + info->menuCallback.onAIMenuOption, OH_DEFAULT_AI_MENU_ADDRESS) }, + { OH_DEFAULT_AI_MENU_DATETIME, ConvertToVoidFunction( + info->menuCallback.onAIMenuOption, OH_DEFAULT_AI_MENU_DATETIME) }, }; return systemCallback; } @@ -724,7 +822,8 @@ std::string GetSystemIconPath(const std::string& id, const std::string& iconPath return iconPath; } -std::string GetItemContent(const std::string& id, const std::string& content) +std::string GetItemContent(const std::string& id, const std::string& content, + const std::shared_ptr& info = nullptr) { auto pipeline = PipelineContext::GetCurrentContextSafelyWithCheck(); CHECK_NULL_RETURN(pipeline, content); @@ -757,6 +856,10 @@ std::string GetItemContent(const std::string& id, const std::string& content) if (id == OH_DEFAULT_CAMERA_INPUT) { return textOverlayTheme->GetCameraInput(); } + if (IsAIMenuOption(id)) { + CHECK_NULL_RETURN(info, content); + return textOverlayTheme->GetAiMenuOptionName(info->menuInfo.aiMenuOptionType); + } return content; } @@ -816,13 +919,15 @@ std::vector GetCreateMenuOptionsParams(const std::vector CreateMenuTextNode(const std::string& value, const RefPtr& parent) +RefPtr CreateMenuTextNode(const std::string& value, const RefPtr& parent, + bool isAIMenuEnabled = false) { auto textId = ElementRegister::GetInstance()->MakeUniqueId(); auto textNode = FrameNode::CreateFrameNode(V2::TEXT_ETS_TAG, textId, AceType::MakeRefPtr()); @@ -838,6 +943,19 @@ RefPtr CreateMenuTextNode(const std::string& value, const RefPtrUpdateFontSize(theme->GetMenuFontSize()); textProperty->UpdateFontWeight(FontWeight::REGULAR); textProperty->UpdateTextColor(Color::TRANSPARENT); + auto textOverlayTheme = pipeline->GetTheme(); + if (isAIMenuEnabled == true && textOverlayTheme) { + TextStyle textStyle; + textStyle.SetFontSize(theme->GetMenuFontSize()); + textStyle.SetFontWeight(FontWeight::REGULAR); + auto textSize = MeasureUtil::MeasureTextSize(textStyle, value); + FontForegroudGradiantColor colorInfo; + colorInfo.points = { NG::PointF(0, 0), + NG::PointF(static_cast(textSize.Width()), static_cast(textSize.Height())) }; + colorInfo.colors = textOverlayTheme->GetAiMenuFontGradientColors(); + colorInfo.scalars = textOverlayTheme->GetAiMenuFontGradientScalars(); + textProperty->UpdateFontForegroudGradiantColor(colorInfo); + } auto textRenderContext = textNode->GetRenderContext(); CHECK_NULL_RETURN(textRenderContext, nullptr); textRenderContext->UpdateForegroundColor(theme->GetMenuFontColor()); @@ -915,7 +1033,7 @@ void SetupMenuItemChildrenAndFocus(const RefPtr& menuItem, const std: leftRowLayoutProps->UpdateMainAxisAlign(FlexAlign::FLEX_START); leftRowLayoutProps->UpdateCrossAxisAlign(FlexAlign::CENTER); leftRowLayoutProps->UpdateSpace(theme->GetIconContentPadding()); - auto leftTextNode = CreateMenuTextNode(content, leftRow); + auto leftTextNode = CreateMenuTextNode(content, leftRow, param.isAIMenuOption); CHECK_NULL_VOID(leftTextNode); leftRow->MountToParent(menuItem); auto rightRow = FrameNode::CreateFrameNode(V2::ROW_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), @@ -926,7 +1044,7 @@ void SetupMenuItemChildrenAndFocus(const RefPtr& menuItem, const std: rightRowLayoutProps->UpdateMainAxisAlign(FlexAlign::CENTER); rightRowLayoutProps->UpdateCrossAxisAlign(FlexAlign::CENTER); rightRowLayoutProps->UpdateSpace(theme->GetIconContentPadding()); - auto rightTextNode = CreateMenuTextNode(labelInfo, rightRow); + auto rightTextNode = CreateMenuTextNode(labelInfo, rightRow, param.isAIMenuOption); CHECK_NULL_VOID(rightTextNode); rightRow->MountToParent(menuItem); auto leftTextRenderContext = leftTextNode->GetRenderContext(); @@ -1684,6 +1802,16 @@ void SelectOverlayNode::GetFlexibleOptionsParams( params.emplace_back(iconName, iconPath, GetMenuCallbackWithContainerId(info->menuCallback.onAIWrite), GetSymbolFunc(OH_DEFAULT_AI_WRITE)); } + if (!isShowInDefaultMenu_[OPTION_INDEX_AI_MENU]) { + // use AI write, replace correct icon and symbolFunc according to needed + auto iconPath = iconTheme ? iconTheme->GetIconPath(InternalResource::ResourceId::IC_AI_WRITE_SVG) : ""; + std::function)> symbolFunc = GetSymbolFunc(OH_DEFAULT_AI_WRITE); + auto iconName = textOverlayTheme ? + textOverlayTheme->GetAiMenuOptionName(info->menuInfo.aiMenuOptionType) : ""; + params.emplace_back(iconName, iconPath, GetMenuCallbackWithContainerId( + info->menuCallback.onAIMenuOption, iconName), symbolFunc); + params.back().isAIMenuOption = true; + } } void SelectOverlayNode::addMenuOptionItemsParams( @@ -1847,6 +1975,8 @@ void SelectOverlayNode::AddCreateMenuExtensionMenuParams(const std::vectorGetSearchLabel()); ShowCamera(maxWidth, allocatedSize, info, theme->GetCameraInput()); ShowAIWrite(maxWidth, allocatedSize, info, theme->GetAIWrite()); + ShowAIMenuOptions(maxWidth, allocatedSize, info, theme->GetAiMenuOptionName(info->menuInfo.aiMenuOptionType)); if (isDefaultBtnOverMaxWidth_) { isDefaultBtnOverMaxWidth_ = false; return true; @@ -2075,7 +2206,7 @@ void SelectOverlayNode::ShowCopyAll( if (info->menuInfo.showCopyAll) { CHECK_EQUAL_VOID(isDefaultBtnOverMaxWidth_, true); float buttonWidth = 0.0f; - auto button = BuildButton(label, info->menuCallback.onSelectAll, GetId(), buttonWidth, true); + auto button = BuildButton(label, info->menuCallback.onSelectAll, GetId(), buttonWidth); CHECK_NULL_VOID(button); if (GreatOrEqual(maxWidth - allocatedSize, buttonWidth)) { button->MountToParent(selectMenuInner_); @@ -2171,7 +2302,7 @@ void SelectOverlayNode::ShowAIWrite( if (info->menuInfo.showAIWrite) { CHECK_EQUAL_VOID(isDefaultBtnOverMaxWidth_, true); float buttonWidth = 0.0f; - auto button = BuildButton(label, info->menuCallback.onAIWrite, GetId(), buttonWidth, true); + auto button = BuildButton(label, info->menuCallback.onAIWrite, GetId(), buttonWidth); CHECK_NULL_VOID(button); if (GreatOrEqual(maxWidth - allocatedSize, buttonWidth)) { button->MountToParent(selectMenuInner_); @@ -2192,7 +2323,7 @@ void SelectOverlayNode::ShowCamera( if (info->menuInfo.showCameraInput) { CHECK_EQUAL_VOID(isDefaultBtnOverMaxWidth_, true); float buttonWidth = 0.0f; - auto button = BuildButton(label, info->menuCallback.onCameraInput, GetId(), buttonWidth, false); + auto button = BuildButton(label, info->menuCallback.onCameraInput, GetId(), buttonWidth); CHECK_NULL_VOID(button); if (GreatOrEqual(maxWidth - allocatedSize, buttonWidth)) { button->MountToParent(selectMenuInner_); @@ -2207,6 +2338,28 @@ void SelectOverlayNode::ShowCamera( } } +void SelectOverlayNode::ShowAIMenuOptions( + float maxWidth, float& allocatedSize, std::shared_ptr& info, const std::string& label) +{ + if (info->menuInfo.aiMenuOptionType != TextDataDetectType::INVALID) { + CHECK_EQUAL_VOID(isDefaultBtnOverMaxWidth_, true); + float buttonWidth = 0.0f; + auto button = BuildButton(label, info->menuCallback.onAIMenuOption, GetId(), buttonWidth, + SelectOverlayMenuButtonType::AIBUTTON); + CHECK_NULL_VOID(button); + if (GreatOrEqual(maxWidth - allocatedSize, buttonWidth)) { + button->MountToParent(selectMenuInner_); + allocatedSize += buttonWidth; + isShowInDefaultMenu_[OPTION_INDEX_AI_MENU] = true; + } else { + button.Reset(); + isDefaultBtnOverMaxWidth_ = true; + } + } else { + isShowInDefaultMenu_[OPTION_INDEX_AI_MENU] = true; + } +} + bool SelectOverlayNode::IsShowOnTargetAPIVersion() { if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) && @@ -2299,7 +2452,8 @@ int32_t SelectOverlayNode::AddCreateMenuItems( #endif } else { item.isFirstOption = index == -1; - item.content = GetItemContent(item.id, item.content.value_or("")); + item.content = GetItemContent(item.id, item.content.value_or(""), info); + button = BuildCreateMenuItemButton(item, callback != systemCallback.end() ? callback->second : nullptr, info->onCreateCallback, id, remainderWidth); if (button) { @@ -2373,6 +2527,16 @@ const std::vector SelectOverlayNode::GetSystemMenuItemParams( param.menuOptionsParam = menuOptionsParam; systemItemParams.emplace_back(param); } + if (info->menuInfo.aiMenuOptionType != TextDataDetectType::INVALID) { // editMenuOptions route + MenuItemParam param; + MenuOptionsParam menuOptionsParam; + auto findIter = AI_TYPE_ID_MAP.find(info->menuInfo.aiMenuOptionType); + CHECK_EQUAL_RETURN(findIter, AI_TYPE_ID_MAP.end(), systemItemParams); + menuOptionsParam.id = findIter->second; + menuOptionsParam.content = theme->GetAiMenuOptionName(info->menuInfo.aiMenuOptionType); + param.menuOptionsParam = menuOptionsParam; + systemItemParams.emplace_back(param); + } return systemItemParams; } @@ -2549,12 +2713,12 @@ void SelectOverlayNode::SetSelectMenuInnerSize() void SelectOverlayNode::LandscapeMenuAddMenuOptions(const std::vector& menuOptionItems, bool isDefaultOverMaxWidth, float maxWidth, float allocatedSize, int32_t& extensionOptionStartIndex) { + if (isDefaultOverMaxWidth) { + return; + } auto itemNum = -1; for (auto item : menuOptionItems) { itemNum++; - if (isDefaultOverMaxWidth) { - break; - } float extensionOptionWidth = 0.0f; auto button = BuildButton(item, GetId(), extensionOptionWidth); CHECK_NULL_VOID(button); diff --git a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.h b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.h index 01c122977d6e8caae9e8e60f3037293c328ab41d..51695dc0a106d69c815e74807a0c6ded50d454ed 100644 --- a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.h +++ b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_node.h @@ -138,6 +138,8 @@ private: float maxWidth, float& allocatedSize, std::shared_ptr& info, const std::string& label); void ShowAIWrite( float maxWidth, float& allocatedSize, std::shared_ptr& info, const std::string& label); + void ShowAIMenuOptions( + float maxWidth, float& allocatedSize, std::shared_ptr& info, const std::string& label); bool IsShowOnTargetAPIVersion(); bool IsShowTranslateOnTargetAPIVersion(); std::function GetDefaultOptionCallback(); @@ -240,7 +242,7 @@ private: bool isExtensionMenu_ = false; // Label whether the menu default button needs to appear within the extended menu - bool isShowInDefaultMenu_[9] = { false }; + bool isShowInDefaultMenu_[10] = { false }; // OPTION_INDEX_AI_MENU + 1 bool isDefaultBtnOverMaxWidth_ = false; diff --git a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_property.h b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_property.h index 3d1397471cd6c9bc6aeecd3f0bcab6f939860e40..b97018f67f00c830ecb9c2f84d15f7de7db73681 100644 --- a/frameworks/core/components_ng/pattern/select_overlay/select_overlay_property.h +++ b/frameworks/core/components_ng/pattern/select_overlay/select_overlay_property.h @@ -131,6 +131,7 @@ inline constexpr SelectOverlayDirtyFlag DIRTY_COPY_ALL_ITEM = 1 << 4; inline constexpr SelectOverlayDirtyFlag DIRTY_SELECT_TEXT = 1 << 5; inline constexpr SelectOverlayDirtyFlag DIRTY_VIEWPORT = 1 << 6; inline constexpr SelectOverlayDirtyFlag DIRTY_HANDLE_COLOR_FLAG = 1 << 7; +inline constexpr SelectOverlayDirtyFlag DIRTY_AI_MENU_ITEM = 1 << 8; inline constexpr SelectOverlayDirtyFlag DIRTY_DOUBLE_HANDLE = DIRTY_FIRST_HANDLE | DIRTY_SECOND_HANDLE; inline constexpr SelectOverlayDirtyFlag DIRTY_ALL = DIRTY_DOUBLE_HANDLE | DIRTY_ALL_MENU_ITEM | DIRTY_SELECT_AREA | DIRTY_SELECT_TEXT | DIRTY_VIEWPORT; @@ -149,7 +150,8 @@ enum class OptionMenuActionId { CAMERA_INPUT, AI_WRITE, APPEAR, - DISAPPEAR + DISAPPEAR, + AI_MENU_OPTION }; enum class CloseReason { CLOSE_REASON_NORMAL = 1, @@ -191,6 +193,7 @@ struct SelectMenuInfo { bool showSearch = false; bool showCameraInput = false; bool showAIWrite = false; + OHOS::Ace::TextDataDetectType aiMenuOptionType = TextDataDetectType::INVALID; std::optional menuOffset; OptionMenuType menuType = OptionMenuType::TOUCH_MENU; @@ -205,10 +208,10 @@ struct SelectMenuInfo { return true; } return !((showCopy == info.showCopy) && (showPaste == info.showPaste) && (showCopyAll == info.showCopyAll) && - (showTranslate == info.showTranslate) && - (showCut == info.showCut) && (showSearch == info.showSearch) && (showShare == info.showShare) && - (showCameraInput == info.showCameraInput) && - (showAIWrite == info.showAIWrite)); + (showCut == info.showCut) && (showTranslate == info.showTranslate) && + (showSearch == info.showSearch) && (showShare == info.showShare) && + (showCameraInput == info.showCameraInput) && (showAIWrite == info.showAIWrite) && + (aiMenuOptionType == info.aiMenuOptionType)); } std::string ToString() const @@ -225,6 +228,7 @@ struct SelectMenuInfo { JSON_STRING_PUT_BOOL(jsonValue, showSearch); JSON_STRING_PUT_BOOL(jsonValue, showShare); JSON_STRING_PUT_BOOL(jsonValue, showCameraInput); + JSON_STRING_PUT_INT(jsonValue, aiMenuOptionType); return jsonValue->ToString(); } }; @@ -239,6 +243,7 @@ struct SelectMenuCallback { std::function onShare; std::function onCameraInput; std::function onAIWrite; + std::function onAIMenuOption; std::function onAppear; std::function onDisappear; diff --git a/frameworks/core/components_ng/pattern/text/text_layout_property.h b/frameworks/core/components_ng/pattern/text/text_layout_property.h index 9144c82a45dc534f3a19644ddda9ab76ce4cb910..f4d1b8ed7333f5b91dc6b793b012ef04a3ba8777 100644 --- a/frameworks/core/components_ng/pattern/text/text_layout_property.h +++ b/frameworks/core/components_ng/pattern/text/text_layout_property.h @@ -102,6 +102,8 @@ public: ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FontStyle, AdaptMaxFontSize, Dimension, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FontStyle, LetterSpacing, Dimension, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FontStyle, EnableVariableFontWeight, bool, PROPERTY_UPDATE_MEASURE); + ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FontStyle, FontForegroudGradiantColor, + FontForegroudGradiantColor, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(FontStyle, LineThicknessScale, float, PROPERTY_UPDATE_MEASURE); ACE_DEFINE_PROPERTY_GROUP(TextLineStyle, TextLineStyle); ACE_DEFINE_PROPERTY_ITEM_WITH_GROUP(TextLineStyle, LineHeight, Dimension, PROPERTY_UPDATE_MEASURE); diff --git a/frameworks/core/components_ng/pattern/text/text_pattern.cpp b/frameworks/core/components_ng/pattern/text/text_pattern.cpp index ca4b005213b1b7665dee5c066d674af87fe93a9d..5b2964e1cb39d12b6dee0a9e3d32ce48941816f1 100644 --- a/frameworks/core/components_ng/pattern/text/text_pattern.cpp +++ b/frameworks/core/components_ng/pattern/text/text_pattern.cpp @@ -20,7 +20,8 @@ #include #include #include - +#include +#include "ui/base/utils/utils.h" #include "adapter/ohos/capability/clipboard/clipboard_impl.h" #include "base/geometry/ng/offset_t.h" #include "base/geometry/ng/point_t.h" @@ -33,11 +34,13 @@ #include "base/utils/utils.h" #include "base/window/drag_window.h" #include "core/common/ace_engine_ext.h" +#include "core/common/ai/data_detector_adapter.h" #include "core/common/container_scope.h" #include "core/common/font_manager.h" #include "core/common/recorder/node_data_cache.h" #include "core/common/udmf/udmf_client.h" #include "core/common/vibrator/vibrator_utils.h" +#include "core/components/common/layout/constants.h" #include "core/components/common/properties/text_style_parser.h" #include "core/components_ng/gestures/recognizers/gesture_recognizer.h" #include "core/components_ng/pattern/rich_editor_drag/rich_editor_drag_pattern.h" @@ -64,6 +67,7 @@ constexpr float RICH_DEFAULT_SHADOW_COLOR = 0x33000000; constexpr float RICH_DEFAULT_ELEVATION = 120.0f; constexpr Dimension CLICK_THRESHOLD = 5.0_vp; const OffsetF DEFAULT_NEGATIVE_CARET_OFFSET {-1.0f, -1.0f}; +constexpr int MAX_SELECTED_AI_ENTITY = 1; bool IsJumpLink(const std::string& content) { @@ -708,6 +712,21 @@ void TextPattern::AsyncHandleOnCopySpanStringHtml(RefPtr& subSpanStr }, TaskExecutor::TaskType::BACKGROUND, "AsyncHandleOnCopySpanStringHtml"); } +void TextPattern::HanleAIMenuOption(const std::string& labelInfo) +{ + // lableInfo can be used for further extension: multiple ai entity in selected range + // only support one ai entity's first function now, hence pick begin + CHECK_NE_VOID(isShowAIMenuOption_, true); + CHECK_NE_VOID(aiMenuOptions_.size(), 1); + auto aiSpan = aiMenuOptions_.begin()->second; + auto aiEntityType = aiSpan.type; + auto menuOptionAndActions = dataDetectorAdapter_->textDetectResult_. + menuOptionAndAction[Ace::TEXT_DETECT_MAP.at(aiEntityType)]; + CHECK_EQUAL_VOID(menuOptionAndActions.empty(), true); + HiddenMenu(); + dataDetectorAdapter_->OnClickAIMenuOption(aiSpan, *menuOptionAndActions.begin(), nullptr); +} + void TextPattern::HandleOnCopySpanString() { auto subSpanString = styledString_->GetSubSpanString(textSelector_.GetTextStart(), @@ -720,6 +739,43 @@ void TextPattern::HandleOnCopySpanString() AsyncHandleOnCopySpanStringHtml(subSpanString); } + +// ret: whether show aiMenuOption +bool TextPattern::PrepareAiMenuOptions( + std::unordered_map& aiMenuOptions) +{ + aiMenuOptions.clear(); + CHECK_NULL_RETURN(IsSelected(), false); + CHECK_NULL_RETURN(dataDetectorAdapter_, false); + int selectedAiEntityNum = 0; + auto baseOffset = std::min(textSelector_.baseOffset, textSelector_.destinationOffset); + auto destinationOffset = std::max(textSelector_.baseOffset, textSelector_.destinationOffset); + auto spanIter = dataDetectorAdapter_->aiSpanMap_.lower_bound(baseOffset); + + for (;spanIter != dataDetectorAdapter_->aiSpanMap_.end(); spanIter++) { + auto aiSpanStart = spanIter->first; + auto aiSpanEnd = spanIter->second.end; // [start, end) + if (aiSpanStart >= baseOffset && aiSpanEnd <= destinationOffset) { + ++selectedAiEntityNum; + } else { + break; + } + if (selectedAiEntityNum > MAX_SELECTED_AI_ENTITY) { + break; + } else { // put ai span functions + aiMenuOptions[spanIter->second.type] = spanIter->second; + } + } + return selectedAiEntityNum == MAX_SELECTED_AI_ENTITY; +} + +void TextPattern::UpdateAiMenuOptions() +{ + if (copyOption_ == CopyOptions::Local && NeedShowAIDetect()) { + isShowAIMenuOption_ = PrepareAiMenuOptions(aiMenuOptions_); + } +} + std::list> TextPattern::GetSpanSelectedContent() { std::list> spans; @@ -1014,6 +1070,7 @@ void TextPattern::ShowSelectOverlay(const OverlayRequest& request) ResetSelection(); return; } + UpdateAiMenuOptions(); selectOverlay_->ProcessOverlay(request); } @@ -4862,7 +4919,7 @@ void TextPattern::SetTextDetectEnable(bool enable) host->MarkDirtyWithOnProChange(PROPERTY_UPDATE_MEASURE); } -bool TextPattern::CanStartAITask() +bool TextPattern::CanStartAITask() const { auto textLayoutProperty = GetLayoutProperty(); if (textLayoutProperty) { @@ -5394,7 +5451,7 @@ bool TextPattern::DidExceedMaxLines() const return pManager_->DidExceedMaxLines(); } -bool TextPattern::IsSetObscured() +bool TextPattern::IsSetObscured() const { auto host = GetHost(); CHECK_NULL_RETURN(host, false); diff --git a/frameworks/core/components_ng/pattern/text/text_pattern.h b/frameworks/core/components_ng/pattern/text/text_pattern.h index 348cb5a6da2fcbc203e6382728788a8603e3e6fb..6f713b4bc234dbbbda5f15b6003813ae6865d10a 100644 --- a/frameworks/core/components_ng/pattern/text/text_pattern.h +++ b/frameworks/core/components_ng/pattern/text/text_pattern.h @@ -536,6 +536,7 @@ public: void ResetSelection(); bool IsSelectAll(); void HandleOnCopy(); + void HanleAIMenuOption(const std::string& labelInfo = ""); void HandleOnCopySpanString(); virtual void HandleOnSelectAll(); bool IsShowTranslate(); @@ -566,7 +567,7 @@ public: void OnSensitiveStyleChange(bool isSensitive) override; - bool IsSetObscured(); + bool IsSetObscured() const; bool IsSensitiveEnable(); void CopySelectionMenuParams(SelectOverlayInfo& selectInfo) @@ -721,6 +722,27 @@ public: bool RecordOriginCaretPosition(const OffsetF& offset); TextDragInfo CreateTextDragInfo(); + void SetIsShowAIMenuOption(bool isShowAIMenuOption) + { + isShowAIMenuOption_ = isShowAIMenuOption; + } + + bool IsShowAIMenuOption() const + { + return isShowAIMenuOption_; + } + + void SetAIItemOption(const std::unordered_map& aiMenuOptions) + { + aiMenuOptions_ = aiMenuOptions; + } + + const std::unordered_map& GetAIItemOption() const + { + return aiMenuOptions_; + } + void UpdateAiMenuOptions(); + protected: int32_t GetClickedSpanPosition() { @@ -795,7 +817,7 @@ protected: bool IsSelectableAndCopy(); void SetResponseRegion(const SizeF& frameSize, const SizeF& boundsSize); - virtual bool CanStartAITask(); + virtual bool CanStartAITask() const; void MarkDirtySelf(); void OnAttachToMainTree() override @@ -979,6 +1001,7 @@ private: void AsyncHandleOnCopySpanStringHtml(RefPtr& subSpanString); void AsyncHandleOnCopyWithoutSpanStringHtml(const std::string& pasteData); std::list> GetSpanSelectedContent(); + bool PrepareAiMenuOptions(std::unordered_map& aiMenuOptions); bool isMeasureBoundary_ = false; bool isMousePressed_ = false; @@ -1033,6 +1056,8 @@ private: // Used to record original caret position for "shift + up/down" // Less than 0 is invalid, initialized as invalid in constructor OffsetF originCaretPosition_; + bool isShowAIMenuOption_ = false; + std::unordered_map aiMenuOptions_; }; } // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/text/text_select_overlay.cpp b/frameworks/core/components_ng/pattern/text/text_select_overlay.cpp index d24a24ccb793d8dff854f0cc0a49ec388d7a5d1c..f9d1d0b67da11a358b9e702f9bf1df9c3f615fa0 100644 --- a/frameworks/core/components_ng/pattern/text/text_select_overlay.cpp +++ b/frameworks/core/components_ng/pattern/text/text_select_overlay.cpp @@ -15,6 +15,7 @@ #include "core/components_ng/pattern/text/text_select_overlay.h" +#include "core/components/common/layout/constants.h" #include "core/components_ng/pattern/text/text_pattern.h" namespace OHOS::Ace::NG { @@ -230,8 +231,9 @@ void TextSelectOverlay::OnHandleMoveDone(const RectF& rect, bool isFirst) if (!textPattern->IsSelectedTypeChange()) { overlayManager->ShowOptionMenu(); } + textPattern->UpdateAiMenuOptions(); overlayManager->MarkInfoChange((isFirst ? DIRTY_FIRST_HANDLE : DIRTY_SECOND_HANDLE) | DIRTY_SELECT_AREA | - DIRTY_SELECT_TEXT | DIRTY_COPY_ALL_ITEM); + DIRTY_SELECT_TEXT | DIRTY_COPY_ALL_ITEM | DIRTY_AI_MENU_ITEM); if (textPattern->CheckSelectedTypeChange()) { CloseOverlay(false, CloseReason::CLOSE_REASON_NORMAL); ProcessOverlay({ .animation = true }); @@ -328,8 +330,12 @@ void TextSelectOverlay::OnUpdateMenuInfo(SelectMenuInfo& menuInfo, SelectOverlay menuInfo.showTranslate = menuInfo.showCopy && textPattern->IsShowTranslate() && IsNeedMenuTranslate(); menuInfo.showSearch = menuInfo.showCopy && textPattern->IsShowSearch() && IsNeedMenuSearch(); menuInfo.showShare = menuInfo.showCopy && IsSupportMenuShare() && IsNeedMenuShare(); - if (dirtyFlag == DIRTY_COPY_ALL_ITEM) { - return; + if (textPattern->IsShowAIMenuOption()) { + // do not support two selected ai entity, hence it's enough to pick first item to determine type + auto firstSpanItem = textPattern->GetAIItemOption().begin()->second; + menuInfo.aiMenuOptionType = firstSpanItem.type; + } else { + menuInfo.aiMenuOptionType = TextDataDetectType::INVALID; } menuInfo.menuIsShow = IsShowMenu(); menuInfo.showCut = false; @@ -404,6 +410,24 @@ void TextSelectOverlay::OnMenuItemAction(OptionMenuActionId id, OptionMenuType t } } +void TextSelectOverlay::OnMenuItemAction(OptionMenuActionId id, OptionMenuType type, const std::string& labelInfo) +{ + auto textPattern = GetPattern(); + CHECK_NULL_VOID(textPattern); + if (labelInfo == "") { + OnMenuItemAction(id, type); + } else { + switch (id) { + case OptionMenuActionId::AI_MENU_OPTION: + textPattern->HanleAIMenuOption(labelInfo); + break; + default: + TAG_LOGI(AceLogTag::ACE_TEXT, "Unsupported menu option id %{public}d", id); + break; + } + } +} + void TextSelectOverlay::OnCloseOverlay(OptionMenuType menuType, CloseReason reason, RefPtr info) { BaseTextSelectOverlay::OnCloseOverlay(menuType, reason, info); diff --git a/frameworks/core/components_ng/pattern/text/text_select_overlay.h b/frameworks/core/components_ng/pattern/text/text_select_overlay.h index 8ee397bbe0d9476abf4fb1eceab2f9b6a0b4540f..8ff8ae89afc9c8676e026d936aeed0a4da29d656 100644 --- a/frameworks/core/components_ng/pattern/text/text_select_overlay.h +++ b/frameworks/core/components_ng/pattern/text/text_select_overlay.h @@ -51,6 +51,7 @@ public: // override SelectOverlayCallback void OnMenuItemAction(OptionMenuActionId id, OptionMenuType type) override; + void OnMenuItemAction(OptionMenuActionId id, OptionMenuType type, const std::string& labelInfo) override; void OnHandleMove(const RectF& rect, bool isFirst) override; void OnHandleMoveDone(const RectF& rect, bool isFirst) override; void OnCloseOverlay(OptionMenuType menuType, CloseReason reason, RefPtr info = nullptr) override; diff --git a/frameworks/core/components_ng/pattern/text/text_styles.cpp b/frameworks/core/components_ng/pattern/text/text_styles.cpp index 4c12a3ac054052ea644d996663934a4c84bf8ef6..34fa697b614bc6856e1d7723125943a9c93dd550 100644 --- a/frameworks/core/components_ng/pattern/text/text_styles.cpp +++ b/frameworks/core/components_ng/pattern/text/text_styles.cpp @@ -132,6 +132,7 @@ void UseSelfStyleWithTheme(const RefPtr& property, TextStyle UPDATE_TEXT_STYLE_WITH_THEME(fontStyle, TextCase, TextCase); UPDATE_TEXT_STYLE_WITH_THEME(fontStyle, VariableFontWeight, VariableFontWeight); UPDATE_TEXT_STYLE_WITH_THEME(fontStyle, EnableVariableFontWeight, EnableVariableFontWeight); + UPDATE_TEXT_STYLE_WITH_THEME(fontStyle, FontForegroudGradiantColor, FontForegroudGradiantColor); if (isSymbol) { UPDATE_TEXT_STYLE_WITH_THEME(fontStyle, SymbolColorList, SymbolColorList); diff --git a/frameworks/core/components_ng/pattern/text/text_styles.h b/frameworks/core/components_ng/pattern/text/text_styles.h index 28b5969d79fcda8b6f5fd38289ffef66c9ade543..efdad7d7c87ae2046a41be7d74146bbe77730b31 100644 --- a/frameworks/core/components_ng/pattern/text/text_styles.h +++ b/frameworks/core/components_ng/pattern/text/text_styles.h @@ -218,6 +218,7 @@ struct FontStyle { ACE_DEFINE_PROPERTY_GROUP_ITEM(MaxFontScale, float); ACE_DEFINE_PROPERTY_GROUP_ITEM(SymbolType, SymbolType); ACE_DEFINE_PROPERTY_GROUP_ITEM(LineThicknessScale, float); + ACE_DEFINE_PROPERTY_GROUP_ITEM(FontForegroudGradiantColor, FontForegroudGradiantColor); void UpdateColorByResourceId(); diff --git a/interfaces/inner_api/ace_kit/include/ui/base/utils/utils.h b/interfaces/inner_api/ace_kit/include/ui/base/utils/utils.h index ffa4e2396692190bc2909f69aca0c8f60d785b91..498dde90d8b8554719060e995821afa41e08700c 100644 --- a/interfaces/inner_api/ace_kit/include/ui/base/utils/utils.h +++ b/interfaces/inner_api/ace_kit/include/ui/base/utils/utils.h @@ -56,6 +56,20 @@ } \ } while (0) +#define CHECK_NE_VOID(var, value) \ + do { \ + if ((var) != (value)) { \ + return; \ + } \ + } while (0) + +#define CHECK_NE_RETURN(var, value, ret) \ + do { \ + if ((var) != (value)) { \ + return ret; \ + } \ + } while (0) + #define CHECK_NULL_CONTINUE(ptr) \ if (!(ptr)) { \ continue; \ diff --git a/test/unittest/core/pattern/text/text_base.h b/test/unittest/core/pattern/text/text_base.h index b67325740e6d1683c01d4439728572ee31f0d13d..39ad1115f2866b2519000d27cd260c9e56dded9d 100644 --- a/test/unittest/core/pattern/text/text_base.h +++ b/test/unittest/core/pattern/text/text_base.h @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "test/unittest/core/pattern/test_ng.h" +#include "core/common/ai/data_detector_adapter.h" #include "core/components_ng/pattern/text/text_pattern.h" #include "core/components_ng/pattern/text/text_paint_method.h" @@ -179,6 +180,20 @@ struct ImageSpanNodeProperty { std::optional verticalAlign = std::nullopt; }; +// include span string and position +struct AISpanTestInfo { + std::variant content; + vector> aiSpans; +} + +const AISpanTestInfo U16_TEXT_FOR_AI_INFO = { std::u16string(u"phone: 12345678900,url: www.baidu.com"), + { {7, 18, "12345678900", TextDataDetectType::PHONE_NUMBER}, + {24, 37, "www.baidu.com", TextDataDetectType::URL} }; + +const AISpanTestInfo U16_TEXT_FOR_AI_INFO_2 = { std::u16string(u"email: 1234@abc.com,date: 2025.09.12, "), + { {7, 19, "12345678900", TextDataDetectType::EMAIL}, + {26, 36, "www.baidu.com", TextDataDetectType::DATE_TIME}} }; + class TextBases : public TestNG { public: static void SetUpTestSuite(); diff --git a/test/unittest/core/pattern/text/text_testthree_ng.cpp b/test/unittest/core/pattern/text/text_testthree_ng.cpp index 0e4336007477a9a468fdf6bee986858e0ef3e82a..c65f7dfb387161dc9d57435e2fc12c27510a79c7 100644 --- a/test/unittest/core/pattern/text/text_testthree_ng.cpp +++ b/test/unittest/core/pattern/text/text_testthree_ng.cpp @@ -13,6 +13,7 @@ * limitations under the License. */ +#include "gtest/gtest.h" #include "text_base.h" #include "test/mock/base/mock_task_executor.h" @@ -2373,4 +2374,101 @@ HWTEST_F(TextTestThreeNg, TextMarqueeEvents002, TestSize.Level1) EXPECT_EQ(textPattern->focusInitialized_, false); EXPECT_EQ(textPattern->hoverInitialized_, false); } + +/** + * @tc.name: PrepareAiMenuOptions + * @tc.desc: test test_pattern.h PrepareAiMenuOptions function with valid textSelector + * @tc.type: FUNC + */ +HWTEST_F(TextTestThreeNg, PrepareAiMenuOptions, TestSize.Level1) +{ + /** + * @tc.steps: step1. create frameNode and text textPattern + */ + auto [frameNode, textPattern] = Init(); + textPattern->textSelector_.Update(0, 25); + + /** + * @tc.steps: step2. prepare spanItem1 + */ + auto spanItem1 = AceType::MakeRefPtr(); + spanItem1->content = std::get(U16_TEXT_FOR_AI_INFO.content); + spanItem1->position = spanItem1->content.length(); + textPattern->spans_.emplace_back(spanItem1); + + auto mockParagraph = MockParagraph::GetOrCreateMockParagraph(); + std::vector rects { RectF(0, 0, 20, 20) }; + EXPECT_CALL(*mockParagraph, GetRectsForRange(_, _, _)).WillRepeatedly(SetArgReferee<2>(rects)); + textPattern->pManager_->AddParagraph({ .paragraph = mockParagraph, .start = 0, .end = 100 }); + + textPattern->SetTextDetectEnable(true); + textPattern->copyOption_ = CopyOptions::Local; + + auto aiSpan1 = U16_TEXT_FOR_AI_INFO.aiSpans[0]; + auto aiSpan2 = U16_TEXT_FOR_AI_INFO.aiSpans[1]; + std::map aiSpanMap; + aiSpanMap[aiSpan1.start] = aiSpan1; + aiSpanMap[aiSpan2.start] = aiSpan2; + textPattern->dataDetectorAdapter_->aiSpanMap_ = aiSpanMap; + + /** + * @tc.steps: step3. create GestureEvent and call PrepareAiMenuOptions function. + * @tc.expected: aiMenuOptions is been setted true. + */ + std::unordered_map aiMenuOptions; + textPattern->PrepareAiMenuOptions(aiMenuOptions); + EXPECT_EQ(aiMenuOptions.size(), 1); + auto aiSpan = aiMenuOptions.begin().second; + EXPECT_EQ(aiSpan.type, TextDataDetectType::PHONE_NUMBER); + textPattern->pManager_->Reset(); +} + +/** + * @tc.name: PrepareAiMenuOptions + * @tc.desc: test test_pattern.h PrepareAiMenuOptions function with valid textSelector + * check multi ai entity in selection range + * @tc.type: FUNC + */ +HWTEST_F(TextTestThreeNg, PrepareAiMenuOptions002, TestSize.Level1) +{ + /** + * @tc.steps: step1. create frameNode and text textPattern + */ + auto [frameNode, textPattern] = Init(); + textPattern->textSelector_.Update(0, 40); + + /** + * @tc.steps: step2. prepare spanItem with at least 2 ai entity + */ + auto spanItem = AceType::MakeRefPtr(); + spanItem->content = std::get(U16_TEXT_FOR_AI_INFO_2.content); + spanItem->position = spanItem->content.length(); + textPattern->spans_.emplace_back(spanItem); + + auto paragraph = MockParagraph::GetOrCreateMockParagraph(); + std::vector selectedRects { RectF(0, 0, 20, 20), RectF(30, 30, 20, 20), RectF(60, 60, 20, 20) }; + EXPECT_CALL(*paragraph, GetRectsForPlaceholders(_)).WillRepeatedly(SetArgReferee<0>(selectedRects)); + pattern->pManager_->AddParagraph({ .paragraph = paragraph, .start = 0, .end = 100 }); + + textPattern->SetTextDetectEnable(true); + textPattern->copyOption_ = CopyOptions::Local; + + auto aiSpan1 = U16_TEXT_FOR_AI_INFO_2.aiSpans[0]; + auto aiSpan2 = U16_TEXT_FOR_AI_INFO_2.aiSpans[1]; + std::map aiSpanMap; + aiSpanMap[aiSpan1.start] = aiSpan1; + aiSpanMap[aiSpan2.start] = aiSpan2; + textPattern->dataDetectorAdapter_->aiSpanMap_ = aiSpanMap; + + /** + * @tc.steps: step3. create GestureEvent and call PrepareAiMenuOptions function. + * @tc.expected: aiMenuOptions is been setted true. + */ + std::unordered_map aiMenuOptions; + auto ret = textPattern->PrepareAiMenuOptions(aiMenuOptions); + auto aiSpan = aiMenuOptions.begin().second; + EXPECT_EQ(aiSpan.type, TextDataDetectType::EMAIL); + EXPECT_EQ(ret, false); + textPattern->pManager_->Reset(); +} } // namespace OHOS::Ace::NG