""" 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()