/*
* Copyright (c) 2023-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import pasteboard from '@ohos.pasteboard'
import { BusinessError } from '@ohos.base';
import hilog from '@ohos.hilog';
import common from '@ohos.app.ability.common';
import { SymbolGlyphModifier } from '@kit.ArkUI';
import uiMaterial from '@ohos.arkui.uiMaterial';
const WITHOUT_BUILDER = -2
const MAX_FONT_STANDARD = 1.0
const MAX_FONT_SCALE = 1.75
const SYMBOL_SIZE: number = 24;
export interface EditorMenuOptions {
icon: ResourceStr
symbolStyle?: SymbolGlyphModifier
action?: () => void
builder?: () => void
}
export interface ExpandedMenuOptions extends MenuItemOptions {
action?: () => void;
}
export interface EditorEventInfo {
content?: RichEditorSelection;
}
export interface SelectionMenuOptions {
editorMenuOptions?: Array<EditorMenuOptions>
expandedMenuOptions?: Array<ExpandedMenuOptions>
controller?: RichEditorController
onPaste?: (event?: EditorEventInfo) => void
onCopy?: (event?: EditorEventInfo) => void
onCut?: (event?: EditorEventInfo) => void;
onSelectAll?: (event?: EditorEventInfo) => void;
backgroundSystemMaterial?: uiMaterial.Material;
}
interface SelectionMenuSymbolTheme {
fontSize: string;
fontColor: Array<ResourceColor>;
symbolCutIcon: SymbolGlyphModifier;
symbolCopyIcon: SymbolGlyphModifier;
symbolPasteIcon: SymbolGlyphModifier;
symbolSelectAllIcon: SymbolGlyphModifier;
symbolShareIcon: SymbolGlyphModifier;
symbolTranslateIcon: SymbolGlyphModifier;
symbolSearchIcon: SymbolGlyphModifier;
symbolArrowDownIcon: SymbolGlyphModifier;
}
interface SelectionMenuTheme {
imageSize: number;
buttonSize: number;
menuSpacing: number;
expandedOptionPadding: number;
defaultMenuWidth: number;
menuItemPadding: Resource;
imageFillColor: Resource;
backGroundColor: Resource;
iconBorderRadius: Resource;
containerBorderRadius: Resource;
borderWidth: Resource;
borderColor: Resource;
outlineWidth: Resource;
outlineColor: Resource;
cutIcon: Resource;
copyIcon: Resource;
pasteIcon: Resource;
selectAllIcon: Resource;
shareIcon: Resource;
translateIcon: Resource;
searchIcon: Resource;
arrowDownIcon: Resource;
iconPanelShadowStyle: ShadowStyle;
defaultSymbolTheme: SelectionMenuSymbolTheme;
}
const defaultTheme: SelectionMenuTheme = {
imageSize: 24,
buttonSize: 40,
menuSpacing: 8,
expandedOptionPadding: 4,
defaultMenuWidth: 224,
menuItemPadding: $r('sys.float.padding_level1'),
imageFillColor: $r('sys.color.ohos_id_color_primary'),
backGroundColor: $r('sys.color.ohos_id_color_dialog_bg'),
iconBorderRadius: $r('sys.float.corner_radius_level2'),
containerBorderRadius: $r('sys.float.corner_radius_level4'),
borderWidth: $r('sys.float.ohos_id_menu_inner_border_width'),
borderColor: $r('sys.color.ohos_id_menu_inner_border_color'),
outlineWidth: $r('sys.float.ohos_id_menu_outer_border_width'),
outlineColor: $r('sys.color.ohos_id_menu_outer_border_color'),
cutIcon: $r('sys.media.ohos_ic_public_cut'),
copyIcon: $r('sys.media.ohos_ic_public_copy'),
pasteIcon: $r('sys.media.ohos_ic_public_paste'),
selectAllIcon: $r('sys.media.ohos_ic_public_select_all'),
shareIcon: $r('sys.media.ohos_ic_public_share'),
translateIcon: $r('sys.media.ohos_ic_public_translate_c2e'),
searchIcon: $r('sys.media.ohos_ic_public_search_filled'),
arrowDownIcon: $r('sys.media.ohos_ic_public_arrow_down'),
iconPanelShadowStyle: ShadowStyle.OUTER_DEFAULT_SM,
defaultSymbolTheme: {
fontSize: `${SYMBOL_SIZE}vp`,
fontColor: [$r('sys.color.ohos_id_color_primary')],
symbolCutIcon: new SymbolGlyphModifier($r('sys.symbol.cut')),
symbolCopyIcon: new SymbolGlyphModifier($r('sys.symbol.plus_square_on_square')),
symbolPasteIcon: new SymbolGlyphModifier($r('sys.symbol.plus_square_dashed_on_square')),
symbolSelectAllIcon: new SymbolGlyphModifier($r('sys.symbol.checkmark_square_on_square')),
symbolShareIcon: new SymbolGlyphModifier($r('sys.symbol.share')),
symbolTranslateIcon: new SymbolGlyphModifier($r('sys.symbol.translate_c2e')),
symbolSearchIcon: new SymbolGlyphModifier($r('sys.symbol.magnifyingglass')),
symbolArrowDownIcon: new SymbolGlyphModifier($r('sys.symbol.chevron_down')),
},
}
@Component
struct SelectionMenuComponent {
editorMenuOptions?: Array<EditorMenuOptions>
expandedMenuOptions?: Array<ExpandedMenuOptions>
controller?: RichEditorController
onPaste?: (event?: EditorEventInfo) => void
onCopy?: (event?: EditorEventInfo) => void
onCut?: (event?: EditorEventInfo) => void;
onSelectAll?: (event?: EditorEventInfo) => void;
backgroundSystemMaterial?: uiMaterial.Material;
private theme: SelectionMenuTheme = defaultTheme;
@Builder
CloserFun() {
}
@BuilderParam builder: CustomBuilder = this.CloserFun
@State showExpandedMenuOptions: boolean = false
@State showCustomerIndex: number = -1
@State customerChange: boolean = false
@State cutAndCopyEnable: boolean = false
@State pasteEnable: boolean = false
@State visibilityValue: Visibility = Visibility.Visible
@State fontScale: number = 1
@State customMenuWidth: number = this.theme.defaultMenuWidth
@State horizontalMenuHeight: number = 0
@State horizontalMenuWidth: number = this.theme.defaultMenuWidth
private fontWeightTable: string[] =
['100', '200', '300', '400', '500', '600', '700', '800', '900', 'bold', 'normal', 'bolder', 'lighter', 'medium',
'regular']
private isFollowingSystemFontScale: boolean = false
private appMaxFontScale: number = 3.2
aboutToAppear() {
if (this.controller) {
let richEditorSelection = this.controller.getSelection()
let start = richEditorSelection.selection[0]
let end = richEditorSelection.selection[1]
if (start !== end) {
this.cutAndCopyEnable = true
}
if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
this.visibilityValue = Visibility.None
} else {
this.visibilityValue = Visibility.Visible
}
} else if (this.expandedMenuOptions && this.expandedMenuOptions.length > 0) {
this.showExpandedMenuOptions = true
}
let sysBoard = pasteboard.getSystemPasteboard()
if (sysBoard && sysBoard.hasDataSync()) {
this.pasteEnable = true
}
let uiContext: UIContext = this.getUIContext()
if (uiContext) {
this.isFollowingSystemFontScale = uiContext.isFollowingSystemFontScale()
this.appMaxFontScale = uiContext.getMaxFontScale()
}
this.fontScale = this.getFontScale()
}
hasSystemMenu(): boolean {
let showMenuOption = this.showCustomerIndex === -1 &&
(this.controller || (this.expandedMenuOptions && this.expandedMenuOptions.length > 0))
let showBuilder = this.showCustomerIndex > -1 && this.builder
return Boolean(showMenuOption || showBuilder)
}
private getSystemMaterial(inputMaterial: uiMaterial.Material | undefined): uiMaterial.Material | undefined {
let info = uiMaterial.getMaterialInfo();
let isValidMaterial = inputMaterial instanceof uiMaterial.Material
if (info.state === uiMaterial.MaterialState.ENABLE && !isValidMaterial) {
return new uiMaterial.ImmersiveMaterial({
style: uiMaterial.ImmersiveStyle.THICK
});
} else if (!isValidMaterial || info.state === uiMaterial.MaterialState.DISABLE) {
return undefined;
}
return inputMaterial;
}
build() {
Column() {
if (this.editorMenuOptions && this.editorMenuOptions.length > 0) {
this.IconPanel()
}
Scroll() {
this.SystemMenu()
}
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? undefined : this.theme.backGroundColor)
.shadow(this.theme.iconPanelShadowStyle)
.borderRadius(this.theme.containerBorderRadius)
.outline(this.hasSystemMenu() ? {
width: this.theme.outlineWidth, color: this.theme.outlineColor,
radius: this.theme.containerBorderRadius
} : undefined)
.constraintSize({
maxHeight: `calc(100% - ${this.horizontalMenuHeight > 0 ? this.horizontalMenuHeight + this.theme.menuSpacing :
0}vp)`,
minWidth: this.theme.defaultMenuWidth
})
.systemMaterial(this.getSystemMaterial(this.backgroundSystemMaterial))
}
.useShadowBatching(true)
.constraintSize({
maxHeight: '100%',
minWidth: this.theme.defaultMenuWidth
})
}
pushDataToPasteboard(richEditorSelection: RichEditorSelection) {
let sysBoard = pasteboard.getSystemPasteboard()
let pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '')
if (richEditorSelection.spans && richEditorSelection.spans.length > 0) {
let count = richEditorSelection.spans.length
for (let i = count - 1; i >= 0; i--) {
let item = richEditorSelection.spans[i]
if ((item as RichEditorTextSpanResult)?.textStyle) {
let span = item as RichEditorTextSpanResult
let style = span.textStyle
let data = pasteboard.createRecord(pasteboard.MIMETYPE_TEXT_PLAIN,
span.value.substring(span.offsetInSpan[0], span.offsetInSpan[1]))
let prop = pasteData.getProperty()
let temp: Record<string, Object> = {
'color': style.fontColor,
'size': style.fontSize,
'style': style.fontStyle,
'weight': this.fontWeightTable[style.fontWeight],
'fontFamily': style.fontFamily,
'decorationType': style.decoration.type,
'decorationColor': style.decoration.color
}
prop.additions[i] = temp;
pasteData.addRecord(data)
pasteData.setProperty(prop)
}
}
}
sysBoard.clearData()
sysBoard.setData(pasteData).then(() => {
hilog.info(0x3900, 'Ace', 'SelectionMenu copy option, Succeeded in setting PasteData.');
}).catch((err: BusinessError) => {
hilog.info(0x3900, 'Ace', 'SelectionMenu copy option, Failed to set PasteData. Cause:' + err.message);
})
}
popDataFromPasteboard(richEditorSelection: RichEditorSelection) {
let start = richEditorSelection.selection[0]
let end = richEditorSelection.selection[1]
if (start === end && this.controller) {
start = this.controller.getCaretOffset()
end = this.controller.getCaretOffset()
}
let moveOffset = 0
let sysBoard = pasteboard.getSystemPasteboard()
sysBoard.getData((err, data) => {
if (err) {
return
}
let count = data.getRecordCount()
for (let i = 0; i < count; i++) {
const element = data.getRecord(i);
let tex: RichEditorTextStyle = {
fontSize: 16,
fontColor: Color.Black,
fontWeight: FontWeight.Normal,
fontFamily: "HarmonyOS Sans",
fontStyle: FontStyle.Normal,
decoration: { type: TextDecorationType.None, color: '#FF000000' }
}
if (data.getProperty() && data.getProperty().additions[i]) {
const tmp = data.getProperty().additions[i] as Record<string, Object | undefined>;
if (tmp.color) {
tex.fontColor = tmp.color as ResourceColor;
}
if (tmp.size) {
tex.fontSize = tmp.size as Length | number;
}
if (tmp.style) {
tex.fontStyle = tmp.style as FontStyle;
}
if (tmp.weight) {
tex.fontWeight = tmp.weight as number | FontWeight | string;
}
if (tmp.fontFamily) {
tex.fontFamily = tmp.fontFamily as ResourceStr;
}
if (tmp.decorationType && tex.decoration) {
tex.decoration.type = tmp.decorationType as TextDecorationType;
}
if (tmp.decorationColor && tex.decoration) {
tex.decoration.color = tmp.decorationColor as ResourceColor;
}
if (tex.decoration) {
tex.decoration = { type: tex.decoration.type, color: tex.decoration.color }
}
}
if (element && element.plainText && element.mimeType === pasteboard.MIMETYPE_TEXT_PLAIN && this.controller) {
this.controller.addTextSpan(element.plainText,
{
style: tex,
offset: start + moveOffset
}
)
moveOffset += element.plainText.length
}
}
if (this.controller) {
this.controller.setCaretOffset(start + moveOffset)
}
if (start !== end && this.controller) {
this.controller.deleteSpans({ start: start + moveOffset, end: end + moveOffset })
}
})
}
measureButtonWidth(): number {
let numOfBtnPerRow = 5
let width = this.fontScale > MAX_FONT_SCALE ? this.customMenuWidth : this.theme.defaultMenuWidth;
if (this.editorMenuOptions && this.editorMenuOptions.length <= numOfBtnPerRow) {
return (width - this.theme.expandedOptionPadding * 2) / this.editorMenuOptions.length
}
return (width - this.theme.expandedOptionPadding * 2) / numOfBtnPerRow
}
measureFlexPadding(): number {
return Math.floor((this.theme.expandedOptionPadding - px2vp(2.0)) * 10) / 10
}
getFontScale(): number {
try {
let uiContext: UIContext = this.getUIContext();
let systemFontScale = (uiContext.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
if (!this.isFollowingSystemFontScale) {
return 1;
}
return Math.min(systemFontScale, this.appMaxFontScale);
} catch (exception) {
let code: number = (exception as BusinessError).code;
let message: string = (exception as BusinessError).message;
hilog.error(0x3900, 'Ace', `Faild to init fontsizescale info,cause, code: ${code}, message: ${message}`);
return 1;
}
}
onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult {
this.fontScale = this.getFontScale();
let sizeResult: SizeResult = { height: 0, width: 0 };
children.forEach((child) => {
let childMeasureResult: MeasureResult = child.measure(constraint);
sizeResult.width = childMeasureResult.width;
sizeResult.height = childMeasureResult.height;
});
return sizeResult;
}
updateMenuItemVisibility() {
if (!this.controller) {
return
}
let richEditorSelection = this.controller.getSelection()
let start = richEditorSelection.selection[0]
let end = richEditorSelection.selection[1]
if (start !== end) {
this.cutAndCopyEnable = true
}
if (start === 0 && this.controller.getSpans({ start: end + 1, end: end + 1 }).length === 0) {
this.visibilityValue = Visibility.None
} else {
this.visibilityValue = Visibility.Visible
}
}
@Builder
IconPanel() {
Flex({ wrap: FlexWrap.Wrap }) {
if (this.editorMenuOptions) {
ForEach(this.editorMenuOptions, (item: EditorMenuOptions, index: number) => {
Button() {
if (item.symbolStyle !== undefined) {
SymbolGlyph()
.fontColor(this.theme.defaultSymbolTheme.fontColor)
.attributeModifier(item.symbolStyle)
.focusable(true)
.draggable(false)
.effectStrategy(SymbolEffectStrategy.NONE)
.symbolEffect(new SymbolEffect(), false)
.fontSize(this.theme.defaultSymbolTheme.fontSize)
} else {
if (Util.isSymbolResource(item.icon)) {
SymbolGlyph(item.icon as Resource)
.fontColor(this.theme.defaultSymbolTheme.fontColor)
.focusable(true)
.draggable(false)
.fontSize(this.theme.defaultSymbolTheme.fontSize)
} else {
Image(item.icon)
.width(this.theme.imageSize)
.height(this.theme.imageSize)
.fillColor(this.theme.imageFillColor)
.focusable(true)
.draggable(false)
}
}
}
.enabled(!(!item.action && !item.builder))
.type(ButtonType.Normal)
.backgroundColor(
this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : this.theme.backGroundColor)
.onClick(() => {
if (item.builder) {
this.builder = item.builder
this.showCustomerIndex = index
this.showExpandedMenuOptions = false
this.customerChange = !this.customerChange
} else {
this.showCustomerIndex = WITHOUT_BUILDER
if (!this.controller) {
this.showExpandedMenuOptions = true
}
}
if (item.action) {
item.action()
}
})
.borderRadius(this.theme.iconBorderRadius)
.width(this.measureButtonWidth())
.height(this.theme.buttonSize)
})
}
}
.onAreaChange((oldValue: Area, newValue: Area) => {
let newValueHeight = newValue.height as number
let newValueWidth = newValue.width as number
this.horizontalMenuHeight = newValueHeight
this.horizontalMenuWidth = newValueWidth
})
.clip(true)
.width(this.fontScale > MAX_FONT_SCALE ? this.customMenuWidth : this.theme.defaultMenuWidth)
.padding({
top: this.measureFlexPadding(),
bottom: this.measureFlexPadding(),
left: this.measureFlexPadding() - 0.1,
right: this.measureFlexPadding() - 0.1
})
.borderRadius(this.theme.containerBorderRadius)
.margin({ bottom: this.theme.menuSpacing })
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? undefined : this.theme.backGroundColor)
.shadow(this.theme.iconPanelShadowStyle)
.border({
width: this.theme.borderWidth,
color: this.theme.borderColor,
radius: this.theme.containerBorderRadius
})
.outline({
width: this.theme.outlineWidth, color: this.theme.outlineColor,
radius: this.theme.containerBorderRadius
})
.systemMaterial(this.getSystemMaterial(this.backgroundSystemMaterial))
}
@Builder
SystemMenu() {
Column() {
if (this.showCustomerIndex === -1 &&
(this.controller || (this.expandedMenuOptions && this.expandedMenuOptions.length > 0))) {
Menu() {
if (this.controller) {
MenuItemGroup() {
MenuItem({
startIcon: this.theme.cutIcon,
symbolStartIcon: this.theme.defaultSymbolTheme.symbolCutIcon,
content: '剪切',
labelInfo: 'Ctrl+X'
})
.enabled(this.cutAndCopyEnable)
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.onClick(() => {
if (!this.controller) {
return
}
let richEditorSelection = this.controller.getSelection()
if (this.onCut) {
this.onCut({ content: richEditorSelection })
} else {
this.pushDataToPasteboard(richEditorSelection);
this.controller.deleteSpans({
start: richEditorSelection.selection[0],
end: richEditorSelection.selection[1]
})
}
})
MenuItem({
startIcon: this.theme.copyIcon,
symbolStartIcon: this.theme.defaultSymbolTheme.symbolCopyIcon,
content: '复制',
labelInfo: 'Ctrl+C'
})
.enabled(this.cutAndCopyEnable)
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.margin({ top: this.theme.menuItemPadding })
.onClick(() => {
if (!this.controller) {
return
}
let richEditorSelection = this.controller.getSelection()
if (this.onCopy) {
this.onCopy({ content: richEditorSelection })
} else {
this.pushDataToPasteboard(richEditorSelection);
this.controller.closeSelectionMenu()
}
})
MenuItem({
startIcon: this.theme.pasteIcon,
symbolStartIcon: this.theme.defaultSymbolTheme.symbolPasteIcon,
content: '粘贴',
labelInfo: 'Ctrl+V'
})
.enabled(this.pasteEnable)
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.margin({ top: this.theme.menuItemPadding })
.onClick(() => {
if (!this.controller) {
return
}
let richEditorSelection = this.controller.getSelection()
if (this.onPaste) {
this.onPaste({ content: richEditorSelection })
} else {
this.popDataFromPasteboard(richEditorSelection)
this.controller.closeSelectionMenu()
}
})
MenuItem({
startIcon: this.theme.selectAllIcon,
symbolStartIcon: this.theme.defaultSymbolTheme.symbolSelectAllIcon,
content: '全选',
labelInfo: 'Ctrl+A'
})
.visibility(this.visibilityValue)
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.margin({ top: this.theme.menuItemPadding })
.onClick(() => {
if (!this.controller) {
return
}
if (this.onSelectAll) {
let richEditorSelection = this.controller.getSelection()
this.onSelectAll({ content: richEditorSelection })
} else {
this.controller.setSelection(-1, -1)
this.visibilityValue = Visibility.None
}
this.controller.closeSelectionMenu()
})
}
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
}
if (this.controller && !this.showExpandedMenuOptions &&
this.expandedMenuOptions && this.expandedMenuOptions.length > 0) {
MenuItem({
content: '更多',
endIcon: this.theme.arrowDownIcon,
symbolEndIcon: this.theme.defaultSymbolTheme.symbolArrowDownIcon
})
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.margin({ top: this.theme.menuItemPadding })
.onClick(() => {
this.showExpandedMenuOptions = true
})
} else if (this.showExpandedMenuOptions && this.expandedMenuOptions && this.expandedMenuOptions.length > 0) {
ForEach(this.expandedMenuOptions, (expandedMenuOptionItem: ExpandedMenuOptions, index) => {
MenuItem({
startIcon: expandedMenuOptionItem.startIcon,
symbolStartIcon: expandedMenuOptionItem.symbolStartIcon,
content: expandedMenuOptionItem.content,
endIcon: expandedMenuOptionItem.endIcon,
symbolEndIcon: expandedMenuOptionItem.symbolEndIcon,
labelInfo: expandedMenuOptionItem.labelInfo,
builder: expandedMenuOptionItem.builder
})
.height(this.fontScale > MAX_FONT_STANDARD ? 'auto' : this.theme.buttonSize)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.borderRadius(this.theme.iconBorderRadius)
.margin({ top: this.theme.menuItemPadding })
.onClick(() => {
if (expandedMenuOptionItem.action) {
expandedMenuOptionItem.action()
}
})
})
}
}
.radius(this.theme.containerBorderRadius)
.clip(true)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.width(this.fontScale > MAX_FONT_SCALE ? 'auto' : this.theme.defaultMenuWidth)
.constraintSize({
minWidth: this.theme.defaultMenuWidth
})
.onVisibleAreaChange([0.0, 1.0], () => {
this.updateMenuItemVisibility()
})
.onAreaChange((oldValue: Area, newValue: Area) => {
let newValueWidth = newValue.width as number
this.customMenuWidth =
this.fontScale > MAX_FONT_SCALE && newValueWidth > this.theme.defaultMenuWidth ? newValueWidth :
this.theme.defaultMenuWidth
this.updateMenuItemVisibility()
})
} else if (this.showCustomerIndex > -1 && this.builder) {
Column() {
if (this.customerChange) {
this.builder()
} else {
this.builder()
}
}
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.width(this.horizontalMenuWidth)
}
}
.width(this.fontScale > MAX_FONT_SCALE ? 'auto' : this.theme.defaultMenuWidth)
.shadow(this.theme.iconPanelShadowStyle)
.backgroundColor(this.getSystemMaterial(this.backgroundSystemMaterial) ? Color.Transparent : undefined)
.border({
width: this.theme.borderWidth,
color: this.theme.borderColor,
radius: this.theme.containerBorderRadius
})
.constraintSize({
minWidth: this.theme.defaultMenuWidth
})
}
}
@Builder
export function SelectionMenu(options: SelectionMenuOptions) {
SelectionMenuComponent({
editorMenuOptions: options.editorMenuOptions,
expandedMenuOptions: options.expandedMenuOptions,
controller: options.controller,
onPaste: options.onPaste,
onCopy: options.onCopy,
onCut: options.onCut,
onSelectAll: options.onSelectAll,
backgroundSystemMaterial: options.backgroundSystemMaterial
})
}
class Util {
private static RESOURCE_TYPE_SYMBOL = 40000;
public static isSymbolResource(resourceStr: ResourceStr | undefined): boolean {
if (!Util.isResourceType(resourceStr)) {
return false;
}
let resource = resourceStr as Resource;
return resource.type === Util.RESOURCE_TYPE_SYMBOL;
}
public static isResourceType(resource: ResourceStr | Resource | undefined): boolean {
if (!resource) {
return false;
}
if (typeof resource === 'string' || typeof resource === 'undefined') {
return false;
}
return true;
}
}