diff --git a/home_page.py b/home_page.py new file mode 100644 index 0000000..99d5eb3 --- /dev/null +++ b/home_page.py @@ -0,0 +1,103 @@ +from textual.containers import Center, Vertical, Horizontal +from textual.widgets import Rule, Static +from textual import on + +from textualeffects.widgets import EffectLabel +from textualeffects.effects import effects +from terminaltexteffects.utils.graphics import Color + + +class HomePage(Vertical): + LOGO = r""" ______ ______ ______ ______ __ __ +/\ == \ /\ ___\ /\ == \ /\ == \ /\ \_\ \ +\ \ __< \ \ __\ \ \ __< \ \ __< \ \____ \ + \ \_____\ \ \_____\ \ \_\ \_\ \ \_\ \_\ \/\_____\ + \/_____/ \/_____/ \/_/ /_/ \/_/ /_/ \/_____/ + +""" + + DEFAULT_CSS = """ + HomePage { + content-align: center top; + + Static { + text-align: center; + } + + #banner { + height: 5; # probs will need to be updated + margin-bottom: 1; + + Rule { + color: $surface-lighten-2; + height: 100%; + content-align: center middle; + margin: 0 3; + } + + EffectLabel { + height: 100%; + } + + + } + + #bindings { + Horizontal { + max-width: 50; + width: 50%; + margin-top: 1; + height: 1; + + .bind-name { + width: 85%; + } + .bind-keys { + width: 15%; + } + + Static { + text-align: left; + link-background: transparent; + link-color: $primary; + } + + + } + + } + } + """ + + @on(EffectLabel.EffectFinished) + def restart_effect(self): + self.banner_label.run_worker(self.banner_label.run_effect, exclusive=True) + + def compose(self): + with Horizontal(id="banner"): + yield Rule() + self.banner_label = EffectLabel(self.LOGO, effect="Highlight", config={ + "final_gradient_stops": [Color("#f263ff"), Color("#946ff2")], + "cycles": 0 + }) + yield self.banner_label + yield Rule() + + yield Static("[dim]The text editor that no one asked for and didn't need.[/]") + + # bindings + with Center(id="bindings"): + binds = self.app.bindings + + with Horizontal(): + yield Static("[b] New file[/]", classes="bind-name") + yield Static(f"[@click=app.new]{binds["new"]}[/]", classes="bind-keys") + + with Horizontal(): + yield Static("[b] Open file[/]", classes="bind-name") + yield Static(f"[@click=app.open]{binds["open"]}[/]", classes="bind-keys") + + with Horizontal(): + yield Static("[b] Settings[/]", classes="bind-name") + yield Static(f"[@click=app.settings]{binds["settings"]}[/]", classes="bind-keys") + \ No newline at end of file diff --git a/main.py b/main.py index 93b9481..ea72d04 100644 --- a/main.py +++ b/main.py @@ -8,6 +8,8 @@ from textual_fspicker import FileOpen, FileSave from pathlib import Path +from home_page import HomePage + from assets.theme_mappings import theme_mappings from plugin_loader import PluginLoader from settings import SettingsScreen @@ -32,19 +34,32 @@ class Berry(App): CSS_PATH = "assets/style.tcss" SUB_TITLE = "New File" - BINDINGS = [ - Binding("ctrl+o", "open", "Open File"), - Binding("ctrl+n", "new", "New File"), - Binding("ctrl+s", "save", "Save"), - Binding("ctrl+shift+s", "save_as", "Save As...", priority=True), - Binding("ctrl+f", "find", "Find", priority=True), - Binding("ctrl+f1", "settings", "Settings") - ] + def __init__(self, path: str): + self.config_handler = ConfigHandler(self) + + self.bindings = eval(self.config_handler.get("editor", "bindings")) + super().__init__() self.path = path - self.config_handler = ConfigHandler(self) + + self.BINDINGS = ( + Binding(self.bindings["new"], "new", "New File"), + Binding(self.bindings["open"], "open", "Open File"), + Binding(self.bindings["save"], "save", "Save File"), + Binding(self.bindings["save-as"], "save_as", "Save File As..."), + Binding(self.bindings["find"], "find", "Find and Replace"), + Binding(self.bindings["settings"], "settings", "Settings"), + ) + + for binding in self.BINDINGS: + self._bindings._add_binding(binding) + + self.refresh_bindings() + + self.current_editor = None + self.file_tabs = {} def compose(self) -> ComposeResult: yield Header() @@ -59,14 +74,18 @@ class Berry(App): yield CustomDirectoryTree(self.path, id="directory") with Vertical(id="editor"): - first_tab = Tab("New File") - first_tab.file_path = None + first_tab = Tab("Home") + first_tab.file_path = "berry://home" + self.file_tabs["berry://home"] = first_tab yield Tabs( first_tab, id="file-tabs" ) - yield TextArea.code_editor(placeholder="This file is empty.", theme="css", id="code-editor", disabled=True, soft_wrap=True, show_line_numbers=bool(int(self.config_handler.get("editor", "line_numbers")))) + + with Vertical() as editor: + yield HomePage() + self.current_editor = editor #if os.name == "nt": #with Vertical(id="console-container"): @@ -201,7 +220,9 @@ class Berry(App): tabs.active = self.file_tabs[str(event.path)].id def open_text_editor(self, file_content: bytes, file_extension: str): - code_editor: TextArea = self.query_one("#code-editor") + code_editor = TextArea.code_editor(placeholder="This file is empty.", theme="css", disabled=True, soft_wrap=bool(int(self.config_handler.get("editor", "word_wrap"))), show_line_numbers=bool(int(self.config_handler.get("editor", "line_numbers")))) + + self.current_editor.mount(code_editor) code_editor.language = theme_mappings.get(file_extension, None) @@ -215,10 +236,12 @@ class Berry(App): code_editor.focus() - def open_editor(self, file_extension: str, file_content: str): + def open_editor(self, file_extension: str, file_content: bytes): default_editors: dict = eval(self.config_handler.get("editor", "default_editors")) editor_for_file = default_editors.get(file_extension, None) - + + self.current_editor.remove_children() + if not editor_for_file: self.open_text_editor(file_content, file_extension) @@ -230,8 +253,12 @@ class Berry(App): else: self.open_file = getattr(event.tab, "file_path") + if self.open_file and self.open_file.startswith(r"berry://"): + return + self.switching = True + content = b"" file_extension = "" @@ -265,8 +292,6 @@ class Berry(App): event.window.remove_window() def on_text_area_changed(self, event: TextArea.Changed): - if event.text_area.id != "code-editor": - return if self.switching: self.switching = False return @@ -301,10 +326,9 @@ class Berry(App): self.open_file = None self.switching = True - code_editor: TextArea = self.query_one("#code-editor") - - code_editor.disabled = False - code_editor.text = "" + + # open empty editor + self.open_editor("", b"") def done_saving(self, result): if result is None: return @@ -342,8 +366,6 @@ class Berry(App): return super().action_quit() def on_ready(self): - # src/main.py: Tab<> - self.file_tabs = {} self.open_file = None self.unsaved_files = {} # list of paths self.switching = False diff --git a/plugins/Markdown Reader/plugin.json b/plugins/Markdown Reader/plugin.json index 1d9be49..9e991b8 100644 --- a/plugins/Markdown Reader/plugin.json +++ b/plugins/Markdown Reader/plugin.json @@ -2,5 +2,6 @@ "name": "Markdown Reader", "author": "SpookyDervish", "version": "1.0.0", + "description": "", "dependencies": [] } \ No newline at end of file diff --git a/settings.py b/settings.py index f785275..e907c60 100644 --- a/settings.py +++ b/settings.py @@ -1,5 +1,5 @@ from textual.screen import ModalScreen -from textual.widgets import Label, Select, TabbedContent, TabPane, Switch, Input, Rule, Static +from textual.widgets import Label, Select, TextArea, TabbedContent, TabPane, Switch, Input, Rule, Static from textual.containers import Vertical, HorizontalGroup, VerticalGroup, VerticalScroll from textual.binding import Binding @@ -70,11 +70,15 @@ class SettingsScreen(ModalScreen): self.dismiss() def on_switch_changed(self, event: Switch.Changed): + text_area = self.app.current_editor.query_one_optional(TextArea) + if event.switch.id == "word-wrap": - self.app.query_one("#code-editor").soft_wrap = event.value + if text_area: + text_area.soft_wrap = event.value self.app.config_handler.set("editor", "word_wrap", str(int(event.value))) elif event.switch.id == "line-numbers": - self.app.query_one("#code-editor").show_line_numbers = event.value + if text_area: + text_area.show_line_numbers = event.value self.app.config_handler.set("editor", "line_numbers", str(int(event.value))) elif event.switch.id == "plugins-enabled": self.app.config_handler.set("plugins", "enabled", str(int(event.value))) diff --git a/settings_store.py b/settings_store.py index 0266fe3..5e90c22 100644 --- a/settings_store.py +++ b/settings_store.py @@ -6,104 +6,114 @@ from typing import Any class ConfigHandler: - def __init__(self, app: App): - self.app: App = app - self.config: configparser.ConfigParser = configparser.ConfigParser() - - self.plugin_defined_settings = {} - self.config_dir: str = self.ensure_hidden_config_dir() - self.load_settings() + def __init__(self, app: App): + self.app: App = app + self.config: configparser.ConfigParser = configparser.ConfigParser() + + self.plugin_defined_settings = {} + self.config_dir: str = self.ensure_hidden_config_dir() + self.load_settings() - def define_plugin_setting(self, plugin_name: str, option_name: str, description: str, option_type: str, default_value: Any, on_changed_func = None): - actual_type = None - if option_type == "boolean": - actual_type = bool - elif option_type == "string": - actual_type = str - elif option_type == "float": - actual_type = float - elif option_type == "integer": - actual_type = int - else: - raise Exception(f"Invalid type name \"{option_type}\".") + def define_plugin_setting(self, plugin_name: str, option_name: str, description: str, option_type: str, default_value: Any, on_changed_func = None): + actual_type = None + if option_type == "boolean": + actual_type = bool + elif option_type == "string": + actual_type = str + elif option_type == "float": + actual_type = float + elif option_type == "integer": + actual_type = int + else: + raise Exception(f"Invalid type name \"{option_type}\".") - if plugin_name in self.plugin_defined_settings: - self.plugin_defined_settings[plugin_name][option_name] = { - "option_name": option_name, - "description": description, - "type": actual_type, - "default_value": default_value, - "on_changed_func": on_changed_func - } - else: - self.plugin_defined_settings[plugin_name] = {option_name: { - "option_name": option_name, - "description": description, - "type": actual_type, - "default_value": default_value, - "on_changed_func": on_changed_func - }} + if plugin_name in self.plugin_defined_settings: + self.plugin_defined_settings[plugin_name][option_name] = { + "option_name": option_name, + "description": description, + "type": actual_type, + "default_value": default_value, + "on_changed_func": on_changed_func + } + else: + self.plugin_defined_settings[plugin_name] = {option_name: { + "option_name": option_name, + "description": description, + "type": actual_type, + "default_value": default_value, + "on_changed_func": on_changed_func + }} - # user hasnt used this plugin before - if self.get("plugin_" + plugin_name, option_name) == None: - self.set("plugin_" + plugin_name, option_name, default_value) - - def ensure_hidden_config_dir(self): - config_dir = Path.home() / ".berry" - config_dir.mkdir(parents=True, exist_ok=True) + # user hasnt used this plugin before + if self.get("plugin_" + plugin_name, option_name) == None: + self.set("plugin_" + plugin_name, option_name, default_value) + + def ensure_hidden_config_dir(self): + config_dir = Path.home() / ".berry" + config_dir.mkdir(parents=True, exist_ok=True) - return config_dir + return config_dir - def get(self, section: str, option: str): - if not self.config.has_section(section): - self.config.add_section(section) - if not self.config.has_option(section, option): - self.set(section, option, "0") - return "0" + def get(self, section: str, option: str): + if not self.config.has_section(section): + self.config.add_section(section) + if not self.config.has_option(section, option): + self.set(section, option, "0") + return "0" - return self.config.get(section, option, fallback=None) - - def set(self, section: str, option: str, new_value: str): - if not self.config.has_section(section): - self.config.add_section(section) - self.config.set(section, option, new_value) + return self.config.get(section, option, fallback=None) + + def set(self, section: str, option: str, new_value: str): + if not self.config.has_section(section): + self.config.add_section(section) + self.config.set(section, option, new_value) - if section.startswith("plugin_"): - section = section.removeprefix("plugin_") - if self.plugin_defined_settings[section].get("on_changed_func", None) != None: - self.plugin_defined_settings[section]["on_changed_func"](new_value) - - self.write_settings() + if section.startswith("plugin_"): + section = section.removeprefix("plugin_") + if self.plugin_defined_settings[section].get("on_changed_func", None) != None: + self.plugin_defined_settings[section]["on_changed_func"](new_value) + + self.write_settings() - def apply_settings(self): - self.app.query_one("#code-editor").soft_wrap = bool(int(self.get("editor", "word_wrap"))) - self.app.theme = self.get("appearance", "colour_theme") + def apply_settings(self): + if self.app.current_editor and self.app.current_editor.id == "code-editor": + self.app.current_editor.soft_wrap = bool(int(self.get("editor", "word_wrap"))) + + self.app.theme = self.get("appearance", "colour_theme") - def write_settings(self): - with open(self.config_dir / "config.ini", "w") as configfile: - self.config.write(configfile) + def write_settings(self): + with open(self.config_dir / "config.ini", "w") as configfile: + self.config.write(configfile) - def load_settings(self): - if os.path.isfile(self.config_dir / "config.ini"): - self.config.read(self.config_dir / "config.ini") - return + def load_settings(self): + if os.path.isfile(self.config_dir / "config.ini"): + self.config.read(self.config_dir / "config.ini") + return - self.config["config"] = { - "version": "1" - } - self.config["editor"] = { - "word_wrap": "0", - "line_numbers": "1", - "default_editors": {} - } - self.config["plugins"] = { - "enabled": "1", - "log": "1", - "log_timeout": "10" - } - self.config["appearance"] = { - "colour_theme": "textual-dark" - } + self.config["config"] = { + "version": "1" + } + self.config["editor"] = { + "word_wrap": "0", + "line_numbers": "1", + "default_editors": {}, + "bindings": { + "open": "ctrl+o", + "new": "ctrl+n", + "save": "ctrl+s", + "save-as": "ctrl+shift+s", + "find": "ctrl+f", + "settings": "ctrl+f1" + } + } + self.config["plugins"] = { + "enabled": "1", + "log": "1", + "log_timeout": "10" + } + self.config["appearance"] = { + "colour_theme": "textual-dark" + } - with open(self.config_dir / "config.ini", "w") as configfile: - self.config.write(configfile) \ No newline at end of file + with open(self.config_dir / "config.ini", "w") as configfile: + self.config.write(configfile) \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index 4648e70..0000000 --- a/test.py +++ /dev/null @@ -1 +0,0 @@ -print("Hello, World!") \ No newline at end of file