Skip to content

Commit

Permalink
on_change
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Sep 16, 2024
1 parent bf71c57 commit c044e01
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 75 deletions.
Binary file added asset/submitButton.avif
Binary file not shown.
5 changes: 5 additions & 0 deletions docs/Experimental.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Experimental
::: mininterface.experimental
options:
members:
- SubmitButton
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,11 @@ m.form(my_dictionary)











Expand Down
56 changes: 51 additions & 5 deletions mininterface/experimental.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,55 @@

# class SubmitToTrue(bool):
# pass
# SubmitButton = TypeVar("SubmitButton")

from types import FunctionType


class SubmitButton:
""" Create a button. When clicked, the form submits.
If submission succeeds (validation checks pass), its value becomes True.
```python
from pathlib import Path
from mininterface import run, Tag
from mininterface.experimental import SubmitButton
m = run(interface="gui")
out = m.form({
"File name": Tag("/tmp", annotation=Path),
"Append text": {
"My text": "",
"Append now": SubmitButton()
},
"Duplicate": {
"Method": Tag("twice", choices=["twice", "thrice"]),
"Duplicate now": SubmitButton()
}
})
# Clicking on 'Append now' button
print(out)
# {'File name': PosixPath('/tmp'),
# 'Append text': {'My text': '', 'Append now': True},
# 'Duplicate': {'Method': 'twice', 'Duplicate now': False}}
```
![Submit button](asset/submitButton.avif)
"""
pass
# NOTE I would prefer this is a mere type, not a class.


# FunctionType is not acceptable base type
class FacetCallback():
""" This type denotes the Tag value is a function.
A button should be created. When clicked, it gets the facet as the argument.
"""
pass
# TODO, not complete

# NOTE EXPERIMENTAL
# def SubmitToTrue(name=None, description=""):
# """ Create a button. When click, the form submits. If submission succeeds, its value becomes True. """
# def SubmitButton(name=None, description=""):
# """ Create a button. When clicked, the form submits.
# If submission succeeds (validation checks pass), its value becomes True.
# """
# from .tag import Tag
# return Tag(val=False, description=description, name=None, annotation=SubmitToTrue)
# return Tag(val=False, description=description, name=None, annotation=SubmitButton)
45 changes: 28 additions & 17 deletions mininterface/gui_interface/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from tkinter.ttk import Checkbutton, Combobox, Frame, Radiobutton, Widget

from ..auxiliary import flatten
# from ..experimental import SubmitToTrue # NOTE EXPERIMENTAL
from ..experimental import FacetCallback, SubmitButton
from ..form_dict import TagDict
from ..tag import Tag

Expand Down Expand Up @@ -53,6 +53,13 @@ def _hand(variable: Variable, tag: Tag):
return lambda *args: tag._on_change_trigger(variable.get())


def _set_true(variable: Variable, tag: Tag):
def _(*args):
variable.set(True)
tag.facet.submit()
return _


def replace_widgets(nested_widgets, form: TagDict):
def _fetch(variable):
return ready_to_replace(widget, var_name, tag, variable)
Expand Down Expand Up @@ -83,25 +90,20 @@ def _fetch(variable):
widget2 = Radiobutton(nested_frame, text=choice_label, variable=variable, value=choice_val)
widget2.grid(row=i, column=1)
subwidgets.append(widget2)
# elif tag.annotation is SubmitToTrue: # NOTE EXPERIMENTAL
# def _set_true(variable):
# variable.set(True)
# tag.facet.submit()
# variable = AnyVariable(tag.val)
# master, grid_info = _fetch(variable)
# widget2 = Button(master, text=tag.name, command=lambda variable=variable:_set_true(variable))
# widget2.grid(row=grid_info['row'], column=grid_info['column'])
# label1.grid_forget()
# variable = False

# Special type: Submit button
elif tag.annotation is SubmitButton: # NOTE EXPERIMENTAL
variable, widget = create_button(_fetch, tag, label1)
widget.config(command=_set_true(variable, tag))
# variable.set(False)

# Special type: FacetCallback button
elif tag.annotation is FacetCallback: # NOTE EXPERIMENTAL
variable, widget = create_button(_fetch, tag, label1, lambda tag=tag: tag.val(tag.facet))

# Replace with a callback button
elif tag._is_a_callable():
variable = AnyVariable(tag.val)
master, grid_info = _fetch(variable)
# widget2 = Button(master, text=tag.name, command=lambda tag=tag: tag.val(tag.facet)) NOTE how to receive the facet?
widget2 = Button(master, text=tag.name, command=tag.val)
widget2.grid(row=grid_info['row'], column=grid_info['column'])
label1.grid_forget()
variable, widget = create_button(_fetch, tag, label1, tag.val)

# Add event handler
if tag.on_change:
Expand All @@ -122,6 +124,15 @@ def _fetch(variable):
label1.config(text=tag.name)


def create_button(_fetch, tag, label1, command=None):
variable = AnyVariable(tag.val)
master, grid_info = _fetch(variable)
widget2 = Button(master, text=tag.name, command=command)
widget2.grid(row=grid_info['row'], column=grid_info['column'])
label1.grid_forget()
return variable, widget2


def widgets_to_dict(widgets_dict) -> dict:
""" Convert tkinter_form.widgets to a dict """
result = {}
Expand Down
11 changes: 7 additions & 4 deletions mininterface/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, TypeVar, get_args, get_origin, get_type_hints
from warnings import warn

# from .experimental import SubmitToTrue # NOTE EXPERIMENTAL
from .experimental import SubmitButton


from .auxiliary import flatten
Expand Down Expand Up @@ -163,7 +163,7 @@ class Env:
def callback(tag: Tag):
tag.facet.set_title(f"Value changed to {tag.val}")
m = run(interface="gui")
m = run()
m.facet.set_title("Click the checkbox")
m.form({
"My choice": Tag(choices=["one", "two"], on_change=callback)
Expand Down Expand Up @@ -256,6 +256,9 @@ def __post_init__(self):
# annotated as a NoneType.
self.annotation = type(self.val)

if self.annotation is SubmitButton:
self.val = False

if not self.name and self._src_key:
self.name = self._src_key
self._original_desc = self.description
Expand Down Expand Up @@ -330,8 +333,8 @@ class Env:
"""
if self.annotation is None:
return True
# elif self.annotation is SubmitToTrue: # NOTE EXPERIMENTAL
# return val is True or val is False
elif self.annotation is SubmitButton: # NOTE EXPERIMENTAL
return val is True or val is False

try:
return isinstance(val, self.annotation)
Expand Down
72 changes: 24 additions & 48 deletions mininterface/textual_interface/textual_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.containers import VerticalScroll
from textual.widgets import (Checkbox, Footer, Header, Input, Label,
RadioButton, RadioSet, Static)
from textual.widget import Widget
from textual.widgets import Footer, Header, Label, RadioButton, Static, Checkbox, Input

from mininterface.textual_interface.widgets import Changeable, MyButton, MyCheckbox, MyInput, MyRadioSet, MySubmitButton

from ..experimental import SubmitButton

from ..auxiliary import flatten
from ..common import Cancelled
Expand All @@ -15,42 +19,7 @@
from . import TextualInterface


class Changeable:
""" Widget that can implement on_change method. """

_link: Tag

def trigger_change(self):
if tag := self._link:
tag: Tag
tag._on_change_trigger(self.get_ui_value())

def get_ui_value(self):
if isinstance(self, RadioSet):
if self.pressed_button:
return str(self.pressed_button.label)
else:
return None
else:
return self.value


WidgetList = list[Checkbox | Input | Changeable]


class RadioSet_onchangeable(RadioSet, Changeable):
def on_radio_set_changed(self):
return self.trigger_change()


class Chb_onchangeable(Checkbox, Changeable):
def on_checkbox_changed(self):
return self.trigger_change()


class Input_onchangeable(Input, Changeable):
async def on_blur(self):
return self.trigger_change()
WidgetList = list[Widget | Changeable]


class TextualApp(App[bool | None]):
Expand Down Expand Up @@ -79,25 +48,32 @@ def __init__(self, interface: "TextualInterface"):
self.output = Static("")

@staticmethod
def widgetize(tag: Tag) -> Checkbox | Input:
def widgetize(tag: Tag) -> Widget | Changeable:
""" Wrap Tag to a textual widget. """

v = tag._get_ui_val()
# Handle boolean
if tag.annotation is bool or not tag.annotation and (v is True or v is False):
# o = Checkbox(tag.name or "", v)
o = Chb_onchangeable(tag.name or "", v)
# print("73: ", o.on_checkbox_changed) # TODO
o.on_checkbox_changed = None # on_CHANGE
o = MyCheckbox(tag.name or "", v)
# Replace with radio buttons
elif tag._get_choices():
o = RadioSet_onchangeable(*(RadioButton(label, value=val == tag.val)
for label, val in tag._get_choices().items()))
o = MyRadioSet(*(RadioButton(label, value=val == tag.val)
for label, val in tag._get_choices().items()))
# Special type: Submit button
elif tag.annotation is SubmitButton: # NOTE EXPERIMENTAL
o = MySubmitButton(tag.name)

# Replace with a callback button
elif tag._is_a_callable():
o = MyButton(tag.name)

else:
if not isinstance(v, (float, int, str, bool)):
v = str(v)
o = Input_onchangeable(str(v), placeholder=tag.name or "")
# TODO onchangeable Radio + Checkbox + docs.
tag._last_ui_val = o.get_ui_value()
o = MyInput(str(v), placeholder=tag.name or "")

o._link = tag # The Textual widgets need to get back to this value
tag._last_ui_val = o.get_ui_value()
return o

# Why class method? I do not know how to re-create the dialog if needed.
Expand Down
3 changes: 2 additions & 1 deletion mininterface/textual_interface/textual_facet.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ def set_title(self, title: str):
self._title = title
self.window.title = title

# TODO missing def submit(self):
def submit(self):
self.window.action_confirm()
59 changes: 59 additions & 0 deletions mininterface/textual_interface/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from textual.widgets import Button, Checkbox, Input, RadioSet

from ..tag import Tag


class Changeable:
""" Widget that can implement on_change method. """

_link: Tag

def trigger_change(self):
if tag := self._link:
tag: Tag
tag._on_change_trigger(self.get_ui_value())

def get_ui_value(self):
if isinstance(self, RadioSet):
if self.pressed_button:
return str(self.pressed_button.label)
else:
return None
elif isinstance(self, Button):
return None
else:
return self.value


class MySubmitButton(Button, Changeable):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._val = False

def on_button_pressed(self):
self._val = True
self._link.facet.submit()

def get_ui_value(self):
return self._val


class MyInput(Input, Changeable):
async def on_blur(self):
return self.trigger_change()


class MyCheckbox(Checkbox, Changeable):
def on_checkbox_changed(self):
return self.trigger_change()


class MyRadioSet(RadioSet, Changeable):
def on_radio_set_changed(self):
return self.trigger_change()


class MyButton(Button, Changeable):

def on_button_pressed(self):
return self._link.val()
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ nav:
- Facet: "Facet.md"
- Validation: "Validation.md"
- Standalone: "Standalone.md"
- Experimental: "Experimental.md"
- Changelog: "Changelog.md"

0 comments on commit c044e01

Please sign in to comment.