build out ui
This commit is contained in:
70
src/main.py
70
src/main.py
@@ -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()
|
||||||
@@ -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
34
src/widgets/file_tabs.py
Normal 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
102
src/widgets/home_page.py
Normal 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")
|
||||||
|
|
||||||
@@ -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
13
test.py
Normal 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
|
||||||
Reference in New Issue
Block a user