This commit is contained in:
+113
-430
@@ -41,19 +41,19 @@ import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from pyWebLayout.io.readers.epub_reader import read_epub
|
||||
from pyWebLayout.io.readers.html_extraction import parse_html_string
|
||||
from pyWebLayout.abstract.block import Block, HeadingLevel
|
||||
from pyWebLayout.layout.ereader_manager import EreaderLayoutManager
|
||||
from pyWebLayout.layout.ereader_layout import RenderingPosition
|
||||
from pyWebLayout.style.page_style import PageStyle
|
||||
from pyWebLayout.concrete.page import Page
|
||||
from pyWebLayout.core.query import QueryResult, SelectionRange
|
||||
from pyWebLayout.core.highlight import Highlight, HighlightManager, HighlightColor, create_highlight_from_query_result
|
||||
from pyWebLayout.core.highlight import Highlight, HighlightColor
|
||||
|
||||
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
|
||||
|
||||
|
||||
class EbookReader:
|
||||
@@ -109,22 +109,25 @@ class EbookReader:
|
||||
inter_block_spacing=inter_block_spacing
|
||||
)
|
||||
|
||||
# State
|
||||
# Core managers (NEW: Refactored into separate modules)
|
||||
self.doc_manager = DocumentManager()
|
||||
self.settings_manager = SettingsManager()
|
||||
self.highlight_coordinator: Optional[HighlightCoordinator] = None
|
||||
self.gesture_router = GestureRouter(self)
|
||||
|
||||
# Layout manager (initialized after loading)
|
||||
self.manager: Optional[EreaderLayoutManager] = None
|
||||
|
||||
# Legacy compatibility properties
|
||||
self.blocks: Optional[List[Block]] = None
|
||||
self.document_id: Optional[str] = None
|
||||
self.book_title: Optional[str] = None
|
||||
self.book_author: Optional[str] = None
|
||||
self.highlight_manager: Optional[HighlightManager] = None
|
||||
self.highlight_manager = None # Will delegate to highlight_coordinator
|
||||
|
||||
# Font scale state
|
||||
# Font scale state (delegated to settings_manager but kept for compatibility)
|
||||
self.base_font_scale = 1.0
|
||||
self.font_scale_step = 0.1 # 10% change per step
|
||||
|
||||
# Selection state (for text selection gestures)
|
||||
self._selection_start: Optional[Tuple[int, int]] = None
|
||||
self._selection_end: Optional[Tuple[int, int]] = None
|
||||
self._selected_range: Optional[SelectionRange] = None
|
||||
self.font_scale_step = 0.1
|
||||
|
||||
# Overlay management
|
||||
self.overlay_manager = OverlayManager(page_size=page_size)
|
||||
@@ -133,58 +136,47 @@ class EbookReader:
|
||||
def load_epub(self, epub_path: str) -> bool:
|
||||
"""
|
||||
Load an EPUB file into the reader.
|
||||
|
||||
|
||||
Args:
|
||||
epub_path: Path to the EPUB file
|
||||
|
||||
|
||||
Returns:
|
||||
True if loaded successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Validate path
|
||||
if not os.path.exists(epub_path):
|
||||
raise FileNotFoundError(f"EPUB file not found: {epub_path}")
|
||||
|
||||
# Load the EPUB
|
||||
book = read_epub(epub_path)
|
||||
|
||||
# Extract metadata
|
||||
self.book_title = book.get_title() or "Unknown Title"
|
||||
self.book_author = book.get_metadata('AUTHOR') or "Unknown Author"
|
||||
|
||||
# Create document ID from filename
|
||||
self.document_id = Path(epub_path).stem
|
||||
|
||||
# Extract all blocks from chapters
|
||||
self.blocks = []
|
||||
for chapter in book.chapters:
|
||||
if hasattr(chapter, '_blocks'):
|
||||
self.blocks.extend(chapter._blocks)
|
||||
|
||||
if not self.blocks:
|
||||
raise ValueError("No content blocks found in EPUB")
|
||||
|
||||
# Initialize the ereader manager
|
||||
self.manager = EreaderLayoutManager(
|
||||
blocks=self.blocks,
|
||||
page_size=self.page_size,
|
||||
document_id=self.document_id,
|
||||
buffer_size=self.buffer_size,
|
||||
page_style=self.page_style,
|
||||
bookmarks_dir=self.bookmarks_dir
|
||||
)
|
||||
# Use DocumentManager to load the EPUB
|
||||
success = self.doc_manager.load_epub(epub_path)
|
||||
|
||||
# Initialize highlight manager for this document
|
||||
self.highlight_manager = HighlightManager(
|
||||
document_id=self.document_id,
|
||||
highlights_dir=self.highlights_dir
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading EPUB: {e}")
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# Set compatibility properties
|
||||
self.book_title = self.doc_manager.title
|
||||
self.book_author = self.doc_manager.author
|
||||
self.document_id = self.doc_manager.document_id
|
||||
self.blocks = self.doc_manager.blocks
|
||||
|
||||
# Initialize the ereader manager
|
||||
self.manager = EreaderLayoutManager(
|
||||
blocks=self.blocks,
|
||||
page_size=self.page_size,
|
||||
document_id=self.document_id,
|
||||
buffer_size=self.buffer_size,
|
||||
page_style=self.page_style,
|
||||
bookmarks_dir=self.bookmarks_dir
|
||||
)
|
||||
|
||||
# Initialize managers that depend on layout manager
|
||||
self.settings_manager.set_manager(self.manager)
|
||||
|
||||
# Initialize highlight coordinator for this document
|
||||
self.highlight_coordinator = HighlightCoordinator(
|
||||
document_id=self.document_id,
|
||||
highlights_dir=self.highlights_dir
|
||||
)
|
||||
self.highlight_coordinator.set_layout_manager(self.manager)
|
||||
self.highlight_manager = self.highlight_coordinator.highlight_manager # Compatibility
|
||||
|
||||
return True
|
||||
|
||||
def load_html(self, html_string: str, title: str = "HTML Document", author: str = "Unknown", document_id: str = "html_doc") -> bool:
|
||||
"""
|
||||
@@ -202,41 +194,41 @@ class EbookReader:
|
||||
Returns:
|
||||
True if loaded successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
# Parse HTML into blocks
|
||||
blocks = parse_html_string(html_string)
|
||||
# Use DocumentManager to load HTML
|
||||
success = self.doc_manager.load_html(html_string, title, author, document_id)
|
||||
|
||||
if not blocks:
|
||||
raise ValueError("No content blocks parsed from HTML")
|
||||
|
||||
# Set metadata
|
||||
self.book_title = title
|
||||
self.book_author = author
|
||||
self.document_id = document_id
|
||||
self.blocks = blocks
|
||||
|
||||
# Initialize the ereader manager
|
||||
self.manager = EreaderLayoutManager(
|
||||
blocks=self.blocks,
|
||||
page_size=self.page_size,
|
||||
document_id=self.document_id,
|
||||
buffer_size=self.buffer_size,
|
||||
page_style=self.page_style,
|
||||
bookmarks_dir=self.bookmarks_dir
|
||||
)
|
||||
|
||||
# Initialize highlight manager for this document
|
||||
self.highlight_manager = HighlightManager(
|
||||
document_id=self.document_id,
|
||||
highlights_dir=self.highlights_dir
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading HTML: {e}")
|
||||
if not success:
|
||||
return False
|
||||
|
||||
# Set compatibility properties
|
||||
self.book_title = self.doc_manager.title
|
||||
self.book_author = self.doc_manager.author
|
||||
self.document_id = self.doc_manager.document_id
|
||||
self.blocks = self.doc_manager.blocks
|
||||
|
||||
# Initialize the ereader manager
|
||||
self.manager = EreaderLayoutManager(
|
||||
blocks=self.blocks,
|
||||
page_size=self.page_size,
|
||||
document_id=self.document_id,
|
||||
buffer_size=self.buffer_size,
|
||||
page_style=self.page_style,
|
||||
bookmarks_dir=self.bookmarks_dir
|
||||
)
|
||||
|
||||
# Initialize managers that depend on layout manager
|
||||
self.settings_manager.set_manager(self.manager)
|
||||
|
||||
# Initialize highlight coordinator for this document
|
||||
self.highlight_coordinator = HighlightCoordinator(
|
||||
document_id=self.document_id,
|
||||
highlights_dir=self.highlights_dir
|
||||
)
|
||||
self.highlight_coordinator.set_layout_manager(self.manager)
|
||||
self.highlight_manager = self.highlight_coordinator.highlight_manager # Compatibility
|
||||
|
||||
return True
|
||||
|
||||
def is_loaded(self) -> bool:
|
||||
"""Check if a book is currently loaded."""
|
||||
return self.manager is not None
|
||||
@@ -466,52 +458,50 @@ class EbookReader:
|
||||
def set_font_size(self, scale: float) -> Optional[Image.Image]:
|
||||
"""
|
||||
Set the font size scale and re-render current page.
|
||||
|
||||
|
||||
Args:
|
||||
scale: Font scale factor (1.0 = normal, 2.0 = double size, 0.5 = half size)
|
||||
|
||||
|
||||
Returns:
|
||||
PIL Image of the re-rendered page with new font size
|
||||
"""
|
||||
if not self.manager:
|
||||
return None
|
||||
|
||||
try:
|
||||
self.base_font_scale = max(0.5, min(3.0, scale)) # Clamp between 0.5x and 3.0x
|
||||
page = self.manager.set_font_scale(self.base_font_scale)
|
||||
return page.render()
|
||||
except Exception as e:
|
||||
print(f"Error setting font size: {e}")
|
||||
return None
|
||||
|
||||
result = self.settings_manager.set_font_size(scale)
|
||||
if result:
|
||||
self.base_font_scale = self.settings_manager.font_scale # Sync compatibility property
|
||||
return result
|
||||
|
||||
def increase_font_size(self) -> Optional[Image.Image]:
|
||||
"""
|
||||
Increase font size by one step and re-render.
|
||||
|
||||
|
||||
Returns:
|
||||
PIL Image of the re-rendered page
|
||||
"""
|
||||
new_scale = self.base_font_scale + self.font_scale_step
|
||||
return self.set_font_size(new_scale)
|
||||
|
||||
result = self.settings_manager.increase_font_size()
|
||||
if result:
|
||||
self.base_font_scale = self.settings_manager.font_scale
|
||||
return result
|
||||
|
||||
def decrease_font_size(self) -> Optional[Image.Image]:
|
||||
"""
|
||||
Decrease font size by one step and re-render.
|
||||
|
||||
|
||||
Returns:
|
||||
PIL Image of the re-rendered page
|
||||
"""
|
||||
new_scale = self.base_font_scale - self.font_scale_step
|
||||
return self.set_font_size(new_scale)
|
||||
|
||||
result = self.settings_manager.decrease_font_size()
|
||||
if result:
|
||||
self.base_font_scale = self.settings_manager.font_scale
|
||||
return result
|
||||
|
||||
def get_font_size(self) -> float:
|
||||
"""
|
||||
Get the current font size scale.
|
||||
|
||||
|
||||
Returns:
|
||||
Current font scale factor
|
||||
"""
|
||||
return self.base_font_scale
|
||||
return self.settings_manager.get_font_size()
|
||||
|
||||
def set_line_spacing(self, spacing: int) -> Optional[Image.Image]:
|
||||
"""
|
||||
@@ -523,28 +513,8 @@ class EbookReader:
|
||||
Returns:
|
||||
PIL Image of the re-rendered page
|
||||
"""
|
||||
if not self.manager:
|
||||
return None
|
||||
return self.settings_manager.set_line_spacing(spacing)
|
||||
|
||||
try:
|
||||
# Calculate delta from current spacing
|
||||
current_spacing = self.manager.page_style.line_spacing
|
||||
target_spacing = max(0, spacing)
|
||||
delta = target_spacing - current_spacing
|
||||
|
||||
# Use pyWebLayout's built-in methods to adjust spacing
|
||||
if delta > 0:
|
||||
self.manager.increase_line_spacing(abs(delta))
|
||||
elif delta < 0:
|
||||
self.manager.decrease_line_spacing(abs(delta))
|
||||
|
||||
# Get re-rendered page
|
||||
page = self.manager.get_current_page()
|
||||
return page.render()
|
||||
except Exception as e:
|
||||
print(f"Error setting line spacing: {e}")
|
||||
return None
|
||||
|
||||
def set_inter_block_spacing(self, spacing: int) -> Optional[Image.Image]:
|
||||
"""
|
||||
Set inter-block spacing using pyWebLayout's native support.
|
||||
@@ -555,27 +525,7 @@ class EbookReader:
|
||||
Returns:
|
||||
PIL Image of the re-rendered page
|
||||
"""
|
||||
if not self.manager:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Calculate delta from current spacing
|
||||
current_spacing = self.manager.page_style.inter_block_spacing
|
||||
target_spacing = max(0, spacing)
|
||||
delta = target_spacing - current_spacing
|
||||
|
||||
# Use pyWebLayout's built-in methods to adjust spacing
|
||||
if delta > 0:
|
||||
self.manager.increase_inter_block_spacing(abs(delta))
|
||||
elif delta < 0:
|
||||
self.manager.decrease_inter_block_spacing(abs(delta))
|
||||
|
||||
# Get re-rendered page
|
||||
page = self.manager.get_current_page()
|
||||
return page.render()
|
||||
except Exception as e:
|
||||
print(f"Error setting inter-block spacing: {e}")
|
||||
return None
|
||||
return self.settings_manager.set_inter_block_spacing(spacing)
|
||||
|
||||
def set_word_spacing(self, spacing: int) -> Optional[Image.Image]:
|
||||
"""
|
||||
@@ -587,27 +537,7 @@ class EbookReader:
|
||||
Returns:
|
||||
PIL Image of the re-rendered page
|
||||
"""
|
||||
if not self.manager:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Calculate delta from current spacing
|
||||
current_spacing = self.manager.page_style.word_spacing
|
||||
target_spacing = max(0, spacing)
|
||||
delta = target_spacing - current_spacing
|
||||
|
||||
# Use pyWebLayout's built-in methods to adjust spacing
|
||||
if delta > 0:
|
||||
self.manager.increase_word_spacing(abs(delta))
|
||||
elif delta < 0:
|
||||
self.manager.decrease_word_spacing(abs(delta))
|
||||
|
||||
# Get re-rendered page
|
||||
page = self.manager.get_current_page()
|
||||
return page.render()
|
||||
except Exception as e:
|
||||
print(f"Error setting word spacing: {e}")
|
||||
return None
|
||||
return self.settings_manager.set_word_spacing(spacing)
|
||||
|
||||
def get_position_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -715,12 +645,7 @@ class EbookReader:
|
||||
Returns:
|
||||
Dictionary with all current settings
|
||||
"""
|
||||
return {
|
||||
'font_scale': self.base_font_scale,
|
||||
'line_spacing': self.page_style.line_spacing if self.manager else 5,
|
||||
'inter_block_spacing': self.page_style.inter_block_spacing if self.manager else 15,
|
||||
'word_spacing': self.page_style.word_spacing if self.manager else 0
|
||||
}
|
||||
return self.settings_manager.get_current_settings()
|
||||
|
||||
def apply_settings(self, settings: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
@@ -734,35 +659,11 @@ class EbookReader:
|
||||
Returns:
|
||||
True if settings applied successfully, False otherwise
|
||||
"""
|
||||
if not self.manager:
|
||||
return False
|
||||
|
||||
try:
|
||||
# Apply font scale
|
||||
font_scale = settings.get('font_scale', 1.0)
|
||||
if font_scale != self.base_font_scale:
|
||||
self.set_font_size(font_scale)
|
||||
|
||||
# Apply line spacing
|
||||
line_spacing = settings.get('line_spacing', 5)
|
||||
if line_spacing != self.page_style.line_spacing:
|
||||
self.set_line_spacing(line_spacing)
|
||||
|
||||
# Apply inter-block spacing
|
||||
inter_block_spacing = settings.get('inter_block_spacing', 15)
|
||||
if inter_block_spacing != self.page_style.inter_block_spacing:
|
||||
self.set_inter_block_spacing(inter_block_spacing)
|
||||
|
||||
# Apply word spacing
|
||||
word_spacing = settings.get('word_spacing', 0)
|
||||
if word_spacing != self.page_style.word_spacing:
|
||||
self.set_word_spacing(word_spacing)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error applying settings: {e}")
|
||||
return False
|
||||
success = self.settings_manager.apply_settings(settings)
|
||||
if success:
|
||||
# Sync compatibility property
|
||||
self.base_font_scale = self.settings_manager.font_scale
|
||||
return success
|
||||
|
||||
# ===== Gesture Handling =====
|
||||
# All business logic for touch input is handled here
|
||||
@@ -780,44 +681,8 @@ class EbookReader:
|
||||
Returns:
|
||||
GestureResponse with action and data for UI to process
|
||||
"""
|
||||
if not self.is_loaded():
|
||||
return GestureResponse(ActionType.ERROR, {"message": "No book loaded"})
|
||||
|
||||
# Handle overlay-specific gestures first
|
||||
if self.is_overlay_open():
|
||||
if event.gesture == GestureType.TAP:
|
||||
return self._handle_overlay_tap(event.x, event.y)
|
||||
elif event.gesture == GestureType.SWIPE_DOWN:
|
||||
# Swipe down closes overlay
|
||||
return self._handle_overlay_close()
|
||||
|
||||
# Dispatch based on gesture type for normal reading mode
|
||||
if event.gesture == GestureType.TAP:
|
||||
return self._handle_tap(event.x, event.y)
|
||||
elif event.gesture == GestureType.LONG_PRESS:
|
||||
return self._handle_long_press(event.x, event.y)
|
||||
elif event.gesture == GestureType.SWIPE_LEFT:
|
||||
return self._handle_page_forward()
|
||||
elif event.gesture == GestureType.SWIPE_RIGHT:
|
||||
return self._handle_page_back()
|
||||
elif event.gesture == GestureType.SWIPE_UP:
|
||||
# Swipe up from bottom opens TOC overlay
|
||||
return self._handle_swipe_up(event.y)
|
||||
elif event.gesture == GestureType.SWIPE_DOWN:
|
||||
# Swipe down from top opens settings overlay
|
||||
return self._handle_swipe_down(event.y)
|
||||
elif event.gesture == GestureType.PINCH_IN:
|
||||
return self._handle_zoom_out()
|
||||
elif event.gesture == GestureType.PINCH_OUT:
|
||||
return self._handle_zoom_in()
|
||||
elif event.gesture == GestureType.DRAG_START:
|
||||
return self._handle_selection_start(event.x, event.y)
|
||||
elif event.gesture == GestureType.DRAG_MOVE:
|
||||
return self._handle_selection_move(event.x, event.y)
|
||||
elif event.gesture == GestureType.DRAG_END:
|
||||
return self._handle_selection_end(event.x, event.y)
|
||||
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
# Delegate to gesture router
|
||||
return self.gesture_router.handle_touch(event)
|
||||
|
||||
def query_pixel(self, x: int, y: int) -> Optional[QueryResult]:
|
||||
"""
|
||||
@@ -835,184 +700,6 @@ class EbookReader:
|
||||
page = self.manager.get_current_page()
|
||||
return page.query_point((x, y))
|
||||
|
||||
def _handle_tap(self, x: int, y: int) -> GestureResponse:
|
||||
"""Handle tap gesture - activates links or selects words"""
|
||||
page = self.manager.get_current_page()
|
||||
result = page.query_point((x, y))
|
||||
|
||||
if not result or result.object_type == "empty":
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
# If it's a link, navigate
|
||||
if result.is_interactive and result.link_target:
|
||||
# Handle different link types
|
||||
if result.link_target.endswith('.epub'):
|
||||
# Open new book
|
||||
success = self.load_epub(result.link_target)
|
||||
if success:
|
||||
return GestureResponse(ActionType.BOOK_LOADED, {
|
||||
"title": self.book_title,
|
||||
"author": self.book_author,
|
||||
"path": result.link_target
|
||||
})
|
||||
else:
|
||||
return GestureResponse(ActionType.ERROR, {
|
||||
"message": f"Failed to load {result.link_target}"
|
||||
})
|
||||
else:
|
||||
# Internal navigation (chapter)
|
||||
self.jump_to_chapter(result.link_target)
|
||||
return GestureResponse(ActionType.NAVIGATE, {
|
||||
"target": result.link_target,
|
||||
"chapter": self.get_current_chapter_info()
|
||||
})
|
||||
|
||||
# Just a tap on text - select word
|
||||
if result.text:
|
||||
return GestureResponse(ActionType.WORD_SELECTED, {
|
||||
"word": result.text,
|
||||
"bounds": result.bounds
|
||||
})
|
||||
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
def _handle_long_press(self, x: int, y: int) -> GestureResponse:
|
||||
"""Handle long-press - show definition or menu"""
|
||||
page = self.manager.get_current_page()
|
||||
result = page.query_point((x, y))
|
||||
|
||||
if result and result.text:
|
||||
return GestureResponse(ActionType.DEFINE, {
|
||||
"word": result.text,
|
||||
"bounds": result.bounds
|
||||
})
|
||||
|
||||
# Long-press on empty - show menu
|
||||
return GestureResponse(ActionType.SHOW_MENU, {
|
||||
"options": ["bookmark", "settings", "toc", "search"]
|
||||
})
|
||||
|
||||
def _handle_page_forward(self) -> GestureResponse:
|
||||
"""Handle swipe left - next page"""
|
||||
img = self.next_page()
|
||||
if img:
|
||||
return GestureResponse(ActionType.PAGE_TURN, {
|
||||
"direction": "forward",
|
||||
"progress": self.get_reading_progress(),
|
||||
"chapter": self.get_current_chapter_info()
|
||||
})
|
||||
return GestureResponse(ActionType.AT_END, {})
|
||||
|
||||
def _handle_page_back(self) -> GestureResponse:
|
||||
"""Handle swipe right - previous page"""
|
||||
img = self.previous_page()
|
||||
if img:
|
||||
return GestureResponse(ActionType.PAGE_TURN, {
|
||||
"direction": "back",
|
||||
"progress": self.get_reading_progress(),
|
||||
"chapter": self.get_current_chapter_info()
|
||||
})
|
||||
return GestureResponse(ActionType.AT_START, {})
|
||||
|
||||
def _handle_zoom_in(self) -> GestureResponse:
|
||||
"""Handle pinch out - increase font"""
|
||||
self.increase_font_size()
|
||||
return GestureResponse(ActionType.ZOOM, {
|
||||
"direction": "in",
|
||||
"font_scale": self.base_font_scale
|
||||
})
|
||||
|
||||
def _handle_zoom_out(self) -> GestureResponse:
|
||||
"""Handle pinch in - decrease font"""
|
||||
self.decrease_font_size()
|
||||
return GestureResponse(ActionType.ZOOM, {
|
||||
"direction": "out",
|
||||
"font_scale": self.base_font_scale
|
||||
})
|
||||
|
||||
def _handle_selection_start(self, x: int, y: int) -> GestureResponse:
|
||||
"""Start text selection"""
|
||||
self._selection_start = (x, y)
|
||||
self._selection_end = None
|
||||
self._selected_range = None
|
||||
|
||||
return GestureResponse(ActionType.SELECTION_START, {
|
||||
"start": (x, y)
|
||||
})
|
||||
|
||||
def _handle_selection_move(self, x: int, y: int) -> GestureResponse:
|
||||
"""Update text selection"""
|
||||
if not self._selection_start:
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
self._selection_end = (x, y)
|
||||
|
||||
# Query range
|
||||
page = self.manager.get_current_page()
|
||||
self._selected_range = page.query_range(
|
||||
self._selection_start,
|
||||
self._selection_end
|
||||
)
|
||||
|
||||
return GestureResponse(ActionType.SELECTION_UPDATE, {
|
||||
"start": self._selection_start,
|
||||
"end": self._selection_end,
|
||||
"text_count": len(self._selected_range.results),
|
||||
"bounds": self._selected_range.bounds_list
|
||||
})
|
||||
|
||||
def _handle_selection_end(self, x: int, y: int) -> GestureResponse:
|
||||
"""End text selection and return selected text"""
|
||||
if not self._selection_start:
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
self._selection_end = (x, y)
|
||||
|
||||
page = self.manager.get_current_page()
|
||||
self._selected_range = page.query_range(
|
||||
self._selection_start,
|
||||
self._selection_end
|
||||
)
|
||||
|
||||
return GestureResponse(ActionType.SELECTION_COMPLETE, {
|
||||
"text": self._selected_range.text,
|
||||
"word_count": len(self._selected_range.results),
|
||||
"bounds": self._selected_range.bounds_list
|
||||
})
|
||||
|
||||
def _handle_swipe_up(self, y: int) -> GestureResponse:
|
||||
"""Handle swipe up gesture - opens TOC overlay if from bottom of screen"""
|
||||
# Check if swipe started from bottom 20% of screen
|
||||
bottom_threshold = self.page_size[1] * 0.8
|
||||
|
||||
if y >= bottom_threshold:
|
||||
# Open TOC overlay
|
||||
overlay_image = self.open_toc_overlay()
|
||||
if overlay_image:
|
||||
return GestureResponse(ActionType.OVERLAY_OPENED, {
|
||||
"overlay_type": "toc",
|
||||
"chapters": self.get_chapters()
|
||||
})
|
||||
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
def _handle_swipe_down(self, y: int) -> GestureResponse:
|
||||
"""Handle swipe down gesture - opens settings overlay if from top of screen"""
|
||||
# Check if swipe started from top 20% of screen
|
||||
top_threshold = self.page_size[1] * 0.2
|
||||
|
||||
if y <= top_threshold:
|
||||
# Open settings overlay
|
||||
overlay_image = self.open_settings_overlay()
|
||||
if overlay_image:
|
||||
return GestureResponse(ActionType.OVERLAY_OPENED, {
|
||||
"overlay_type": "settings",
|
||||
"font_scale": self.base_font_scale,
|
||||
"line_spacing": self.page_style.line_spacing,
|
||||
"inter_block_spacing": self.page_style.inter_block_spacing
|
||||
})
|
||||
|
||||
return GestureResponse(ActionType.NONE, {})
|
||||
|
||||
def _handle_overlay_tap(self, x: int, y: int) -> GestureResponse:
|
||||
"""Handle tap when overlay is open - select chapter, adjust settings, or close overlay"""
|
||||
@@ -1142,10 +829,6 @@ class EbookReader:
|
||||
self.close_overlay()
|
||||
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
|
||||
|
||||
def _handle_overlay_close(self) -> GestureResponse:
|
||||
"""Handle overlay close gesture (swipe down)"""
|
||||
self.close_overlay()
|
||||
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
|
||||
|
||||
# ===================================================================
|
||||
# Highlighting API
|
||||
|
||||
Reference in New Issue
Block a user