""" 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 """ # Query the overlay to see what was tapped query_result = self.query_overlay_pixel(x, y) # If query failed (tap outside overlay), close it if not query_result: 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"] # Parse "setting:action" format if link_target.startswith("setting:"): action = link_target.split(":", 1)[1] 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": return GestureResponse(ActionType.BACK_TO_LIBRARY, {}) # Not a setting control, close overlay return GestureResponse(ActionType.OVERLAY_CLOSED, {}) 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 })