Update coverage badges [skip ci]
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user