diff --git a/context_menu.py b/context_menu.py new file mode 100644 index 0000000..fecc0b8 --- /dev/null +++ b/context_menu.py @@ -0,0 +1,119 @@ +# totally not stolen from my code for my chat app Portal ;) +from __future__ import annotations +from textual.screen import ModalScreen +from textual.containers import Container +from textual.widgets import Static +from textual.geometry import Offset +from textual.message import Message +from textual.visual import VisualType +from textual import on, events + + +class NoSelectStatic(Static): + """This class is used in window.py and windowbar.py to create buttons.""" + + @property + def allow_select(self) -> bool: + return False + + +class ButtonStatic(NoSelectStatic): + """This class is used in window.py, windowbar.py, and switcher.py to create buttons.""" + + class Pressed(Message): + def __init__(self, button: ButtonStatic) -> None: + super().__init__() + self.button = button + + @property + def control(self) -> ButtonStatic: + return self.button + + def __init__( + self, + content: VisualType = "", + *, + expand: bool = False, + shrink: bool = False, + markup: bool = True, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False, + ) -> None: + super().__init__( + content=content, + expand=expand, + shrink=shrink, + markup=markup, + name=name, + id=id, + classes=classes, + disabled=disabled, + ) + self.click_started_on: bool = False + + def on_mouse_down(self, event: events.MouseDown) -> None: + + self.add_class("pressed") + self.click_started_on = True + + def on_mouse_up(self, event: events.MouseUp) -> None: + + self.remove_class("pressed") + if self.click_started_on: + self.post_message(self.Pressed(self)) + self.click_started_on = False + + def on_leave(self, event: events.Leave) -> None: + + self.remove_class("pressed") + self.click_started_on = False + + +class ContextMenu(ModalScreen): + DEFAULT_CSS = """ + ContextMenu { + background: $background 0%; + align: left top; + } + + #menu_container { + padding: 0 1; + background: $surface; + width: 21; + border: hkey $panel; + & > ButtonStatic { + content-align: left middle; + &:hover { background: $panel-lighten-2; } + &.pressed { background: $primary; } + + } + } + """ + + def __init__(self, options: list[str], offset: Offset): + super().__init__() + self.options = options + self.mouse_offset = offset + + def on_mouse_up(self, event: events.MouseUp): + if not self.query_one("#menu_container").region.contains(event.screen_x, event.screen_y): + self.dismiss(None) + + @on(ButtonStatic.Pressed) + async def thingy(self, event: ButtonStatic.Pressed): + self.dismiss(event.button.content) + + def compose(self): + with Container(id="menu_container"): + for option in self.options: + if isinstance(option, str): + yield ButtonStatic(option) + else: + yield option + + def on_mount(self): + menu_container = self.query_one("#menu_container") + menu_container.styles.height = len(menu_container.children) + 2 + menu_container.offset = self.mouse_offset \ No newline at end of file diff --git a/directory_tree_custom.py b/directory_tree_custom.py new file mode 100644 index 0000000..ecc27c9 --- /dev/null +++ b/directory_tree_custom.py @@ -0,0 +1,31 @@ +from textual.widgets import DirectoryTree, Rule +from textual.widgets.tree import TreeNode +from textual.events import MouseDown +from context_menu import ContextMenu, NoSelectStatic + + +class CustomDirectoryTree(DirectoryTree): + def __init__(self, path, *, name = None, id = None, classes = None, disabled = False): + super().__init__(path, name=name, id=id, classes=classes, disabled=disabled) + self.right_clicked_node: TreeNode | None = None + + def context_menu_chosen(self, result): + self.right_clicked_node = None + + def on_mouse_down(self, event: MouseDown): + if event.button != 3 or not "line" in event.style.meta: + return + selected_node = self.get_node_at_line(event.style.meta["line"]) + self.right_clicked_node = selected_node + + options = None + if self._safe_is_dir(self.right_clicked_node.data.path): + options = ["New Folder", "New File", NoSelectStatic(f'[d]{"-" * 17}[/]'), "Delete", "Rename", "Open"] + else: + options = ["Delete", "Rename", "Open"] + + self.app.push_screen(ContextMenu( + [NoSelectStatic(f"[b]{self.right_clicked_node.label}[/]"), NoSelectStatic(f'[d]{"-" * 17}[/]')] + options, + event.screen_offset + ), self.context_menu_chosen) + diff --git a/main.py b/main.py index 6b9f6aa..5bbeeed 100644 --- a/main.py +++ b/main.py @@ -12,11 +12,11 @@ from assets.theme_mappings import theme_mappings from plugin_loader import PluginLoader from settings import SettingsScreen from settings_store import ConfigHandler +from directory_tree_custom import CustomDirectoryTree from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler -import subprocess import os @@ -56,7 +56,7 @@ class Berry(App): with ContentSwitcher(initial="files", id="sidebar-switcher"): with Vertical(id="files"): yield Static("EXPLORER") - yield DirectoryTree(self.path, id="directory") + yield CustomDirectoryTree(self.path, id="directory") with Vertical(id="editor"): first_tab = Tab("New File")