from textual.app import App, ComposeResult from textual.widgets import Header, Footer, ListView, ProgressBar, DirectoryTree, ListItem, Label, Static, Button from textual.containers import Container, Horizontal, Center from textual.screen import Screen import pathlib import threading import edge_tts as tts from playsound import playsound import particles as pt from reactor import Reactor, Apparatus import auxiliary as aux import compositions as compo import builtins # 保存原始的 open 函数 _original_open = builtins.open # 定义新的 open 函数,默认使用 UTF-8 def _open(*args, **kwargs): if 'encoding' not in kwargs: kwargs['encoding'] = 'utf-8' return _original_open(*args, **kwargs) # 替换全局的 open builtins.open = _open ver = '0.3.0' config = aux.ConfigFile("config.toml") class MemScreen(Screen): BINDINGS = [ ("d", "toggle_dark", "改变色调"), ("q", "pop_screen", "返回主菜单"), ("v", "play_voice", "朗读"), ("0", "press('q0')", None), ("1", "press('q1')", None), ("2", "press('q2')", None), ("3", "press('q3')", None), ("4", "press('q4')", None), ("5", "press('q5')", None), ("[", "press('q5')", None), ("]", "press('q4')", None), (";", "press('q3')", None), ("'", "press('q2')", None), (".", "press('q1')", None), ("/", "press('q0')", None), ] btn = dict() def __init__( self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion, tasked_num ): super().__init__(name=None, id=None, classes=None) self.reactor = Reactor(nucleon_file, electron_file, self, tasked_num) self.stage = 1 self.stage += self.reactor.set_round_templated(self.stage) ##print(self.reactor.procession) self.reactor.forward() #self.compo:compo.Composition = compo.Placeholder(self) self.compo = next(self.reactor.current_appar) def compose(self) -> ComposeResult: #print(self.compo) yield Header(show_clock=True) with Center(): yield Static(f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}") #yield ProgressBar(total=len(self.reactor.procession) - 1, show_percentage=False, show_eta=False, id="progress") yield from self.compo.compose() yield Footer() """ def _get_progress_text(self): return f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}" """ def on_mount(self): # 首次挂载时调用 pass def on_button_pressed(self, event): ret = self.compo.handler(event, "button") self._forward_judge(ret) def _forward_judge(self, ret): if ret == -1: return if ret == 0: # 成功 try: self.compo = next(self.reactor.current_appar) self.refresh_ui() except StopIteration: nxt = self.reactor.forward(1) #print(2) try: self.compo = next(self.reactor.current_appar) except: pass if nxt == -1: if self.reactor.round_set == 0: if self.stage == 4: #if config.get("save"): #self.reactor.save() self.compo = compo.Finished(self, None, pt.Atom.placeholder()) self.refresh_ui() #self._show_finished_screen("今日目标已完成") else: self.reactor.set_round_templated(self.stage) self.reactor.forward(1) #self._update_ui() self.stage += 1 self.compo = next(self.reactor.current_appar) self.refresh_ui() return return else: self.refresh_ui() return if ret == 1: # 不允许前进 self.refresh_ui() return def refresh_ui(self): self.call_later(self.recompose) #self.call_later(lambda: self.query_one("#progress", expect_type=ProgressBar).advance(self.reactor.index)) ##print(area.children) #for child in list(area.children): # child.remove() # 致敬传奇组件树 DOM ##print(1,list(self.compo.compose())) #area.mount(*list(self.compo.compose())) def report(self, quality): assessment = self.reactor.report(self.reactor.current_atom, quality) return assessment """if assessment == 1: # 需要复习 feedback_label.update(f"评分为 {quality}, 已经加入至复习, 请重复记忆") else: ret = self.reactor.forward(1) if ret == -1: if self.reactor.round_set == 0: if self.stage == 4: # NOTE # if config.get("save"): self.reactor.save() self._show_finished_screen("今日目标已完成") else: self.reactor.set_round_templated(self.stage) self.reactor.forward(1) self._update_ui() self.stage += 1 return #feedback_label.update("") # 清除反馈消息 self._update_ui()""" #def action_press(self, btnid): # self.on_button_pressed(btnid) def action_play_voice(self): def play(): cache_dir = pathlib.Path(f"./cache/voice/") cache_dir.mkdir(parents = True, exist_ok = True) cache = cache_dir / f"{self.reactor.current_atom[1].content.replace("/","")}.wav" if not cache.exists(): communicate = tts.Communicate(self.reactor.current_atom[1].content.replace("/",""), "zh-CN-YunjianNeural") communicate.save_sync(f"./cache/voice/{self.reactor.current_atom[1].content.replace("/","")}.wav") playsound(str(cache)) threading.Thread(target=play).start() def action_toggle_dark(self): self.app.action_toggle_dark() def action_pop_screen(self): """返回到上一个屏幕""" self.app.pop_screen() class PreparationScreen(Screen): BINDINGS = [ ("q", "go_back", "返回"), ("escape", "quit_app", "退出") ] def __init__(self, nucleon_file: pt.NucleonUnion, electron_file: pt.ElectronUnion) -> None: super().__init__(name=None, id=None, classes=None) self.nucleon_file = nucleon_file self.electron_file = electron_file def compose(self) -> ComposeResult: yield Header(show_clock=True) with Container(id="learning_screen_container"): yield Label(f"记忆项目: [b]{self.nucleon_file.name}[/b]\n") yield Label(f"核子文件对象: ./nucleon/[b]{self.nucleon_file.name}[/b].toml") yield Label(f"电子文件对象: ./electron/[b]{self.electron_file.name}[/b].toml") yield Label(f"核子数量:{len(self.nucleon_file)}") yield Button("开始记忆", id="start_memorizing_button", variant="primary", classes="start-button") yield Static(f"\n全文如下:\n") yield Static(self._get_full_content(), classes="full") yield Footer() def _get_full_content(self): content = "" for i in self.nucleon_file.nucleons: content += i['content'] return content def action_go_back(self): self.app.pop_screen() def action_quit_app(self): self.app.exit() def on_button_pressed(self, event: Button.Pressed) -> None: pass if event.button.id == "start_memorizing_button": #init_file(Path(self.atom_file).name) newscr = MemScreen(self.nucleon_file, self.electron_file, config.get("tasked_number", 8)) self.app.push_screen( newscr ) #if event.button.id == "edit_metadata_button": # init_file(Path(self.atom_file).name) # os.system("reset;nano ./data/" + str(Path(self.atom_file).name.replace(".txt", "_atoms.json"))) class FileSelectorScreen(Screen): global ver def compose(self) -> ComposeResult: yield Header(show_clock=True) yield Container( Label(f'欢迎使用 "潜进" 辅助记忆软件, 版本 {ver}', classes="title-label"), Label("选择要学习的文件:", classes="title-label"), ListView(id="file-list", classes="file-list-view") ) yield Footer() def on_mount(self) -> None: file_list_widget = self.query_one("#file-list", ListView) nucleon_path = pathlib.Path("./nucleon") nucleon_files = sorted([f.name for f in nucleon_path.iterdir() if f.suffix == ".toml"]) if nucleon_files: for filename in nucleon_files: file_list_widget.append(ListItem(Label(filename))) else: file_list_widget.append(ListItem(Static("在 ./nucleon/ 中未找到任何核子文件. 请放置文件后重启应用."))) file_list_widget.disabled = True def on_list_view_selected(self, event: ListView.Selected) -> None: if not isinstance(event.item, ListItem): self.notify("无法选择此项。", severity="error") return selected_label = event.item.query_one(Label) if "未找到任何 .toml 文件" in str(selected_label.renderable): self.notify("请先在 `./atoms/` 目录中放置 .toml 文件。", severity="warning") return selected_filename = str(selected_label.renderable) nucleon_file = pt.NucleonUnion(pathlib.Path("./nucleon") / selected_filename) electron_file_path = pathlib.Path("./electron") / selected_filename if electron_file_path.exists(): pass else: electron_file_path.touch() electron_file = pt.ElectronUnion(pathlib.Path("./electron") / selected_filename) # self.notify(f"已选择: {selected_filename}", timeout=2) self.app.push_screen(PreparationScreen(nucleon_file, electron_file)) def action_quit_app(self) -> None: self.app.exit() class AppLauncher(App): CSS_PATH = "styles.tcss" TITLE = '潜进 - 辅助记忆程序' BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")] SCREENS = { "file_selection_screen": FileSelectorScreen, } def on_mount(self) -> None: #self.action_toggle_dark() self.push_screen("file_selection_screen") if __name__ == "__main__": app = AppLauncher() app.run()