230 lines
8.7 KiB
Python
230 lines
8.7 KiB
Python
"""
|
|
Settings overlay sub-application.
|
|
|
|
Provides interactive controls for adjusting reading settings with live preview.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING, Optional
|
|
from PIL import Image
|
|
|
|
from .base import OverlaySubApplication
|
|
from ..gesture import GestureResponse, ActionType
|
|
from ..state import OverlayState
|
|
from ..html_generator import generate_settings_overlay
|
|
|
|
if TYPE_CHECKING:
|
|
from ..application import EbookReader
|
|
|
|
|
|
class SettingsOverlay(OverlaySubApplication):
|
|
"""
|
|
Settings overlay with live preview.
|
|
|
|
Features:
|
|
- Font size adjustment (increase/decrease)
|
|
- Line spacing adjustment
|
|
- Inter-block spacing adjustment
|
|
- Word spacing adjustment
|
|
- Live preview of changes on base page
|
|
- Back to library button
|
|
"""
|
|
|
|
def get_overlay_type(self) -> OverlayState:
|
|
"""Return SETTINGS overlay type."""
|
|
return OverlayState.SETTINGS
|
|
|
|
def open(self, base_page: Image.Image, **kwargs) -> Image.Image:
|
|
"""
|
|
Open the settings overlay.
|
|
|
|
Args:
|
|
base_page: Current reading page to show underneath
|
|
font_scale: Current font scale
|
|
line_spacing: Current line spacing in pixels
|
|
inter_block_spacing: Current inter-block spacing in pixels
|
|
word_spacing: Current word spacing in pixels
|
|
|
|
Returns:
|
|
Composited image with settings overlay
|
|
"""
|
|
font_scale = kwargs.get('font_scale', 1.0)
|
|
line_spacing = kwargs.get('line_spacing', 5)
|
|
inter_block_spacing = kwargs.get('inter_block_spacing', 15)
|
|
word_spacing = kwargs.get('word_spacing', 0)
|
|
|
|
# Calculate panel size (60% width, 70% height)
|
|
panel_size = self._calculate_panel_size(0.6, 0.7)
|
|
|
|
# Generate settings HTML with current values
|
|
html = generate_settings_overlay(
|
|
font_scale=font_scale,
|
|
line_spacing=line_spacing,
|
|
inter_block_spacing=inter_block_spacing,
|
|
word_spacing=word_spacing,
|
|
page_size=panel_size
|
|
)
|
|
|
|
# Render HTML to image
|
|
overlay_panel = self.render_html_to_image(html, panel_size)
|
|
|
|
# Cache for later use
|
|
self._cached_base_page = base_page.copy()
|
|
self._cached_overlay_image = overlay_panel
|
|
|
|
# Composite and return
|
|
return self.composite_overlay(base_page, overlay_panel)
|
|
|
|
def handle_tap(self, x: int, y: int) -> GestureResponse:
|
|
"""
|
|
Handle tap within settings overlay.
|
|
|
|
Detects:
|
|
- Setting adjustment controls (setting:action)
|
|
- Back to library button (action:back_to_library)
|
|
- Tap outside overlay (closes)
|
|
|
|
Args:
|
|
x, y: Screen coordinates of tap
|
|
|
|
Returns:
|
|
GestureResponse with appropriate action
|
|
"""
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(f"[SETTINGS_OVERLAY] Handling tap at ({x}, {y})")
|
|
logger.info(f"[SETTINGS_OVERLAY] Panel offset: {self._overlay_panel_offset}, Panel size: {self._panel_size}")
|
|
|
|
# Query the overlay to see what was tapped
|
|
query_result = self.query_overlay_pixel(x, y)
|
|
|
|
logger.info(f"[SETTINGS_OVERLAY] Query result: {query_result}")
|
|
|
|
# If query failed (tap outside overlay panel), close it
|
|
if query_result is None:
|
|
logger.info(f"[SETTINGS_OVERLAY] Tap outside overlay panel, closing")
|
|
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
|
|
|
|
# Check if tapped on a settings control link
|
|
if query_result.get("is_interactive") and query_result.get("link_target"):
|
|
link_target = query_result["link_target"]
|
|
logger.info(f"[SETTINGS_OVERLAY] Found interactive link: {link_target}")
|
|
|
|
# Parse "setting:action" format
|
|
if link_target.startswith("setting:"):
|
|
action = link_target.split(":", 1)[1]
|
|
logger.info(f"[SETTINGS_OVERLAY] Applying setting change: {action}")
|
|
return self._apply_setting_change(action)
|
|
|
|
# Parse "action:command" format for other actions
|
|
elif link_target.startswith("action:"):
|
|
action = link_target.split(":", 1)[1]
|
|
|
|
if action == "back_to_library":
|
|
logger.info(f"[SETTINGS_OVERLAY] Back to library clicked")
|
|
return GestureResponse(ActionType.BACK_TO_LIBRARY, {})
|
|
|
|
# Tap inside overlay but not on interactive element - keep overlay open
|
|
logger.info(f"[SETTINGS_OVERLAY] Tap on non-interactive area inside overlay, ignoring")
|
|
return GestureResponse(ActionType.NONE, {})
|
|
|
|
def refresh(self, updated_base_page: Image.Image,
|
|
font_scale: float,
|
|
line_spacing: int,
|
|
inter_block_spacing: int,
|
|
word_spacing: int = 0) -> Image.Image:
|
|
"""
|
|
Refresh the settings overlay with updated values and background page.
|
|
|
|
This is used for live preview when settings change - it updates both
|
|
the background page (with new settings applied) and the overlay panel
|
|
(with new values displayed).
|
|
|
|
Args:
|
|
updated_base_page: Updated reading page with new settings applied
|
|
font_scale: Updated font scale
|
|
line_spacing: Updated line spacing
|
|
inter_block_spacing: Updated inter-block spacing
|
|
word_spacing: Updated word spacing
|
|
|
|
Returns:
|
|
Composited image with updated settings overlay
|
|
"""
|
|
# Calculate panel size (60% width, 70% height)
|
|
panel_size = self._calculate_panel_size(0.6, 0.7)
|
|
|
|
# Generate updated settings HTML
|
|
html = generate_settings_overlay(
|
|
font_scale=font_scale,
|
|
line_spacing=line_spacing,
|
|
inter_block_spacing=inter_block_spacing,
|
|
word_spacing=word_spacing,
|
|
page_size=panel_size
|
|
)
|
|
|
|
# Render HTML to image
|
|
overlay_panel = self.render_html_to_image(html, panel_size)
|
|
|
|
# Update caches
|
|
self._cached_base_page = updated_base_page.copy()
|
|
self._cached_overlay_image = overlay_panel
|
|
|
|
# Composite and return
|
|
return self.composite_overlay(updated_base_page, overlay_panel)
|
|
|
|
def _apply_setting_change(self, action: str) -> GestureResponse:
|
|
"""
|
|
Apply a setting change and refresh the overlay.
|
|
|
|
Args:
|
|
action: Setting action (e.g., "font_increase", "line_spacing_decrease")
|
|
|
|
Returns:
|
|
GestureResponse with SETTING_CHANGED action
|
|
"""
|
|
# Apply the setting change via reader
|
|
if action == "font_increase":
|
|
self.reader.increase_font_size()
|
|
elif action == "font_decrease":
|
|
self.reader.decrease_font_size()
|
|
elif action == "line_spacing_increase":
|
|
new_spacing = self.reader.page_style.line_spacing + 2
|
|
self.reader.set_line_spacing(new_spacing)
|
|
elif action == "line_spacing_decrease":
|
|
new_spacing = max(0, self.reader.page_style.line_spacing - 2)
|
|
self.reader.set_line_spacing(new_spacing)
|
|
elif action == "block_spacing_increase":
|
|
new_spacing = self.reader.page_style.inter_block_spacing + 3
|
|
self.reader.set_inter_block_spacing(new_spacing)
|
|
elif action == "block_spacing_decrease":
|
|
new_spacing = max(0, self.reader.page_style.inter_block_spacing - 3)
|
|
self.reader.set_inter_block_spacing(new_spacing)
|
|
elif action == "word_spacing_increase":
|
|
new_spacing = self.reader.page_style.word_spacing + 2
|
|
self.reader.set_word_spacing(new_spacing)
|
|
elif action == "word_spacing_decrease":
|
|
new_spacing = max(0, self.reader.page_style.word_spacing - 2)
|
|
self.reader.set_word_spacing(new_spacing)
|
|
|
|
# Re-render the base page with new settings applied
|
|
# Must get directly from manager, not get_current_page() which returns overlay
|
|
page = self.reader.manager.get_current_page()
|
|
updated_page = page.render()
|
|
|
|
# Refresh the settings overlay with updated values and page
|
|
self.refresh(
|
|
updated_base_page=updated_page,
|
|
font_scale=self.reader.base_font_scale,
|
|
line_spacing=self.reader.page_style.line_spacing,
|
|
inter_block_spacing=self.reader.page_style.inter_block_spacing,
|
|
word_spacing=self.reader.page_style.word_spacing
|
|
)
|
|
|
|
return GestureResponse(ActionType.SETTING_CHANGED, {
|
|
"action": action,
|
|
"font_scale": self.reader.base_font_scale,
|
|
"line_spacing": self.reader.page_style.line_spacing,
|
|
"inter_block_spacing": self.reader.page_style.inter_block_spacing,
|
|
"word_spacing": self.reader.page_style.word_spacing
|
|
})
|