refinements
This commit is contained in:
parent
a552eb0951
commit
0f9e38eb7c
@ -24,7 +24,6 @@ from dreader.state import (
|
||||
OverlayState
|
||||
)
|
||||
from dreader.library import LibraryManager
|
||||
from dreader.overlay import OverlayManager
|
||||
from dreader.main import DReaderApplication, AppConfig
|
||||
from dreader.hal import DisplayHAL, KeyboardInputHAL, EventLoopHAL
|
||||
|
||||
@ -56,9 +55,6 @@ __all__ = [
|
||||
# Library
|
||||
"LibraryManager",
|
||||
|
||||
# Overlay
|
||||
"OverlayManager",
|
||||
|
||||
# Main application
|
||||
"DReaderApplication",
|
||||
"AppConfig",
|
||||
|
||||
@ -51,7 +51,6 @@ from pyWebLayout.core.highlight import Highlight, HighlightColor, create_highlig
|
||||
|
||||
from .gesture import TouchEvent, GestureType, GestureResponse, ActionType
|
||||
from .state import OverlayState
|
||||
from .overlay import OverlayManager
|
||||
from .managers import DocumentManager, SettingsManager, HighlightCoordinator
|
||||
from .handlers import GestureRouter
|
||||
from .overlays import NavigationOverlay, SettingsOverlay, TOCOverlay
|
||||
@ -130,17 +129,14 @@ class EbookReader:
|
||||
self.base_font_scale = 1.0
|
||||
self.font_scale_step = 0.1
|
||||
|
||||
# Overlay management (legacy - kept for backward compatibility)
|
||||
self.overlay_manager = OverlayManager(page_size=page_size)
|
||||
self.current_overlay_state = OverlayState.NONE
|
||||
|
||||
# Overlay sub-applications (NEW architecture)
|
||||
# Overlay sub-applications
|
||||
self._overlay_subapps = {
|
||||
OverlayState.NAVIGATION: NavigationOverlay(self),
|
||||
OverlayState.SETTINGS: SettingsOverlay(self),
|
||||
OverlayState.TOC: TOCOverlay(self),
|
||||
}
|
||||
self._active_overlay = None # Current active overlay sub-application
|
||||
self.current_overlay_state = OverlayState.NONE
|
||||
|
||||
def load_epub(self, epub_path: str) -> bool:
|
||||
"""
|
||||
@ -1014,29 +1010,12 @@ class EbookReader:
|
||||
"""
|
||||
Open the bookmarks overlay.
|
||||
|
||||
This is a convenience method that opens the navigation overlay with the bookmarks tab active.
|
||||
|
||||
Returns:
|
||||
Composited image with bookmarks overlay on top of current page, or None if no book loaded
|
||||
"""
|
||||
if not self.is_loaded():
|
||||
return None
|
||||
|
||||
# Get current page as base
|
||||
base_page = self.get_current_page(include_highlights=False)
|
||||
if not base_page:
|
||||
return None
|
||||
|
||||
# Get bookmarks
|
||||
bookmark_names = self.list_saved_positions()
|
||||
bookmarks = [
|
||||
{"name": name, "position": f"Saved position"}
|
||||
for name in bookmark_names
|
||||
]
|
||||
|
||||
# Open overlay and get composited image
|
||||
result = self.overlay_manager.open_bookmarks_overlay(bookmarks, base_page)
|
||||
self.current_overlay_state = OverlayState.BOOKMARKS
|
||||
|
||||
return result
|
||||
return self.open_navigation_overlay(active_tab="bookmarks")
|
||||
|
||||
def open_navigation_overlay(self, active_tab: str = "contents") -> Optional[Image.Image]:
|
||||
"""
|
||||
|
||||
@ -247,8 +247,12 @@ class GestureRouter:
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
def _handle_swipe_down(self, y: int) -> GestureResponse:
|
||||
"""Handle swipe down gesture - opens Settings overlay"""
|
||||
# Open settings overlay from anywhere on screen
|
||||
"""Handle swipe down gesture - opens Settings overlay (only from top 20% of screen)"""
|
||||
# Only open settings overlay if swipe starts from top 20% of screen
|
||||
top_threshold = self.reader.page_size[1] * 0.2
|
||||
if y > top_threshold:
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
overlay_image = self.reader.open_settings_overlay()
|
||||
if overlay_image:
|
||||
return GestureResponse(ActionType.OVERLAY_OPENED, {
|
||||
|
||||
@ -1,563 +0,0 @@
|
||||
"""
|
||||
Overlay management for dreader application.
|
||||
|
||||
Handles rendering and compositing of overlay screens (TOC, Settings, Bookmarks)
|
||||
on top of the base reading page.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
from .state import OverlayState
|
||||
from .html_generator import (
|
||||
generate_toc_overlay,
|
||||
generate_settings_overlay,
|
||||
generate_bookmarks_overlay,
|
||||
generate_navigation_overlay
|
||||
)
|
||||
|
||||
|
||||
class OverlayManager:
|
||||
"""
|
||||
Manages overlay rendering and interaction.
|
||||
|
||||
Handles:
|
||||
- Generating overlay HTML
|
||||
- Rendering HTML to images using pyWebLayout
|
||||
- Compositing overlays on top of base pages
|
||||
- Tracking current overlay state
|
||||
"""
|
||||
|
||||
def __init__(self, page_size: Tuple[int, int] = (800, 1200)):
|
||||
"""
|
||||
Initialize overlay manager.
|
||||
|
||||
Args:
|
||||
page_size: Size of the page/overlay (width, height)
|
||||
"""
|
||||
self.page_size = page_size
|
||||
self.current_overlay = OverlayState.NONE
|
||||
self._cached_base_page: Optional[Image.Image] = None
|
||||
self._cached_overlay_image: Optional[Image.Image] = None
|
||||
self._overlay_reader = None # Will be EbookReader instance for rendering overlays
|
||||
self._overlay_panel_offset: Tuple[int, int] = (0, 0) # Panel position on screen
|
||||
|
||||
def render_html_to_image(self, html: str, size: Optional[Tuple[int, int]] = None) -> Image.Image:
|
||||
"""
|
||||
Render HTML content to a PIL Image using pyWebLayout.
|
||||
|
||||
This creates a temporary EbookReader instance to render the HTML,
|
||||
then extracts the rendered page as an image.
|
||||
|
||||
Args:
|
||||
html: HTML string to render
|
||||
size: Optional (width, height) for rendering size. Defaults to self.page_size
|
||||
|
||||
Returns:
|
||||
PIL Image of the rendered HTML
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from .application import EbookReader
|
||||
|
||||
render_size = size if size else self.page_size
|
||||
|
||||
# Create a temporary reader for rendering this HTML
|
||||
temp_reader = EbookReader(
|
||||
page_size=render_size,
|
||||
margin=15,
|
||||
background_color=(255, 255, 255)
|
||||
)
|
||||
|
||||
# Load the HTML content
|
||||
success = temp_reader.load_html(
|
||||
html_string=html,
|
||||
title="Overlay",
|
||||
author="",
|
||||
document_id="temp_overlay"
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise ValueError("Failed to load HTML for overlay rendering")
|
||||
|
||||
# Get the rendered page
|
||||
image = temp_reader.get_current_page()
|
||||
|
||||
# Clean up
|
||||
temp_reader.close()
|
||||
|
||||
return image
|
||||
|
||||
def composite_overlay(self, base_image: Image.Image, overlay_panel: Image.Image) -> Image.Image:
|
||||
"""
|
||||
Composite overlay panel on top of base image with darkened background.
|
||||
|
||||
Creates a popup effect by:
|
||||
1. Darkening the base image (multiply by 0.5)
|
||||
2. Placing the overlay panel (60% size) centered on top
|
||||
|
||||
Args:
|
||||
base_image: Base page image (reading page)
|
||||
overlay_panel: Rendered overlay panel (TOC, settings, etc.)
|
||||
|
||||
Returns:
|
||||
Composited PIL Image with popup overlay effect
|
||||
"""
|
||||
from PIL import ImageDraw, ImageEnhance
|
||||
import numpy as np
|
||||
|
||||
# Convert base image to RGB
|
||||
result = base_image.convert('RGB').copy()
|
||||
|
||||
# Lighten the background slightly (70% brightness for e-ink visibility)
|
||||
enhancer = ImageEnhance.Brightness(result)
|
||||
result = enhancer.enhance(0.7)
|
||||
|
||||
# Convert overlay panel to RGB
|
||||
if overlay_panel.mode != 'RGB':
|
||||
overlay_panel = overlay_panel.convert('RGB')
|
||||
|
||||
# Calculate centered position for the panel
|
||||
panel_x = int((self.page_size[0] - overlay_panel.width) / 2)
|
||||
panel_y = int((self.page_size[1] - overlay_panel.height) / 2)
|
||||
|
||||
# Add a thick black border around the panel for e-ink clarity
|
||||
draw = ImageDraw.Draw(result)
|
||||
border_width = 3
|
||||
draw.rectangle(
|
||||
[panel_x - border_width, panel_y - border_width,
|
||||
panel_x + overlay_panel.width + border_width, panel_y + overlay_panel.height + border_width],
|
||||
outline=(0, 0, 0),
|
||||
width=border_width
|
||||
)
|
||||
|
||||
# Paste the panel onto the dimmed background
|
||||
result.paste(overlay_panel, (panel_x, panel_y))
|
||||
|
||||
return result
|
||||
|
||||
def open_toc_overlay(self, chapters: List[Tuple[str, int]], base_page: Image.Image) -> Image.Image:
|
||||
"""
|
||||
Open the table of contents overlay.
|
||||
|
||||
Args:
|
||||
chapters: List of (chapter_title, chapter_index) tuples
|
||||
base_page: Current reading page to show underneath
|
||||
|
||||
Returns:
|
||||
Composited image with TOC overlay on top
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from .application import EbookReader
|
||||
|
||||
# Calculate panel size (60% of screen)
|
||||
panel_width = int(self.page_size[0] * 0.6)
|
||||
panel_height = int(self.page_size[1] * 0.7)
|
||||
|
||||
# Convert chapters to format expected by HTML generator
|
||||
chapter_data = [
|
||||
{"index": idx, "title": title}
|
||||
for title, idx in chapters
|
||||
]
|
||||
|
||||
# Generate TOC HTML with clickable links
|
||||
html = generate_toc_overlay(chapter_data, page_size=(panel_width, panel_height))
|
||||
|
||||
# Create reader for overlay and keep it alive for querying
|
||||
if self._overlay_reader:
|
||||
self._overlay_reader.close()
|
||||
|
||||
self._overlay_reader = EbookReader(
|
||||
page_size=(panel_width, panel_height),
|
||||
margin=15,
|
||||
background_color=(255, 255, 255)
|
||||
)
|
||||
|
||||
# Load the HTML content
|
||||
success = self._overlay_reader.load_html(
|
||||
html_string=html,
|
||||
title="Table of Contents",
|
||||
author="",
|
||||
document_id="toc_overlay"
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise ValueError("Failed to load TOC overlay HTML")
|
||||
|
||||
# Get the rendered page
|
||||
overlay_panel = self._overlay_reader.get_current_page()
|
||||
|
||||
# Calculate and store panel position for coordinate translation
|
||||
panel_x = int((self.page_size[0] - panel_width) / 2)
|
||||
panel_y = int((self.page_size[1] - panel_height) / 2)
|
||||
self._overlay_panel_offset = (panel_x, panel_y)
|
||||
|
||||
# Cache for later use
|
||||
self._cached_base_page = base_page.copy()
|
||||
self._cached_overlay_image = overlay_panel
|
||||
self.current_overlay = OverlayState.TOC
|
||||
|
||||
# Composite and return
|
||||
return self.composite_overlay(base_page, overlay_panel)
|
||||
|
||||
def open_settings_overlay(
|
||||
self,
|
||||
base_page: Image.Image,
|
||||
font_scale: float = 1.0,
|
||||
line_spacing: int = 5,
|
||||
inter_block_spacing: int = 15,
|
||||
word_spacing: int = 0
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Open the settings overlay with current settings values.
|
||||
|
||||
Args:
|
||||
base_page: Current reading page to show underneath
|
||||
font_scale: Current font scale
|
||||
line_spacing: Current line spacing
|
||||
inter_block_spacing: Current inter-block spacing
|
||||
word_spacing: Current word spacing
|
||||
|
||||
Returns:
|
||||
Composited image with settings overlay on top
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from .application import EbookReader
|
||||
|
||||
# Calculate panel size (60% of screen)
|
||||
panel_width = int(self.page_size[0] * 0.6)
|
||||
panel_height = int(self.page_size[1] * 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_width, panel_height)
|
||||
)
|
||||
|
||||
# Create reader for overlay and keep it alive for querying
|
||||
if self._overlay_reader:
|
||||
self._overlay_reader.close()
|
||||
|
||||
self._overlay_reader = EbookReader(
|
||||
page_size=(panel_width, panel_height),
|
||||
margin=15,
|
||||
background_color=(255, 255, 255)
|
||||
)
|
||||
|
||||
# Load the HTML content
|
||||
success = self._overlay_reader.load_html(
|
||||
html_string=html,
|
||||
title="Settings",
|
||||
author="",
|
||||
document_id="settings_overlay"
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise ValueError("Failed to load settings overlay HTML")
|
||||
|
||||
# Get the rendered page
|
||||
overlay_panel = self._overlay_reader.get_current_page()
|
||||
|
||||
# Calculate and store panel position for coordinate translation
|
||||
panel_x = int((self.page_size[0] - panel_width) / 2)
|
||||
panel_y = int((self.page_size[1] - panel_height) / 2)
|
||||
self._overlay_panel_offset = (panel_x, panel_y)
|
||||
|
||||
# Cache for later use
|
||||
self._cached_base_page = base_page.copy()
|
||||
self._cached_overlay_image = overlay_panel
|
||||
self.current_overlay = OverlayState.SETTINGS
|
||||
|
||||
# Composite and return
|
||||
return self.composite_overlay(base_page, overlay_panel)
|
||||
|
||||
def refresh_settings_overlay(
|
||||
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
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from .application import EbookReader
|
||||
|
||||
# Calculate panel size (60% of screen)
|
||||
panel_width = int(self.page_size[0] * 0.6)
|
||||
panel_height = int(self.page_size[1] * 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_width, panel_height)
|
||||
)
|
||||
|
||||
# Recreate overlay reader with updated HTML
|
||||
if self._overlay_reader:
|
||||
self._overlay_reader.close()
|
||||
|
||||
self._overlay_reader = EbookReader(
|
||||
page_size=(panel_width, panel_height),
|
||||
margin=15,
|
||||
background_color=(255, 255, 255)
|
||||
)
|
||||
|
||||
success = self._overlay_reader.load_html(
|
||||
html_string=html,
|
||||
title="Settings",
|
||||
author="",
|
||||
document_id="settings_overlay"
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise ValueError("Failed to load updated settings overlay HTML")
|
||||
|
||||
# Get the updated rendered panel
|
||||
overlay_panel = self._overlay_reader.get_current_page()
|
||||
|
||||
# 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 open_bookmarks_overlay(self, bookmarks: List[Dict[str, Any]], base_page: Image.Image) -> Image.Image:
|
||||
"""
|
||||
Open the bookmarks overlay.
|
||||
|
||||
Args:
|
||||
bookmarks: List of bookmark dictionaries with 'name' and 'position' keys
|
||||
base_page: Current reading page to show underneath
|
||||
|
||||
Returns:
|
||||
Composited image with bookmarks overlay on top
|
||||
"""
|
||||
# Generate bookmarks HTML
|
||||
html = generate_bookmarks_overlay(bookmarks)
|
||||
|
||||
# Render HTML to image
|
||||
overlay_image = self.render_html_to_image(html)
|
||||
|
||||
# Cache for later use
|
||||
self._cached_base_page = base_page.copy()
|
||||
self._cached_overlay_image = overlay_image
|
||||
self.current_overlay = OverlayState.BOOKMARKS
|
||||
|
||||
# Composite and return
|
||||
return self.composite_overlay(base_page, overlay_image)
|
||||
|
||||
def open_navigation_overlay(
|
||||
self,
|
||||
chapters: List[Tuple[str, int]],
|
||||
bookmarks: List[Dict],
|
||||
base_page: Image.Image,
|
||||
active_tab: str = "contents"
|
||||
) -> Image.Image:
|
||||
"""
|
||||
Open the unified navigation overlay with Contents and Bookmarks tabs.
|
||||
|
||||
This replaces the separate TOC and Bookmarks overlays with a single
|
||||
overlay that has tabs for switching between contents and bookmarks.
|
||||
|
||||
Args:
|
||||
chapters: List of (chapter_title, chapter_index) tuples
|
||||
bookmarks: List of bookmark dictionaries with 'name' and optional 'position'
|
||||
base_page: Current reading page to show underneath
|
||||
active_tab: Which tab to show ("contents" or "bookmarks")
|
||||
|
||||
Returns:
|
||||
Composited image with navigation overlay on top
|
||||
"""
|
||||
# Import here to avoid circular dependency
|
||||
from .application import EbookReader
|
||||
|
||||
# Calculate panel size (60% of screen width, 70% height)
|
||||
panel_width = int(self.page_size[0] * 0.6)
|
||||
panel_height = int(self.page_size[1] * 0.7)
|
||||
|
||||
# Convert chapters to format expected by HTML generator
|
||||
chapter_data = [
|
||||
{"index": idx, "title": title}
|
||||
for title, idx in chapters
|
||||
]
|
||||
|
||||
# Generate navigation HTML with tabs
|
||||
html = generate_navigation_overlay(
|
||||
chapters=chapter_data,
|
||||
bookmarks=bookmarks,
|
||||
active_tab=active_tab,
|
||||
page_size=(panel_width, panel_height)
|
||||
)
|
||||
|
||||
# Create reader for overlay and keep it alive for querying
|
||||
if self._overlay_reader:
|
||||
self._overlay_reader.close()
|
||||
|
||||
self._overlay_reader = EbookReader(
|
||||
page_size=(panel_width, panel_height),
|
||||
margin=15,
|
||||
background_color=(255, 255, 255)
|
||||
)
|
||||
|
||||
# Load the HTML content
|
||||
success = self._overlay_reader.load_html(
|
||||
html_string=html,
|
||||
title="Navigation",
|
||||
author="",
|
||||
document_id="navigation_overlay"
|
||||
)
|
||||
|
||||
if not success:
|
||||
raise ValueError("Failed to load navigation overlay HTML")
|
||||
|
||||
# Get the rendered page
|
||||
overlay_panel = self._overlay_reader.get_current_page()
|
||||
|
||||
# Calculate and store panel position for coordinate translation
|
||||
panel_x = int((self.page_size[0] - panel_width) / 2)
|
||||
panel_y = int((self.page_size[1] - panel_height) / 2)
|
||||
self._overlay_panel_offset = (panel_x, panel_y)
|
||||
|
||||
# Cache for later use
|
||||
self._cached_base_page = base_page.copy()
|
||||
self._cached_overlay_image = overlay_panel
|
||||
self.current_overlay = OverlayState.NAVIGATION
|
||||
|
||||
# Store active tab for tab switching
|
||||
self._active_nav_tab = active_tab
|
||||
self._cached_chapters = chapters
|
||||
self._cached_bookmarks = bookmarks
|
||||
|
||||
# Composite and return
|
||||
return self.composite_overlay(base_page, overlay_panel)
|
||||
|
||||
def switch_navigation_tab(self, new_tab: str) -> Optional[Image.Image]:
|
||||
"""
|
||||
Switch between tabs in the navigation overlay.
|
||||
|
||||
Args:
|
||||
new_tab: Tab to switch to ("contents" or "bookmarks")
|
||||
|
||||
Returns:
|
||||
Updated composited image with new tab active, or None if not in navigation overlay
|
||||
"""
|
||||
if self.current_overlay != OverlayState.NAVIGATION:
|
||||
return None
|
||||
|
||||
# Re-open navigation overlay with new active tab
|
||||
if hasattr(self, '_cached_chapters') and hasattr(self, '_cached_bookmarks'):
|
||||
return self.open_navigation_overlay(
|
||||
chapters=self._cached_chapters,
|
||||
bookmarks=self._cached_bookmarks,
|
||||
base_page=self._cached_base_page,
|
||||
active_tab=new_tab
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def close_overlay(self) -> Optional[Image.Image]:
|
||||
"""
|
||||
Close the current overlay and return to base page.
|
||||
|
||||
Returns:
|
||||
Base page image (without overlay), or None if no overlay was open
|
||||
"""
|
||||
if self.current_overlay == OverlayState.NONE:
|
||||
return None
|
||||
|
||||
self.current_overlay = OverlayState.NONE
|
||||
base_page = self._cached_base_page
|
||||
|
||||
# Clear caches
|
||||
self._cached_base_page = None
|
||||
self._cached_overlay_image = None
|
||||
self._overlay_panel_offset = (0, 0)
|
||||
|
||||
# Close overlay reader
|
||||
if self._overlay_reader:
|
||||
self._overlay_reader.close()
|
||||
self._overlay_reader = None
|
||||
|
||||
return base_page
|
||||
|
||||
def is_overlay_open(self) -> bool:
|
||||
"""Check if an overlay is currently open."""
|
||||
return self.current_overlay != OverlayState.NONE
|
||||
|
||||
def get_current_overlay_type(self) -> OverlayState:
|
||||
"""Get the type of currently open overlay."""
|
||||
return self.current_overlay
|
||||
|
||||
def query_overlay_pixel(self, x: int, y: int) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Query a pixel in the current overlay to detect interactions.
|
||||
|
||||
Uses pyWebLayout's query_point() to detect which element was tapped,
|
||||
including link targets and data attributes.
|
||||
|
||||
Args:
|
||||
x, y: Pixel coordinates to query (in screen space)
|
||||
|
||||
Returns:
|
||||
Dictionary with query result data (text, link_target, is_interactive),
|
||||
or None if no overlay open or query failed
|
||||
"""
|
||||
if not self.is_overlay_open() or not self._overlay_reader:
|
||||
return None
|
||||
|
||||
# Translate screen coordinates to overlay panel coordinates
|
||||
panel_x, panel_y = self._overlay_panel_offset
|
||||
overlay_x = x - panel_x
|
||||
overlay_y = y - panel_y
|
||||
|
||||
# Check if coordinates are within the overlay panel
|
||||
if overlay_x < 0 or overlay_y < 0:
|
||||
return None
|
||||
|
||||
# Get the current page from the overlay reader
|
||||
if not self._overlay_reader.manager:
|
||||
return None
|
||||
|
||||
current_page = self._overlay_reader.manager.get_current_page()
|
||||
if not current_page:
|
||||
return None
|
||||
|
||||
# Query the point
|
||||
result = current_page.query_point((overlay_x, overlay_y))
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
# Extract relevant data from QueryResult
|
||||
return {
|
||||
"text": result.text,
|
||||
"link_target": result.link_target,
|
||||
"is_interactive": result.is_interactive,
|
||||
"bounds": result.bounds,
|
||||
"object_type": result.object_type
|
||||
}
|
||||
@ -29,12 +29,18 @@ class TestSettingsOverlay(unittest.TestCase):
|
||||
|
||||
self.reader = EbookReader(page_size=(800, 1200))
|
||||
|
||||
# Load a test EPUB - use any available EPUB in test data
|
||||
# Load a test EPUB - use a larger EPUB for spacing tests
|
||||
epub_dir = Path(__file__).parent / 'data' / 'library-epub'
|
||||
epubs = list(epub_dir.glob('*.epub'))
|
||||
if not epubs:
|
||||
self.skipTest("No test EPUB files available")
|
||||
|
||||
# Prefer larger EPUBs for better testing of spacing changes
|
||||
# Skip minimal-test.epub as it has too little content
|
||||
epubs = [e for e in epubs if 'minimal' not in e.name]
|
||||
if not epubs:
|
||||
epubs = list(epub_dir.glob('*.epub'))
|
||||
|
||||
test_epub = epubs[0]
|
||||
|
||||
# Debug logging
|
||||
@ -175,9 +181,12 @@ class TestSettingsOverlay(unittest.TestCase):
|
||||
self.reader.open_settings_overlay()
|
||||
initial_font_scale = self.reader.base_font_scale
|
||||
|
||||
# Get overlay reader to query button positions
|
||||
overlay_manager = self.reader.overlay_manager
|
||||
overlay_reader = overlay_manager._overlay_reader
|
||||
# Get overlay reader to query button positions from the active overlay sub-application
|
||||
overlay_subapp = self.reader._active_overlay
|
||||
if not overlay_subapp:
|
||||
self.skipTest("No active overlay sub-application")
|
||||
|
||||
overlay_reader = overlay_subapp._overlay_reader
|
||||
|
||||
if not overlay_reader or not overlay_reader.manager:
|
||||
self.skipTest("Overlay reader not available for querying")
|
||||
@ -302,15 +311,17 @@ class TestSettingsOverlay(unittest.TestCase):
|
||||
# Open overlay
|
||||
self.reader.open_settings_overlay()
|
||||
|
||||
# Access refresh method through overlay manager
|
||||
overlay_manager = self.reader.overlay_manager
|
||||
# Access refresh method through active overlay sub-application
|
||||
overlay_subapp = self.reader._active_overlay
|
||||
if not overlay_subapp:
|
||||
self.skipTest("No active overlay sub-application")
|
||||
|
||||
# Change a setting programmatically
|
||||
self.reader.increase_font_size()
|
||||
new_page = self.reader.get_current_page(include_highlights=False)
|
||||
|
||||
# Refresh overlay
|
||||
refreshed_image = overlay_manager.refresh_settings_overlay(
|
||||
refreshed_image = overlay_subapp.refresh(
|
||||
updated_base_page=new_page,
|
||||
font_scale=self.reader.base_font_scale,
|
||||
line_spacing=self.reader.page_style.line_spacing,
|
||||
|
||||
@ -44,9 +44,14 @@ class TestTOCOverlay(unittest.TestCase):
|
||||
self.reader.close()
|
||||
|
||||
def test_overlay_manager_initialization(self):
|
||||
"""Test that overlay manager is properly initialized"""
|
||||
self.assertIsNotNone(self.reader.overlay_manager)
|
||||
self.assertEqual(self.reader.overlay_manager.page_size, (800, 1200))
|
||||
"""Test that overlay sub-applications are properly initialized"""
|
||||
# Check that overlay sub-applications exist
|
||||
self.assertIsNotNone(self.reader._overlay_subapps)
|
||||
self.assertIn(OverlayState.TOC, self.reader._overlay_subapps)
|
||||
self.assertIn(OverlayState.SETTINGS, self.reader._overlay_subapps)
|
||||
self.assertIn(OverlayState.NAVIGATION, self.reader._overlay_subapps)
|
||||
|
||||
# Initially no overlay should be active
|
||||
self.assertFalse(self.reader.is_overlay_open())
|
||||
self.assertEqual(self.reader.get_overlay_state(), OverlayState.NONE)
|
||||
|
||||
@ -298,13 +303,12 @@ class TestOverlayRendering(unittest.TestCase):
|
||||
self.assertIsNotNone(html)
|
||||
self.assertIn("Table of Contents", html)
|
||||
|
||||
# Render HTML to image using overlay manager
|
||||
overlay_manager = self.reader.overlay_manager
|
||||
image = overlay_manager.render_html_to_image(html)
|
||||
# Open the TOC overlay which internally renders HTML to image
|
||||
overlay_image = self.reader.open_toc_overlay()
|
||||
|
||||
# Should produce valid image
|
||||
self.assertIsNotNone(image)
|
||||
self.assertEqual(image.size, (800, 1200))
|
||||
self.assertIsNotNone(overlay_image)
|
||||
self.assertEqual(overlay_image.size, (800, 1200))
|
||||
|
||||
|
||||
class TestTOCPagination(unittest.TestCase):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user