283 lines
12 KiB
Python
283 lines
12 KiB
Python
"""
|
||
ImGui样式管理器
|
||
用于统一管理ImGui UI样式,与主程序Qt UI保持一致
|
||
"""
|
||
|
||
from imgui_bundle import imgui, imgui_ctx
|
||
import os
|
||
import platform
|
||
from pathlib import Path
|
||
from panda3d.core import Filename
|
||
|
||
|
||
class ImGuiStyleManager:
|
||
"""ImGui样式管理器 - 负责UI样式的统一管理"""
|
||
|
||
def __init__(self, imgui_backend):
|
||
"""
|
||
初始化样式管理器
|
||
|
||
Args:
|
||
imgui_backend: ImGui后端对象
|
||
"""
|
||
self.imgui_backend = imgui_backend
|
||
self.io = imgui_backend.io
|
||
self.style = None # 延迟初始化,在apply_style中设置
|
||
|
||
# 颜色定义 - 与Qt UI保持一致
|
||
self.colors = {
|
||
# 主要颜色
|
||
'primary': (0.188, 0.404, 0.753, 1.0), # #3067C0 - 主色调
|
||
'primary_dark': (0.149, 0.337, 0.627, 1.0), # #2556A0 - 按钮按下
|
||
'background': (0.0, 0.0, 0.0, 1.0), # #000000 - 主背景
|
||
'secondary_bg': (0.096, 0.096, 0.106, 1.0), # #19191B - 次背景
|
||
'panel_bg': (0.18, 0.188, 0.208, 1.0), # #2E3035 - 面板背景
|
||
'text': (0.922, 0.922, 0.922, 1.0), # #EBEBEB - 主文字
|
||
'text_secondary': (0.922, 0.922, 0.922, 0.6), # #EBEBEB - 次文字
|
||
'border': (0.243, 0.243, 0.259, 1.0), # #3E3E42 - 主要边框
|
||
'border_secondary': (0.173, 0.184, 0.212, 1.0), # #2C2F36 - 次要边框
|
||
'button_bg': (0.349, 0.384, 0.463, 0.5), # 半透明按钮背景
|
||
'input_bg': (0.349, 0.392, 0.443, 0.2), # 输入框背景
|
||
'input_border': (0.298, 0.361, 0.431, 0.6), # 输入框边框
|
||
'success': (0.176, 1.0, 0.769, 1.0), # #2dffc4 - 成功状态
|
||
'warning': (0.953, 0.616, 0.471, 1.0), # #f39d78 - 警告状态
|
||
'info': (0.157, 0.620, 1.0, 1.0), # #289eff - 信息状态
|
||
}
|
||
|
||
# 尺寸定义
|
||
self.sizes = {
|
||
'font_size': 12.0, # 与Qt一致的字体大小
|
||
'window_padding': (8.0, 8.0),
|
||
'window_rounding': 2.0,
|
||
'item_spacing': (6.0, 6.0),
|
||
'item_inner_spacing': (4.0, 4.0),
|
||
'frame_padding': (4.0, 3.0),
|
||
'frame_rounding': 2.0,
|
||
'indent_spacing': 20.0,
|
||
'scrollbar_size': 14.0,
|
||
}
|
||
|
||
# 字体设置在demo.py中直接处理,这里不再初始化
|
||
|
||
def _setup_fonts(self):
|
||
"""设置字体,包括中文字体支持"""
|
||
try:
|
||
# 尝试加载中文字体
|
||
font_path = self._get_chinese_font_path()
|
||
if font_path:
|
||
# 使用p3dimgui的字体加载方法
|
||
self.imgui_backend.load_font(font_path, self.sizes['font_size'])
|
||
print(f"✓ ImGui中文字体加载成功: {font_path}")
|
||
else:
|
||
print("⚠ 无法加载中文字体,使用默认字体")
|
||
|
||
except Exception as e:
|
||
print(f"⚠ ImGui字体设置失败: {e}")
|
||
# 备用方案:尝试使用默认字体
|
||
try:
|
||
# 使用p3dimgui的默认字体
|
||
self.imgui_backend.load_font(None, self.sizes['font_size'])
|
||
except:
|
||
pass
|
||
|
||
def _get_chinese_font_path(self):
|
||
"""获取中文字体路径"""
|
||
system = platform.system().lower()
|
||
project_root = Path(__file__).resolve().parent.parent
|
||
|
||
# 候选字体路径
|
||
if system == "windows":
|
||
win_dir = os.environ.get("WINDIR") or r"C:\Windows"
|
||
font_candidates = [
|
||
project_root / "RenderPipelineFile" / "data" / "font" / "msyh.ttc",
|
||
Path(win_dir) / "Fonts" / "msyh.ttc",
|
||
Path(win_dir) / "Fonts" / "msyh.ttf",
|
||
Path(win_dir) / "Fonts" / "simhei.ttf",
|
||
]
|
||
elif system == "darwin":
|
||
font_candidates = [
|
||
Path("/System/Library/Fonts/PingFang.ttc"),
|
||
Path("/System/Library/Fonts/STHeiti.ttc"),
|
||
]
|
||
else: # Linux
|
||
font_candidates = [
|
||
Path("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc"),
|
||
Path("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"),
|
||
]
|
||
|
||
# 尝试找到存在的字体文件
|
||
for font_path in font_candidates:
|
||
if font_path.exists():
|
||
return str(font_path)
|
||
|
||
return None
|
||
|
||
def apply_style(self):
|
||
"""应用与Qt UI一致的样式"""
|
||
# 先应用深色主题作为基础
|
||
imgui.style_colors_dark()
|
||
|
||
# 获取样式对象
|
||
self.style = imgui.get_style()
|
||
style = self.style
|
||
|
||
# 应用自定义颜色
|
||
style.colors[imgui.COLOR_WINDOW_BG] = self.colors['background']
|
||
style.colors[imgui.COLOR_CHILD_BG] = self.colors['secondary_bg']
|
||
style.colors[imgui.COLOR_POPUP_BG] = self.colors['panel_bg']
|
||
style.colors[imgui.COLOR_BORDER] = self.colors['border']
|
||
style.colors[imgui.COLOR_BORDER_SHADOW] = self.colors['border_secondary']
|
||
|
||
# 文本颜色
|
||
style.colors[imgui.COLOR_TEXT] = self.colors['text']
|
||
style.colors[imgui.COLOR_TEXT_DISABLED] = self.colors['text_secondary']
|
||
style.colors[imgui.COLOR_TEXT_SELECTED_BG] = self.colors['primary']
|
||
|
||
# 菜单栏颜色
|
||
style.colors[imgui.COLOR_MENU_BAR_BG] = self.colors['background']
|
||
|
||
# 按钮颜色
|
||
style.colors[imgui.COLOR_BUTTON] = self.colors['button_bg']
|
||
style.colors[imgui.COLOR_BUTTON_HOVERED] = self.colors['primary']
|
||
style.colors[imgui.COLOR_BUTTON_ACTIVE] = self.colors['primary_dark']
|
||
|
||
# 复选框和单选按钮
|
||
style.colors[imgui.COLOR_CHECK_MARK] = self.colors['primary']
|
||
style.colors[imgui.COLOR_RADIO_BUTTON_HOVERED] = self.colors['primary']
|
||
style.colors[imgui.COLOR_RADIO_BUTTON_ACTIVE] = self.colors['primary_dark']
|
||
|
||
# 滑块
|
||
style.colors[imgui.COLOR_SLIDER_GRAB] = self.colors['primary']
|
||
style.colors[imgui.COLOR_SLIDER_GRAB_ACTIVE] = self.colors['primary_dark']
|
||
|
||
# 进度条
|
||
style.colors[imgui.COLOR_PROGRESS_BAR_FG] = self.colors['primary']
|
||
|
||
# 标题栏
|
||
style.colors[imgui.COLOR_TITLE_BG] = self.colors['panel_bg']
|
||
style.colors[imgui.COLOR_TITLE_BG_ACTIVE] = self.colors['primary']
|
||
style.colors[imgui.COLOR_TITLE_BG_COLLAPSED] = self.colors['border_secondary']
|
||
|
||
# 输入框
|
||
style.colors[imgui.COLOR_FRAME_BG] = self.colors['input_bg']
|
||
style.colors[imgui.COLOR_FRAME_BG_HOVERED] = self.colors['input_border']
|
||
style.colors[imgui.COLOR_FRAME_BG_ACTIVE] = (
|
||
self.colors['primary'][0],
|
||
self.colors['primary'][1],
|
||
self.colors['primary'][2],
|
||
0.1 # 低透明度
|
||
)
|
||
|
||
# 标签页
|
||
style.colors[imgui.COLOR_TAB] = self.colors['button_bg']
|
||
style.colors[imgui.COLOR_TAB_HOVERED] = self.colors['primary']
|
||
style.colors[imgui.COLOR_TAB_ACTIVE] = self.colors['primary']
|
||
style.colors[imgui.COLOR_TAB_UNFOCUSED] = self.colors['button_bg']
|
||
style.colors[imgui.COLOR_TAB_UNFOCUSED_ACTIVE] = self.colors['primary_dark']
|
||
|
||
# 选择高亮
|
||
style.colors[imgui.COLOR_HEADER] = self.colors['primary']
|
||
style.colors[imgui.COLOR_HEADER_HOVERED] = self.colors['primary']
|
||
style.colors[imgui.COLOR_HEADER_ACTIVE] = self.colors['primary_dark']
|
||
|
||
# 分隔符
|
||
style.colors[imgui.COLOR_SEPARATOR] = self.colors['border_secondary']
|
||
style.colors[imgui.COLOR_SEPARATOR_HOVERED] = self.colors['border']
|
||
style.colors[imgui.COLOR_SEPARATOR_ACTIVE] = self.colors['primary']
|
||
|
||
# 调整尺寸和间距
|
||
style.window_padding = self.sizes['window_padding']
|
||
style.window_rounding = self.sizes['window_rounding']
|
||
style.window_min_size = (200, 100)
|
||
style.child_rounding = self.sizes['frame_rounding']
|
||
style.frame_padding = self.sizes['frame_padding']
|
||
style.frame_rounding = self.sizes['frame_rounding']
|
||
style.item_spacing = self.sizes['item_spacing']
|
||
style.item_inner_spacing = self.sizes['item_inner_spacing']
|
||
style.indent_spacing = self.sizes['indent_spacing']
|
||
style.scrollbar_size = self.sizes['scrollbar_size']
|
||
style.scrollbar_rounding = self.sizes['frame_rounding']
|
||
style.grab_min_size = 10.0
|
||
style.grab_rounding = self.sizes['frame_rounding']
|
||
|
||
# 禁用一些ImGui的默认效果,使其更像Qt
|
||
style.window_border_size = 1.0
|
||
style.child_border_size = 1.0
|
||
style.popup_border_size = 1.0
|
||
style.frame_border_size = 1.0
|
||
|
||
print("✓ ImGui样式已应用,与Qt UI保持一致")
|
||
|
||
def get_window_flags(self, window_type="default"):
|
||
"""获取不同类型窗口的标志(支持docking)"""
|
||
base_flags = 0
|
||
|
||
if window_type == "main_menu":
|
||
return imgui.WindowFlags_.menu_bar
|
||
elif window_type == "dockable":
|
||
# 移除NO_MOVE和NO_RESIZE以支持docking
|
||
return (base_flags |
|
||
imgui.WindowFlags_.no_title_bar |
|
||
imgui.WindowFlags_.no_collapse)
|
||
elif window_type == "toolbar":
|
||
# 工具栏允许docking但保持无标题栏
|
||
return (base_flags |
|
||
imgui.WindowFlags_.no_title_bar |
|
||
imgui.WindowFlags_.no_collapse |
|
||
imgui.WindowFlags_.no_scrollbar)
|
||
elif window_type == "panel":
|
||
# 面板窗口完全支持docking
|
||
return base_flags # 移除NO_COLLAPSE限制
|
||
|
||
return base_flags
|
||
|
||
def begin_styled_window(self, name, open=True, flags=None, window_type="default"):
|
||
"""开始一个带样式的窗口"""
|
||
if flags is None:
|
||
flags = self.get_window_flags(window_type)
|
||
|
||
return imgui_ctx.begin(name, open, flags)
|
||
|
||
def styled_button(self, label, size=(0, 0)):
|
||
"""绘制带样式的按钮"""
|
||
return imgui.button(label, size)
|
||
|
||
def styled_input_text(self, label, value, buffer_size=256):
|
||
"""绘制带样式的文本输入框"""
|
||
changed, new_value = imgui.input_text(label, value, buffer_size)
|
||
return changed, new_value
|
||
|
||
def styled_slider_float(self, label, value, min_val, max_val, format="%.3f"):
|
||
"""绘制带样式的浮点滑块"""
|
||
changed, new_value = imgui.slider_float(label, value, min_val, max_val, format)
|
||
return changed, new_value
|
||
|
||
def load_icon(self, icon_name):
|
||
"""加载图标纹理"""
|
||
try:
|
||
# 构建图标路径
|
||
project_root = Path(__file__).resolve().parent.parent
|
||
icon_path = project_root / "icons" / f"{icon_name}.png"
|
||
|
||
if icon_path.exists():
|
||
return self.imgui_backend.loadTexture(str(icon_path))
|
||
else:
|
||
print(f"⚠ 图标文件不存在: {icon_path}")
|
||
return None
|
||
except Exception as e:
|
||
print(f"⚠ 加载图标失败: {e}")
|
||
return None
|
||
|
||
def image_button(self, texture_id, size=(32, 32), bg_col=(0, 0, 0, 0), tint_col=(1, 1, 1, 1)):
|
||
"""绘制图像按钮"""
|
||
return imgui.image_button(texture_id, size, bg_col, tint_col)
|
||
|
||
def get_icon_text_button(self, icon_texture, text, size=(0, 0)):
|
||
"""绘制带图标的文本按钮"""
|
||
if icon_texture:
|
||
# 先绘制图标
|
||
imgui.image(icon_texture, (16, 16))
|
||
imgui.same_line()
|
||
|
||
# 再绘制文本按钮
|
||
return imgui.button(text, size) |