GUI插件完成房间窗口

pull/168/head
John Smith 7 months ago
parent 8277c56224
commit 67a935866c

@ -48,7 +48,7 @@
<property name="minimum_size"></property>
<property name="name">RoomFrameBase</property>
<property name="pos"></property>
<property name="size">750,650</property>
<property name="size">800,650</property>
<property name="style">wxDEFAULT_FRAME_STYLE</property>
<property name="subclass">; ; forward_declare</property>
<property name="title">blivechat - 房间 123456</property>
@ -58,6 +58,7 @@
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<property name="xrc_skip_sizer">1</property>
<event name="OnClose">_on_close</event>
<object class="wxBoxSizer" expanded="true">
<property name="minimum_size"></property>
<property name="name">bSizer1</property>
@ -153,6 +154,7 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">_on_config_button_click</event>
</object>
</object>
<object class="sizeritem" expanded="true">
@ -225,6 +227,7 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnToggleButton">_on_stay_on_top_button_toggle</event>
</object>
</object>
<object class="sizeritem" expanded="true">
@ -276,7 +279,7 @@
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">&lt;&lt;</property>
<property name="label">&gt;&gt;</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
@ -309,6 +312,7 @@
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">_on_collapse_console_button_click</event>
</object>
</object>
</object>
@ -417,7 +421,7 @@
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="minimum_size">200,-1</property>
<property name="moveable">1</property>
<property name="name">console_notebook</property>
<property name="pane_border">1</property>
@ -674,7 +678,7 @@
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxLC_REPORT</property>
<property name="style">wxLC_REPORT|wxLC_SINGLE_SEL</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
@ -801,7 +805,7 @@
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxLC_REPORT</property>
<property name="style">wxLC_REPORT|wxLC_SINGLE_SEL</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
@ -940,7 +944,7 @@
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxLC_REPORT</property>
<property name="style">wxLC_REPORT|wxLC_SINGLE_SEL</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>

@ -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
###########################################################################

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

@ -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')

@ -1,2 +1,3 @@
PyPubSub==4.0.3
wxasync==0.49
wxPython==4.2.1

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

@ -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')

@ -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)
Loading…
Cancel
Save