build out ui

This commit is contained in:
2026-07-27 20:51:13 +10:00
parent 074791f430
commit de7e3b9070
6 changed files with 226 additions and 10 deletions

View File

@@ -1,15 +1,77 @@
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Footer from textual.widgets import Header, Footer, TabPane, TabbedContent
from widgets.text_editor import TextEditor from textual.containers import Vertical
from widgets.text_editor import TextEditor, TextArea
from widgets.file_tabs import FileTabs
from widgets.directory_tree_custom import CustomDirectoryTree
import sys, os
import argparse
import pathlib
class Berry(App): class Berry(App):
DEFAULT_CSS = """
Berry {
#main {
padding: 1;
}
}
"""
bind_keys = {
"new": "ctrl+n",
"open": "ctrl+o",
"open-folder": "ctrl+shift+o",
"save": "ctrl+s",
"save-as": "ctrl+shift+s",
"settings": "ctrl+f1",
}
def __init__(self, path: str):
super().__init__()
self.open_tab = None
self.path = pathlib.Path(path)
self.BINDINGS = (
Binding(self.bind_keys['new'], "new", "New file"),
Binding(self.bind_keys['open'], "open_file", "Open file"),
Binding(self.bind_keys['open-folder'], "open_folder", "Open directory"),
Binding(self.bind_keys['save'], "save", "Save"),
Binding(self.bind_keys['save-as'], "save_as", "Save as...")
)
for bind in self.BINDINGS:
self._bindings._add_binding(bind)
self.refresh_bindings()
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield TextEditor(language="python") yield Header()
with Vertical(id="main"):
yield CustomDirectoryTree(self.path.parent if self.path.is_file() else self.path)
self.file_tabs = FileTabs()
yield self.file_tabs
yield Footer() yield Footer()
async def on_ready(self) -> None:
if self.path.is_file():
await self.file_tabs.open_file(self.path)
return
await self.file_tabs.open_home()
if __name__ == "__main__": if __name__ == "__main__":
app = Berry() arg_parser = argparse.ArgumentParser()
arg_parser.add_argument("path", default=".")
args = arg_parser.parse_args()
app = Berry(args.path)
app.run() app.run()

View File

@@ -2,24 +2,29 @@ from textual.widgets import DirectoryTree, Rule
from textual.widgets.tree import TreeNode from textual.widgets.tree import TreeNode
from textual.events import MouseDown from textual.events import MouseDown
from prompt import Prompt from widgets.prompt import Prompt
from context_menu import ContextMenu, NoSelectStatic from widgets.context_menu import ContextMenu, NoSelectStatic
import os, shutil import os, shutil
class CustomDirectoryTree(DirectoryTree): class CustomDirectoryTree(DirectoryTree):
DEFAULT_CSS = """
CustomDirectoryTree {
dock: left;
width: 30;
}
"""
ICON_NODE = "" ICON_NODE = ""
ICON_NODE_EXPANDED = "" ICON_NODE_EXPANDED = ""
ICON_FILE = "" ICON_FILE = ""
def __init__(self, path, *, name = None, id = None, classes = None, disabled = False): def __init__(self, path, *, name = None, id = None, classes = None, disabled = False):
super().__init__(path, name=name, id=id, classes=classes, disabled=disabled) super().__init__(path, name=name, id=id, classes=classes, disabled=disabled)
self.right_clicked_node: TreeNode | None = None self.right_clicked_node: TreeNode | None = None
def context_menu_chosen(self, file_name: str, result: str): def context_menu_chosen(self, file_name: str, result: str):
if result == "Delete": if result == "Delete":
def delete_confirm(will_delete: bool | None): def delete_confirm(will_delete: bool | None):

34
src/widgets/file_tabs.py Normal file
View File

@@ -0,0 +1,34 @@
from textual.widgets import TabbedContent, TabPane
from widgets.text_editor import TextEditor
from widgets.home_page import HomePage
import os
class FileTabs(TabbedContent):
DEFAULT_CSS = """
FileTabs {
margin: 0 1;
}
"""
async def open_home(self) -> None:
new_pane = TabPane("Home")
new_pane.path = None
await self.add_pane(new_pane)
await new_pane.mount(HomePage())
async def open_file(self, path: str) -> None:
if self.tab_count > 0:
for tab in self.query(TabPane):
if tab.path == path:
self.active = tab.id
return
new_pane = TabPane(os.path.basename(path))
new_pane.path = path
await self.add_pane(new_pane)
await new_pane.mount(TextEditor())

102
src/widgets/home_page.py Normal file
View File

@@ -0,0 +1,102 @@
from textual.widgets import Static, Rule
from textual.containers import Vertical, Horizontal, Center
from textual import on
from textualeffects.widgets import EffectLabel
from terminaltexteffects.utils.graphics import Color
import pyfiglet
class HomePage(Vertical):
DEFAULT_CSS = """
HomePage {
content-align: center top;
Static {
text-align: center;
}
#banner {
height: 6; # 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: 75%;
}
.bind-keys {
width: 25%;
}
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(pyfiglet.figlet_format("berry", font="sub-zero"), 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.bind_keys
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] Open Folder[/]", classes="bind-name")
yield Static(f"[@click=app.open_folder]{binds["open-folder"]}[/]", classes="bind-keys")
with Horizontal():
yield Static("[b] Settings[/]", classes="bind-name")
yield Static(f"[@click=app.settings]{binds["settings"]}[/]", classes="bind-keys")

View File

@@ -3,4 +3,4 @@ from textual.widgets import TextArea
class TextEditor(TextArea): class TextEditor(TextArea):
def __init__(self, text = "", language = None, read_only = False): def __init__(self, text = "", language = None, read_only = False):
super().__init__(text, language=language, placeholder="Type here . . .", read_only=read_only, theme="css") super().__init__(text, language=language, placeholder="Type here . . .", read_only=read_only, theme="vscode_dark", show_line_numbers=True, tab_behavior="indent")

13
test.py Normal file
View File

@@ -0,0 +1,13 @@
import pyfiglet
text_to_render = "Test"
fonts = pyfiglet.FigletFont.getFonts()
for font in fonts:
try:
print(f"\nFont: {font}")
result = pyfiglet.figlet_format(text_to_render, font=font)
print(result)
except Exception:
# Prevents the script from crashing if a specific font configuration fails
continue