From 67a935866c67e9bcbb487b211d36bb4c161ab2a7 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 11 Mar 2024 22:17:55 +0800 Subject: [PATCH] =?UTF-8?q?GUI=E6=8F=92=E4=BB=B6=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=88=BF=E9=97=B4=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/native-ui/designer/native-ui.fbp | 16 +- plugins/native-ui/designer/ui_base.py | 32 +++- plugins/native-ui/listener.py | 129 +++++++++++++--- plugins/native-ui/main.pyw | 15 +- plugins/native-ui/requirements.txt | 1 + plugins/native-ui/ui/app.py | 53 +++++++ plugins/native-ui/ui/room.py | 10 -- plugins/native-ui/ui/room_frame.py | 185 +++++++++++++++++++++++ 8 files changed, 389 insertions(+), 52 deletions(-) create mode 100644 plugins/native-ui/ui/app.py delete mode 100644 plugins/native-ui/ui/room.py create mode 100644 plugins/native-ui/ui/room_frame.py diff --git a/plugins/native-ui/designer/native-ui.fbp b/plugins/native-ui/designer/native-ui.fbp index 421a526..ba427e0 100644 --- a/plugins/native-ui/designer/native-ui.fbp +++ b/plugins/native-ui/designer/native-ui.fbp @@ -48,7 +48,7 @@ RoomFrameBase - 750,650 + 800,650 wxDEFAULT_FRAME_STYLE ; ; forward_declare blivechat - 房间 123456 @@ -58,6 +58,7 @@ wxTAB_TRAVERSAL 1 + _on_close bSizer1 @@ -153,6 +154,7 @@ + _on_config_button_click @@ -225,6 +227,7 @@ + _on_stay_on_top_button_toggle @@ -276,7 +279,7 @@ 0 0 wxID_ANY - << + >> 0 @@ -309,6 +312,7 @@ + _on_collapse_console_button_click @@ -417,7 +421,7 @@ 0 - + 200,-1 1 console_notebook 1 @@ -674,7 +678,7 @@ Resizable 1 - wxLC_REPORT + wxLC_REPORT|wxLC_SINGLE_SEL ; ; forward_declare 0 @@ -801,7 +805,7 @@ Resizable 1 - wxLC_REPORT + wxLC_REPORT|wxLC_SINGLE_SEL ; ; forward_declare 0 @@ -940,7 +944,7 @@ Resizable 1 - wxLC_REPORT + wxLC_REPORT|wxLC_SINGLE_SEL ; ; forward_declare 0 diff --git a/plugins/native-ui/designer/ui_base.py b/plugins/native-ui/designer/ui_base.py index f6b5de4..d41351c 100644 --- a/plugins/native-ui/designer/ui_base.py +++ b/plugins/native-ui/designer/ui_base.py @@ -18,7 +18,7 @@ import wx.html2 class RoomFrameBase ( wx.Frame ): def __init__( self, parent ): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"blivechat - 房间 123456", pos = wx.DefaultPosition, size = wx.Size( 750,650 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"blivechat - 房间 123456", pos = wx.DefaultPosition, size = wx.Size( 800,650 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) self.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) @@ -39,7 +39,7 @@ class RoomFrameBase ( wx.Frame ): bSizer3.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - self.collapse_console_button = wx.Button( self, wx.ID_ANY, u"<<", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.collapse_console_button = wx.Button( self, wx.ID_ANY, u">>", wx.DefaultPosition, wx.DefaultSize, 0 ) bSizer3.Add( self.collapse_console_button, 0, 0, 5 ) @@ -52,6 +52,8 @@ class RoomFrameBase ( wx.Frame ): bSizer1.Add( bSizer2, 1, wx.EXPAND, 5 ) self.console_notebook = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.console_notebook.SetMinSize( wx.Size( 200,-1 ) ) + self.paid_panel = wx.Panel( self.console_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer4 = wx.BoxSizer( wx.VERTICAL ) @@ -66,7 +68,7 @@ class RoomFrameBase ( wx.Frame ): self.super_chat_panel = wx.Panel( self.console_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer5 = wx.BoxSizer( wx.VERTICAL ) - self.super_chat_list = wx.ListCtrl( self.super_chat_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT ) + self.super_chat_list = wx.ListCtrl( self.super_chat_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT|wx.LC_SINGLE_SEL ) bSizer5.Add( self.super_chat_list, 1, wx.EXPAND, 5 ) @@ -77,7 +79,7 @@ class RoomFrameBase ( wx.Frame ): self.gift_panel = wx.Panel( self.console_notebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) bSizer6 = wx.BoxSizer( wx.VERTICAL ) - self.gift_list = wx.ListCtrl( self.gift_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT ) + self.gift_list = wx.ListCtrl( self.gift_panel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT|wx.LC_SINGLE_SEL ) bSizer6.Add( self.gift_list, 1, wx.EXPAND, 5 ) @@ -90,7 +92,7 @@ class RoomFrameBase ( wx.Frame ): sbSizer1 = wx.StaticBoxSizer( wx.StaticBox( self.statistics_panel, wx.ID_ANY, u"付费用户" ), wx.VERTICAL ) - self.paid_user_list = wx.ListCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT ) + self.paid_user_list = wx.ListCtrl( sbSizer1.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LC_REPORT|wx.LC_SINGLE_SEL ) sbSizer1.Add( self.paid_user_list, 1, wx.EXPAND, 5 ) @@ -115,10 +117,30 @@ class RoomFrameBase ( wx.Frame ): self.Centre( wx.BOTH ) + # Connect Events + self.Bind( wx.EVT_CLOSE, self._on_close ) + self.config_button.Bind( wx.EVT_BUTTON, self._on_config_button_click ) + self.stay_on_top_button.Bind( wx.EVT_TOGGLEBUTTON, self._on_stay_on_top_button_toggle ) + self.collapse_console_button.Bind( wx.EVT_BUTTON, self._on_collapse_console_button_click ) + def __del__( self ): pass + # Virtual event handlers, override them in your derived class + def _on_close( self, event ): + event.Skip() + + def _on_config_button_click( self, event ): + event.Skip() + + def _on_stay_on_top_button_toggle( self, event ): + event.Skip() + + def _on_collapse_console_button_click( self, event ): + event.Skip() + + ########################################################################### ## Class RoomConfigDialogBase ########################################################################### diff --git a/plugins/native-ui/listener.py b/plugins/native-ui/listener.py index d7859f6..a418086 100644 --- a/plugins/native-ui/listener.py +++ b/plugins/native-ui/listener.py @@ -5,6 +5,8 @@ import datetime import logging from typing import * +import pubsub.pub as pub + import blcsdk import blcsdk.models as sdk_models @@ -41,7 +43,7 @@ class MsgHandler(blcsdk.BaseHandler): def _on_open_plugin_admin_ui( self, client: blcsdk.BlcPluginClient, message: sdk_models.OpenPluginAdminUiMsg, extra: sdk_models.ExtraData ): - pass + pub.sendMessage('open_admin_ui') def _on_room_init( self, client: blcsdk.BlcPluginClient, message: sdk_models.RoomInitMsg, extra: sdk_models.ExtraData @@ -119,6 +121,10 @@ class MsgHandler(blcsdk.BaseHandler): )) +def iter_rooms() -> Iterable['Room']: + return _key_room_dict.values() + + def get_room(room_key: sdk_models.RoomKey): return _key_room_dict.get(room_key, None) @@ -128,14 +134,15 @@ def _get_or_add_room(room_key: sdk_models.RoomKey, room_id): if room is None: if room_id is None: raise TypeError('room_id is None') - room = _key_room_dict[room_id] = Room(room_key, room_id) - # TODO 打开房间窗口 + room = _key_room_dict[room_key] = Room(room_key, room_id) + pub.sendMessage('add_room', room_key=room_key) return room def _del_room(room_key: sdk_models.RoomKey): - _key_room_dict.pop(room_key, None) - # TODO 关闭房间窗口 + room = _key_room_dict.pop(room_key, None) + if room is not None: + pub.sendMessage('del_room', room_key=room_key) @dataclasses.dataclass @@ -177,26 +184,107 @@ class Room: self._interact_uids: Set[str] = set() self._total_paid_price = 0 + @property + def room_key(self): + return self._room_key + + @property + def room_id(self): + return self._room_id + + @property + def super_chats(self): + return self._super_chats + + @property + def gifts(self): + return self._gifts + + @property + def uid_paid_user_dict(self): + return self._uid_paid_user_dict + + @property + def danmaku_num(self): + return self._danmaku_num + + @property + def interact_uids(self): + return self._interact_uids + + @property + def total_paid_price(self): + return self._total_paid_price + def add_danmaku(self, uid): self._danmaku_num += 1 + pub.sendMessage('room_data_change.danmaku_num', room=self, value=self._danmaku_num) + + self._add_interact_uid(uid) + + def _add_interact_uid(self, uid): + if uid in self._interact_uids: + return + self._interact_uids.add(uid) + pub.sendMessage( + 'room_data_change.interact_uids', + room=self, + value=self._interact_uids, + index=uid, + is_new=True, + ) def add_super_chat(self, super_chat: SuperChatRecord): self._super_chats.append(super_chat) + pub.sendMessage( + 'room_data_change.super_chats', + room=self, + value=self._super_chats, + index=len(self._super_chats) - 1, + is_new=True, + ) + self._add_user_paid_price(PaidUserRecord( uid=super_chat.uid, name=super_chat.author_name, price=super_chat.price, )) - self._danmaku_num += 1 - self._interact_uids.add(super_chat.uid) + self._total_paid_price += super_chat.price + pub.sendMessage('room_data_change.total_paid_price', room=self, value=self._total_paid_price) + + self.add_danmaku(super_chat.uid) + + def _add_user_paid_price(self, paid_user: PaidUserRecord): + old_paid_user = self._uid_paid_user_dict.get(paid_user.uid, None) + if old_paid_user is None: + old_paid_user = self._uid_paid_user_dict[paid_user.uid] = PaidUserRecord( + uid=paid_user.uid, + name=paid_user.name, + price=0, + ) + is_new = True + else: + is_new = False + old_paid_user.price += paid_user.price + + pub.sendMessage( + 'room_data_change.uid_paid_user_dict', + room=self, + value=self._uid_paid_user_dict, + index=paid_user.uid, + is_new=is_new, + ) def add_gift(self, gift: GiftRecord): # 尝试合并 is_merged = False min_time_to_merge = gift.time - datetime.timedelta(seconds=10) - for old_gift in reversed(self._gifts): + index = len(self._gifts) + for index in range(len(self._gifts) - 1, -1, -1): + old_gift = self._gifts[index] + if old_gift.time < min_time_to_merge: break if old_gift.uid == gift.uid and old_gift.gift_name == gift.gift_name: @@ -204,24 +292,25 @@ class Room: old_gift.price += gift.price is_merged = True break - if not is_merged: + index = len(self._gifts) self._gifts.append(gift) + pub.sendMessage( + 'room_data_change.gifts', + room=self, + value=self._gifts, + index=index, + is_new=not is_merged, + ) + if gift.price > 0.: self._add_user_paid_price(PaidUserRecord( uid=gift.uid, name=gift.author_name, price=gift.price, )) - self._interact_uids.add(gift.uid) - self._total_paid_price += gift.price - def _add_user_paid_price(self, paid_user: PaidUserRecord): - old_paid_user = self._uid_paid_user_dict.get(paid_user.uid, None) - if old_paid_user is None: - old_paid_user = self._uid_paid_user_dict[paid_user.uid] = PaidUserRecord( - uid=paid_user.uid, - name=paid_user.name, - price=0, - ) - old_paid_user.price += paid_user.price + self._add_interact_uid(gift.uid) + + self._total_paid_price += gift.price + pub.sendMessage('room_data_change.total_paid_price', room=self, value=self._total_paid_price) diff --git a/plugins/native-ui/main.pyw b/plugins/native-ui/main.pyw index 1d79add..c4a29be 100755 --- a/plugins/native-ui/main.pyw +++ b/plugins/native-ui/main.pyw @@ -5,19 +5,16 @@ import logging.handlers import os import signal import sys -from typing import * import wx -import wxasync import blcsdk import config import listener +import ui.app logger = logging.getLogger('native-ui') -app: Optional[wxasync.WxAsyncApp] = None - async def main(): try: @@ -37,7 +34,7 @@ async def init(): if not blcsdk.is_sdk_version_compatible(): raise RuntimeError('SDK version is not compatible') - init_ui() + ui.app.init() await listener.init() @@ -57,6 +54,7 @@ def init_signal_handlers(): def start_shut_down(*_args): + app = wx.GetApp() if app is not None: app.ExitMainLoop() else: @@ -78,14 +76,9 @@ def init_logging(): ) -def init_ui(): - global app - app = wxasync.WxAsyncApp(clearSigInt=False) - - async def run(): logger.info('Running event loop') - await app.MainLoop() + await wx.GetApp().MainLoop() logger.info('Start to shut down') diff --git a/plugins/native-ui/requirements.txt b/plugins/native-ui/requirements.txt index e8c6f1b..43c69b2 100644 --- a/plugins/native-ui/requirements.txt +++ b/plugins/native-ui/requirements.txt @@ -1,2 +1,3 @@ +PyPubSub==4.0.3 wxasync==0.49 wxPython==4.2.1 diff --git a/plugins/native-ui/ui/app.py b/plugins/native-ui/ui/app.py new file mode 100644 index 0000000..2b4e096 --- /dev/null +++ b/plugins/native-ui/ui/app.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +import logging +from typing import * + +import pubsub.pub as pub +import wxasync + +import blcsdk.models as sdk_models +import listener +import ui.room_frame + +logger = logging.getLogger('native-ui.' + __name__) + +_app: Optional['App'] = None + + +def init(): + global _app + _app = App() + + +class App(wxasync.WxAsyncApp): + def __init__(self, *args, **kwargs): + super().__init__(*args, clearSigInt=False, **kwargs) + self.SetExitOnFrameDelete(False) + + self._key_room_frame_dict: Dict[sdk_models.RoomKey, ui.room_frame.RoomFrame] = {} + + def OnInit(self): + pub.subscribe(self._on_add_room, 'add_room') + pub.subscribe(self._on_del_room, 'del_room') + pub.subscribe(self._on_room_frame_close, 'room_frame_close') + pub.subscribe(self._on_open_admin_ui, 'open_admin_ui') + return True + + def _on_add_room(self, room_key: sdk_models.RoomKey): + if room_key in self._key_room_frame_dict: + return + + room_frame = self._key_room_frame_dict[room_key] = ui.room_frame.RoomFrame(None, room_key) + room_frame.Show() + + def _on_del_room(self, room_key: sdk_models.RoomKey): + room_frame = self._key_room_frame_dict.pop(room_key, None) + if room_frame is not None: + room_frame.Close(True) + + def _on_room_frame_close(self, room_key: sdk_models.RoomKey): + self._key_room_frame_dict.pop(room_key, None) + + def _on_open_admin_ui(self): + for room in listener.iter_rooms(): + self._on_add_room(room.room_key) diff --git a/plugins/native-ui/ui/room.py b/plugins/native-ui/ui/room.py deleted file mode 100644 index 9e0e600..0000000 --- a/plugins/native-ui/ui/room.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -import designer.ui_base - - -class RoomFrame(designer.ui_base.RoomFrameBase): - def __init__(self, parent): - super().__init__(parent) - - self.chat_web_view.LoadURL('http://localhost:12450/room/test?minGiftPrice=0&showGiftName=true&relayMessagesByServer=true&lang=zh') - self.paid_web_view.LoadURL('http://localhost:12450/room/test?showDanmaku=false&showGiftName=true&relayMessagesByServer=true&lang=zh') diff --git a/plugins/native-ui/ui/room_frame.py b/plugins/native-ui/ui/room_frame.py new file mode 100644 index 0000000..64ae758 --- /dev/null +++ b/plugins/native-ui/ui/room_frame.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +import datetime +import logging +import urllib.parse +from typing import * + +import pubsub.pub as pub +import wx + +import blcsdk +import blcsdk.models as sdk_models +import designer.ui_base +import listener + +logger = logging.getLogger('native-ui.' + __name__) + + +class RoomFrame(designer.ui_base.RoomFrameBase): + def __init__(self, parent, room_key: sdk_models.RoomKey): + super().__init__(parent) + self._room_key = room_key + + room = listener.get_room(self._room_key) + room_str = str(room.room_id) if room is not None else str(self._room_key) + self.SetTitle(f'blivechat - 房间 {room_str}') + + room_params = {'minGiftPrice': 0, 'showGiftName': 'true'} + self.chat_web_view.LoadURL(self._get_room_url(room_params)) + room_params['showDanmaku'] = 'false' + self.paid_web_view.LoadURL(self._get_room_url(room_params)) + + self.super_chat_list.AppendColumn('时间', width=50) + self.super_chat_list.AppendColumn('用户名', width=120) + self.super_chat_list.AppendColumn('金额', width=50) + self.super_chat_list.AppendColumn('内容', width=300) + for index in range(len(room.super_chats)): + self._on_super_chats_change(room, room.super_chats, index, True) + + self.gift_list.AppendColumn('时间', width=50) + self.gift_list.AppendColumn('用户名', width=120) + self.gift_list.AppendColumn('礼物名', width=100) + self.gift_list.AppendColumn('数量', width=50) + self.gift_list.AppendColumn('总价', width=50) + for index in range(len(room.gifts)): + self._on_gifts_change(room, room.gifts, index, True) + + # item_data只能存int,这里做个映射 + self._uid_to_paid_user_item_data: Dict[str, int] = {} + self._next_paid_user_item_data = 1 + self.paid_user_list.AppendColumn('用户名', width=120) + self.paid_user_list.AppendColumn('总付费', width=60) + for index in room.uid_paid_user_dict: + self._on_uid_paid_user_dict_change(room, room.uid_paid_user_dict, index, True) + + pub.subscribe(self._on_super_chats_change, 'room_data_change.super_chats') + pub.subscribe(self._on_gifts_change, 'room_data_change.gifts') + pub.subscribe(self._on_uid_paid_user_dict_change, 'room_data_change.uid_paid_user_dict') + pub.subscribe(self._on_simple_statistics_change, 'room_data_change.danmaku_num') + pub.subscribe(self._on_simple_statistics_change, 'room_data_change.interact_uids') + pub.subscribe(self._on_simple_statistics_change, 'room_data_change.total_paid_price') + + def _get_room_url(self, params: dict): + params = params.copy() + params['roomKeyType'] = self._room_key.type.value + params['relayMessagesByServer'] = 'true' + + query = '&'.join( + f'{urllib.parse.quote_plus(key)}={urllib.parse.quote_plus(str(value))}' + for key, value in params.items() + ) + blc_port = blcsdk.get_blc_port() + encoded_room_key_value = urllib.parse.quote_plus(str(self._room_key.value)) + url = f'http://localhost:{blc_port}/room/{encoded_room_key_value}?{query}' + return url + + # + # UI事件 + # + + def _on_close(self, event): + pub.sendMessage('room_frame_close', room_key=self._room_key) + super()._on_close(event) + + def _on_config_button_click(self, event): + # TODO WIP + dialog = designer.ui_base.RoomConfigDialogBase(self) + dialog.Show() + + def _on_stay_on_top_button_toggle(self, event: wx.CommandEvent): + style = self.GetWindowStyle() + if event.IsChecked(): + style |= wx.STAY_ON_TOP + else: + style &= ~wx.STAY_ON_TOP + self.SetWindowStyle(style) + + def _on_collapse_console_button_click(self, event): + window_size = self.GetSize() + if self.console_notebook.IsShown(): + window_size.Scale(0.5, 1) + self.console_notebook.Hide() + self.collapse_console_button.SetLabelText('<<') + else: + window_size.Scale(2, 1) + self.console_notebook.Show() + self.collapse_console_button.SetLabelText('>>') + self.SetSize(window_size) + self.Layout() + + # + # 模型事件 + # + + def _on_super_chats_change(self, room: listener.Room, value: List[listener.SuperChatRecord], index, is_new): # noqa + super_chat = value[index] + col_texts = [ + self._format_time(super_chat.time), + super_chat.author_name, + str(super_chat.price), + super_chat.content, + ] + self._update_list_ctrl(self.super_chat_list, index, is_new, col_texts) + + @staticmethod + def _format_time(time: datetime.datetime): + return time.strftime('%H:%M') + + def _update_list_ctrl(self, list_ctrl: wx.ListCtrl, item_data: int, is_new, col_texts: List[str]): + if is_new: + row_index = list_ctrl.Append(col_texts) + list_ctrl.SetItemData(row_index, item_data) + + self._maybe_scroll_list_ctrl_to_bottom(list_ctrl) + return + + for row_index in range(list_ctrl.GetItemCount() - 1, -1, -1): + if list_ctrl.GetItemData(row_index) != item_data: + continue + for col_index, text in enumerate(col_texts): + list_ctrl.SetItem(row_index, col_index, text) + break + + @staticmethod + def _maybe_scroll_list_ctrl_to_bottom(list_ctrl: wx.ListCtrl): + """如果原来就在底端则滚动到底端""" + last_row_index = list_ctrl.GetItemCount() - 1 + if last_row_index < 0: + return + + # 没有找到更简单的方法 + list_height = list_ctrl.GetClientSize().GetHeight() * list_ctrl.GetContentScaleFactor() + last_row_rect = list_ctrl.GetItemRect(max(last_row_index, 0)) + height_to_bottom = last_row_rect.GetBottom() - list_height + if height_to_bottom < last_row_rect.GetHeight() * 3: + list_ctrl.Focus(last_row_index) + + def _on_gifts_change(self, room: listener.Room, value: List[listener.GiftRecord], index, is_new): # noqa + gift = value[index] + col_texts = [ + self._format_time(gift.time), + gift.author_name, + gift.gift_name, + str(gift.num), + str(gift.price), + ] + self._update_list_ctrl(self.gift_list, index, is_new, col_texts) + + def _on_uid_paid_user_dict_change( + self, room: listener.Room, value: Dict[str, listener.PaidUserRecord], index, is_new # noqa + ): + item_data = self._uid_to_paid_user_item_data.get(index, None) + if item_data is None: + item_data = self._uid_to_paid_user_item_data[index] = self._next_paid_user_item_data + self._next_paid_user_item_data += 1 + + paid_user = value[index] + col_texts = [ + paid_user.name, + str(paid_user.price), + ] + self._update_list_ctrl(self.paid_user_list, item_data, is_new, col_texts) + + def _on_simple_statistics_change(self, room: listener.Room, value=None, index=None, is_new=None): # noqa + text = f'总弹幕数:{room.danmaku_num} 互动用户数:{len(room.interact_uids)} 总付费:{room.total_paid_price} 元' + self.statistics_text.SetLabelText(text)