329 lines
11 KiB
Python
329 lines
11 KiB
Python
# -------------------------------
|
||
# Difficult Rocket
|
||
# Copyright © 2021 by shenjackyuanjie
|
||
# All rights reserved
|
||
# -------------------------------
|
||
|
||
"""
|
||
writen by shenjackyuanjie
|
||
mail: 3695888@qq.com
|
||
github: @shenjackyuanjie
|
||
gitee: @shenjackyuanjie
|
||
"""
|
||
|
||
import time
|
||
import re
|
||
|
||
from typing import Union
|
||
from decimal import Decimal
|
||
|
||
# from DR
|
||
from Difficult_Rocket import translate
|
||
from Difficult_Rocket.api import new_thread
|
||
from Difficult_Rocket.guis.widgets import InputBox
|
||
|
||
# from libs.pyglet
|
||
from libs import pyglet
|
||
from libs.pyglet.text import Label
|
||
from libs.pyglet.window import key
|
||
from libs.pyglet.gui import widgets
|
||
from libs.pyglet.graphics import Batch, Group
|
||
|
||
|
||
class CommandText:
|
||
"""
|
||
CommandLine返回的字符,可以用来搜索
|
||
"""
|
||
|
||
def __init__(self, text: str):
|
||
self.text = text
|
||
self.value_dict = {}
|
||
self.value_list = []
|
||
|
||
def find(self, text: str) -> bool:
|
||
finding = re.match(text, self.text)
|
||
if finding:
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def match(self, text: str) -> bool:
|
||
finding = re.match(text, self.text)
|
||
if finding: # 如果找到了
|
||
try:
|
||
next_find = self.text[finding.span()[1]]
|
||
# 这里try因为可能匹配到的是字符串末尾
|
||
except IndexError:
|
||
next_find = ' '
|
||
# 直接过滤掉
|
||
if next_find == ' ':
|
||
self.text = self.text[finding.span()[1] + 1:]
|
||
return True
|
||
# 将匹配到的字符串,和最后一个匹配字符后面的字符删除(相当暴力的操作)
|
||
return False
|
||
else:
|
||
return False
|
||
|
||
def greedy(self, name: str = None) -> str:
|
||
if name:
|
||
self.value_dict[name] = self.text
|
||
self.value_list.append(self.text)
|
||
return self.text
|
||
|
||
def value(self,
|
||
name: str = None,
|
||
split: str = ' ',
|
||
middle: list = ('\'', '\"')):
|
||
pass
|
||
|
||
def __str__(self):
|
||
return str(self.text)
|
||
|
||
def __int__(self):
|
||
return int(self.text)
|
||
|
||
|
||
class CommandLine(widgets.WidgetBase):
|
||
"""
|
||
command line show
|
||
"""
|
||
|
||
def __init__(self,
|
||
x: int,
|
||
y: int,
|
||
width: int,
|
||
height: int,
|
||
length: int,
|
||
batch: Batch,
|
||
group: Group = None,
|
||
command_text: str = '/',
|
||
font_size: int = 20):
|
||
super().__init__(x, y, width, height)
|
||
|
||
# normal values
|
||
self.length = length
|
||
self._command_list = ['' for line in range(length)]
|
||
self._command_text = command_text
|
||
self._text_position = 0
|
||
self._command_view = 0
|
||
self._value = 0
|
||
self._text = ''
|
||
self.command_split = 25
|
||
self.command_distance = 20
|
||
|
||
# group
|
||
self._user_group = group
|
||
bg_group = Group(order=0, parent=group)
|
||
fg_group = Group(order=1, parent=group)
|
||
|
||
# hidden value
|
||
self._text = ''
|
||
self._line = Label(x=x, y=y, batch=batch, text=self.text,
|
||
color=(100, 255, 255, 255),
|
||
anchor_x='left', anchor_y='bottom',
|
||
font_size=font_size, font_name=translate.微软等宽,
|
||
group=fg_group)
|
||
self._label = [Label(x=x + 10, y=y + self.command_distance + (line * self.command_split), batch=batch, text='a',
|
||
anchor_x='left', anchor_y='bottom',
|
||
font_size=font_size - 3, font_name=translate.鸿蒙简体,
|
||
group=bg_group)
|
||
for line in range(length)]
|
||
# Rectangular outline with 5-pixel pad:
|
||
color = (100, 100, 100, 100)
|
||
self._pad = p = 5
|
||
self._outline = pyglet.shapes.Rectangle(x=x - p, y=y - p,
|
||
width=width + p, height=height + p,
|
||
color=color[:3],
|
||
batch=batch, group=fg_group)
|
||
self._outline.opacity = color[3]
|
||
|
||
self.editing = False
|
||
|
||
def _update_position(self):
|
||
pass
|
||
|
||
def update_groups(self, order):
|
||
self._line.group = Group(order=order + 1, parent=self._user_group)
|
||
for label in self._label:
|
||
label.group = Group(order=order + 1, parent=self._user_group)
|
||
self._outline.group = Group(order=order + 2, parent=self._user_group)
|
||
|
||
"""
|
||
values
|
||
"""
|
||
|
||
@property
|
||
def value(self):
|
||
return self.text
|
||
|
||
@property
|
||
def text(self):
|
||
return self._text
|
||
|
||
@text.setter
|
||
def text(self, value):
|
||
assert type(value) is str, 'CommandLine\'s text must be string!'
|
||
self._text = value
|
||
self._line.text = value
|
||
|
||
@property
|
||
def command_view(self):
|
||
return self._command_view
|
||
|
||
@command_view.setter
|
||
def command_view(self, value):
|
||
"""
|
||
value:
|
||
-1 -> 将整个列表添加一个数据
|
||
如果长度超过length就删掉多余的
|
||
将视角移动到最下面,刷新显示列表
|
||
0 ~ (self.length-1) -> 切换视角到对应的行数
|
||
实际上还有一个限制
|
||
"""
|
||
assert type(value) is int, 'Command View must be integer'
|
||
assert -self.length < value < self.length, f'Command View must be bigger than {-self.length} and smaller than {self.length}'
|
||
if value == -1: # flush command list
|
||
self._label.insert(0, self._label[-1])
|
||
self._label.pop(-1)
|
||
for line in range(self.length):
|
||
self._label[line].y = self.y + self.command_distance + (line * self.command_split)
|
||
self._label[0].text = self.text
|
||
self.text = ''
|
||
self._command_view = 0
|
||
self._text_position = 0
|
||
elif value == self._command_view: # not doing anything
|
||
pass
|
||
elif value > self._command_view: # move upwards
|
||
pass
|
||
else: # move downwards
|
||
pass
|
||
# self._command_view = value
|
||
|
||
@property
|
||
def editing(self):
|
||
return self._editing
|
||
|
||
@editing.setter
|
||
def editing(self, value):
|
||
assert type(value) is bool, 'Command editing must be bool!'
|
||
self._editing = value
|
||
self._line.visible = value
|
||
self._outline.visible = value
|
||
for label in self._label:
|
||
label.visible = value
|
||
|
||
@new_thread('command wait', daemon=True, log_thread=False)
|
||
def wait(self, wait: Union[float, int] = 0):
|
||
this = self._label[0]
|
||
self._label[0].visible = True
|
||
time.sleep(wait)
|
||
if self._label[0].visible and not self.editing:
|
||
while (self._label[0].opacity >= 30) and self._label[0].visible and (self._label[0] is this) and not self.editing:
|
||
# (label 的透明度不是 0) and (label 还在显示) and (label 还是载入线程时候的那个label) and (现在不在输入新行)
|
||
self._label[0].opacity -= 2
|
||
time.sleep(0.01)
|
||
if self._label[0] is this: # 如果结束的时候label还是这个label
|
||
self._label[0].opacity = 255
|
||
else: # 如果不是就赶快找回来!
|
||
now = self._label.index(this)
|
||
self._label[now].opacity = 255
|
||
if not self.editing: # 如果不在编辑再隐藏
|
||
self._label[0].visible = False
|
||
|
||
@property
|
||
def command_list(self):
|
||
return self._command_list
|
||
|
||
"""
|
||
events
|
||
"""
|
||
|
||
def on_text(self, text):
|
||
# 这里的大部分东西都会在最近被重写
|
||
# TODO 重写成基于新的 InputBox 的解析
|
||
if self.editing:
|
||
if text in ('\r', '\n'): # goto a new line
|
||
if not self.text:
|
||
pass
|
||
elif self.text[0] == self._command_text:
|
||
self.dispatch_event('on_command', CommandText(self.text[1:]))
|
||
else:
|
||
self.dispatch_event('on_message', CommandText(self.text))
|
||
# on_message 和 on_command 可能会覆盖 self.text 需要再次判定
|
||
if self.text:
|
||
self.command_view = -1
|
||
self.editing = False
|
||
self.wait()
|
||
else:
|
||
self.text = f'{self.text[:self._text_position]}{text}{self.text[self._text_position:]}' # 插入字符(简单粗暴)
|
||
self._text_position += 1
|
||
elif text == 't': # open message line
|
||
self.editing = True
|
||
elif text == '/': # open command line
|
||
self.editing = True
|
||
self.text = '/'
|
||
self._text_position = 1
|
||
|
||
def on_text_motion(self, motion):
|
||
if self.editing:
|
||
# edit motion
|
||
if motion == key.MOTION_DELETE: # 确保不越界
|
||
self.text = f'{self.text[:self._text_position]}{self.text[self._text_position + 1:]}' # 简单粗暴的删除
|
||
elif motion == key.MOTION_BACKSPACE and self._text_position >= 1: # 确保不越界
|
||
self.text = f'{self.text[:self._text_position - 1]}{self.text[self._text_position:]}' # 简单粗暴的删除
|
||
self._text_position -= 1 # 记得切换光标位置
|
||
|
||
# move motion
|
||
elif motion == key.MOTION_LEFT and self._text_position >= 0: # 确保不越界
|
||
self._text_position -= 1
|
||
elif motion == key.MOTION_RIGHT and self._text_position <= len(self.text): # 确保不越界
|
||
self._text_position += 1
|
||
elif motion in (key.MOTION_BEGINNING_OF_LINE, key.MOTION_BEGINNING_OF_FILE, key.MOTION_PREVIOUS_PAGE):
|
||
self._text_position = 0
|
||
elif motion in (key.MOTION_END_OF_LINE, key.MOTION_END_OF_FILE, key.MOTION_NEXT_PAGE):
|
||
self._text_position = len(self.text)
|
||
|
||
# view move motion
|
||
elif motion == key.MOTION_DOWN:
|
||
if not self.command_view == -1:
|
||
self.command_view -= 1
|
||
else:
|
||
pass
|
||
|
||
def on_text_motion_select(self, motion):
|
||
if self.editing:
|
||
pass
|
||
|
||
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
|
||
if self.editing:
|
||
pass
|
||
|
||
def on_mouse_press(self, x, y, buttons, modifiers):
|
||
if self.editing:
|
||
pass
|
||
|
||
"""
|
||
custom event
|
||
"""
|
||
|
||
def on_command(self, command: text):
|
||
if self.editing:
|
||
return
|
||
"""give command to it"""
|
||
|
||
def on_message(self, message: text):
|
||
if self.editing:
|
||
return
|
||
"""give message to it"""
|
||
|
||
def push_line(self, line: Union[str, int, float, Decimal], block_line: bool = False):
|
||
_text = self.text
|
||
self.text = str(line)
|
||
self.command_view = -1
|
||
if not block_line:
|
||
self.text = _text
|
||
|
||
|
||
CommandLine.register_event_type('on_command')
|
||
CommandLine.register_event_type('on_message')
|