"""luiFunction component and HTTP creation mixin.""" import json import threading import time import urllib.error import urllib.request from pathlib import Path import panda3d.core as p3d from panda3d.core import CardMaker, NodePath from imgui_bundle import imgui, imgui_ctx from ui.LUI.lui_shared import ( LUICheckbox, LUIFrame, LUIHorizontalLayout, LUIButton, LUILabel, LUIInputField, LUISlider, LUISprite, LUIObject, LUIProgressbar, LUISelectbox, LUIScrollableRegion, LUITabbedFrame, LUIVerticalLayout, ) class LUIFunctionComponentsMixin: def create_canvas(manager, name=None): """创建一个可视化的 UI 面板作为画布""" manager.canvas_counter += 1 canvas_name = name or f"{manager.canvas_counter}" # 获取窗口尺寸 win_width = 800 win_height = 600 if hasattr(manager.world, 'win'): win_width = manager.world.win.getXSize() win_height = manager.world.win.getYSize() # 计算场景窗口区域(排除Hierarchy面板) left_offset = 0 if hasattr(manager.world, 'hierarchy_size'): left_offset = manager.world.hierarchy_size.x target_width = win_width - left_offset target_height = win_height canvas_panel = LUIObject(parent=manager.overlay_root, x=left_offset, y=0) # 显式设置尺寸 canvas_panel.width = target_width canvas_panel.height = target_height canvas_panel.solid = True # 接收事件 # 创建背景 Sprite bg = LUISprite(canvas_panel, "blank", "skin") bg.width = target_width bg.height = target_height bg.color = (0.5, 0.5, 0.5, 1.0) # 半透明灰色背景,可见但不遮挡ImGui # Canvas背景在LUI内部的底层 if hasattr(bg, 'set_sort'): bg.set_sort(-1) # LUI内部的底层 # Canvas面板使用默认层级(继承自LUI Region的sort=5) # 不需要额外设置sort,会自动继承LUI Region的层级 canvas_panel.solid = True # 接收事件,用于Canvas交互 # 创建Canvas的NodePath节点用于层级树显示 canvas_node = NodePath(canvas_name) canvas_node.reparent_to(manager.world.render) # 存储Canvas数据 canvas_data = { 'name': canvas_name, 'node': canvas_node, 'panel': canvas_panel, 'visible': True, 'background': bg, 'fill_mode': True, # 默认开启填充模式 'margins': { 'left': 240, 'right': 480, # 默认右侧面板宽度 'top': 89, 'bottom': 220 # 默认底部面板高度 } } # 应用初始几何计算 (调用 manager 的方法) # 注意:这里需要 manager 是 public 的或者我们访问 protected 方法 if hasattr(manager, '_update_canvas_geometry'): manager._update_canvas_geometry(canvas_data) # 添加面板标题 title_text = f"Canvas: {canvas_name}" title = LUILabel(text=title_text, font_size=16) title.set_parent(canvas_panel) title.set_pos(10, 10) # 内部 padding if hasattr(title, 'set_color'): title.set_color((0.8, 0.8, 0.8, 1.0)) # 存储标题引用以便后续修改 canvas_data['title_label'] = title # 启用Canvas拖动 if hasattr(manager, '_make_canvas_draggable'): manager._make_canvas_draggable(canvas_panel) manager.canvases.append(canvas_data) manager.current_canvas_index = len(manager.canvases) - 1 manager.switch_canvas(manager.current_canvas_index) # 调试输出:确认Canvas层级 print(f"✓ Created Canvas: {canvas_name}") print(f" Canvas panel sort: {getattr(canvas_panel, 'sort', 'N/A')}") print(f" Background sprite sort: {getattr(bg, 'sort', 'N/A')}") if hasattr(manager.world, 'updateSceneTree'): manager.world.updateSceneTree() return canvas_data def create_button(manager, text="Button", x=100, y=100, parent=None): """Create a LUI Button""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root btn = LUIButton(parent=parent, text=text, x=x, y=y) # Ensure size is initialized so selection bounds match the actual button default_w = 100 default_h = 30 try: if not hasattr(btn, 'width') or btn.width is None or btn.width <= 0: btn.width = default_w if not hasattr(btn, 'height') or btn.height is None or btn.height <= 0: btn.height = default_h except Exception: pass width = getattr(btn, 'width', default_w) or default_w height = getattr(btn, 'height', default_h) or default_h # 设置组件层级,在Canvas背景之上 if hasattr(btn, 'set_sort'): btn.set_sort(1) # 在Canvas背景之上 comp_data = { 'type': 'Button', 'object': btn, 'widget': btn, 'text': text, 'left': x, 'top': y, 'width': float(width), 'height': float(height), 'canvas_index': manager.current_canvas_index, 'name': f"Button_{len(manager.components)}", 'color': (1.0, 1.0, 1.0, 1.0), 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) if hasattr(manager, '_setup_component_drag'): manager._setup_component_drag(comp_data, comp_index) if hasattr(manager, '_add_to_scene_tree'): manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return btn def create_label(manager,text="Text", x=100, y=100, font_size=14, parent=None): """Create a LUI Text""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root lbl = LUILabel(parent=parent, text=text, x=x, y=y, solid=True, font_size=font_size) # 设置组件层级,在Canvas背景之上 if hasattr(lbl, 'set_sort'): lbl.set_sort(1) # 在Canvas背景之上 comp_data = { 'type': 'Text', 'object': lbl, 'text': text, 'left': x, 'top': y, 'width': 80, 'height': 20, 'canvas_index': manager.current_canvas_index, 'name': f"Text_{len(manager.components)}", 'color': (1, 1, 1, 1), 'font_size': font_size, 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return lbl def create_input_field(manager,text="", x=100, y=100, width=200, parent=None): """Create a LUI InputField""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root inf = LUIInputField(parent=parent, value=text, x=x, y=y, width=width) comp_data = { 'type': 'InputField', 'object': inf, 'text': text, 'left': x, 'top': y, 'width': width, 'height': 24, 'canvas_index': manager.current_canvas_index, 'name': f"InputField_{len(manager.components)}", 'value': text, 'placeholder': "输入文本...", 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return inf def create_slider(manager,x=100, y=100, width=200, parent=None): """Create a LUI Slider""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root sld = LUISlider(parent=parent, x=x, y=y, width=width) if hasattr(sld, 'set_height'): sld.set_height(16) if hasattr(sld, 'height'): sld.height = 16 comp_data = { 'type': 'Slider', 'object': sld, 'left': x, 'top': y, 'width': width, 'height': 16, 'canvas_index': manager.current_canvas_index, 'name': f"Slider_{len(manager.components)}", 'min_value': 0.0, 'max_value': 100.0, 'value': 50.0, 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return sld def create_frame(manager,x=100, y=100, width=300, height=200, parent=None): """Create a LUI Frame""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root frm = LUIFrame(parent=parent, x=x, y=y, width=width, height=height) comp_data = { 'type': 'Frame', 'object': frm, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"Frame_{len(manager.components)}", 'color': (0.7, 0.7, 0.7, 0.8), 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return frm def create_vertical_layout(manager, x=100, y=100, width=300, height=200, spacing=0.0, parent=None): """Create a LUI Vertical Layout""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root container = LUIObject(parent=parent, x=x, y=y) if hasattr(container, 'width'): container.width = width if hasattr(container, 'height'): container.height = height if hasattr(container, 'solid'): container.solid = True layout = LUIVerticalLayout(parent=container, spacing=spacing) padding_left = 0.0 padding_right = 0.0 padding_top = 0.0 padding_bottom = 0.0 inner_w = max(1.0, float(width) - (padding_left + padding_right)) inner_h = max(1.0, float(height) - (padding_top + padding_bottom)) if hasattr(layout, 'left'): layout.left = padding_left if hasattr(layout, 'top'): layout.top = padding_top if hasattr(layout, 'width'): layout.width = inner_w if hasattr(layout, 'height'): layout.height = inner_h comp_data = { 'type': 'VerticalLayout', 'object': container, 'layout_obj': layout, 'left': x, 'top': y, 'width': width, 'height': height, 'layout_spacing': spacing, 'layout_padding_left': padding_left, 'layout_padding_right': padding_right, 'layout_padding_top': padding_top, 'layout_padding_bottom': padding_bottom, 'layout_align': 'start', 'layout_wrap': True, 'layout_line_spacing': 0.0, 'canvas_index': manager.current_canvas_index, 'name': f"VerticalLayout_{len(manager.components)}", 'sort': 1, 'draggable': True } comp_index = len(manager.components) if hasattr(manager, '_setup_component_drag'): manager._setup_component_drag(comp_data, comp_index) if hasattr(manager, '_add_to_scene_tree'): manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return container def create_horizontal_layout(manager, x=100, y=100, width=300, height=200, spacing=0.0, parent=None): """Create a LUI Horizontal Layout""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root container = LUIObject(parent=parent, x=x, y=y) if hasattr(container, 'width'): container.width = width if hasattr(container, 'height'): container.height = height if hasattr(container, 'solid'): container.solid = True layout = LUIHorizontalLayout(parent=container, spacing=spacing) padding_left = 0.0 padding_right = 0.0 padding_top = 0.0 padding_bottom = 0.0 inner_w = max(1.0, float(width) - (padding_left + padding_right)) inner_h = max(1.0, float(height) - (padding_top + padding_bottom)) if hasattr(layout, 'left'): layout.left = padding_left if hasattr(layout, 'top'): layout.top = padding_top if hasattr(layout, 'width'): layout.width = inner_w if hasattr(layout, 'height'): layout.height = inner_h comp_data = { 'type': 'HorizontalLayout', 'object': container, 'layout_obj': layout, 'left': x, 'top': y, 'width': width, 'height': height, 'layout_spacing': spacing, 'layout_padding_left': padding_left, 'layout_padding_right': padding_right, 'layout_padding_top': padding_top, 'layout_padding_bottom': padding_bottom, 'layout_align': 'start', 'layout_wrap': True, 'layout_line_spacing': 0.0, 'canvas_index': manager.current_canvas_index, 'name': f"HorizontalLayout_{len(manager.components)}", 'sort': 1, 'draggable': True } comp_index = len(manager.components) if hasattr(manager, '_setup_component_drag'): manager._setup_component_drag(comp_data, comp_index) if hasattr(manager, '_add_to_scene_tree'): manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return container def create_checkbox(manager, label="Checkbox", x=100, y=100, parent=None): """Create a LUI Checkbox""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root chk = LUICheckbox(parent=parent, label=label, x=x, y=y) comp_data = { 'type': 'Checkbox', 'object': chk, 'text': label, 'left': x, 'top': y, 'width': 120, 'height': 20, 'canvas_index': manager.current_canvas_index, 'name': f"Checkbox_{len(manager.components)}", 'checked': False, 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return chk def create_plane(manager, x=100, y=100, width=100, height=100, color=(1,1,1,1), parent=None): """Create a LUI Plane (using Sprite with blank texture)""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root # 创建LUIObject容器 obj = LUIObject(parent=parent, x=x, y=y) obj.width = width obj.height = height obj.solid = True # 重要:设置为solid才能接收鼠标事件 # 设置组件层级,在Canvas背景之上 (Plane) if hasattr(obj, 'set_sort'): obj.set_sort(1) # 在Canvas背景之上 # 创建Sprite作为平面背景 spr = LUISprite(obj, "blank", "skin") spr.width = width spr.height = height spr.color = color spr.left = 0 spr.top = 0 comp_data = { 'type': 'Plane', 'object': obj, 'sprite': spr, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"Plane_{len(manager.components)}", 'color': color, 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return obj def create_image(manager, texture_path="blank", x=100, y=100, width=100, height=100, parent=None): """Create a LUI Image""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root # 创建LUIObject容器 obj = LUIObject(parent=parent, x=x, y=y) obj.width = width obj.height = height obj.solid = True # 重要:设置为solid才能接收鼠标事件 # 设置组件层级,在Canvas背景之上 (Image) if hasattr(obj, 'set_sort'): obj.set_sort(1) # 在Canvas背景之上 # 加载纹理 (使用皮肤中的'blank'或外部路径) # 注意: LUISprite通常使用图集图像名称或纹理 # 对于外部图像,可能需要加载纹理并使用不同的LUI元素 # 或将其注册到图集中。LUISprite(parent, image, atlas) # 为了简单起见,我们使用默认皮肤sprite,它有'blank' img_name = "blank" spr = LUISprite(obj, img_name, "skin") spr.width = width spr.height = height spr.left = 0 spr.top = 0 # 设置默认图像颜色(浅灰色,表示这是一个图像占位符) spr.color = (1.0, 1.0, 1.0, 1.0) comp_data = { 'type': 'Image', 'object': obj, 'sprite': spr, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"Image_{len(manager.components)}", 'texture_path': texture_path, 'color': (1.0, 1.0, 1.0, 1.0), 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return obj def create_video(manager, video_path="", x=100, y=100, width=320, height=240, parent=None): """Create a LUI Video Player component""" if not manager.lui_enabled: return None # 如果没有指定parent,使用当前Canvas的panel作为parent if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root # 1. 创建容器对象 obj = LUIObject(parent=parent, x=x, y=y) obj.width = width obj.height = height obj.solid = True # 设置层级 if hasattr(obj, 'set_sort'): obj.set_sort(1) # 2. 准备视频纹理 video_texture = None if video_path: try: from panda3d.core import Filename, MovieTexture, MovieVideo, loadPrcFileData # Trim whitespace which could cause "file not found" errors video_path = video_path.strip() # Allow network protocols loadPrcFileData("", "ffmpeg-protocol-whitelist http,https,tcp,tls,file") if "://" in video_path: # 尝试更稳定的加载序列 try: video_src = MovieVideo.get(Filename(video_path)) video_texture = MovieTexture("RemoteVideo") if not video_texture.load(video_src): video_texture = manager.world.loader.loadTexture(video_path) except: video_texture = manager.world.loader.loadTexture(video_path) else: video_texture = manager.world.loader.loadTexture(Filename.from_os_specific(video_path)) if video_texture: print(f"✓ 视频加载成功: {video_path}") # 尝试同步视频尺寸比例 orig_w = video_texture.getOrigFileXSize() orig_h = video_texture.getOrigFileYSize() if orig_w > 0 and orig_h > 0: ratio = orig_w / orig_h # 保持宽度,调整高度 new_height = width / ratio obj.height = new_height height = new_height # Update local var except Exception as e: print(f"⚠ 加载视频失败: {e}") # 2.1 ??????????????????????????????????????? audio_sound = None audio_path = "" audio_from_video = False if video_path and "://" not in video_path: try: audio_path = video_path audio_sound = manager.world.loader.loadSfx(p3d.Filename.from_os_specific(video_path)) if audio_sound: audio_from_video = True if hasattr(audio_sound, 'setLoop'): audio_sound.setLoop(True) if hasattr(audio_sound, 'setVolume'): audio_sound.setVolume(1.0) except Exception: audio_sound = None audio_path = "" # 3. 创建Sprite if video_texture: # 直接使用纹理创建,不使用Atlas spr = LUISprite(obj, video_texture) spr.color = (1, 1, 1, 1) # Video needs white to show original colors else: # 使用默认黑色背景 spr = LUISprite(obj, "blank", "skin") spr.color = (0.0, 0.0, 0.0, 1.0) spr.width = width spr.height = height if video_texture: obj.video_texture = video_texture # Prevent GC # Ensure full UVs if hasattr(spr, 'set_uv_range'): spr.set_uv_range(0, 0, 1, 1) # 如果支持,自动播放 if hasattr(video_texture, 'play'): # Stop first to reset state, critical for some video formats if hasattr(video_texture, 'stop'): video_texture.stop() video_texture.play() if hasattr(video_texture, 'setLoop'): video_texture.setLoop(True) if audio_sound: try: if hasattr(audio_sound, 'stop'): audio_sound.stop() if hasattr(audio_sound, 'play'): audio_sound.play() except Exception: pass # 4. 创建简单的控制栏 (可选,这里先做个占位符) # TODO: Play/Pause controls comp_data = { 'type': 'Video', 'object': obj, 'sprite': spr, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"Video_{len(manager.components)}", 'video_path': video_path, 'texture': video_texture, 'audio': audio_sound, 'audio_path': audio_path, 'audio_from_video': audio_from_video, 'volume': 1.0, 'is_playing': True, 'loop': True, 'sort': 1 } # 设置拖拽功能 comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) # 添加到层级树 manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return obj def create_http_text(manager, x=100, y=100, width=320, height=120, url="https://api.open-meteo.com/v1/forecast?latitude=39.9042&longitude=116.4074¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m&timezone=Asia%2FShanghai", parent=None): """Create a HTTP text component that fetches data from URL and renders it.""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root obj = LUIObject(parent=parent, x=x, y=y) obj.width = width obj.height = height obj.solid = True if hasattr(obj, 'set_sort'): obj.set_sort(1) # Panel background spr = LUISprite(obj, "blank", "skin") spr.width = width spr.height = height spr.left = 0 spr.top = 0 spr.color = (0.08, 0.14, 0.20, 0.92) # Text content text_label = LUILabel( parent=obj, text="HTTP组件\n点击“立即请求”获取数据", x=8, y=8, font_size=14, wordwrap=True ) text_label.color = (0.92, 0.95, 1.0, 1.0) comp_data = { 'type': 'HttpText', 'object': obj, 'sprite': spr, 'text_label': text_label, 'text': "HTTP组件\n点击“立即请求”获取数据", 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"HttpText_{len(manager.components)}", 'sort': 1, 'color': (0.08, 0.14, 0.20, 0.92), 'text_color': (0.92, 0.95, 1.0, 1.0), 'error_color': (1.0, 0.40, 0.40, 1.0), 'text_padding': 8.0, # HTTP settings 'http_url': url, 'http_method': 'GET', 'http_headers': '{}', 'http_body': '', 'http_timeout': 8.0, 'http_json_path': 'current', 'auto_refresh': True, 'refresh_interval': 60.0, 'max_chars': 300, 'http_status': '未请求', 'last_error': '', 'last_response_raw': '', 'last_refresh_time': 0.0, # Runtime flags '_http_inflight': False, '_http_pending_result': None, } manager.luiFunction.sync_http_text_layout(manager, comp_data) comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) # Trigger first request in background manager.luiFunction.trigger_http_request(manager, comp_data, force=True) return obj def sync_http_text_layout(manager, comp_data): """Keep HttpText internal label in sync with panel size and padding.""" if comp_data.get('type') != 'HttpText': return label = comp_data.get('text_label') if label is None: return pad = float(comp_data.get('text_padding', 8.0)) width = float(comp_data.get('width', 100.0)) height = float(comp_data.get('height', 40.0)) inner_w = max(1.0, width - pad * 2.0) inner_h = max(1.0, height - pad * 2.0) try: label.left = pad label.top = pad if hasattr(label, 'width'): label.width = inner_w if hasattr(label, 'height'): label.height = inner_h except Exception: pass def _extract_json_by_path(path_text, payload): """Extract value from JSON payload by a simple path: a.b[0].c""" if not path_text: return payload path = str(path_text).strip() if not path: return payload tokens = [] current = "" i = 0 while i < len(path): ch = path[i] if ch == '.': if current: tokens.append(('key', current)) current = "" i += 1 continue if ch == '[': if current: tokens.append(('key', current)) current = "" j = path.find(']', i + 1) if j < 0: raise ValueError(f"JSON路径非法: {path_text}") idx_text = path[i + 1:j].strip() if not idx_text: raise ValueError(f"JSON路径非法: {path_text}") tokens.append(('index', int(idx_text))) i = j + 1 continue current += ch i += 1 if current: tokens.append(('key', current)) node = payload for token_type, token_val in tokens: if token_type == 'key': if not isinstance(node, dict): raise KeyError(f"JSON路径错误,期望对象: {token_val}") node = node[token_val] else: if not isinstance(node, (list, tuple)): raise KeyError(f"JSON路径错误,期望数组: [{token_val}]") node = node[token_val] return node def _set_http_text_display(manager, comp_data, text, is_error=False): """Update HttpText display text and color.""" msg = text if text is not None else "" comp_data['text'] = msg label = comp_data.get('text_label') if label is None: return try: label.text = msg if is_error: color = comp_data.get('error_color', (1.0, 0.4, 0.4, 1.0)) else: color = comp_data.get('text_color', (0.92, 0.95, 1.0, 1.0)) if hasattr(label, 'set_color'): label.set_color(color) else: label.color = color except Exception: pass def trigger_http_request(manager, comp_data, force=False): """Start one async HTTP request for HttpText component.""" if comp_data.get('type') != 'HttpText': return False if comp_data.get('_http_inflight'): return False now_ts = time.time() if not force: interval = max(1.0, float(comp_data.get('refresh_interval', 60.0))) last_ts = float(comp_data.get('last_refresh_time', 0.0)) if now_ts - last_ts < interval: return False url = str(comp_data.get('http_url', '')).strip() if not url: comp_data['http_status'] = "URL为空" comp_data['last_error'] = "URL为空" manager.luiFunction._set_http_text_display(manager, comp_data, "HTTP请求失败: URL为空", is_error=True) return False method = str(comp_data.get('http_method', 'GET')).upper().strip() if method not in ('GET', 'POST'): method = 'GET' comp_data['http_method'] = 'GET' timeout_sec = max(1.0, float(comp_data.get('http_timeout', 8.0))) headers_raw = str(comp_data.get('http_headers', '{}')) body_raw = str(comp_data.get('http_body', '')) json_path = str(comp_data.get('http_json_path', '')).strip() max_chars = max(32, int(comp_data.get('max_chars', 300))) comp_data['_http_inflight'] = True comp_data['http_status'] = "请求中..." def _worker(): result = { 'ok': False, 'message': '', 'status_code': None, 'error': '', 'time': time.time(), 'raw': '', } try: headers = {} if headers_raw.strip(): parsed_headers = json.loads(headers_raw) if not isinstance(parsed_headers, dict): raise ValueError("Headers 必须是 JSON 对象") headers = {str(k): str(v) for k, v in parsed_headers.items()} data_bytes = None if body_raw: data_bytes = body_raw.encode('utf-8') request = urllib.request.Request( url=url, data=data_bytes, headers=headers, method=method ) with urllib.request.urlopen(request, timeout=timeout_sec) as response: status_code = getattr(response, 'status', 200) raw_bytes = response.read() charset = None response_headers = getattr(response, 'headers', None) if response_headers is not None and hasattr(response_headers, 'get_content_charset'): charset = response_headers.get_content_charset() if not charset: charset = "utf-8" raw_text = raw_bytes.decode(charset, errors='replace') display_text = raw_text.strip() if json_path: json_payload = json.loads(raw_text) extracted = manager.luiFunction._extract_json_by_path(json_path, json_payload) if isinstance(extracted, (dict, list)): display_text = json.dumps(extracted, ensure_ascii=False) else: display_text = str(extracted) if len(display_text) > max_chars: display_text = display_text[:max_chars] + "..." result['ok'] = True result['message'] = display_text result['status_code'] = status_code result['raw'] = raw_text except Exception as err: result['error'] = str(err) comp_data['_http_pending_result'] = result thread = threading.Thread(target=_worker, daemon=True) thread.start() return True def update_http_components(manager): """Consume async HTTP results and run auto-refresh.""" now_ts = time.time() for comp_data in manager.components: if comp_data.get('type') != 'HttpText': continue pending = comp_data.get('_http_pending_result') if pending is not None: comp_data['_http_pending_result'] = None comp_data['_http_inflight'] = False comp_data['last_refresh_time'] = float(pending.get('time', now_ts)) comp_data['last_response_raw'] = pending.get('raw', '') if pending.get('ok'): status_code = pending.get('status_code') stamp = time.strftime("%H:%M:%S", time.localtime(comp_data['last_refresh_time'])) comp_data['http_status'] = f"HTTP {status_code} @ {stamp}" comp_data['last_error'] = '' manager.luiFunction._set_http_text_display( manager, comp_data, pending.get('message', ''), is_error=False ) else: error_msg = pending.get('error', '未知错误') comp_data['last_error'] = error_msg comp_data['http_status'] = "请求失败" manager.luiFunction._set_http_text_display( manager, comp_data, f"HTTP请求失败: {error_msg}", is_error=True ) # Auto refresh if comp_data.get('auto_refresh', False) and not comp_data.get('_http_inflight'): interval = max(1.0, float(comp_data.get('refresh_interval', 60.0))) last_ts = float(comp_data.get('last_refresh_time', 0.0)) if now_ts - last_ts >= interval: manager.luiFunction.trigger_http_request(manager, comp_data, force=True) def create_progressbar(manager, value=50, x=100, y=100, width=200, parent=None): """Create a LUI Progressbar""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root pg = LUIProgressbar(parent=parent, width=width, value=value) pg.left = x pg.top = y pg.solid = True # Ensure it receives input if hasattr(pg, 'set_sort'): pg.set_sort(1) comp_data = { 'type': 'Progressbar', 'object': pg, 'left': x, 'top': y, 'width': width, 'height': 30, # default height 'canvas_index': manager.current_canvas_index, 'name': f"Progressbar_{len(manager.components)}", 'value': value, 'sort': 1 } comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return pg def create_selectbox(manager, options=None, x=100, y=100, width=200, parent=None): """Create a LUI Selectbox""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root if options is None: options = [(0, "Option 1"), (1, "Option 2"), (2, "Option 3")] sb = LUISelectbox(width=width, options=options, selected_option=0) sb.parent = parent sb.left = x sb.top = y sb.solid = True # Ensure it receives input if hasattr(sb, 'set_sort'): sb.set_sort(1) comp_data = { 'type': 'Selectbox', 'object': sb, 'left': x, 'top': y, 'width': width, 'height': 30, # default height 'canvas_index': manager.current_canvas_index, 'name': f"Selectbox_{len(manager.components)}", 'options': options, 'sort': 1 } comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return sb def create_scrollable_region(manager, x=100, y=100, width=200, height=200, parent=None): """Create a LUI Scrollable Region""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root sr = LUIScrollableRegion(parent=parent, width=width, height=height) sr.left = x sr.top = y sr.solid = True # Ensure it receives input if hasattr(sr, 'set_sort'): sr.set_sort(1) comp_data = { 'type': 'ScrollableRegion', 'object': sr, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"ScrollableRegion_{len(manager.components)}", 'sort': 1 } comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return sr def create_tabbed_frame(manager, x=100, y=100, width=300, height=200, parent=None): """Create a LUI Tabbed Frame""" if not manager.lui_enabled: return None if parent is None and manager.current_canvas_index >= 0: parent = manager.canvases[manager.current_canvas_index]['panel'] elif parent is None: parent = manager.overlay_root tf = LUITabbedFrame(parent=parent, width=width, height=height) tf.left = x tf.top = y tf.solid = True # Ensure it receives input # Add default tabs f1 = LUIFrame(width=width, height=height) tf.add("Tab 1", f1) f2 = LUIFrame(width=width, height=height) tf.add("Tab 2", f2) if hasattr(tf, 'set_sort'): tf.set_sort(1) comp_data = { 'type': 'TabbedFrame', 'object': tf, 'left': x, 'top': y, 'width': width, 'height': height, 'canvas_index': manager.current_canvas_index, 'name': f"TabbedFrame_{len(manager.components)}", 'sort': 1 } comp_index = len(manager.components) manager._setup_component_drag(comp_data, comp_index) manager._add_to_scene_tree(comp_data) manager.components.append(comp_data) return tf