【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.pswp.cn/diannao/72113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/72113.shtml
英文地址,請注明出處:http://en.pswp.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、…