From a5b20ec40df80ab700d952906b0c3ce4429a828b Mon Sep 17 00:00:00 2001 From: march3 Date: Sun, 19 Mar 2023 10:15:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=AA=E9=98=B3=E7=B3=BB=E4=B8=89=E4=BD=93?= =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/func.py | 2 + simulators/ursina/ui/__init__.py | 0 simulators/ursina/ui/control_handler.py | 209 ++++++++++++++++++++++++ simulators/ursina/ui/control_ui.py | 95 +++++++++++ simulators/ursina/ui/event_handler.py | 18 ++ simulators/ursina/ui/ui_panel.py | 62 +++++++ simulators/ursina/ui_component.py | 20 +++ simulators/ursina/ursina_event.py | 13 ++ simulators/ursina/ursina_ui.py | 12 +- simulators/ursina_simulator.py | 9 +- 10 files changed, 437 insertions(+), 3 deletions(-) create mode 100644 simulators/ursina/ui/__init__.py create mode 100644 simulators/ursina/ui/control_handler.py create mode 100644 simulators/ursina/ui/control_ui.py create mode 100644 simulators/ursina/ui/event_handler.py create mode 100644 simulators/ursina/ui/ui_panel.py diff --git a/scenes/func.py b/scenes/func.py index 81b51bb..7842db8 100644 --- a/scenes/func.py +++ b/scenes/func.py @@ -9,6 +9,7 @@ import matplotlib.pyplot as plt from common.consts import SECONDS_PER_WEEK, SECONDS_PER_DAY, SECONDS_PER_HALF_DAY from common.system import System +from simulators.ursina.ursina_event import UrsinaEvent def mayavi_run(bodies, dt=SECONDS_PER_WEEK, @@ -111,6 +112,7 @@ def ursina_run(bodies, # camera.look_at(entity.position) def callback_update(): + UrsinaEvent.on_application_run() for ursina_view in simulator.ursina_views: simulator.check_and_evolve() if ursina_view.appeared: diff --git a/simulators/ursina/ui/__init__.py b/simulators/ursina/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/simulators/ursina/ui/control_handler.py b/simulators/ursina/ui/control_handler.py new file mode 100644 index 0000000..83a6e97 --- /dev/null +++ b/simulators/ursina/ui/control_handler.py @@ -0,0 +1,209 @@ +# -*- coding:utf-8 -*- +# title :ursina天体运行模拟器UI控制 +# description :ursina天体运行模拟器UI控制 +# author :Python超人 +# date :2023-02-11 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== +from ursina import Ursina, window, Entity, Grid, Mesh, camera, Text, application, color, mouse, Vec2, Vec3, \ + load_texture, held_keys, Button, ButtonList, destroy, scene, distance, Sequence, Wait, Func +from ursina.prefabs.first_person_controller import FirstPersonController +from common.consts import SECONDS_PER_HOUR, SECONDS_PER_HALF_DAY, \ + SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR +from common.consts import AU +from simulators.ursina.ui.ui_panel import UiPanel +from simulators.ursina.ui_component import UiSlider, SwithButton, UiButton, Buttons +from simulators.ursina.ursina_config import UrsinaConfig +from simulators.ursina.ursina_event import UrsinaEvent +from ursina import WindowPanel, InputField, Button, Slider, ButtonGroup, Panel, invoke +from simulators.ursina.ui.event_handler import EventHandler + + +class ControlHandler(EventHandler): + + def handler_input_init(self): + self.settings_handler = Entity(ignore_paused=True) + self.settings_handler.input = self.settings_handler_input + key_info_str = "方位控制[键盘QWEASD]+[鼠标右键],按[空格]更多控制" + key_info = Text(text=key_info_str, font=UrsinaConfig.CN_FONT, position=(-1, 0.5), origin=(-1, 1), + background=True) + + def sec_per_time_switch_changed(self): + # ("默认", "天", "周", "月", "年", "十年", "百年") + if self.ui.sec_per_time_switch.value == "天": + UrsinaConfig.seconds_per = SECONDS_PER_DAY + elif self.ui.sec_per_time_switch.value == "周": + UrsinaConfig.seconds_per = SECONDS_PER_WEEK + elif self.ui.sec_per_time_switch.value == "月": + UrsinaConfig.seconds_per = SECONDS_PER_MONTH + elif self.ui.sec_per_time_switch.value == "年": + UrsinaConfig.seconds_per = SECONDS_PER_YEAR + elif self.ui.sec_per_time_switch.value == "十年": + UrsinaConfig.seconds_per = SECONDS_PER_YEAR * 10 + elif self.ui.sec_per_time_switch.value == "百年": + UrsinaConfig.seconds_per = SECONDS_PER_YEAR * 100 + else: + UrsinaConfig.seconds_per = 0 + + def on_off_trail_changed(self): + if self.ui.on_off_trail.value == self.ui.trail_button_text: + UrsinaConfig.show_trail = True + else: + UrsinaConfig.show_trail = False + + def move_camera_to_entity(self, camera_pos: Vec3, entity_pos: Vec3, _distance: float) -> Vec3: + # 计算摄像机到实体的向量 + direction = entity_pos - camera_pos + # 计算当前距离 + current_distance = direction.length() + # 如果当前距离已经小于等于要求的距离,则直接返回实体坐标 + if current_distance <= _distance: + return camera_pos + # 计算需要移动的距离 + _distance = current_distance - _distance + # 根据需要移动的距离计算移动向量 + move_vector = direction.normalized() * _distance + # 返回摄像机移动后的坐标 + return camera_pos + move_vector + + def move_camera_to_entity(self, entity, d): + camera.position = entity.position # - Vec3(0, 0, d) # 设置摄像机位置 + camera.world_position = entity.position + + def bodies_button_list_click(self, item): + if item is not None: + # TODO: 先找到位置,确定摄像机的位置 + try: + d = item.planet.scale_x * 20 + self.move_camera_to_entity(item.planet, d) + except Exception as e: + self.ui.show_message(f"{item}飞不见了") + + self.bodies_button_list_close() + + def bodies_button_list_close(self): + if hasattr(self, "bodies_button_list"): + self.bodies_button_list.enabled = False + destroy(self.bodies_button_list) + + def on_searching_bodies_click(self): + results = UrsinaEvent.on_searching_bodies() + if len(results) > 0: + sub_name, bodies = results[0] + if len(bodies) == 0: + self.show_message("天体都飞不见了,请重新运行。") + # button_dict = {"天体都飞不见了,请重新运行。": lambda: self.bodies_button_list_click(None)} + return + # print(results[0]) + button_dict = {"[关闭] == 寻找天体 ==": lambda: self.bodies_button_list_click(None)} + camera = scene.camera + for body in bodies: + def callback_action(b=body): + self.bodies_button_list_click(b) + + if body.appeared: + distance_to_entity = distance(body.planet, camera) + d = distance_to_entity / UrsinaConfig.SCALE_FACTOR / AU + name = f"{body.name}\t距离:{d:.4f}天文单位" + button_dict[name] = callback_action + else: + if hasattr(self, "bodies_button_list"): + self.bodies_button_list_close() + name = f"{body.name}\t距离太远,找不到了" + button_dict[name] = lambda: self.bodies_button_list_click(None) + + if hasattr(self, "bodies_button_list"): + self.bodies_button_list_close() + + self.bodies_button_list = ButtonList(button_dict, font=UrsinaConfig.CN_FONT, button_height=1.5) + + def on_reset_button_click(self): + UrsinaEvent.on_reset() + + def on_buttons_changed(self): + if self.ui.buttons.value == "寻找": + self.on_searching_bodies_click() + elif self.ui.buttons.value == "重启": + self.on_reset_button_click() + + def on_off_switch_changed(self): + if self.ui.on_off_switch.value == self.ui.pause_button_text: + self.ui.on_off_switch.selected_color = color.green + application.paused = True + for c in self.ui.children: + if not c.ignore_paused: + # c.enabled = True + c.disabled = False + else: + self.ui.on_off_switch.selected_color = color.red + application.paused = False + for c in self.ui.children: + if not c.ignore_paused: + # c.enabled = True + c.disabled = False + + def on_slider_trail_length_changed(self): + UrsinaConfig.trail_length = int(self.ui.slider_trail_length.value) + + def on_slider_control_speed_changed(self): + application.time_scale = self.ui.slider_control_speed_factor.value + + def on_slider_body_spin_changed(self): + UrsinaConfig.body_spin_factor = self.ui.slider_body_spin_factor.value + + def on_slider_body_size_changed(self): + UrsinaConfig.body_size_factor = self.ui.slider_body_size_factor.value + + def on_slider_run_speed_changed(self): + UrsinaConfig.run_speed_factor = self.ui.slider_run_speed_factor.value + + def settings_handler_input(self, key): + import sys + if key == "escape": + sys.exit() + # print(key) + elif key == 'space': + self.ui.enabled = not self.ui.enabled + elif key == 'left mouse down': + print(key) + elif key == 'y': # 寻找天体 + if hasattr(self, "bodies_button_list"): + if self.ui.bodies_button_list.enabled: + self.bodies_button_list_close() + return + self.on_searching_bodies_click() + elif key == 'o': # 重新开始 + paused = application.paused + if paused: # 如果是暂停状态,先不暂停,等重新开始后再暂停 + application.paused = False + self.on_reset_button_click() + if paused: + def application_paused(): + application.paused = True + UrsinaEvent.on_application_run_callback_subscription(application_paused) + + elif key == 'i': # 拖尾开关 + if self.ui.on_off_trail.value == self.ui.trail_button_text: + self.ui.on_off_trail.value = self.ui.no_trail_button_text + else: + self.ui.on_off_trail.value = self.ui.trail_button_text + self.on_off_trail_changed() + elif key == 'p': # 开始、暂停 + if self.ui.on_off_switch.value == self.ui.pause_button_text: + self.ui.on_off_switch.value = self.ui.start_button_text + else: + self.ui.on_off_switch.value = self.ui.pause_button_text + self.on_off_switch_changed() + elif key == '+' or key == "= up": + run_speed_factor = self.ui.slider_run_speed_factor.value + self.ui.slider_run_speed_factor.step * 50 + if run_speed_factor > self.ui.slider_run_speed_factor.max: + run_speed_factor = self.ui.slider_run_speed_factor.max + self.ui.slider_run_speed_factor.value = run_speed_factor + self.ui.slider_run_speed_factor.knob.drop() + elif key == '-' or key == "- up": + run_speed_factor = self.ui.slider_run_speed_factor.value - self.ui.slider_run_speed_factor.step * 50 + if run_speed_factor < self.ui.slider_run_speed_factor.min: + run_speed_factor = self.ui.slider_run_speed_factor.min + self.ui.slider_run_speed_factor.value = run_speed_factor + self.ui.slider_run_speed_factor.knob.drop() diff --git a/simulators/ursina/ui/control_ui.py b/simulators/ursina/ui/control_ui.py new file mode 100644 index 0000000..d3eb249 --- /dev/null +++ b/simulators/ursina/ui/control_ui.py @@ -0,0 +1,95 @@ +# -*- coding:utf-8 -*- +# title :ursina天体运行模拟器UI控制 +# description :ursina天体运行模拟器UI控制 +# author :Python超人 +# date :2023-02-11 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== +from ursina import Ursina, window, Entity, Grid, Mesh, camera, Text, application, color, mouse, Vec2, Vec3, \ + load_texture, held_keys, Button, ButtonList, destroy, scene, distance, Sequence, Wait, Func +from ursina.prefabs.first_person_controller import FirstPersonController +from common.consts import SECONDS_PER_HOUR, SECONDS_PER_HALF_DAY, \ + SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR +from common.consts import AU +from simulators.ursina.ui.control_handler import ControlHandler +from simulators.ursina.ui.ui_panel import UiPanel +from simulators.ursina.ui_component import UiSlider, SwithButton, UiButton, Buttons +from simulators.ursina.ursina_config import UrsinaConfig +from simulators.ursina.ursina_event import UrsinaEvent +from ursina import WindowPanel, InputField, Button, Slider, ButtonGroup, Panel, invoke + + +class ControlUI(UiPanel): + def component_init(self): + self.start_button_text = "●" # 》●▲○◎ + self.pause_button_text = "〓" # 〓 || ‖ + self.no_trail_button_text = "○ " + self.trail_button_text = "○--" + + self.slider_body_spin_factor = UiSlider(text='自转速度', min=0.01, max=30, default=1) + self.slider_body_size_factor = UiSlider(text='天体缩放', min=0.1, max=100, step=0.1, default=1) + self.slider_run_speed_factor = UiSlider(text="运行速度", min=0.01, max=80, default=1) + self.slider_control_speed_factor = UiSlider(text="控制速度", min=0.01, max=20, default=application.time_scale) + self.slider_trail_length = UiSlider(text="拖尾长度", min=30, max=500, step=10, default=UrsinaConfig.trail_length) + + self.on_off_switch = SwithButton((self.pause_button_text, + self.start_button_text), + default=self.start_button_text, + tooltips=('暂停(P)', '运行(P)')) + self.on_off_switch.selected_color = color.red + + self.sec_per_time_switch = SwithButton(("默认", "天", "周", "月", "年", "十年", "百年"), + default="默认", + tooltips=("系统默认", "每秒相当于1天", "每秒相当于1周", + "每秒相当于1个月", + "每秒相当于1年", "每秒相当于十年", "每秒相当于1百年")) + + self.on_off_trail = SwithButton((self.no_trail_button_text, self.trail_button_text), + default=self.no_trail_button_text, + tooltips=('天体运行无轨迹', '天体运行有拖尾轨迹')) + + self.point_button = UiButton(text='寻找天体(Y)', on_click=None) + self.reset_button = UiButton(text='重新开始(O)', on_click=None) + + content = ( + # InputField(name='name_field'), + # Button(text='Submit', color=color.azure), + self.point_button, + self.reset_button, + # self.buttons, + self.sec_per_time_switch, + self.on_off_switch, + self.on_off_trail, + self.slider_trail_length, + self.slider_body_size_factor, + self.slider_body_spin_factor, + self.slider_run_speed_factor, + self.slider_control_speed_factor + + ) + + return content + + def after_component_init(self): + self.sec_per_time_switch.x = -0.4 + self.on_off_switch.x = 0.2 + self.on_off_trail.x = 0.2 # -0.4 + + def event_handler_init(self): + self.slider_body_size_factor.on_value_changed = self.handler.on_slider_body_size_changed + self.slider_body_spin_factor.on_value_changed = self.handler.on_slider_body_spin_changed + self.slider_run_speed_factor.on_value_changed = self.handler.on_slider_run_speed_changed + self.slider_control_speed_factor.on_value_changed = self.handler.on_slider_control_speed_changed + self.slider_trail_length.on_value_changed = self.handler.on_slider_trail_length_changed + self.on_off_trail.on_value_changed = self.handler.on_off_trail_changed + self.on_off_switch.on_value_changed = self.handler.on_off_switch_changed + self.point_button.on_click = self.handler.on_searching_bodies_click + self.reset_button.on_click = self.handler.on_reset_button_click + self.sec_per_time_switch.on_value_changed = self.handler.sec_per_time_switch_changed + + +if __name__ == '__main__': + app = Ursina() + ctl = ControlUI(ControlHandler(), position=(0.6, 0.5), enabled=True) + app.run() diff --git a/simulators/ursina/ui/event_handler.py b/simulators/ursina/ui/event_handler.py new file mode 100644 index 0000000..3bddb01 --- /dev/null +++ b/simulators/ursina/ui/event_handler.py @@ -0,0 +1,18 @@ +# -*- coding:utf-8 -*- +# title :ursina天体运行模拟器UI控制 +# description :ursina天体运行模拟器UI控制 +# author :Python超人 +# date :2023-02-11 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== + + +class EventHandler: + def __init__(self): + self.ui = None + self.handler_input_init() + + def handler_input_init(self): + pass + diff --git a/simulators/ursina/ui/ui_panel.py b/simulators/ursina/ui/ui_panel.py new file mode 100644 index 0000000..8a6b4b5 --- /dev/null +++ b/simulators/ursina/ui/ui_panel.py @@ -0,0 +1,62 @@ +# -*- coding:utf-8 -*- +# title :ursina天体运行模拟器UI控制 +# description :ursina天体运行模拟器UI控制 +# author :Python超人 +# date :2023-02-11 +# link :https://gitcode.net/pythoncr/ +# python_version :3.8 +# ============================================================================== +from ursina import Ursina, window, Entity, Grid, Mesh, camera, Text, application, color, mouse, Vec2, Vec3, \ + load_texture, held_keys, Button, ButtonList, destroy, scene, distance, Sequence, Wait, Func +from ursina.prefabs.first_person_controller import FirstPersonController +from common.consts import SECONDS_PER_HOUR, SECONDS_PER_HALF_DAY, \ + SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR +from common.consts import AU +from simulators.ursina.ui_component import UiSlider, SwithButton, UiButton, Buttons +from simulators.ursina.ursina_config import UrsinaConfig +from simulators.ursina.ursina_event import UrsinaEvent +from ursina import WindowPanel, InputField, Button, Slider, ButtonGroup, Panel, invoke +from simulators.ursina.ui.event_handler import EventHandler + + +class UiPanel(WindowPanel): + def __init__(self, handler: EventHandler, position=(0, 0), enabled=False, title=''): + self.components = self.component_init() + self.handler = handler + self.handler.ui = self + self.event_handler_init() + super().__init__(title=title, content=self.components, ignore_paused=True, color=color.rgba(0.0, 0.0, 0.0, 0.5)) + + self.y = position[1] # wp.panel.scale_y / 2 * wp.scale_y # center the window panel + self.x = position[0] # wp.scale_x + 0.1 + self.enabled = enabled + self.after_component_init() + + def after_component_init(self): + pass + + def component_init(self): + pass + + def event_handler_init(self): + pass + + def show_message(self, message, close_time=3): + """ + 创建消息框 + :param message: 消息内容 + :param close_time: 定义关闭时间 + :return: + """ + # 创建消息框 + message_box = Text(text=message, font=UrsinaConfig.CN_FONT, background=True, origin=(0, 0), y=.25) + + # 定义关闭函数 + def close_message(): + destroy(message_box) + + s = Sequence( + Wait(close_time), + Func(close_message) + ) + s.start() diff --git a/simulators/ursina/ui_component.py b/simulators/ursina/ui_component.py index c0e2037..307df6b 100644 --- a/simulators/ursina/ui_component.py +++ b/simulators/ursina/ui_component.py @@ -52,6 +52,26 @@ class SwithButton(ButtonGroup): self.x = -0.5 +class Buttons(ButtonGroup): + def __init__(self, options, default=None, tooltips=None): + min_selection = len(options) + super().__init__(options, min_selection=1, default=default, + color=color.rgba(0.1, 0.6, 0.1, 1.0), ignore_paused=True, + selected_color=color.rgba(0.0, 0.0, 0.0, 0.5)) + # self.label.scale = 0.8 + # self.label.font = UrsinaConfig.CN_FONT\ + for i, button in enumerate(self.buttons): + button.text_entity.font = UrsinaConfig.CN_FONT + + # button.scale_x = 2 + if tooltips is not None: + if len(tooltips) > i: + tooltip = Tooltip(tooltips[i]) + tooltip.font = UrsinaConfig.CN_FONT + button.tooltip = tooltip + self.x = -0.5 + + class UiButton(Button): def __init__(self, text, on_click): super(UiButton, self).__init__(text=text, origin=(0, 0), y=2, diff --git a/simulators/ursina/ursina_event.py b/simulators/ursina/ursina_event.py index 2ec48a8..95454c9 100644 --- a/simulators/ursina/ursina_event.py +++ b/simulators/ursina/ursina_event.py @@ -20,6 +20,11 @@ class UrsinaEvent: return UrsinaEvent.on_reset_funcs = [] UrsinaEvent.on_searching_bodies_funcs = [] + UrsinaEvent.on_application_run_callback = [] + + @staticmethod + def on_application_run_callback_subscription(fun): + UrsinaEvent.on_application_run_callback.append(fun) @staticmethod def on_searching_bodies_subscription(subscription_name, fun): @@ -34,6 +39,14 @@ class UrsinaEvent: for f in UrsinaEvent.on_reset_funcs: f() + @staticmethod + def on_application_run(): + if len(UrsinaEvent.on_application_run_callback) == 0: + return + for f in UrsinaEvent.on_application_run_callback: + f() + UrsinaEvent.on_application_run_callback.clear() + @staticmethod def on_searching_bodies(**kwargs): results = [] diff --git a/simulators/ursina/ursina_ui.py b/simulators/ursina/ursina_ui.py index 6244878..bd4967e 100644 --- a/simulators/ursina/ursina_ui.py +++ b/simulators/ursina/ursina_ui.py @@ -12,7 +12,7 @@ from ursina.prefabs.first_person_controller import FirstPersonController from common.consts import SECONDS_PER_HOUR, SECONDS_PER_HALF_DAY, \ SECONDS_PER_DAY, SECONDS_PER_WEEK, SECONDS_PER_MONTH, SECONDS_PER_YEAR from common.consts import AU -from simulators.ursina.ui_component import UiSlider, SwithButton, UiButton +from simulators.ursina.ui_component import UiSlider, SwithButton, UiButton, Buttons from simulators.ursina.ursina_config import UrsinaConfig from simulators.ursina.ursina_event import UrsinaEvent from ursina import WindowPanel, InputField, Button, Slider, ButtonGroup, Panel, invoke @@ -58,6 +58,9 @@ class UrsinaUI: tooltips=('天体运行无轨迹', '天体运行有拖尾轨迹')) self.on_off_trail.on_value_changed = self.on_off_trail_changed + # self.buttons = Buttons(("寻找", "重启"), tooltips=("寻找天体(Y)", "重新开始(O)")) + + self.point_button = UiButton(text='寻找天体(Y)', on_click=self.on_searching_bodies_click) self.reset_button = UiButton(text='重新开始(O)', on_click=self.on_reset_button_click) @@ -85,6 +88,7 @@ class UrsinaUI: # Button(text='Submit', color=color.azure), self.point_button, self.reset_button, + # self.buttons, self.sec_per_time_switch, self.on_off_switch, self.on_off_trail, @@ -275,6 +279,12 @@ class UrsinaUI: def on_reset_button_click(self): UrsinaEvent.on_reset() + def on_buttons_changed(self): + if self.buttons.value == "寻找": + self.on_searching_bodies_click() + elif self.buttons.value == "重启": + self.on_reset_button_click() + def on_off_switch_changed(self): if self.on_off_switch.value == self.pause_button_text: self.on_off_switch.selected_color = color.green diff --git a/simulators/ursina_simulator.py b/simulators/ursina_simulator.py index 6d42a56..e3617da 100644 --- a/simulators/ursina_simulator.py +++ b/simulators/ursina_simulator.py @@ -12,7 +12,10 @@ from ursina import Ursina, window, Entity, Grid, Mesh, camera, Text, application from ursina.prefabs.first_person_controller import FirstPersonController from simulators.ursina.ursina_event import UrsinaEvent -from simulators.ursina.ursina_ui import UrsinaUI +# from simulators.ursina.ursina_ui import UrsinaUI +from simulators.ursina.ui.control_ui import ControlUI +from simulators.ursina.ui.control_handler import ControlHandler + from simulators.views.ursina_view import UrsinaView, UrsinaPlayer from simulators.ursina.ursina_config import UrsinaConfig from simulators.simulator import Simulator @@ -236,7 +239,9 @@ class UrsinaSimulator(Simulator): if cosmic_bg is not None and os.path.exists(cosmic_bg): self.cosmic_background(cosmic_bg) - ui = UrsinaUI() + # ui = UrsinaUI() + ctl = ControlUI(ControlHandler(), position=(0.6, 0.5)) + EditorCamera(ignore_paused=True) # 防止打开中文输入法 # self.switch_to_english_input_method() -- GitLab