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 ver = "0.3.2" config = aux.ConfigFile("config.toml") class MemScreen(Screen): BINDINGS = [ ("d", "toggle_dark", "改变色调"), ("q", "pop_screen", "返回主菜单"), ("v", "play_voice", "朗读"), ] if config.get("quick_pass"): BINDINGS.append(("k", "quick_pass", "快速通过[调试]")) 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) self.reactor.forward() self.compo = next(self.reactor.current_appar) def compose(self) -> ComposeResult: if type(self.compo).__name__ == "Recognition": self.action_play_voice() yield Header(show_clock=True) with Center(): yield Static( f"{len(self.reactor.procession) - self.reactor.index}/{len(self.reactor.procession)}" ) yield from self.compo.compose() yield Footer() 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) 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() else: self.reactor.set_round_templated(self.stage) self.reactor.forward(1) 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) print(type(self.compo).__name__) def action_play_voice(self): print("VOICE") 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_quick_pass(self): self.reactor.report(self.reactor.current_atom, 5) self._forward_judge(0) 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().replace("/", ""), 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: if event.button.id == "start_memorizing_button": newscr = MemScreen( self.nucleon_file, self.electron_file, config.get("tasked_number", 6) ) self.app.push_screen(newscr) 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): return selected_label = event.item.query_one(Label) if "未找到任何 .toml 文件" in str(selected_label.renderable): 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.app.push_screen(PreparationScreen(nucleon_file, electron_file)) def action_quit_app(self) -> None: self.app.exit() class AppLauncher(App): CSS_PATH = "styles.css" TITLE = "潜进 - 辅助记忆程序" BINDINGS = [("escape", "quit", "退出"), ("d", "toggle_dark", "改变色调")] SCREENS = { "file_selection_screen": FileSelectorScreen, } def on_mount(self) -> None: self.push_screen("file_selection_screen") if __name__ == "__main__": app = AppLauncher() app.run()