Skip to content

Commit

Permalink
GUI scrollbars if window is bigger than screen
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Nov 16, 2024
1 parent ffcab28 commit dea85be
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 21 deletions.
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.0
## 0.7.1
* GUI scrollbars if window is bigger than the screen

## 0.7.0 (2024-11-08)
* hidden [`--integrate-to-system`](Overview.md#bash-completion) argument
* interfaces migrated to [`mininterface.interfaces`](Interfaces.md) to save around 50 ms starting time due to lazy loading
* [SubcommandPlaceholder][mininterface.subcommands.Command]
10 changes: 8 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ It was all the code you need. No lengthy blocks of code imposed by an external d


```bash
$ ./hello.py --help
$ ./program.py --help
usage: My application [-h] [-v] [--my-flag | --no-my-flag] [--my-number INT]

This calculates something.
Expand All @@ -67,6 +67,12 @@ Loading config file is a piece of cake. Alongside `program.py`, put `program.yam
my_number: 555
```
```bash
$ program.py --help
...
│ --my-number INT This number is very important (default: 555) │
```
## You got dialogues
Check out several useful methods to handle user dialogues. Here we bound the interface to a `with` statement that redirects stdout directly to the window.

Expand Down Expand Up @@ -113,7 +119,7 @@ See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic

# Examples

A powerful [`m.form`][mininterface.Mininterface.form] dialog method accepts either a dataclass or a dict. Take a look on both.
A powerful [`m.form`](https://cz-nic.github.io/mininterface/Mininterface/#mininterface.Mininterface.form) dialog method accepts either a dataclass or a dict. Take a look on both.

## A complex dataclass.

Expand Down
12 changes: 8 additions & 4 deletions mininterface/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from dataclasses import dataclass
import sys
from typing import Literal
from typing import Literal, Optional
from tyro.conf import FlagConversionOff

from .exceptions import DependencyRequired
Expand All @@ -25,6 +25,10 @@ class Web:
port: int = 64646


InterfaceType = Literal["gui"] | Literal["tui"] | Literal["all"]
Showcase = Literal[1] | Literal[2]


@dataclass
class CliInteface:
web: Web
Expand All @@ -39,8 +43,8 @@ class CliInteface:
is_no: str = ""
""" Display confirm box, focusing 'no'. """

showcase: Literal["gui"] | Literal["tui"] | Literal["all"] = None
""" Prints various form just to show what's possible. """
showcase: Optional[tuple[InterfaceType, Showcase]] = None
""" Prints various form just to show what's possible."""


def web(m: Mininterface):
Expand Down Expand Up @@ -71,7 +75,7 @@ def main():
if m.env.web.cmd:
web(m)
if m.env.showcase:
showcase(m.env.showcase)
showcase(*m.env.showcase)


if __name__ == "__main__":
Expand Down
53 changes: 45 additions & 8 deletions mininterface/showcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@
from . import Tag, Validation, run
from .validators import not_empty

from dataclasses import dataclass, field
from pathlib import Path
from mininterface import run
from mininterface.exceptions import ValidationFail
from mininterface.subcommands import Command, SubcommandPlaceholder
from tyro.conf import Positional


@dataclass
class SharedArgs(Command):
common: int
files: Positional[list[Path]] = field(default_factory=list)

def init(self):
self.internal = "value"


@dataclass
class Subcommand1(SharedArgs):
my_local: int = 1

def run(self):
print("Common:", self.common) # user input
print("Number:", self.my_local) # 1 or user input
print("Internal:", self.internal)
raise ValidationFail("The submit button blocked!")


@dataclass
class Subcommand2(SharedArgs):
def run(self):
self._facet.set_title("Button clicked") # you can access internal self._facet: Facet
print("Common files", self.files)


@dataclass
class NestedEnv:
Expand Down Expand Up @@ -31,12 +65,15 @@ class Env:
""" A validated field """


def showcase(interface: Literal["gui"] | Literal["tui"] | Literal["all"]):
if interface in ["gui", "all"]:
m = run(Env, title="My program", args=[], interface="gui")
print("GUI output", m.env)
m.form()
if interface in ["tui", "all"]:
m = run(Env, title="My program", args=[], interface="tui")
print("TUI output", m.env)
def showcase(interface: Literal["gui"] | Literal["tui"] | Literal["all"], case: int):
if interface == "all":
interface = None
kw = {"args": [], "interface": interface}
if case == 1:
m = run(Env, title="My program", **kw)
m.form()
print("Output", m.env)
elif case == 2:
m = run([Subcommand1, Subcommand2, SubcommandPlaceholder], **kw)
else:
print("Unknown showcase")
35 changes: 29 additions & 6 deletions mininterface/tk_interface/tk_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
from tkinter import LEFT, Button, Frame, Label, Text, Tk
from typing import TYPE_CHECKING, Any, Callable

from tkscrollableframe import ScrolledFrame
from tktooltip import ToolTip

from tkinter_form import Form, Value

from ..exceptions import Cancelled

from ..facet import BackendAdaptor
from ..form_dict import TagDict, formdict_to_widgetdict
from ..ValidationFail import ValidationFail
from ..tag import Tag
from .tk_facet import TkFacet
from .utils import recursive_set_focus, replace_widgets
Expand All @@ -23,7 +22,6 @@ class TkWindow(Tk, BackendAdaptor):
""" An editing window. """

def __init__(self, interface: "TkInterface"):
# NOTE I really need scrollbar if content is long
super().__init__()
self.facet = interface.facet = TkFacet(self, interface.env)
self.params = None
Expand All @@ -33,7 +31,12 @@ def __init__(self, interface: "TkInterface"):
self.title(interface.title)
self.bind('<Escape>', lambda _: self._ok(Cancelled))

self.frame = Frame(self)
# NOTE it would be nice to auto-hide the scrollbars if not needed
self.sf = ScrolledFrame(self, use_ttk=True)
""" scrollable superframe """
self.sf.pack(side="top", expand=1, fill="both")

self.frame = self.sf.display_widget(Frame)
""" dialog frame """

self.label = Label(self, text="")
Expand Down Expand Up @@ -72,7 +75,7 @@ def run_dialog(self, form: TagDict, title: str = "", submit: bool | str = True)
name_config=submit if isinstance(submit, str) else "Ok",
button=bool(submit)
)
self.form.pack()
self.form.grid()

# Add radio etc.
replace_widgets(self, self.form.widgets, form)
Expand Down Expand Up @@ -114,10 +117,30 @@ def _bind_event(self, event, handler):
self._event_bindings[event] = handler
self.bind(event, handler)

def _refresh_size(self):
""" Autoshow scrollbars."""
self.update_idletasks() # finish drawing
width = self.frame.winfo_width()
height = self.frame.winfo_height()

if width < self.winfo_screenwidth():
self.sf._x_scrollbar.grid_forget()
else:
self.sf._x_scrollbar.grid(row=1, column=0, sticky="we")

if height < self.winfo_screenheight():
self.sf._y_scrollbar.grid_forget()
else:
self.sf._y_scrollbar.grid(row=0, column=1, sticky="ns")

# The widgets do not know their size at the begginning, they must be drawn.
# Hence we recommend the window size here and not in the constructor.
self.geometry(f"{width}x{height}")

def mainloop(self, callback: Callable = None):
self.frame.pack(pady=5)
self.deiconify() # show if hidden
self.pending_buffer.clear()
self.after(1, self._refresh_size)
super().mainloop()
if not self.interface._always_shown:
self.withdraw() # hide
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ autocombobox = "1.4.2"
textual = "~0.84"
tkinter-tooltip = "*"
tkinter_form = "0.1.5.2"
tkscrollableframe = "*"

[tool.poetry.extras]
web = ["textual-serve"]
Expand Down

0 comments on commit dea85be

Please sign in to comment.