【Godot4.3】自定义简易菜单栏节点ETDMenuBar

概述

Godot中的菜单创建是一个复杂的灾难性工作,往往无从下手,我也是不止一次尝试简化菜单的创建。

从自己去年的发明“简易树形数据”用于简化Tree控件获得灵感,于是尝试编写了用于表示菜单数据的EasyMenuData类,以及对应的纯文本数据格式和对应的MenuBar控件扩展ETDMenuBar

于是乎,你只需要在创建菜单栏时,添加一个ETDMenuBar控件,并为其data属性指定符合规定的简易数据,以及图标集。便可以轻松设计和获得复杂层次的菜单,以用于你创建的Godot桌面程序。

在这里插入图片描述

得到的效果:

运行效果

原理

  • MenuBar生成菜单的原理,可以参看我之前写的《【Godot4.2】菜单相关控件和节点完全解析》
  • 简易树形数据解析的原理,可以参看我之前写的《【Godot4.2】EasyTreeData通用解析》

EasyMenuData

  • 我创建了一个名叫EasyMenuData的类,可以解析形如下的数据,并通过其to_menu()方法返回一个PopUpMenu实例
文件新建 | 0 | -1 | Ctrl+S打开 | 1 | 0最近 | 1 | 1文件1文件2文件3
========关闭 | -1 | -1 | Ctrl+Q

其格式采用:

文本 | 图标索引 | 复选状态标记 | 快捷键

其中:

  • |是分隔符,顺序和意义对应,可以忽略后面的设定,但顺序依然不能改变,符合要求的形式举例如下:
文本
文本 | 图标索引
文本 | 图标索引 | 复选状态标记
文本 | 图标索引 | 复选状态标记 | 快捷键
  • 图标索引:为负数或超出icons属性提供的图标集范围时,将不显示
  • 复选状态标记:为负数则不显示复选框,大于等于0,显示复选框,0不选中,大于0选中
  • 快捷键:只需要指定以+号连接的字符串形式即可,不区分大小写

ETDMenuBar

  • 我创建了一个扩展的MenuBar类型ETDMenuBar,其data属性接收如下形式的数据:
文件新建 | 0 | -1 | Ctrl+S打开 | 1 | 0最近 | 1 | 1文件1文件2文件3---关闭 | -1 | -1 | Ctrl+Q
========
编辑撤销重做---清空

也就是在多个一级菜单之间用========进行分隔。

  • 运行场景后,会自动根据data属性给定的数据,生成菜单栏。

EasyMenuData源码

# ========================================================
# 名称:EasyMenuData
# 类型:类
# 简介:基于ETD构造PopupMenu的类
# 作者:巽星石
# Godot版本:v4.3.stable.steam [77dcf97d8]
# 创建时间:202522120:11:05
# 最后修改时间:20253122:24:40
# ========================================================
class_name EasyMenuDatavar _root:EasyMenuItem# 获取生成的菜单
func to_menu() -> PopupMenu:return _root.to_menu()# 获取根节点信息
func get_root_data() -> String:return _root.data if _root else ""# ============================ 内部类 ============================
# 单项数据
class EasyMenuItem:# ------- 图标集var icons:Array[Texture2D]var icon_width:float   # 图标最大宽度# ------- 菜单项信息var label:String       # 菜单项文本var icon:Texture2D     # 菜单项图标var shortcut:Shortcut  # 菜单项快捷键var show_checkbox:bool # 是否显示复选var checked:bool       # 是否选中# ------- 节点信息var data:String                    # 原始未解析的文本var deep:int                       # 节点深度var parent:EasyMenuItem            # 父节点var children:Array[EasyMenuItem]   # 子节点集合func _init(_data:String,_deep:int,_icons:Array[Texture2D],_icon_width:float) -> void:data = _datadeep = _deepicons = _iconsicon_width = _icon_widthparse_data()children = []# 解析传入的数据func parse_data():if data != "" or data != "---":var ds = data.split(" | ",false)match ds.size():1:label = data2:label = ds[0]var idx = int(ds[1])icon = get_idx_icon(idx)3:label = ds[0]var idx = int(ds[1])icon = get_idx_icon(idx)show_checkbox = is_show_box(int(ds[2]))checked = int(ds[2])4:label = ds[0]var idx = int(ds[1])icon = get_idx_icon(idx)show_checkbox = is_show_box(int(ds[2]))checked = int(ds[2])shortcut = new_shortcut(ds[3])# 是否显示复选框func is_show_box(tag:int) -> bool:return true if tag >= 0 else false  # 仅在大于等于0显示# 获取对应下标的图标func get_idx_icon(idx:int) -> Texture2D:var icn:Texture2Dif icons:if idx in range(icons.size()):icn = icons[idx]return icn# 按给定字符串创建并返回一个快捷键func new_shortcut(key_str:String) -> Shortcut:var sc:Shortcutif key_str != "":sc = Shortcut.new()var event := InputEventKey.new()event.pressed = truevar keys = key_str.split("+",false)for key in keys:match key.to_lower(): # 小写"ctrl":event.ctrl_pressed = true"alt":event.alt_pressed = true"shift":event.shift_pressed = true_:event.keycode = OS.find_keycode_from_string(key)sc.events.append(event)return scfunc get_path():var path = ""path += labelif parent:path = parent.get_path() + "/" + pathreturn path# 转为菜单项或子菜单func to_menu(menu:PopupMenu = null):var root_menu:PopupMenuif menu == null:  # 根节点menu = PopupMenu.new()if data != "":menu.name = label  # 名称与菜单项一致root_menu = menufor child in children:child.to_menu(root_menu)return root_menuelse:# 添加菜单项if data == "---":  # 分割线menu.add_separator()else:menu.add_item(label)  # 文本var last = menu.item_count-1# 将路径以元数据形式存储menu.set_item_metadata(last,get_path())# 图标if icon: menu.set_item_icon(last,icon)menu.set_item_icon_max_width(last,icon_width) # 设定图标宽度# 复选框menu.set_item_as_checkable(last,show_checkbox)if show_checkbox:menu.set_item_checked(last,checked)# 快捷键if shortcut:menu.set_item_shortcut(last,shortcut,true)# 如果有子节点if children.size()>0:# 创建子PopupMenuvar sub_menu = PopupMenu.new()sub_menu.name = label  # 名称与菜单项一致menu.add_child(sub_menu)# 指定为当前项的子菜单menu.set_item_submenu_node(menu.item_count-1,sub_menu)for child in children:child.to_menu(sub_menu)# ============================ 方法 ============================
# 创建并返回一个EasyTreeItem实例
func create_item(text:String,icons:Array[Texture2D],icon_width:float,p_node:EasyMenuItem = null) -> EasyMenuItem:var itm = EasyMenuItem.new(text,0,icons,icon_width)if _root:if p_node:itm.deep = p_node.deep + 1itm.parent = p_nodep_node.children.append(itm)else:itm.deep = _root.deep + 1itm.parent = _root_root.children.append(itm)else:_root = itmreturn itm# 由多行文本创建
static func new_with_etd_str(etd_str:String,icons:Array[Texture2D],icon_width:float) ->EasyMenuData:var edt = EasyMenuData.new()var items = etd_str.split("\n",false)    # 将ETD字符串按行切分为字符串数组var pre_itm:EasyMenuItem                 # 记录前一项var p_itm:EasyMenuItem = null            # 记录父节点# 遍历每行数据for i in range(items.size()):if items[i].strip_edges() != "":# 第1行直接添加为Tree控件的根节点(跳过下面if部分)# 从第2行开始比较当前行与前一行的缩进深度(也就是\t的数目)if i > 0: var d_deep = deep(items[i-1]) - deep(items[i])  # 与前一行数据的缩进差值match d_deep:-1:                                # 缩进比前一项深:p_itm = pre_itm                # 将前一项作为父节点0:                                 # 缩进深度与前一项一样:p_itm = pre_itm.parent   # 父节点与前一项父节点一样_:                                 if d_deep>0:                   # 缩进比前一项浅# 通过缩进差值计算获得合适的父节点p_itm = pre_itm            for j in range(d_deep+1):p_itm = p_itm.parent# 实际创建和添加TreeItemTree控件var itm:EasyMenuItem = edt.create_item(items[i].replace("\t",""),icons,icon_width,p_itm)pre_itm = itm                              # 将当前项记录为前一项return edt# 返回字符串的Tab缩进值
static func deep(sttr:String):return sttr.rstrip(" ").count("\t")

ETDMenuBar源码

# ========================================================
# 名称:ETDMenuBar
# 类型:自定义控件(MenuBar扩展)
# 简介:基于EasyMenuData构造的MenuBar
# 作者:巽星石
# Godot版本:v4.3.stable.steam [77dcf97d8]
# 创建时间:202522120:49:44
# 最后修改时间:20253122:26
# ========================================================class_name ETDMenuBar extends MenuBarsignal item_click(path:String) # 菜单项被点击# ================================ 参数 ================================
@export_multiline var data:String   ## 菜单栏简易数据
@export var icons:Array[Texture2D]  ## 图标集合
@export var icon_width:float = 16.0 ## 图标最大宽度func _ready() -> void:reload()# ============================ 方法 ============================
# 重新加载
func reload():clear()for dt in data.split("=".repeat(8),false):var emd = EasyMenuData.new_with_etd_str(dt,icons,icon_width)var menu = emd.to_menu()set_connects(menu)add_child(menu)# 递归形式为所有层级的菜单处理菜单项点击的信号处理
func set_connects(menu:PopupMenu):# 统一设置字号var font_size = get("theme_override_font_sizes/font_size")menu.set("theme_override_font_sizes/font_size",font_size)menu.connect("index_pressed",func(index:int):emit_signal("item_click",menu.get_item_metadata(index)))for sub_menu in menu.get_children():if sub_menu is PopupMenu:set_connects(sub_menu)# 清空子节点
func clear():for child in get_children():child.queue_free()

菜单项点击的处理

只需要链接item_click信号,就可以处理菜单项的点击了。

在这里插入图片描述

信号参数path会返回菜单项的路径,类似于下面这样:

文件/最近/文件2
文件/关闭

这样通过写一个math分支语句来匹配菜单项的路径,就可以实现具体菜单项的功能。

美化

通过外面套一个PanelContainer,并且设定flat,可以快速的美化菜单栏。

而且我设定所有的子菜单都保持于MenuBar一致的字号,只要在MenuBar上设置一次就可以了。

在这里插入图片描述

总结

  • EasyMenuData其实对应的是单个PopupMenu的描述和生成,可以用于普通菜单生成,也可用于弹出菜单的设计
  • ETDMenuBar,则是对应菜单栏生成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/72113.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据与金融科技:革新金融行业的动力引擎

大数据与金融科技:革新金融行业的动力引擎 在今天的金融行业,大数据与金融科技的结合正在以惊人的速度推动着金融服务的创新与变革。通过精准的数据分析与智能化决策,金融机构能够更高效地进行风险管理、客户服务、资产管理等一系列关键操作…

二、IDE集成DeepSeek保姆级教学(使用篇)

各位看官老爷好,如果还没有安装DeepSeek请查阅前一篇 一、IDE集成DeepSeek保姆级教学(安装篇) 一、DeepSeek在CodeGPT中使用教学 1.1、Edit Code 编辑代码 选中代码片段 —> 右键 —> CodeGPT —> Edit Code, 输入自然语言可编辑代码,点击S…

Rohm发布TOLL封装650V GaN HEMT,引领汽车用GaN器件大规模生产新浪潮

Rohm震撼发布TOLL封装650V GaN HEMT,引领汽车用GaN器件大规模生产新浪潮。在创新的TOLL(TO LeadLess)封装技术的怀抱中,Rohm精心孕育出650V GaN HEMT这一瑰宝,此技术正如一股强劲东风,日益吹拂于高功率处理…

Spring Boot 3.x 基于 Redis 实现邮箱验证码认证

文章目录 依赖配置开启 QQ 邮箱 SMTP 服务配置文件代码实现验证码服务邮件服务接口实现执行流程 依赖配置 <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr…

PHP的学习

PHP的基础前提【HTML、CSS】 第一步先进行VS cood的下载&#xff1a;Visual Studio Code - Code Editing. Redefined 【选择适合自己的电脑的版本eg:我就是64位的win】

XML 编辑器:全面指南与最佳实践

XML 编辑器:全面指南与最佳实践 引言 XML(可扩展标记语言)编辑器是处理XML文件的关键工具,对于开发人员、系统管理员以及任何需要处理XML数据的人来说至关重要。本文将全面介绍XML编辑器的概念、功能、选择标准以及最佳实践,旨在帮助读者了解如何选择和使用合适的XML编辑…

《Effective Objective-C》阅读笔记(下)

目录 内存管理 理解引用计数 引用计数工作原理 自动释放池 保留环 以ARC简化引用计数 使用ARC时必须遵循的方法命名规则 变量的内存管理语义 ARC如何清理实例变量 在dealloc方法中只释放引用并解除监听 编写“异常安全代码”时留意内存管理问题 以弱引用避免保留环 …

ORM Bee V2.5.2.x 发布,支持 CQRS; sql 性能分析;更新 MongoDB ORM分片

Bee, 一个具有分片功能的 ORM 框架. Bee Hibernate/MyBatis plus Sharding JDBC Jpa Spring data GraphQL App ORM (Android, 鸿蒙) 小巧玲珑&#xff01;仅 940K, 还不到 1M, 但却是功能强大&#xff01; V2.5.2 (2025・LTS 版) 开发中... **2.5.2.1 新年 ** 支持 Mong…

springboot之HTML与图片生成

背景 后台需要根据字段动态生成HTML&#xff0c;并生成图片&#xff0c;发送邮件到给定邮箱 依赖 <!-- freemarker模板引擎--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifa…

《从0到1:用Python在鸿蒙系统开发安防图像分类AI功能》

在人工智能与移动应用深度融合的当下,类目标签AI功能成为众多行业提升效率和用户体验的关键技术。本文聚焦于HarmonyOS NEXT API 12及以上版本,以图像分类在智能家居安防领域的应用为例,为开发者详细阐述如何利用Python开发类目标签AI功能,助力鸿蒙技术在该领域的创新应用。…

【AD】3-10 原理图PDF导出

文件—智能PDF 多页原理图导出 导出设置时选择工程&#xff0c;可自行选择导出一页或多页原理图&#xff0c;一般PCB不用导出

【deepseek第一课】从0到1介绍 采用ollama安装deepseek私有化部署,并实现页面可视化

【deepseek第一课】从0到1介绍 采用ollama安装deepseek私有化部署,并实现页面可视化 1. ollama安装1.1 linux安装1.2 windows安装2. deepSeek支持的7种蒸馏模型2.1 蒸馏模型介绍2.2 7种模型特点2.3 安装deepseek-r1:14b模型3. openwebui图形化页面安装4. java连接大模型的三…

【在线用户监控】在线用户查询、强退用户

文章目录 在线用户监控在线用户监控API(RestController)当前在线会话在线用户查询强退用户知识扩展: JwtJwtTokenUtil生成jwt解析token登录授权的实现:json web token + redis + springboot在线用户监控 在线用户监控API(RestController) @RestController @Tag(name = &qu…

超详细,多图文介绍redis集群方式并搭建redis伪集群

超详细&#xff0c;多图文介绍redis集群方式并搭建redis伪集群 超多图文&#xff0c;对新手友好度极好。敲命令的过程中&#xff0c;难免会敲错&#xff0c;但为了截好一张合适的图&#xff0c;一旦出现一点问题&#xff0c;为了好的演示效果&#xff0c;就要从头开始敲。且看且…

Hue Load Balance配置

个人博客地址&#xff1a;Hue Load Balance配置 | 一张假钞的真实世界 直接上配置&#xff1a; server {server_name 192.168.72.31;listen 8001;charset utf-8;proxy_connect_timeout 600s;proxy_read_timeout 600s;proxy_send_timeout 600s;location / {proxy_set_header H…

992. K 个不同整数的子数组

目录 一、题目二、思路2.1 解题思路2.2 代码尝试2.3 疑难问题 三、解法四、收获4.1 心得4.2 举一反三 一、题目 二、思路 2.1 解题思路 2.2 代码尝试 class Solution { public:int subarraysWithKDistinct(vector<int>& nums, int k) {//需要有数据结构来存储数组…

领域驱动设计:事件溯源架构简介

概述 事件溯源架构通常由3种应用设计模式组成,分别是:事件驱动(Event Driven),事件溯源(Event Source)、CQRS(读写分离)。这三种应用设计模式常见于领域驱动设计(DDD)中,但它们本身是一种应用设计的思想,不仅仅局限于DDD,每一种模式都可以单独拿出来使用。 E…

PT2035 TWS 蓝牙耳机双触控双输出 IC

1. 产品概述 PT2035 是一款支持入耳检测的蓝牙耳机专用触摸芯片&#xff0c;该芯片具有宽工作电压、低功耗、高抗 干扰能力的特性。 2. 主要特性 工作电压范围&#xff1a; 2.4~5.5V 待机电流约 2.5uAV DD3V/CMOD5nF 入耳有效&#xff0c;无触摸时工作电流约 8uAV DD3…

AI编程界的集大成者——通义灵码AI程序员

一、引言 随着软件行业的快速发展和技术的进步&#xff0c;人工智能&#xff08;AI&#xff09;正在成为软件开发领域的一个重要组成部分。近年来&#xff0c;越来越多的AI辅助工具被引入到开发流程中&#xff0c;旨在提高效率、减少错误并加速创新。在这样的背景下&#xff0…

Rocky Linux 8.5 6G内存 静默模式(没图形界面)安装Oracle 19C

Oracle19c 下载地址 Database Software Downloads | Oraclehttps://www.oracle.com/database/technologies/oracle-database-software-downloads.html#db_ee 目录 一、准备服务器 1、服务器可以克隆、自己装 2、修改主机名 3、重启 4、关闭selinux 5、关闭防火墙 5.1、…