293 lines
8.5 KiB
Python
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()
|