pyWebLayout/examples/06_functional_elements_demo.py
Duncan Tourolle 39622c7dd7
All checks were successful
Python CI / test (push) Successful in 6m46s
integration of functional elements
2025-11-08 10:17:01 +01:00

293 lines
8.5 KiB
Python

"""
Demonstration of functional elements (buttons, forms, links) with callback binding.
This example shows how to:
1. Create functional elements programmatically
2. Layout them on a page
3. Bind callbacks after layout using the CallbackRegistry
4. Simulate user interactions
This pattern is useful for:
- Manual GUI construction
- Applications where callbacks need access to runtime state
- Interactive document interfaces
"""
from pyWebLayout.concrete import Page
from pyWebLayout.abstract.functional import Button, Form, FormField, FormFieldType
from pyWebLayout.abstract import Paragraph, Word
from pyWebLayout.style import Font
from pyWebLayout.style.page_style import PageStyle
from pyWebLayout.layout.document_layouter import DocumentLayouter
import numpy as np
class SimpleApp:
"""
A simple application that demonstrates functional element usage.
This app has:
- A settings form
- Save and Cancel buttons
- Application state that callbacks can access
"""
def __init__(self):
self.settings = {
"username": "",
"theme": "light",
"notifications": True
}
self.saved = False
def on_save_click(self, point, **kwargs):
"""Callback for save button"""
print(f"Save button clicked at {point}")
self.saved = True
print("Settings saved!")
return "saved"
def on_cancel_click(self, point, **kwargs):
"""Callback for cancel button"""
print(f"Cancel button clicked at {point}")
print("Changes cancelled!")
return "cancelled"
def on_reset_click(self, point, **kwargs):
"""Callback for reset button"""
print(f"Reset button clicked at {point}")
self.settings = {
"username": "",
"theme": "light",
"notifications": True
}
print("Settings reset to defaults!")
return "reset"
def create_settings_page():
"""
Create a settings page with functional elements.
Returns:
Tuple of (page, app, element_ids) where element_ids maps
semantic names to registered callback ids
"""
# Create the application instance
app = SimpleApp()
# Create page
page = Page(size=(600, 800), style=PageStyle(border_width=10))
layouter = DocumentLayouter(page)
# Create content
font = Font(font_size=16, colour=(0, 0, 0))
# Title paragraph
title_font = Font(font_size=24, colour=(0, 0, 100))
title = Paragraph(title_font)
title.add_word(Word("Settings", title_font))
# Description paragraph
desc = Paragraph(font)
desc.add_word(Word("Configure", font))
desc.add_word(Word("your", font))
desc.add_word(Word("application", font))
desc.add_word(Word("preferences", font))
desc.add_word(Word("below.", font))
# Layout title and description
layouter.layout_paragraph(title)
page._current_y_offset += 10 # Add some spacing
layouter.layout_paragraph(desc)
page._current_y_offset += 20 # Add more spacing before form
# Create form
settings_form = Form(
form_id="settings-form",
action="/save-settings",
html_id="settings-form"
)
# Add form fields
username_field = FormField(
name="username",
field_type=FormFieldType.TEXT,
label="Username",
value="john_doe"
)
theme_field = FormField(
name="theme",
field_type=FormFieldType.SELECT,
label="Theme",
value="light",
options=[("light", "Light"), ("dark", "Dark")]
)
notifications_field = FormField(
name="notifications",
field_type=FormFieldType.CHECKBOX,
label="Enable Notifications",
value=True
)
settings_form.add_field(username_field)
settings_form.add_field(theme_field)
settings_form.add_field(notifications_field)
# Layout the form
success, field_ids = layouter.layout_form(settings_form)
if not success:
print("Warning: Form didn't fit on page!")
page._current_y_offset += 20 # Spacing before buttons
# Create buttons (NO callbacks yet - will be bound later)
save_button = Button(
label="Save Settings",
callback=None, # No callback yet!
html_id="save-btn"
)
cancel_button = Button(
label="Cancel",
callback=None, # No callback yet!
html_id="cancel-btn"
)
reset_button = Button(
label="Reset to Defaults",
callback=None, # No callback yet!
html_id="reset-btn"
)
# Layout buttons
button_font = Font(font_size=14, colour=(255, 255, 255))
success1, save_id = layouter.layout_button(save_button, font=button_font)
page._current_y_offset += 10 # Spacing between buttons
success2, cancel_id = layouter.layout_button(cancel_button, font=button_font)
page._current_y_offset += 10
success3, reset_id = layouter.layout_button(reset_button, font=button_font)
# ==============================================================
# IMPORTANT: Callbacks are bound AFTER layout is complete
# This allows callbacks to access the application instance
# ==============================================================
# Bind callbacks using the page's callback registry
page.callbacks.set_callback("save-btn", app.on_save_click)
page.callbacks.set_callback("cancel-btn", app.on_cancel_click)
page.callbacks.set_callback("reset-btn", app.on_reset_click)
# Track element ids for later reference
element_ids = {
"save_button": save_id,
"cancel_button": cancel_id,
"reset_button": reset_id,
"form_fields": field_ids
}
return page, app, element_ids
def demonstrate_callback_binding():
"""Demonstrate various callback binding patterns"""
print("=" * 60)
print("Functional Elements Demo: Manual GUI Construction")
print("=" * 60)
print()
# Create the page
page, app, element_ids = create_settings_page()
print(f"Page created with {page.callbacks.count()} interactable elements")
print()
# Show what's registered
print("Registered interactables:")
for id_name in page.callbacks.get_all_ids():
print(f" - {id_name}")
print()
# Show breakdown by type
print("Breakdown by type:")
for type_name in page.callbacks.get_all_types():
count = page.callbacks.count_by_type(type_name)
print(f" - {type_name}: {count}")
print()
# Simulate user clicking the save button
print("Simulating user interaction:")
print("-" * 60)
print()
# Get the save button
save_button = page.callbacks.get_by_id("save-btn")
print(f"Retrieved save button: {save_button}")
# Simulate a click at position (50, 200)
click_point = np.array([50, 200])
print(f"Simulating click at {click_point}...")
result = save_button.interact(click_point)
print(f"Button interaction returned: {result}")
print(f"App.saved state: {app.saved}")
print()
# Simulate clicking cancel
cancel_button = page.callbacks.get_by_id("cancel-btn")
print("Simulating cancel button click...")
result = cancel_button.interact(click_point)
print(f"Button interaction returned: {result}")
print()
# Simulate clicking reset
reset_button = page.callbacks.get_by_id("reset-btn")
print("Simulating reset button click...")
result = reset_button.interact(click_point)
print(f"Button interaction returned: {result}")
print()
# Demonstrate batch callback modification
print("-" * 60)
print("Demonstrating batch callback modification:")
print()
def log_all_clicks(point, **kwargs):
"""Generic click logger"""
print(f" [LOG] Button clicked at {point}")
return "logged"
# Set this callback for all buttons
count = page.callbacks.set_callbacks_by_type("button", log_all_clicks)
print(f"Set logging callback for {count} buttons")
print()
# Now clicking any button will just log
print("Clicking save button again (now with logging callback):")
result = save_button.interact(click_point)
print(f"Returned: {result}")
print()
# Render the page
print("-" * 60)
print("Rendering page...")
image = page.render()
print(f"Page rendered: {image.size}")
# Save to file
output_path = "functional_elements_demo.png"
image.save(output_path)
print(f"Saved to: {output_path}")
print()
print("=" * 60)
print("Demo complete!")
print("=" * 60)
if __name__ == "__main__":
demonstrate_callback_binding()