feat: 代码格式化, 改进仪表盘, 新增多CSS支持

This commit is contained in:
2026-04-20 16:30:04 +08:00
parent 8677e828c7
commit 4ca9c65bea
43 changed files with 551 additions and 349 deletions

View File

@@ -23,7 +23,7 @@ class AboutScreen(Screen):
yield Header(show_clock=True)
with ScrollableContainer(id="about_container"):
yield Label("[b]关于与版本信息[/b]")
# 获取系统信息
textual_version = self._get_textual_version()
terminal_info = self._get_terminal_info()
@@ -31,7 +31,7 @@ class AboutScreen(Screen):
os_version = self._get_os_version()
disk_usage = self._get_disk_usage()
memory_info = self._get_memory_info()
about_text = f"""
# 关于 "潜进"
@@ -95,36 +95,39 @@ Textual 框架版本: {textual_version}
event.stop()
if event.button.id == "back_button":
self.action_go_back()
def _get_textual_version(self) -> str:
"""获取 Textual 框架版本"""
try:
import textual
return textual.__version__
except (ImportError, AttributeError):
except ImportError, AttributeError:
return "未知"
def _get_terminal_info(self) -> str:
"""获取终端模拟器信息"""
terminal = shutil.which("terminal")
if terminal:
return terminal
# 尝试从环境变量获取
terminal_env = os.environ.get('TERM_PROGRAM') or os.environ.get('TERM')
terminal_env = os.environ.get("TERM_PROGRAM") or os.environ.get("TERM")
return terminal_env or "未知"
def _get_python_version(self) -> str:
"""获取 Python 解释器版本"""
return platform.python_version()
def _get_os_version(self) -> str:
"""获取操作系统版本"""
try:
if platform.system() == "Darwin":
# macOS
import subprocess
result = subprocess.run(['sw_vers', '-productVersion'],
capture_output=True, text=True)
result = subprocess.run(
["sw_vers", "-productVersion"], capture_output=True, text=True
)
return f"macOS {result.stdout.strip()}"
elif platform.system() == "Windows":
# Windows
@@ -133,30 +136,31 @@ Textual 框架版本: {textual_version}
# Linux - 尝试获取发行版信息
try:
import distro
return f"{distro.name()} {distro.version()}"
except (ImportError, AttributeError):
except ImportError, AttributeError:
return platform.platform()
else:
return platform.platform()
except Exception:
return platform.platform()
def _get_disk_usage(self) -> str:
"""获取磁盘使用情况"""
try:
usage = psutil.disk_usage('/')
free_gb = usage.free / (1024 ** 3)
total_gb = usage.total / (1024 ** 3)
usage = psutil.disk_usage("/")
free_gb = usage.free / (1024**3)
total_gb = usage.total / (1024**3)
percent_free = (free_gb / total_gb) * 100
return f"{free_gb:.1f} GB ({percent_free:.1f}%)"
except Exception:
return "未知"
def _get_memory_info(self) -> str:
"""获取内存信息"""
try:
memory = psutil.virtual_memory()
total_gb = memory.total / (1024 ** 3)
total_gb = memory.total / (1024**3)
return f"{total_gb:.1f} GB"
except Exception:
return "未知"
return "未知"

View File

@@ -35,6 +35,8 @@ class DashboardScreen(Screen):
("q", "go_back", "返回"),
]
CSS_PATH = Path(__file__).parent.parent / 'css' / "screens" / "dashboard.tcss"
def __init__(
self,
name: str | None = None,
@@ -42,97 +44,91 @@ class DashboardScreen(Screen):
classes: str | None = None,
) -> None:
super().__init__(name, id, classes)
self.repostat = {}
self.title2dirname = {}
self.title2repo = {}
self.dirname2repo = {}
self._load_data()
self.repolink = {}
def compose(self) -> ComposeResult:
"""组合界面组件"""
self._load_data()
yield Header(show_clock=True)
with ScrollableContainer():
yield Horizontal(
yield Horizontal( # 顶部的状态
Vertical(
Label(f'欢迎使用 "潜进" 版本 {version.ver}'),
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
Label(
f"当前 UNIX 日时间戳: {timer.get_daystamp()}"
f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"
),
Label(f"应用时区修正: UTC+{config_var.get()['services']['timer']['timezone_offset'] / 3600}"),
Label(f"全局算法设置: {config_var.get()['interface']['global']['algorithm']}: {algorithms[config_var.get()['interface']['global']['algorithm']].desc}"),
classes="column infview",
Label(
f"默认算法设置: {config_var.get()['interface']['global']['algorithm']}"
),
classes="left",
),
Vertical(
Label(f"已加载 {len(self.repostat)} 个单元集", classes='dataview'),
Label(f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.get('unit_sum'), self.repostat.values()))} 个单元", classes='dataview'),
Label(f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.get('activated_sum'), self.repostat.values()))} 个单元", classes='dataview'),
Label(f"已加载 {len(self.repos)} 个单元集"),
Label(
f"共计 {reduce(lambda x, y: x + y, map(lambda x: x.progress['total'], self.repos))} 个单元"
),
Label(
f"已激活 {reduce(lambda x, y: x + y, map(lambda x: x.progress['touched'], self.repos))} 个单元"
),
Label(f""),
classes="column dataview",
classes="right",
),
id="dashboardtop"
id="header",
)
yield ListView(id="repo-list", classes="repo-list-view")
yield Label(f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}')
yield ListView(id="repo_list", classes="repo-list") # 单元集选择
yield Label(
f'"潜进" 启发式辅助记忆调度器 版本 {version.ver} {version.stage.capitalize()}'
) # 版本信息
yield Footer()
def _load_data(self):
self.repo_dirs = Repo.probe_valid_repos_in_dir(
Path(config_var.get()['global']["paths"]["data"]) / "repo"
repo_dirs = Repo.probe_valid_repos_in_dir(
Path(config_var.get()["global"]["paths"]["repo"])
)
for repo_dir in self.repo_dirs:
repo = Repo.create_from_repodir(repo_dir)
self.repos = list(map(Repo.from_repodir, repo_dirs))
for repo in self.repos:
self._analyse_repo(repo)
def _analyse_repo(self, repo: Repo):
dirname = repo.source.name # type: ignore
title = repo.manifest["title"]
is_due = 0
unit_sum = len(repo)
activated_sum = 0
nextdate = float('inf')
for i in repo.ident_index:
nucleon = pt.Nucleon.create_on_nucleonic_data(
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
)
electron = pt.Electron.create_on_electonic_data(
electronic_data=repo.electronic_data_lict.get_itemic_unit(i),
algo_name=config_var.get()['repo'][repo.manifest['title']]['algorithm']
)
if electron.is_activated():
activated_sum += 1
if electron.is_due():
is_due = 1
nextdate = min(nextdate, electron.nextdate())
is_unfinished = unit_sum > activated_sum
if is_unfinished:
nextdate = min(nextdate, timer.get_daystamp())
need_to_study = is_due or is_unfinished
prompt = f"{title}\0\n 进度: {activated_sum}/{unit_sum} ({round(activated_sum/unit_sum*100)}%)\n {'需要学习' if need_to_study else '无需操作'}"
stat = {
"is_due": is_due,
"unit_sum": unit_sum,
"title": title,
"activated_sum": activated_sum,
"nextdate": nextdate,
"is_unfinished": is_unfinished,
"need_to_study": need_to_study,
"prompt": prompt,
"dirname": dirname,
# need_review: 需要/不需要学习
# nearest_review_time: 最近下次学习时间
# progress: 进度
# algotype: 算法类型
## initial_time: 起始时间
# package: 包名
# prompt: 最终呈现信息
repo.package = repo.manifest["package"]
repo.nearest_review_time = float("inf")
repo.progress = {
"total": repo.data_length,
"touched": 0,
}
self.repostat[dirname] = stat
self.title2dirname[title] = dirname
self.title2repo[title] = repo
self.dirname2repo[dirname] = repo
initial_time = float("inf")
for i in range(repo.data_length):
e = pt.Electron.from_data(repo.electronic_data_lict[i])
n = pt.Nucleon.from_data(repo.nucleonic_data_lict[i])
if e.is_activated():
repo.algotype = e.algoname
repo.progress["touched"] += 1
repo.nearest_review_time = min(repo.nearest_review_time, e.nextdate())
# initial_time = min(initial_time, e.)
repo.need_review = timer.get_daystamp() >= repo.nearest_review_time
repo.prompt = f"""{repo.manifest['title']} ({repo.algotype})
进度: {repo.progress['touched']}/{repo.progress['total']} ({round(repo.progress['touched']/repo.progress['total']*100, 1)}%)
{'需要学习' if repo.need_review else "无需操作"}
"""
def on_mount(self) -> None:
"""挂载组件时初始化"""
repo_list_widget = self.query_one("#repo-list", ListView)
repo_list_widget = self.query_one("#repo_list", ListView)
# 按下次复习时间排序
repodirs = sorted(
self.repo_dirs,
key=lambda f: self.repostat[f.name]["nextdate"],
self.repos,
key=lambda r: r.nearest_review_time,
reverse=True,
)
repotitles = map(lambda f: self.repostat[f.name]["title"], repodirs)
@@ -141,43 +137,44 @@ class DashboardScreen(Screen):
repo_list_widget.append(
ListItem(
Static(
"./data/repo/ 中未找到任何仓库.\n"
f"{config_var.get()['global']['paths']['repo']} 中未找到任何仓库.\n"
"请导入仓库后重启应用, 或者新建空的仓库."
)
),
id="not-found",
)
)
repo_list_widget.disabled = True
return
for repotitle in repotitles:
prompt = self.repostat[self.title2dirname[repotitle]]["prompt"]
list_item = ListItem(Label(prompt), Button(f"开始学习", flat=True, variant="primary", classes="repo_listitem_btn", id=f"launch_{self.repostat[self.title2dirname[repotitle]]['dirname']}"), classes="repo_listitem")
for r in self.repos:
self.repolink[str(id(r))] = r # 用于规避 ctype id 对象还原
list_item = ListItem(
Label(r.prompt),
Button(
f"开始学习",
flat=True,
variant="primary",
id=f"slaunch_repo_{id(r)}",
classes="repo-list-item-shortcut",
),
classes="repo-list-item",
id=f"launch_repo_{id(r)}",
)
repo_list_widget.append(list_item)
# if not self.stay_enabled[repodir]:
# list_item.disabled = True
def on_list_view_selected(self, event) -> None:
"""处理列表项选择事件"""
if not isinstance(event.item, ListItem):
return
selected_label = event.item.query_one(Label)
label_text = str(selected_label.render())
if "未找到任何仓库" in label_text:
if "not-found" == event.item.id:
return
# 提取文件名
selected_repotitle = label_text.partition("\0")[0].replace("*", "")
selected_repo = self.title2repo[label_text.partition("\0")[0].replace("*", "")]
# 还原对象
selected_repo = self.repolink[event.item.id.lstrip("launch_repo_")]
# 跳转到准备屏幕
self.app.push_screen(
PreparationScreen(
selected_repo, self.repostat[self.title2dirname[selected_repotitle]]
)
)
self.app.push_screen(PreparationScreen(selected_repo))
def action_quit_app(self) -> None:
"""退出应用程序"""
@@ -188,9 +185,10 @@ class DashboardScreen(Screen):
self.app.push_screen(NavigatorScreen())
def on_button_pressed(self, event: Button.Pressed) -> None:
logger.debug(f"event.button.id: {event.button.id}")
"""处理按钮点击事件"""
if str(event.button.id).startswith("launch_"): # type: ignore
logger.debug(f"event.button.id: {event.button.id}")
if event.button.id.startswith("slaunch_repo_"): # type: ignore
from .preparation import launch
launch(repo=self.dirname2repo[event.button.id[7:]], app=self.app, scheduled_num=-1) # type: ignore
launch(repo=self.repolink[event.button.id.lstrip("slaunch_repo_")], app=self.app, scheduled_num=-1) # type: ignore
# TODO: 这样启动的记忆实例的状态机无法绑定到 PreparationScreen 中

View File

@@ -68,7 +68,7 @@ class FavoriteManagerScreen(Screen):
if self.favorites:
list_view = self.query_one("#favorites-list")
for fav in self.favorites:
list_view.append(self._create_favorite_item(fav)) # type: ignore
list_view.append(self._create_favorite_item(fav)) # type: ignore
def _encode_favorite_key(self, repo_path: str, ident: str) -> str:
"""编码仓库路径和标识符为安全的按钮 ID 部分"""
@@ -115,12 +115,12 @@ class FavoriteManagerScreen(Screen):
def _get_repo_info(self, repo_path: str, fav: FavoriteItem) -> Optional[dict]:
"""获取仓库信息(标题、原子内容预览)"""
try:
data_repo = Path(config_var.get()['global']["paths"]["data"]) / "repo"
data_repo = Path(config_var.get()["global"]["paths"]["data"]) / "repo"
repo_dir = data_repo / repo_path
if not repo_dir.exists():
logger.warning("仓库目录不存在: %s", repo_dir)
return None
repo = Repo.create_from_repodir(repo_dir)
repo = Repo.from_repodir(repo_dir)
# 获取原子内容预览
content_preview = ""
payload = repo.payload
@@ -201,4 +201,4 @@ class FavoriteManagerScreen(Screen):
def action_toggle_dark(self) -> None:
"""切换暗黑模式"""
self.app.dark = not self.app.dark # type: ignore
self.app.dark = not self.app.dark # type: ignore

View File

@@ -38,7 +38,7 @@ class MemScreen(Screen):
("0,1,2,3", "app.push_screen('about')", ""),
]
if config_var.get()['interface']['global']["quick_pass"]:
if config_var.get()["interface"]["global"]["quick_pass"]:
BINDINGS.append(("k", "quick_pass", "正确应答"))
BINDINGS.append(("f", "quick_fail", "错误应答"))
rating = reactive(-1)
@@ -70,7 +70,6 @@ class MemScreen(Screen):
"""更新状态机"""
self.procession: Procession = self.phaser.current_procession() # type: ignore
self.atom: pt.Atom = self.procession.current_atom # type: ignore
def on_mount(self):
self.fission = self.procession.get_fission()
@@ -93,7 +92,7 @@ class MemScreen(Screen):
if self.repo is not None:
fav_status = "已收藏" if self._is_current_atom_favorited() else "未收藏"
s += f"收藏: {fav_status}\n"
'''if config_var.get().get("debug_topline", 0):
"""if config_var.get().get("debug_topline", 0):
try:
alia = self.fission.get_current_puzzle_inf()["alia"] # type: ignore
s += f"谜题: {alia}\n"
@@ -113,7 +112,7 @@ class MemScreen(Screen):
stat = self.fission.__repr__("simple", "")
s += f"{stat}\n"
except Exception as e:
s = str(e)'''
s = str(e)"""
s += f"进度: {self.procession.process() + 1}/{self.procession.total_length()}"
return s
@@ -139,9 +138,9 @@ class MemScreen(Screen):
i.remove()
from heurams.interface.widgets.finished import Finished
if config_var.get()['interface']['global']["persist_to_file"]:
if config_var.get()["interface"]["global"]["persist_to_file"]:
self.save_func()
container.mount(Finished(is_saved=['interface']['global']["persist_to_file"]))
container.mount(Finished(is_saved=["interface"]["global"]["persist_to_file"]))
def on_button_pressed(self, event):
event.stop()
@@ -156,7 +155,7 @@ class MemScreen(Screen):
from heurams.services.audio_service import play_by_path
from heurams.services.hasher import get_md5
path = Path(config_var.get()['global']["paths"]["data"]) / "cache" / "voice"
path = Path(config_var.get()["global"]["paths"]["data"]) / "cache" / "voice"
path = path / f"{get_md5(self.atom.registry['nucleon']["tts_text"])}.wav"
if path.exists():
play_by_path(path)
@@ -177,7 +176,6 @@ class MemScreen(Screen):
self.forward(new_rating)
self.rating = -1
def forward(self, rating):
self.update_state()
allow_forward = 1 if rating >= 4 else 0
@@ -226,7 +224,7 @@ class MemScreen(Screen):
return ""
# self.repo.source 是 Path 对象,指向仓库目录
repo_full_path = self.repo.source
data_repo_path = Path(config_var.get()['global']["paths"]["data"]) / "repo"
data_repo_path = Path(config_var.get()["global"]["paths"]["data"]) / "repo"
try:
rel_path = repo_full_path.relative_to(data_repo_path)
return str(rel_path)

View File

@@ -53,7 +53,11 @@ class NavigatorScreen(ModalScreen):
)
yield Static("按下回车以完成切换\n所有会话将被保存")
yield Button(
"关闭 (n)", id="close_button", variant="primary", classes="close-button", flat=True
"关闭 (n)",
id="close_button",
variant="primary",
classes="close-button",
flat=True,
)
def on_mount(self) -> None:

View File

@@ -13,16 +13,16 @@ import heurams.services.hasher as hasher
from heurams.context import *
# 兼容性缓存路径:优先使用 paths.cache否则使用 data/cache
paths = config_var.get()['global']["paths"]
paths = config_var.get()["global"]["paths"]
cache_dir = pathlib.Path(paths.get("cache", paths["data"] + "/cache")) / "voice"
def format_size(bytes_num: int) -> str:
"""将字节数格式化为人类可读的字符串"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_num < 1024.0:
return f"{bytes_num:.2f} {unit}"
bytes_num /= 1024.0 # type: ignore
bytes_num /= 1024.0 # type: ignore
return f"{bytes_num:.2f} PB"
@@ -54,16 +54,24 @@ class PrecachingScreen(Screen):
self.cancel_flag = 0
self.desc = desc
# 不再需要缓存配置,保留配置读取以兼容
self.cache_stats = {"total_size": 0, "file_count": 0, "human_size": "0 B", "cached_units": 0, "total_units": 0, "cache_rate": 0}
self.cache_stats = {
"total_size": 0,
"file_count": 0,
"human_size": "0 B",
"cached_units": 0,
"total_units": 0,
"cache_rate": 0,
}
self._update_cache_stats()
def _get_total_units(self) -> int:
"""获取所有仓库的总单元数"""
from heurams.context import config_var
from heurams.kernel.repolib import Repo
repo_path = pathlib.Path(config_var.get()['global']["paths"]["data"]) / "repo"
repo_path = pathlib.Path(config_var.get()["global"]["paths"]["data"]) / "repo"
repo_dirs = Repo.probe_valid_repos_in_dir(repo_path)
repos = map(Repo.create_from_repodir, repo_dirs)
repos = map(Repo.from_repodir, repo_dirs)
total = 0
for repo in repos:
try:
@@ -86,7 +94,7 @@ class PrecachingScreen(Screen):
cached_units += 1
total_units = self._get_total_units()
cache_rate = (cached_units / total_units * 100) if total_units > 0 else 0
self.cache_stats["total_size"] = total_size
self.cache_stats["file_count"] = file_count
self.cache_stats["human_size"] = format_size(total_size)
@@ -101,11 +109,15 @@ class PrecachingScreen(Screen):
with Container():
yield Static(
f"缓存率: {self.cache_stats.get('cache_rate', 0):.1f}% (已缓存 {self.cache_stats.get('cached_units', 0)} / {self.cache_stats.get('total_units', 0)} 个单元)",
classes="cache-usage-text"
classes="cache-usage-text",
)
if self.nucleons:
yield Static(f"目标单元归属: [b]{self.desc}[/b]", classes="target-info")
yield Static(f"单元数量: {len(self.nucleons)}", classes="target-info")
yield Static(
f"目标单元归属: [b]{self.desc}[/b]", classes="target-info"
)
yield Static(
f"单元数量: {len(self.nucleons)}", classes="target-info"
)
else:
yield Static("目标: 所有单元", classes="target-info")
@@ -114,16 +126,26 @@ class PrecachingScreen(Screen):
yield ProgressBar(total=100, show_eta=False, id="progress_bar")
with Horizontal(classes="button-group"):
if not self.is_precaching:
yield Button("开始预缓存", id="start_precache", variant="primary")
yield Button(
"开始预缓存", id="start_precache", variant="primary"
)
else:
yield Button("取消预缓存", id="cancel_precache", variant="error")
yield Button(
"取消预缓存", id="cancel_precache", variant="error"
)
yield Button("清空缓存", id="clear_cache", variant="warning")
yield Button("返回", id="go_back", variant="default")
with Container(classes="cache-info"):
yield Static(f"缓存路径: {cache_dir}", classes="cache-path")
yield Static(f"文件数: {self.cache_stats['file_count']}", classes="cache-count")
yield Static(f"总大小: {self.cache_stats['human_size']}", classes="cache-size")
yield Button("刷新", id="refresh_cache_stats", variant="default", flat=True)
yield Static(
f"文件数: {self.cache_stats['file_count']}", classes="cache-count"
)
yield Static(
f"总大小: {self.cache_stats['human_size']}", classes="cache-size"
)
yield Button(
"刷新", id="refresh_cache_stats", variant="default", flat=True
)
yield Static("若您离开此界面, 未完成的缓存进程会自动停止.")
yield Static('缓存程序支持 "断点续传".')
@@ -230,9 +252,9 @@ class PrecachingScreen(Screen):
from heurams.context import config_var, rootdir, workdir
from heurams.kernel.repolib import Repo
repo_path = pathlib.Path(config_var.get()['global']["paths"]["data"]) / "repo"
repo_path = pathlib.Path(config_var.get()["global"]["paths"]["data"]) / "repo"
repo_dirs = Repo.probe_valid_repos_in_dir(repo_path)
repos = map(Repo.create_from_repodir, repo_dirs)
repos = map(Repo.from_repodir, repo_dirs)
# 计算总项目数
self.total = 0
@@ -241,7 +263,7 @@ class PrecachingScreen(Screen):
try:
for i in repo.ident_index:
nucleon_list.append(
pt.Nucleon.create_on_nucleonic_data(
pt.Nucleon.from_data(
repo.nucleonic_data_lict.get_itemic_unit(i)
)
)

View File

@@ -5,7 +5,16 @@ from textual.containers import ScrollableContainer
from textual.reactive import reactive
from textual.screen import Screen
from textual.widget import Widget
from textual.widgets import Button, Footer, Header, Label, Markdown, Static, Rule, Sparkline
from textual.widgets import (
Button,
Footer,
Header,
Label,
Markdown,
Static,
Rule,
Sparkline,
)
import heurams.kernel.particles as pt
import heurams.services.hasher as hasher
@@ -28,20 +37,19 @@ class PreparationScreen(Screen):
("0,1,2,3", "app.push_screen('about')", ""),
]
scheduled_num = reactive(config_var.get()['interface']['global']["scheduled_num"])
scheduled_num = reactive(config_var.get()["interface"]["global"]["scheduled_num"])
def __init__(self, repo: Repo, repostat: dict) -> None:
def __init__(self, repo: Repo) -> None:
super().__init__(name=None, id=None, classes=None)
self.repo = repo
self.repostat = repostat
self.load_data()
def compose(self) -> ComposeResult:
yield Header(show_clock=True)
with ScrollableContainer(id="vice_container"):
yield Label(f"准备就绪: [b]{self.repostat['title']}[/b]\n")
yield Label(f"准备就绪: [b]{self.repo.manifest['title']}[/b]\n")
yield Label(
f"仓库路径: {config_var.get()['global']['paths']['data']}/repo/[b]{self.repostat['dirname']}[/b]"
f"[b]仓库路径: {self.repo.source}[/b]"
)
yield Label(f"\n单元数量: {len(self.repo)}\n")
yield Label(f"最小记忆分组: {self.scheduled_num}\n", id="schnum_label")
@@ -62,12 +70,12 @@ class PreparationScreen(Screen):
yield Static()
yield Sparkline(self.spark_line_arr, summary_function=max)
yield Rule()
#yield Static(str(self.spark_line_arr))
# yield Static(str(self.spark_line_arr))
yield Static(f"单元状态预览:\n")
for i in self.content.splitlines():
yield Static(i, classes="full")
yield Footer()
# def watch_scheduled_num(self, old_scheduled_num, new_scheduled_num):
# logger.debug("响应", old_scheduled_num, "->", new_scheduled_num)
# try:
@@ -80,19 +88,21 @@ class PreparationScreen(Screen):
content = ""
spark_line_arr = []
for i in self.repo.ident_index:
n = pt.Nucleon.create_on_nucleonic_data(
n = pt.Nucleon.from_data(
nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)
)
e = pt.Electron.create_on_electonic_data(electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i))
e = pt.Electron.from_data(
electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i)
)
statstr = ""
if e.is_activated():
statstr = '[#00ff00]A[/]'
statstr = "[#00ff00]A[/]"
if e.is_due():
statstr = '[#ffff00]R[/]'
#statstr += ('[dim]' + str(e.rept(real_rept=True)).zfill(2)+'[/]')
statstr = "[#ffff00]R[/]"
# statstr += ('[dim]' + str(e.rept(real_rept=True)).zfill(2)+'[/]')
else:
statstr = '[#ff0000]U[/]'
statstr = "[#ff0000]U[/]"
spark_line_arr.append(e.rept(real_rept=True))
content += f" {statstr} {n['content'].replace('/', '')} \n"
self.content = content
@@ -107,9 +117,7 @@ class PreparationScreen(Screen):
lst = list()
for i in self.repo.ident_index:
lst.append(
pt.Nucleon.create_on_nucleonic_data(
self.repo.nucleonic_data_lict.get_itemic_unit(i)
)
pt.Nucleon.from_data(self.repo.nucleonic_data_lict.get_itemic_unit(i))
)
precache_screen = PrecachingScreen(
nucleons=lst, desc=self.repo.manifest["title"]
@@ -128,15 +136,16 @@ class PreparationScreen(Screen):
elif event.button.id == "precache_button":
self.action_precache()
def launch(repo, app, scheduled_num):
if scheduled_num == -1:
scheduled_num = config_var.get()['interface']['global']["scheduled_num"]
scheduled_num = config_var.get()["interface"]["global"]["scheduled_num"]
atoms = list()
for i in repo.ident_index:
n = pt.Nucleon.create_on_nucleonic_data(
n = pt.Nucleon.from_data(
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
)
e = pt.Electron.create_on_electonic_data(
e = pt.Electron.from_data(
electronic_data=repo.electronic_data_lict.get_itemic_unit(i)
)
a = pt.Atom(n, e, repo.orbitic_data)

View File

@@ -8,7 +8,19 @@ import os
from textual.app import ComposeResult
from textual.containers import ScrollableContainer, Container, Horizontal, Vertical
from textual.screen import Screen
from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static, Collapsible, Input, Switch, Select
from textual.widgets import (
Button,
Footer,
Header,
Label,
ListItem,
ListView,
Static,
Collapsible,
Input,
Switch,
Select,
)
from textual.layouts import horizontal
import heurams.kernel.particles as pt
@@ -45,13 +57,15 @@ class SettingScreen(Screen):
"""组合界面组件"""
yield Header(show_clock=True)
with ScrollableContainer():
yield Label('[b]设置页面[/b]')
yield Label("[b]设置页面[/b]")
for i in config_var.get():
if i.startswith('_'):
if i.startswith("_"):
continue
a = self._get_subcfg(f'{i}')
a = self._get_subcfg(f"{i}")
if a:
yield Collapsible(*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}')
yield Collapsible(
*a, title=i + f'\n{config_var.get().get(f"_{i}_desc", "")}'
)
yield Footer()
def _get_subcfg(self, parent_epath: str):
@@ -60,61 +74,115 @@ class SettingScreen(Screen):
if parent.is_dir:
lst = list()
for i in parent:
if i.startswith('_'):
if i.startswith("_"):
continue
a = self._get_subcfg(f"{parent_epath}.{i}")
if a:
lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'))
lst.append(
Collapsible(
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
)
)
return lst
if isinstance(parent, dict) or (isinstance(parent, ConfigDict) and not parent.is_dir):
if isinstance(parent, dict) or (
isinstance(parent, ConfigDict) and not parent.is_dir
):
lst = list()
for i in parent:
if i.startswith('_'):
if i.startswith("_"):
continue
if isinstance(parent[i], dict):
a = self._get_subcfg(f"{parent_epath}.{i}")
if a:
lst.append(Collapsible(*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'))
elif f'_{i}_candidate' in parent: # 选择框模式
if isinstance(parent[f'_{i}_candidate'], dict):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select(((f"{j} ({k})", j) for j, k in parent[f'_{i}_candidate'].items()), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
classes='container'
))
elif isinstance(parent[f'_{i}_candidate'], list):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select(((j, j) for j in parent[f'_{i}_candidate']), prompt=f'{parent.get(f"{i}", "")}', id=domize(f"{parent_epath}.{i}")),
classes='container'
))
lst.append(
Collapsible(
*a, title=i + f'\n{parent.get(f"_{i}_desc", "")}'
)
)
elif f"_{i}_candidate" in parent: # 选择框模式
if isinstance(parent[f"_{i}_candidate"], dict):
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select(
(
(f"{j} ({k})", j)
for j, k in parent[f"_{i}_candidate"].items()
),
prompt=f'{parent.get(f"{i}", "")}',
id=domize(f"{parent_epath}.{i}"),
),
classes="container",
)
)
elif isinstance(parent[f"_{i}_candidate"], list):
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Select(
((j, j) for j in parent[f"_{i}_candidate"]),
prompt=f'{parent.get(f"{i}", "")}',
id=domize(f"{parent_epath}.{i}"),
),
classes="container",
)
)
else:
if isinstance(parent[i], float):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(value=str(parent[i]), placeholder='要求一个浮点数', type='number', id=domize(f"{parent_epath}.{i}")),
classes='container'))
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(
value=str(parent[i]),
placeholder="要求一个浮点数",
type="number",
id=domize(f"{parent_epath}.{i}"),
),
classes="container",
)
)
elif isinstance(parent[i], str):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(value=parent[i], placeholder='要求一个字符串', type='text', id=domize(f"{parent_epath}.{i}")),
classes='container'))
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(
value=parent[i],
placeholder="要求一个字符串",
type="text",
id=domize(f"{parent_epath}.{i}"),
),
classes="container",
)
)
elif isinstance(parent[i], bool):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Switch(value=parent[i], id=domize(f"{parent_epath}.{i}")),
classes='container'))
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Switch(
value=parent[i], id=domize(f"{parent_epath}.{i}")
),
classes="container",
)
)
elif isinstance(parent[i], int):
lst.append(Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(value=str(parent[i]), placeholder='要求一个整数', type='integer', id=domize(f"{parent_epath}.{i}")),
classes='container'))
lst.append(
Horizontal(
Label(i + f'\n{parent.get(f"_{i}_desc", "")}'),
Input(
value=str(parent[i]),
placeholder="要求一个整数",
type="integer",
id=domize(f"{parent_epath}.{i}"),
),
classes="container",
)
)
elif isinstance(parent[i], list):
pass
else:
lst.append(Label('未知类型'))
lst.append(Label("未知类型"))
return lst
return [Label('无子项')]
return [Label("无子项")]
def on_mount(self) -> None:
"""挂载组件时初始化"""
@@ -133,14 +201,18 @@ class SettingScreen(Screen):
"""打开导航器"""
self.app.push_screen(NavigatorScreen())
def on_input_changed(self, event: Input.Changed) -> None:
widget_id = event.input.id
if not widget_id:
return
eepath = undomize(widget_id)
value = event.value
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
epath(
config_var.get(),
eepath,
enable_modify=True,
new_value=type(epath(config_var.get(), eepath))(value),
)
def on_switch_changed(self, event: Switch.Changed) -> None:
widget_id = event.switch.id
@@ -148,7 +220,12 @@ class SettingScreen(Screen):
return
eepath = undomize(widget_id)
value = event.value
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
epath(
config_var.get(),
eepath,
enable_modify=True,
new_value=type(epath(config_var.get(), eepath))(value),
)
def on_select_changed(self, event: Select.Changed) -> None:
widget_id = event.select.id
@@ -156,4 +233,9 @@ class SettingScreen(Screen):
return
eepath = undomize(widget_id)
value = event.value
epath(config_var.get(), eepath, enable_modify=True, new_value=type(epath(config_var.get(), eepath))(value))
epath(
config_var.get(),
eepath,
enable_modify=True,
new_value=type(epath(config_var.get(), eepath))(value),
)