mirror of
https://github.com/1j01/textual-paint.git
synced 2024-12-22 22:31:43 +03:00
Set up development features for gallery app
This commit is contained in:
parent
937dabb73c
commit
9140d14405
@ -9,6 +9,7 @@ from textual.app import ScreenStackError
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .paint import PaintApp
|
from .paint import PaintApp
|
||||||
|
from .gallery import GalleryApp
|
||||||
|
|
||||||
def restart_program() -> None:
|
def restart_program() -> None:
|
||||||
"""Restarts the current program, after resetting terminal state, and cleaning up file objects and descriptors."""
|
"""Restarts the current program, after resetting terminal state, and cleaning up file objects and descriptors."""
|
||||||
@ -57,7 +58,7 @@ def restart_program() -> None:
|
|||||||
# os.execl(python, python, *sys.argv)
|
# os.execl(python, python, *sys.argv)
|
||||||
os.execl(sys.executable, *sys.orig_argv)
|
os.execl(sys.executable, *sys.orig_argv)
|
||||||
|
|
||||||
def restart_on_changes(app: PaintApp) -> None:
|
def restart_on_changes(app: PaintApp|GalleryApp) -> None:
|
||||||
"""Restarts the current program when a file is changed"""
|
"""Restarts the current program when a file is changed"""
|
||||||
|
|
||||||
from watchdog.events import PatternMatchingEventHandler, FileSystemEvent, EVENT_TYPE_CLOSED, EVENT_TYPE_OPENED
|
from watchdog.events import PatternMatchingEventHandler, FileSystemEvent, EVENT_TYPE_CLOSED, EVENT_TYPE_OPENED
|
||||||
@ -80,7 +81,7 @@ def restart_on_changes(app: PaintApp) -> None:
|
|||||||
# or else nothing happens.
|
# or else nothing happens.
|
||||||
# However, when _app.action_reload is called from the key binding,
|
# However, when _app.action_reload is called from the key binding,
|
||||||
# it seems to work fine with or without unsaved changes.
|
# it seems to work fine with or without unsaved changes.
|
||||||
if _app.is_document_modified():
|
if isinstance(_app, PaintApp) and _app.is_document_modified():
|
||||||
_app.call_from_thread(_app.action_reload)
|
_app.call_from_thread(_app.action_reload)
|
||||||
else:
|
else:
|
||||||
restart_program()
|
restart_program()
|
||||||
|
5
src/textual_paint/gallery.css
Normal file
5
src/textual_paint/gallery.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
GalleryItem {
|
||||||
|
layout: vertical;
|
||||||
|
width: 100%;
|
||||||
|
content-align: center middle;
|
||||||
|
}
|
@ -2,20 +2,29 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.widgets import Footer, Header, Static
|
from textual.binding import Binding
|
||||||
from textual.containers import HorizontalScroll, Vertical
|
from textual.containers import HorizontalScroll, Vertical
|
||||||
|
from textual.widgets import Footer, Header, Static
|
||||||
|
|
||||||
from .paint import AnsiArtDocument
|
|
||||||
from .__init__ import __version__
|
from .__init__ import __version__
|
||||||
|
from .auto_restart import restart_on_changes, restart_program
|
||||||
|
from .paint import AnsiArtDocument
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='ANSI art gallery', usage='%(prog)s [folder]', prog="python -m src.textual_paint.gallery")
|
parser = argparse.ArgumentParser(description='ANSI art gallery', usage='%(prog)s [folder]', prog="python -m src.textual_paint.gallery")
|
||||||
parser.add_argument('folder', nargs='?', default=None, help='Path to a folder containing ANSI art.')
|
parser.add_argument('folder', nargs='?', default=None, help='Path to a folder containing ANSI art.')
|
||||||
|
|
||||||
|
dev_options = parser.add_argument_group('development options')
|
||||||
|
dev_options.add_argument('--inspect-layout', action='store_true', help='Enables DOM inspector (F12) and middle click highlight')
|
||||||
|
dev_options.add_argument('--restart-on-changes', action='store_true', help='Restart the app when the source code is changed')
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
def _(text: str) -> str:
|
||||||
|
"""Placeholder for localization function."""
|
||||||
|
return text
|
||||||
|
|
||||||
class GalleryItem(Vertical):
|
class GalleryItem(Vertical):
|
||||||
"""An image with a caption."""
|
"""An image with a caption."""
|
||||||
|
|
||||||
@ -32,11 +41,31 @@ class GalleryItem(Vertical):
|
|||||||
yield Static(text)
|
yield Static(text)
|
||||||
yield Static(self.caption)
|
yield Static(self.caption)
|
||||||
|
|
||||||
class Gallery(App[None]):
|
class GalleryApp(App[None]):
|
||||||
"""ANSI art gallery TUI"""
|
"""ANSI art gallery TUI"""
|
||||||
|
|
||||||
TITLE = f"ANSI art gallery v{__version__}"
|
TITLE = f"ANSI art gallery v{__version__}"
|
||||||
|
|
||||||
|
CSS_PATH = "gallery.css"
|
||||||
|
|
||||||
|
BINDINGS = [
|
||||||
|
Binding("ctrl+q", "exit", _("Quit")),
|
||||||
|
# dev helper
|
||||||
|
# f5 would be more traditional, but I need something not bound to anything
|
||||||
|
# in the context of the terminal in VS Code, and not used by this app, like Ctrl+R, and detectable in the terminal.
|
||||||
|
# This isn't as important now that I have automatic reloading,
|
||||||
|
# but I still use it regularly.
|
||||||
|
Binding("f2", "reload", _("Reload")),
|
||||||
|
# Temporary quick access to work on a specific dialog.
|
||||||
|
# Can be used together with `--press f3` when using `textual run` to open the dialog at startup.
|
||||||
|
# Would be better if all dialogs were accessible from the keyboard.
|
||||||
|
# Binding("f3", "custom_zoom", _("Custom Zoom")),
|
||||||
|
# Dev tool to inspect the widget tree.
|
||||||
|
Binding("f12", "toggle_inspector", _("Toggle Inspector")),
|
||||||
|
# Update screenshot on readme.
|
||||||
|
# Binding("ctrl+j", "update_screenshot", _("Update Screenshot")),
|
||||||
|
]
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
"""Add widgets to the layout."""
|
"""Add widgets to the layout."""
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
@ -46,6 +75,15 @@ class Gallery(App[None]):
|
|||||||
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
if not args.inspect_layout:
|
||||||
|
return
|
||||||
|
# importing the inspector adds instrumentation which can slow down startup
|
||||||
|
from .inspector import Inspector
|
||||||
|
inspector = Inspector()
|
||||||
|
inspector.display = False
|
||||||
|
yield inspector
|
||||||
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Called when the app is mounted to the DOM."""
|
"""Called when the app is mounted to the DOM."""
|
||||||
self._load()
|
self._load()
|
||||||
@ -68,8 +106,25 @@ class Gallery(App[None]):
|
|||||||
|
|
||||||
self.scroll.mount(GalleryItem(image, caption=filename))
|
self.scroll.mount(GalleryItem(image, caption=filename))
|
||||||
|
|
||||||
|
def action_reload(self) -> None:
|
||||||
|
"""Reload the program."""
|
||||||
|
restart_program()
|
||||||
|
|
||||||
app = Gallery()
|
def action_toggle_inspector(self) -> None:
|
||||||
|
if not args.inspect_layout:
|
||||||
|
return
|
||||||
|
# importing the inspector adds instrumentation which can slow down startup
|
||||||
|
from .inspector import Inspector
|
||||||
|
inspector = self.query_one(Inspector)
|
||||||
|
inspector.display = not inspector.display
|
||||||
|
if not inspector.display:
|
||||||
|
inspector.picking = False
|
||||||
|
|
||||||
|
|
||||||
|
app = GalleryApp()
|
||||||
|
|
||||||
|
if args.restart_on_changes:
|
||||||
|
restart_on_changes(app)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run()
|
app.run()
|
||||||
|
Loading…
Reference in New Issue
Block a user