Added press state, fixed font registry
This commit is contained in:
@@ -0,0 +1,396 @@
|
||||
"""
|
||||
Demonstration of pressed/depressed states for buttons and links with visual feedback.
|
||||
|
||||
This example shows:
|
||||
1. How to use the InteractionHandler for automatic press/release cycles
|
||||
2. How to manually manage states for custom event loops
|
||||
3. How the dirty flag system tracks when re-rendering is needed
|
||||
4. Visual differences between normal, hovered, and pressed states
|
||||
|
||||
The demo creates a page with buttons and links, then simulates clicking them
|
||||
with proper visual feedback timing.
|
||||
"""
|
||||
|
||||
from pyWebLayout.concrete import Page
|
||||
from pyWebLayout.concrete.interaction_handler import InteractionHandler, InteractionStateManager
|
||||
from pyWebLayout.abstract.functional import Button, Link, LinkType
|
||||
from pyWebLayout.abstract import Paragraph, Word
|
||||
from pyWebLayout.abstract.inline import LinkedWord
|
||||
from pyWebLayout.style import Font
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
from pyWebLayout.layout.document_layouter import DocumentLayouter
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
|
||||
def create_interactive_demo_page():
|
||||
"""
|
||||
Create a page with various interactive elements demonstrating state changes.
|
||||
"""
|
||||
# Create page
|
||||
page = Page(size=(600, 500), style=PageStyle(border_width=10))
|
||||
layouter = DocumentLayouter(page)
|
||||
|
||||
# Create fonts
|
||||
title_font = Font(font_size=24, colour=(0, 0, 100))
|
||||
body_font = Font(font_size=16, colour=(0, 0, 0))
|
||||
button_font = Font(font_size=14, colour=(255, 255, 255))
|
||||
|
||||
# Title
|
||||
title = Paragraph(title_font)
|
||||
title.add_word(Word("Interactive", title_font))
|
||||
title.add_word(Word("Elements", title_font))
|
||||
title.add_word(Word("Demo", title_font))
|
||||
layouter.layout_paragraph(title)
|
||||
page._current_y_offset += 15
|
||||
|
||||
# Description
|
||||
desc = Paragraph(body_font)
|
||||
desc.add_word(Word("Click", body_font))
|
||||
desc.add_word(Word("the", body_font))
|
||||
desc.add_word(Word("buttons", body_font))
|
||||
desc.add_word(Word("and", body_font))
|
||||
desc.add_word(Word("links", body_font))
|
||||
desc.add_word(Word("below", body_font))
|
||||
desc.add_word(Word("to", body_font))
|
||||
desc.add_word(Word("see", body_font))
|
||||
desc.add_word(Word("pressed", body_font))
|
||||
desc.add_word(Word("state", body_font))
|
||||
desc.add_word(Word("feedback!", body_font))
|
||||
layouter.layout_paragraph(desc)
|
||||
page._current_y_offset += 20
|
||||
|
||||
# Callback functions
|
||||
def on_save():
|
||||
print("💾 Save button clicked!")
|
||||
return "saved"
|
||||
|
||||
def on_cancel():
|
||||
print("❌ Cancel button clicked!")
|
||||
return "cancelled"
|
||||
|
||||
def on_link_click(location, point):
|
||||
print(f"🔗 Link clicked: {location} at {point}")
|
||||
return location
|
||||
|
||||
# Create buttons
|
||||
save_button = Button(
|
||||
label="Save Document",
|
||||
callback=lambda point, **kwargs: on_save(),
|
||||
html_id="save-btn"
|
||||
)
|
||||
|
||||
cancel_button = Button(
|
||||
label="Cancel",
|
||||
callback=lambda point, **kwargs: on_cancel(),
|
||||
html_id="cancel-btn"
|
||||
)
|
||||
|
||||
# Layout buttons
|
||||
success1, save_id = layouter.layout_button(save_button, font=button_font)
|
||||
page._current_y_offset += 12
|
||||
success2, cancel_id = layouter.layout_button(cancel_button, font=button_font)
|
||||
page._current_y_offset += 25
|
||||
|
||||
# Create paragraph with links
|
||||
link_para = Paragraph(body_font)
|
||||
link_para.add_word(Word("Visit", body_font))
|
||||
link_para.add_word(Word("our", body_font))
|
||||
|
||||
# Add a link
|
||||
internal_link = Link(
|
||||
location="https://example.com",
|
||||
link_type=LinkType.EXTERNAL,
|
||||
callback=on_link_click,
|
||||
title="Example website"
|
||||
)
|
||||
link_para.add_word(LinkedWord(
|
||||
"website",
|
||||
body_font,
|
||||
location="https://example.com",
|
||||
link_type=LinkType.EXTERNAL,
|
||||
callback=on_link_click,
|
||||
title="Example website"
|
||||
))
|
||||
link_para.add_word(Word("or", body_font))
|
||||
|
||||
# Add another link
|
||||
docs_link = Link(
|
||||
location="/docs",
|
||||
link_type=LinkType.INTERNAL,
|
||||
callback=on_link_click,
|
||||
title="Documentation"
|
||||
)
|
||||
link_para.add_word(LinkedWord(
|
||||
"documentation",
|
||||
body_font,
|
||||
location="/docs",
|
||||
link_type=LinkType.INTERNAL,
|
||||
callback=on_link_click,
|
||||
title="Documentation"
|
||||
))
|
||||
link_para.add_word(Word("page.", body_font))
|
||||
|
||||
layouter.layout_paragraph(link_para)
|
||||
|
||||
return page, save_id, cancel_id
|
||||
|
||||
|
||||
def demo_automatic_interaction():
|
||||
"""
|
||||
Demonstrate automatic interaction handling with InteractionHandler.
|
||||
|
||||
This shows the simplest usage pattern where InteractionHandler manages
|
||||
the complete press/release cycle automatically.
|
||||
"""
|
||||
print("=" * 70)
|
||||
print("Demo 1: Automatic Interaction with Visual Feedback")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Create the page
|
||||
page, save_id, cancel_id = create_interactive_demo_page()
|
||||
|
||||
# Create interaction handler
|
||||
handler = InteractionHandler(page, press_duration_ms=150)
|
||||
|
||||
print("Initial render:")
|
||||
initial_render = page.render()
|
||||
initial_render.save("demo_07_initial.png")
|
||||
print(f" ✓ Saved: demo_07_initial.png")
|
||||
print(f" ✓ Page dirty flag: {page.is_dirty}")
|
||||
print()
|
||||
|
||||
# Get the save button
|
||||
save_button = page.callbacks.get_by_id("save-btn")
|
||||
click_point = np.array([50, 150])
|
||||
|
||||
print("Simulating button click with automatic feedback...")
|
||||
print(f" → Setting pressed state at t=0ms")
|
||||
|
||||
# Execute with automatic feedback
|
||||
pressed_frame, released_frame, result = handler.execute_with_feedback(
|
||||
save_button,
|
||||
click_point
|
||||
)
|
||||
|
||||
print(f" → Showing pressed state for 150ms")
|
||||
print(f" → Executing callback")
|
||||
print(f" → Result: {result}")
|
||||
print(f" → Setting released state")
|
||||
|
||||
# Save the frames
|
||||
pressed_frame.save("demo_07_pressed.png")
|
||||
print(f" ✓ Saved: demo_07_pressed.png")
|
||||
|
||||
released_frame.save("demo_07_released.png")
|
||||
print(f" ✓ Saved: demo_07_released.png")
|
||||
print()
|
||||
|
||||
|
||||
def demo_manual_state_management():
|
||||
"""
|
||||
Demonstrate manual state management for custom event loops.
|
||||
|
||||
This shows how an application with its own event loop can manage
|
||||
states and check the dirty flag before re-rendering.
|
||||
"""
|
||||
print("=" * 70)
|
||||
print("Demo 2: Manual State Management with Dirty Flag Checking")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Create the page
|
||||
page, save_id, cancel_id = create_interactive_demo_page()
|
||||
|
||||
# Initial render
|
||||
print("Initial render:")
|
||||
current_frame = page.render()
|
||||
print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)")
|
||||
print()
|
||||
|
||||
# Get the cancel button
|
||||
cancel_button = page.callbacks.get_by_id("cancel-btn")
|
||||
|
||||
# Simulate mouse down
|
||||
print("Mouse down event:")
|
||||
# Set page reference if not already set
|
||||
if not hasattr(cancel_button, '_page') or cancel_button._page is None:
|
||||
cancel_button.set_page(page)
|
||||
cancel_button.set_pressed(True)
|
||||
print(f" ✓ Set pressed state")
|
||||
print(f" ✓ Page dirty: {page.is_dirty} (needs re-render)")
|
||||
|
||||
# Check if we need to re-render
|
||||
if page.is_dirty:
|
||||
print(" → Re-rendering (dirty flag is set)")
|
||||
current_frame = page.render()
|
||||
current_frame.save("demo_07_manual_pressed.png")
|
||||
print(f" ✓ Saved: demo_07_manual_pressed.png")
|
||||
print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)")
|
||||
print()
|
||||
|
||||
# Wait a bit
|
||||
print("Waiting 150ms for visual feedback...")
|
||||
time.sleep(0.15)
|
||||
print()
|
||||
|
||||
# Execute callback
|
||||
print("Executing callback:")
|
||||
result = cancel_button.interact(np.array([50, 200]))
|
||||
print(f" ✓ Result: {result}")
|
||||
print()
|
||||
|
||||
# Simulate mouse up
|
||||
print("Mouse up event:")
|
||||
cancel_button.set_pressed(False)
|
||||
print(f" ✓ Set released state")
|
||||
print(f" ✓ Page dirty: {page.is_dirty} (needs re-render)")
|
||||
|
||||
# Check if we need to re-render
|
||||
if page.is_dirty:
|
||||
print(" → Re-rendering (dirty flag is set)")
|
||||
current_frame = page.render()
|
||||
current_frame.save("demo_07_manual_released.png")
|
||||
print(f" ✓ Saved: demo_07_manual_released.png")
|
||||
print(f" ✓ Page dirty: {page.is_dirty} (cleaned after render)")
|
||||
print()
|
||||
|
||||
|
||||
def demo_state_manager():
|
||||
"""
|
||||
Demonstrate the InteractionStateManager for hover/press tracking.
|
||||
|
||||
This shows how to use the high-level state manager that automatically
|
||||
handles hover and press states based on cursor position.
|
||||
"""
|
||||
print("=" * 70)
|
||||
print("Demo 3: InteractionStateManager for Hover and Press Tracking")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Create the page
|
||||
page, save_id, cancel_id = create_interactive_demo_page()
|
||||
|
||||
# Create state manager
|
||||
state_mgr = InteractionStateManager(page)
|
||||
|
||||
# Initial render
|
||||
print("Initial render:")
|
||||
current_frame = page.render()
|
||||
print(f" ✓ Rendered initial state")
|
||||
print()
|
||||
|
||||
# Simulate cursor moving over a button
|
||||
button_center = (150, 150)
|
||||
print(f"Cursor moves to button position {button_center}:")
|
||||
hover_frame = state_mgr.update_hover(button_center)
|
||||
if hover_frame:
|
||||
print(f" ✓ Hover state changed, page re-rendered")
|
||||
hover_frame.save("demo_07_hover.png")
|
||||
print(f" ✓ Saved: demo_07_hover.png")
|
||||
print()
|
||||
|
||||
# Simulate mouse down
|
||||
print(f"Mouse down at {button_center}:")
|
||||
pressed_frame = state_mgr.handle_mouse_down(button_center)
|
||||
if pressed_frame:
|
||||
print(f" ✓ Pressed state set, page re-rendered")
|
||||
pressed_frame.save("demo_07_state_mgr_pressed.png")
|
||||
print(f" ✓ Saved: demo_07_state_mgr_pressed.png")
|
||||
print()
|
||||
|
||||
# Wait for visual feedback
|
||||
time.sleep(0.15)
|
||||
|
||||
# Simulate mouse up
|
||||
print(f"Mouse up at {button_center}:")
|
||||
released_frame, result = state_mgr.handle_mouse_up(button_center)
|
||||
if released_frame:
|
||||
print(f" ✓ Released state set, page re-rendered")
|
||||
print(f" ✓ Callback result: {result}")
|
||||
released_frame.save("demo_07_state_mgr_released.png")
|
||||
print(f" ✓ Saved: demo_07_state_mgr_released.png")
|
||||
print()
|
||||
|
||||
# Simulate cursor moving away
|
||||
away_point = (50, 50)
|
||||
print(f"Cursor moves away to {away_point}:")
|
||||
away_frame = state_mgr.update_hover(away_point)
|
||||
if away_frame:
|
||||
print(f" ✓ Hover state cleared, page re-rendered")
|
||||
away_frame.save("demo_07_no_hover.png")
|
||||
print(f" ✓ Saved: demo_07_no_hover.png")
|
||||
print()
|
||||
|
||||
|
||||
def demo_performance_optimization():
|
||||
"""
|
||||
Demonstrate how the dirty flag prevents unnecessary re-renders.
|
||||
"""
|
||||
print("=" * 70)
|
||||
print("Demo 4: Performance Optimization with Dirty Flag")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Create the page
|
||||
page, save_id, cancel_id = create_interactive_demo_page()
|
||||
|
||||
print("Scenario: Multiple state queries without changes")
|
||||
print()
|
||||
|
||||
# Initial render
|
||||
page.render()
|
||||
print(f"1. After initial render - dirty: {page.is_dirty}")
|
||||
|
||||
# Check if dirty before rendering again
|
||||
print(f"2. Check dirty flag: {page.is_dirty}")
|
||||
if not page.is_dirty:
|
||||
print(" → Skipping render (no changes)")
|
||||
print()
|
||||
|
||||
# Now make a change
|
||||
button = page.callbacks.get_by_id("save-btn")
|
||||
print("3. Setting button to pressed state")
|
||||
# Ensure page reference is set
|
||||
if not hasattr(button, '_page') or button._page is None:
|
||||
button.set_page(page)
|
||||
button.set_pressed(True)
|
||||
print(f" → dirty: {page.is_dirty}")
|
||||
print()
|
||||
|
||||
# This time we need to render
|
||||
print(f"4. Check dirty flag: {page.is_dirty}")
|
||||
if page.is_dirty:
|
||||
print(" → Re-rendering (state changed)")
|
||||
page.render()
|
||||
print(f" → dirty after render: {page.is_dirty}")
|
||||
print()
|
||||
|
||||
print("Benefit: Only render when actual changes occur!")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n")
|
||||
print("╔" + "═" * 68 + "╗")
|
||||
print("║" + " " * 15 + "PRESSED STATE DEMONSTRATION" + " " * 26 + "║")
|
||||
print("╚" + "═" * 68 + "╝")
|
||||
print()
|
||||
|
||||
# Run all demos
|
||||
demo_automatic_interaction()
|
||||
print("\n")
|
||||
|
||||
demo_manual_state_management()
|
||||
print("\n")
|
||||
|
||||
demo_state_manager()
|
||||
print("\n")
|
||||
|
||||
demo_performance_optimization()
|
||||
print("\n")
|
||||
|
||||
print("=" * 70)
|
||||
print("All demos complete! Check the generated PNG files.")
|
||||
print("=" * 70)
|
||||
Reference in New Issue
Block a user