469 lines
17 KiB
Python
469 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Demo script for Settings overlay feature.
|
|
|
|
This script demonstrates the complete settings overlay workflow:
|
|
1. Display reading page
|
|
2. Swipe down from top to open settings overlay
|
|
3. Display settings overlay with controls
|
|
4. Tap on font size increase button
|
|
5. Show live preview update (background page changes)
|
|
6. Tap on line spacing increase button
|
|
7. Show another live preview update
|
|
8. Close overlay and show final page with new settings
|
|
|
|
Generates a GIF showing all these interactions.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from dreader import EbookReader, TouchEvent, GestureType
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
|
|
def add_gesture_annotation(image: Image.Image, text: str, position: str = "top") -> Image.Image:
|
|
"""
|
|
Add a text annotation to an image showing what gesture is being performed.
|
|
|
|
Args:
|
|
image: Base image
|
|
text: Annotation text
|
|
position: "top" or "bottom"
|
|
|
|
Returns:
|
|
Image with annotation
|
|
"""
|
|
# Create a copy
|
|
annotated = image.copy()
|
|
draw = ImageDraw.Draw(annotated)
|
|
|
|
# Try to use a nice font, fall back to default
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 24)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
# Calculate text position
|
|
bbox = draw.textbbox((0, 0), text, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
text_height = bbox[3] - bbox[1]
|
|
|
|
x = (image.width - text_width) // 2
|
|
if position == "top":
|
|
y = 20
|
|
else:
|
|
y = image.height - text_height - 20
|
|
|
|
# Draw background rectangle
|
|
padding = 10
|
|
draw.rectangle(
|
|
[x - padding, y - padding, x + text_width + padding, y + text_height + padding],
|
|
fill=(0, 0, 0, 200)
|
|
)
|
|
|
|
# Draw text
|
|
draw.text((x, y), text, fill=(255, 255, 255), font=font)
|
|
|
|
return annotated
|
|
|
|
|
|
def add_swipe_arrow(image: Image.Image, start_y: int, end_y: int) -> Image.Image:
|
|
"""
|
|
Add a visual swipe arrow to show gesture direction.
|
|
|
|
Args:
|
|
image: Base image
|
|
start_y: Starting Y position
|
|
end_y: Ending Y position
|
|
|
|
Returns:
|
|
Image with arrow overlay
|
|
"""
|
|
annotated = image.copy()
|
|
draw = ImageDraw.Draw(annotated)
|
|
|
|
# Draw arrow in center of screen
|
|
x = image.width // 2
|
|
|
|
# Draw line
|
|
draw.line([(x, start_y), (x, end_y)], fill=(255, 100, 100), width=5)
|
|
|
|
# Draw arrowhead
|
|
arrow_size = 20
|
|
if end_y < start_y: # Upward arrow
|
|
draw.polygon([
|
|
(x, end_y),
|
|
(x - arrow_size, end_y + arrow_size),
|
|
(x + arrow_size, end_y + arrow_size)
|
|
], fill=(255, 100, 100))
|
|
else: # Downward arrow
|
|
draw.polygon([
|
|
(x, end_y),
|
|
(x - arrow_size, end_y - arrow_size),
|
|
(x + arrow_size, end_y - arrow_size)
|
|
], fill=(255, 100, 100))
|
|
|
|
return annotated
|
|
|
|
|
|
def add_tap_indicator(image: Image.Image, x: int, y: int, label: str = "") -> Image.Image:
|
|
"""
|
|
Add a visual tap indicator to show where user tapped.
|
|
|
|
Args:
|
|
image: Base image
|
|
x, y: Tap coordinates
|
|
label: Optional label for the tap
|
|
|
|
Returns:
|
|
Image with tap indicator
|
|
"""
|
|
annotated = image.copy()
|
|
draw = ImageDraw.Draw(annotated)
|
|
|
|
# Draw circle at tap location
|
|
radius = 30
|
|
draw.ellipse(
|
|
[x - radius, y - radius, x + radius, y + radius],
|
|
outline=(255, 100, 100),
|
|
width=5
|
|
)
|
|
|
|
# Draw crosshair
|
|
draw.line([(x - radius - 10, y), (x + radius + 10, y)], fill=(255, 100, 100), width=3)
|
|
draw.line([(x, y - radius - 10), (x, y + radius + 10)], fill=(255, 100, 100), width=3)
|
|
|
|
# Add label if provided
|
|
if label:
|
|
try:
|
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 18)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
bbox = draw.textbbox((0, 0), label, font=font)
|
|
text_width = bbox[2] - bbox[0]
|
|
|
|
# Position label above tap point
|
|
label_x = x - text_width // 2
|
|
label_y = y - radius - 40
|
|
|
|
draw.text((label_x, label_y), label, fill=(255, 100, 100), font=font)
|
|
|
|
return annotated
|
|
|
|
|
|
def main():
|
|
"""Generate Settings overlay demo GIF"""
|
|
print("=== Settings Overlay Demo ===")
|
|
print()
|
|
|
|
# Find a test EPUB
|
|
epub_dir = Path(__file__).parent.parent / 'tests' / 'data' / 'library-epub'
|
|
epubs = list(epub_dir.glob('*.epub'))
|
|
|
|
if not epubs:
|
|
print("Error: No test EPUB files found!")
|
|
print(f"Looked in: {epub_dir}")
|
|
return
|
|
|
|
epub_path = epubs[0]
|
|
print(f"Using book: {epub_path.name}")
|
|
|
|
# Create reader
|
|
reader = EbookReader(page_size=(800, 1200))
|
|
|
|
# Load book
|
|
print("Loading book...")
|
|
success = reader.load_epub(str(epub_path))
|
|
|
|
if not success:
|
|
print("Error: Failed to load EPUB!")
|
|
return
|
|
|
|
print(f"Loaded: {reader.book_title} by {reader.book_author}")
|
|
print()
|
|
|
|
# Prepare frames for GIF
|
|
frames = []
|
|
frame_duration = [] # Duration in milliseconds for each frame
|
|
|
|
# Frame 1: Initial reading page
|
|
print("Frame 1: Initial reading page...")
|
|
page1 = reader.get_current_page()
|
|
annotated1 = add_gesture_annotation(page1, f"Reading: {reader.book_title}", "top")
|
|
frames.append(annotated1)
|
|
frame_duration.append(2000) # 2 seconds
|
|
|
|
# Frame 2: Show swipe down gesture
|
|
print("Frame 2: Swipe down gesture...")
|
|
swipe_visual = add_swipe_arrow(page1, 100, 300)
|
|
annotated2 = add_gesture_annotation(swipe_visual, "Swipe down from top", "top")
|
|
frames.append(annotated2)
|
|
frame_duration.append(1000) # 1 second
|
|
|
|
# Frame 3: Settings overlay appears
|
|
print("Frame 3: Settings overlay opens...")
|
|
event_swipe_down = TouchEvent(gesture=GestureType.SWIPE_DOWN, x=400, y=100)
|
|
response = reader.handle_touch(event_swipe_down)
|
|
print(f" Response: {response.action}")
|
|
|
|
# Get the overlay image by calling open_settings_overlay again
|
|
overlay_image = reader.open_settings_overlay()
|
|
annotated3 = add_gesture_annotation(overlay_image, "Settings", "top")
|
|
frames.append(annotated3)
|
|
frame_duration.append(3000) # 3 seconds to read
|
|
|
|
# Find actual button coordinates by querying the overlay
|
|
print("Querying overlay for button positions...")
|
|
link_positions = {}
|
|
if reader.overlay_manager._overlay_reader:
|
|
page = reader.overlay_manager._overlay_reader.manager.get_current_page()
|
|
|
|
# Scan for all links with very fine granularity to catch all buttons
|
|
for y in range(0, 840, 3):
|
|
for x in range(0, 480, 3):
|
|
result = page.query_point((x, y))
|
|
if result and result.link_target:
|
|
if result.link_target not in link_positions:
|
|
# Translate to screen coordinates
|
|
panel_x_offset = int((800 - 480) / 2)
|
|
panel_y_offset = int((1200 - 840) / 2)
|
|
screen_x = x + panel_x_offset
|
|
screen_y = y + panel_y_offset
|
|
link_positions[result.link_target] = (screen_x, screen_y)
|
|
|
|
for link, (x, y) in sorted(link_positions.items()):
|
|
print(f" Found: {link} at ({x}, {y})")
|
|
|
|
# Frame 4: Tap on font size increase button
|
|
print("Frame 4: Tap on font size increase...")
|
|
if 'setting:font_increase' in link_positions:
|
|
tap_x, tap_y = link_positions['setting:font_increase']
|
|
print(f" Using coordinates: ({tap_x}, {tap_y})")
|
|
|
|
tap_visual = add_tap_indicator(overlay_image, tap_x, tap_y, "Increase")
|
|
annotated4 = add_gesture_annotation(tap_visual, "Tap to increase font size", "bottom")
|
|
frames.append(annotated4)
|
|
frame_duration.append(1500) # 1.5 seconds
|
|
|
|
# Frames 5-9: Font size increased (live preview) - show each tap individually
|
|
print("Frames 5-9: Font size increased with live preview (5 taps, showing each)...")
|
|
for i in range(5):
|
|
event_tap_font = TouchEvent(gesture=GestureType.TAP, x=tap_x, y=tap_y)
|
|
response = reader.handle_touch(event_tap_font)
|
|
print(f" Tap {i+1}: {response.action} - Font scale: {response.data.get('font_scale', 'N/A')}")
|
|
|
|
# Get updated overlay image after each tap
|
|
updated_overlay = reader.get_current_page() # This gets the composited overlay
|
|
annotated = add_gesture_annotation(
|
|
updated_overlay,
|
|
f"Font: {int(reader.base_font_scale * 100)}% (tap {i+1}/5)",
|
|
"top"
|
|
)
|
|
frames.append(annotated)
|
|
frame_duration.append(800) # 0.8 seconds per tap
|
|
|
|
# Hold on final font size for a bit longer
|
|
final_font_overlay = reader.get_current_page()
|
|
annotated_final = add_gesture_annotation(
|
|
final_font_overlay,
|
|
f"Font: {int(reader.base_font_scale * 100)}% (complete)",
|
|
"top"
|
|
)
|
|
frames.append(annotated_final)
|
|
frame_duration.append(1500) # 1.5 seconds to see the final result
|
|
else:
|
|
print(" Skipping - button not found")
|
|
updated_overlay = overlay_image
|
|
|
|
# Get current overlay state for line spacing section
|
|
current_overlay = reader.get_current_page()
|
|
|
|
# Frame N: Tap on line spacing increase button
|
|
print("Frame N: Tap on line spacing increase...")
|
|
if 'setting:line_spacing_increase' in link_positions:
|
|
tap_x2, tap_y2 = link_positions['setting:line_spacing_increase']
|
|
print(f" Using coordinates: ({tap_x2}, {tap_y2})")
|
|
|
|
tap_visual2 = add_tap_indicator(current_overlay, tap_x2, tap_y2, "Increase")
|
|
annotated_ls_tap = add_gesture_annotation(tap_visual2, "Tap to increase line spacing", "bottom")
|
|
frames.append(annotated_ls_tap)
|
|
frame_duration.append(1500) # 1.5 seconds
|
|
|
|
# Frames N+1 to N+5: Line spacing increased (live preview) - show each tap individually
|
|
print("Frames N+1 to N+5: Line spacing increased with live preview (5 taps, showing each)...")
|
|
for i in range(5):
|
|
event_tap_spacing = TouchEvent(gesture=GestureType.TAP, x=tap_x2, y=tap_y2)
|
|
response = reader.handle_touch(event_tap_spacing)
|
|
print(f" Tap {i+1}: {response.action} - Line spacing: {response.data.get('line_spacing', 'N/A')}")
|
|
|
|
# Get updated overlay image after each tap
|
|
updated_overlay2 = reader.get_current_page()
|
|
annotated = add_gesture_annotation(
|
|
updated_overlay2,
|
|
f"Line Spacing: {reader.page_style.line_spacing}px (tap {i+1}/5)",
|
|
"top"
|
|
)
|
|
frames.append(annotated)
|
|
frame_duration.append(800) # 0.8 seconds per tap
|
|
|
|
# Hold on final line spacing for a bit longer
|
|
final_spacing_overlay = reader.get_current_page()
|
|
annotated_final_ls = add_gesture_annotation(
|
|
final_spacing_overlay,
|
|
f"Line Spacing: {reader.page_style.line_spacing}px (complete)",
|
|
"top"
|
|
)
|
|
frames.append(annotated_final_ls)
|
|
frame_duration.append(1500) # 1.5 seconds to see the final result
|
|
else:
|
|
print(" Skipping - button not found")
|
|
|
|
# Get current overlay state for paragraph spacing section
|
|
current_overlay2 = reader.get_current_page()
|
|
|
|
# Frame M: Tap on paragraph spacing increase button
|
|
print("Frame M: Tap on paragraph spacing increase...")
|
|
if 'setting:block_spacing_increase' in link_positions:
|
|
tap_x3, tap_y3 = link_positions['setting:block_spacing_increase']
|
|
print(f" Using coordinates: ({tap_x3}, {tap_y3})")
|
|
|
|
tap_visual3 = add_tap_indicator(current_overlay2, tap_x3, tap_y3, "Increase")
|
|
annotated_ps_tap = add_gesture_annotation(tap_visual3, "Tap to increase paragraph spacing", "bottom")
|
|
frames.append(annotated_ps_tap)
|
|
frame_duration.append(1500) # 1.5 seconds
|
|
|
|
# Frames M+1 to M+5: Paragraph spacing increased (live preview) - show each tap individually
|
|
print("Frames M+1 to M+5: Paragraph spacing increased with live preview (5 taps, showing each)...")
|
|
for i in range(5):
|
|
event_tap_para = TouchEvent(gesture=GestureType.TAP, x=tap_x3, y=tap_y3)
|
|
response = reader.handle_touch(event_tap_para)
|
|
print(f" Tap {i+1}: {response.action} - Paragraph spacing: {response.data.get('inter_block_spacing', 'N/A')}")
|
|
|
|
# Get updated overlay image after each tap
|
|
updated_overlay3 = reader.get_current_page()
|
|
annotated = add_gesture_annotation(
|
|
updated_overlay3,
|
|
f"Paragraph Spacing: {reader.page_style.inter_block_spacing}px (tap {i+1}/5)",
|
|
"top"
|
|
)
|
|
frames.append(annotated)
|
|
frame_duration.append(800) # 0.8 seconds per tap
|
|
|
|
# Hold on final paragraph spacing for a bit longer
|
|
final_para_overlay = reader.get_current_page()
|
|
annotated_final_ps = add_gesture_annotation(
|
|
final_para_overlay,
|
|
f"Paragraph Spacing: {reader.page_style.inter_block_spacing}px (complete)",
|
|
"top"
|
|
)
|
|
frames.append(annotated_final_ps)
|
|
frame_duration.append(1500) # 1.5 seconds to see the final result
|
|
else:
|
|
print(" Skipping - button not found")
|
|
|
|
# Get current overlay state for word spacing section
|
|
current_overlay3 = reader.get_current_page()
|
|
|
|
# Frame W: Tap on word spacing increase button
|
|
print("Frame W: Tap on word spacing increase...")
|
|
if 'setting:word_spacing_increase' in link_positions:
|
|
tap_x4, tap_y4 = link_positions['setting:word_spacing_increase']
|
|
print(f" Using coordinates: ({tap_x4}, {tap_y4})")
|
|
|
|
tap_visual4 = add_tap_indicator(current_overlay3, tap_x4, tap_y4, "Increase")
|
|
annotated_ws_tap = add_gesture_annotation(tap_visual4, "Tap to increase word spacing", "bottom")
|
|
frames.append(annotated_ws_tap)
|
|
frame_duration.append(1500) # 1.5 seconds
|
|
|
|
# Frames W+1 to W+5: Word spacing increased (live preview) - show each tap individually
|
|
print("Frames W+1 to W+5: Word spacing increased with live preview (5 taps, showing each)...")
|
|
for i in range(5):
|
|
event_tap_word = TouchEvent(gesture=GestureType.TAP, x=tap_x4, y=tap_y4)
|
|
response = reader.handle_touch(event_tap_word)
|
|
print(f" Tap {i+1}: {response.action} - Word spacing: {response.data.get('word_spacing', 'N/A')}")
|
|
|
|
# Get updated overlay image after each tap
|
|
updated_overlay4 = reader.get_current_page()
|
|
annotated = add_gesture_annotation(
|
|
updated_overlay4,
|
|
f"Word Spacing: {reader.page_style.word_spacing}px (tap {i+1}/5)",
|
|
"top"
|
|
)
|
|
frames.append(annotated)
|
|
frame_duration.append(800) # 0.8 seconds per tap
|
|
|
|
# Hold on final word spacing for a bit longer
|
|
final_word_overlay = reader.get_current_page()
|
|
annotated_final_ws = add_gesture_annotation(
|
|
final_word_overlay,
|
|
f"Word Spacing: {reader.page_style.word_spacing}px (complete)",
|
|
"top"
|
|
)
|
|
frames.append(annotated_final_ws)
|
|
frame_duration.append(1500) # 1.5 seconds to see the final result
|
|
else:
|
|
print(" Skipping - button not found")
|
|
|
|
# Frame Z: Tap outside to close
|
|
print("Frame Z: Close overlay...")
|
|
final_overlay_state = reader.get_current_page()
|
|
tap_visual_close = add_tap_indicator(final_overlay_state, 100, 600, "Close")
|
|
annotated_close = add_gesture_annotation(tap_visual_close, "Tap outside to close", "bottom")
|
|
frames.append(annotated_close)
|
|
frame_duration.append(1500) # 1.5 seconds
|
|
|
|
# Final Frame: Back to reading with new settings applied
|
|
print("Final Frame: Back to reading with new settings...")
|
|
event_close = TouchEvent(gesture=GestureType.TAP, x=100, y=600)
|
|
response = reader.handle_touch(event_close)
|
|
print(f" Response: {response.action}")
|
|
|
|
final_page = reader.get_current_page()
|
|
annotated_final = add_gesture_annotation(
|
|
final_page,
|
|
f"Settings Applied: {int(reader.base_font_scale * 100)}% font, {reader.page_style.line_spacing}px line, {reader.page_style.inter_block_spacing}px para, {reader.page_style.word_spacing}px word",
|
|
"top"
|
|
)
|
|
frames.append(annotated_final)
|
|
frame_duration.append(3000) # 3 seconds
|
|
|
|
# Save as GIF
|
|
output_path = Path(__file__).parent.parent / 'docs' / 'images' / 'settings_overlay_demo.gif'
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
print()
|
|
print(f"Saving GIF with {len(frames)} frames...")
|
|
frames[0].save(
|
|
output_path,
|
|
save_all=True,
|
|
append_images=frames[1:],
|
|
duration=frame_duration,
|
|
loop=0,
|
|
optimize=False
|
|
)
|
|
|
|
print(f"✓ GIF saved to: {output_path}")
|
|
print(f" Size: {output_path.stat().st_size / 1024:.1f} KB")
|
|
print(f" Frames: {len(frames)}")
|
|
print(f" Total duration: {sum(frame_duration) / 1000:.1f}s")
|
|
|
|
# Also save individual frames for documentation
|
|
frames_dir = output_path.parent / 'settings_overlay_frames'
|
|
frames_dir.mkdir(exist_ok=True)
|
|
|
|
for i, frame in enumerate(frames):
|
|
frame_path = frames_dir / f'frame_{i+1:02d}.png'
|
|
frame.save(frame_path)
|
|
|
|
print(f"✓ Individual frames saved to: {frames_dir}")
|
|
|
|
# Cleanup
|
|
reader.close()
|
|
|
|
print()
|
|
print("=== Demo Complete ===")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|