EG/core/imgui_style_manager.py
2026-01-16 09:36:35 +08:00

283 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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)