Skip to content

Commit

Permalink
tui layout
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Jan 9, 2025
1 parent 7defc95 commit 18a82e2
Show file tree
Hide file tree
Showing 9 changed files with 69 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ jobs:
- name: Build the package
run: python3 -m pip install --upgrade build && python3 -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@v1.8.10
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_GITHUB_MININTERFACE }}
5 changes: 4 additions & 1 deletion docs/Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## 0.7.2
## 0.7.3 (2025-01-09)
* fix: put GUI descriptions back to the bottom

## 0.7.2 (2024-12-30)
* GUI calendar

## 0.7.1 (2024-11-27)
Expand Down
5 changes: 5 additions & 0 deletions mininterface/textual_interface/textual_adaptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class TextualAdaptor(BackendAdaptor):
def __init__(self, interface: "TextualInterface"):
self.interface = interface
self.facet = interface.facet = TextualFacet(self, interface.env)
self.app: TextualApp | None = None
self.layout_elements = []

@staticmethod
def widgetize(tag: Tag) -> Widget | Changeable:
Expand Down Expand Up @@ -73,6 +75,9 @@ def header(self, text: str):

def run_dialog(self, form: TagDict, title: str = "", submit: bool | str = True) -> TagDict:
super().run_dialog(form, title, submit)
# Unfortunately, there seems to be no way to reuse the app.
# Which blocks using multiple form external .form() calls from the web interface.
# Textual cannot run in a thread, it seems it cannot run in another process, self.suspend() is of no use.
self.app = app = TextualApp(self, submit)
if title:
app.title = title
Expand Down
9 changes: 9 additions & 0 deletions mininterface/textual_interface/textual_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ class TextualApp(App[bool | None]):
# ("down", "go_up", "Go down"),
# ]

DEFAULT_CSS = """
ImageViewer{
height: 20;
}
"""
""" Limit layout image size """

def __init__(self, adaptor: "TextualAdaptor", submit: str | bool = True):
super().__init__()
self.title = adaptor.facet._title
Expand All @@ -52,6 +60,7 @@ def compose(self) -> ComposeResult:
yield Label(text, id="buffered_text")
focus_set = False
with VerticalScroll():
yield from self.adaptor.layout_elements
for i, fieldt in enumerate(self.widgets):
if isinstance(fieldt, Input):
yield Label(fieldt.placeholder)
Expand Down
3 changes: 3 additions & 0 deletions mininterface/textual_interface/textual_button_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import TYPE_CHECKING, Any

from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.widgets import Button, Footer, Label

from ..exceptions import Cancelled
Expand Down Expand Up @@ -60,6 +61,7 @@ def __init__(self, interface: "TextualInterface"):
self.focused_i: int = 0
self.values = {}
self.interface = interface
self.adaptor = self.interface.adaptor

def yes_no(self, text: str, focus_no=True) -> DummyWrapper:
return self.buttons(text, [("Yes", True), ("No", False)], int(focus_no))
Expand All @@ -78,6 +80,7 @@ def compose(self) -> ComposeResult:
yield Footer()
if text := self.interface._redirected.join():
yield Label(text, id="buffered_text")
yield from self.adaptor.layout_elements
yield Label(self.text, id="question")

self.values.clear()
Expand Down
35 changes: 34 additions & 1 deletion mininterface/textual_interface/textual_facet.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from datetime import datetime
from typing import TYPE_CHECKING
from warnings import warn
from ..facet import Facet
from pathlib import Path

from textual.widgets import (Checkbox, Footer, Header, Input, Label,
RadioButton, Static)

from humanize import naturalsize

from ..exceptions import DependencyRequired
from ..facet import Facet, Image, LayoutElement
if TYPE_CHECKING:
from .textual_adaptor import TextualAdaptor

Expand All @@ -22,6 +31,30 @@ def set_title(self, title: str):
# NOTE: When you receive Facet in Command.init, the app does not exist yet
warn("Setting textual title not implemented well.")

def _layout(self, elements: list[LayoutElement]):
append = self.adaptor.layout_elements.append
try:
from PIL import Image as ImagePIL
from textual_imageview.viewer import ImageViewer
PIL = True
except:
PIL = False

for el in elements:
match el:
case Image():
if not PIL:
raise DependencyRequired("img")
append(ImageViewer(ImagePIL.open(el.src)))
case Path():
size = naturalsize(el.stat().st_size)
mtime = datetime.fromtimestamp(el.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
append(Label(f"{el} / {size} / {mtime}"))
case str():
append(Label(el))
case _:
append(Label("Error in the layout: Unknown {el}"))

def submit(self, *args, **kwargs):
super().submit(*args, **kwargs)
try:
Expand Down
5 changes: 3 additions & 2 deletions mininterface/tk_interface/external_fix.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# The purpose of the file is to put the descriptions to the bottom of the widgets as it was in the former version of the tkinter_form.
# The purpose of the file is to put the descriptions to the bottom of the widgets
# as it was in the former version of the tkinter_form and to limit their width.
from tkinter import ttk

from tkinter_form import Form, Value, FieldForm
Expand Down Expand Up @@ -48,7 +49,7 @@ def __create_widgets_monkeypatched(
description_label = None
if not description is None:
index += 1
description_label = ttk.Label(self, text=description)
description_label = ttk.Label(self, text=description, wraplength=1000)
description_label.grid(row=index, column=1, columnspan=2, sticky="nesw", padx=2, pady=2)

self.fields[name_key] = FieldForm(
Expand Down
11 changes: 7 additions & 4 deletions mininterface/tk_interface/tk_facet.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ def _layout(self, elements: list[LayoutElement]):
raise DependencyRequired("img")
filename = el.src
img = ImagePIL.open(filename)
img = img.resize((250, 250))
img = ImageTk.PhotoImage(img)
panel = Label(self.adaptor.frame, image=img)
panel.image = img
max_width, max_height = 250, 250
w_o, h_o = img.size
scale = min(max_width / w_o, max_height / h_o)
img = img.resize((int(w_o * scale), int(h_o * scale)), ImagePIL.LANCZOS)
img_p = ImageTk.PhotoImage(img)
panel = Label(self.adaptor.frame, image=img_p)
panel.image = img_p
panel.pack()
case Path():
size = naturalsize(el.stat().st_size)
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ tkscrollableframe = "*"

[tool.poetry.extras]
web = ["textual-serve"]
img = ["pillow", "textual_imageview"]
tui = ["textual_imageview"]
gui = ["pillow", "tkcalendar"]
all = ["textual-serve", "pillow", "tkcalendar"]
all = ["textual-serve", "pillow", "tkcalendar", "textual_imageview"]

[tool.poetry.scripts]
mininterface = "mininterface.__main__:main"
Expand Down

0 comments on commit 18a82e2

Please sign in to comment.