refactor all navigation to one screen.

This commit is contained in:
Duncan Tourolle 2025-11-08 23:50:49 +01:00
parent 5d3e7fae7b
commit 7518bcf835
7 changed files with 778 additions and 3 deletions

View File

@ -825,6 +825,85 @@ class EbookReader:
self.close_overlay() self.close_overlay()
return GestureResponse(ActionType.OVERLAY_CLOSED, {}) return GestureResponse(ActionType.OVERLAY_CLOSED, {})
# For navigation overlay, handle tab switching, chapter/bookmark selection, and close
elif self.current_overlay_state == OverlayState.NAVIGATION:
# Query the overlay to see what was tapped
query_result = self.overlay_manager.query_overlay_pixel(x, y)
# If query failed (tap outside overlay), close it
if not query_result:
self.close_overlay()
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
# Check if tapped on a link
if query_result.get("is_interactive") and query_result.get("link_target"):
link_target = query_result["link_target"]
# Parse "tab:tabname" format for tab switching
if link_target.startswith("tab:"):
tab_name = link_target.split(":", 1)[1]
# Switch to the selected tab
self.switch_navigation_tab(tab_name)
return GestureResponse(ActionType.TAB_SWITCHED, {
"tab": tab_name
})
# Parse "chapter:N" format for chapter navigation
elif link_target.startswith("chapter:"):
try:
chapter_idx = int(link_target.split(":")[1])
# Get chapter title for response
chapters = self.get_chapters()
chapter_title = None
for title, idx in chapters:
if idx == chapter_idx:
chapter_title = title
break
# Jump to selected chapter
self.jump_to_chapter(chapter_idx)
# Close overlay
self.close_overlay()
return GestureResponse(ActionType.CHAPTER_SELECTED, {
"chapter_index": chapter_idx,
"chapter_title": chapter_title or f"Chapter {chapter_idx}"
})
except (ValueError, IndexError):
pass
# Parse "bookmark:name" format for bookmark navigation
elif link_target.startswith("bookmark:"):
bookmark_name = link_target.split(":", 1)[1]
# Load the bookmark position
page = self.load_position(bookmark_name)
if page:
# Close overlay
self.close_overlay()
return GestureResponse(ActionType.BOOKMARK_SELECTED, {
"bookmark_name": bookmark_name
})
else:
# Failed to load bookmark
return GestureResponse(ActionType.ERROR, {
"message": f"Failed to load bookmark: {bookmark_name}"
})
# Parse "action:close" format for close button
elif link_target.startswith("action:"):
action = link_target.split(":", 1)[1]
if action == "close":
self.close_overlay()
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
# Not an interactive element, close overlay
self.close_overlay()
return GestureResponse(ActionType.OVERLAY_CLOSED, {})
# For other overlays, just close on any tap for now # For other overlays, just close on any tap for now
self.close_overlay() self.close_overlay()
return GestureResponse(ActionType.OVERLAY_CLOSED, {}) return GestureResponse(ActionType.OVERLAY_CLOSED, {})
@ -1127,6 +1206,64 @@ class EbookReader:
return result return result
def open_navigation_overlay(self, active_tab: str = "contents") -> Optional[Image.Image]:
"""
Open the unified navigation overlay with Contents and Bookmarks tabs.
This is the new unified overlay that replaces separate TOC and Bookmarks overlays.
It provides a tabbed interface for switching between table of contents and bookmarks.
Args:
active_tab: Which tab to show initially ("contents" or "bookmarks")
Returns:
Composited image with navigation 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 chapters for Contents tab
chapters = self.get_chapters()
# Get bookmarks for Bookmarks tab
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_navigation_overlay(
chapters=chapters,
bookmarks=bookmarks,
base_page=base_page,
active_tab=active_tab
)
self.current_overlay_state = OverlayState.NAVIGATION
return result
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 image with new tab active, or None if navigation overlay is not open
"""
if self.current_overlay_state != OverlayState.NAVIGATION:
return None
result = self.overlay_manager.switch_navigation_tab(new_tab)
return result if result else self.get_current_page()
def close_overlay(self) -> Optional[Image.Image]: def close_overlay(self) -> Optional[Image.Image]:
""" """
Close the current overlay and return to reading view. Close the current overlay and return to reading view.

View File

@ -125,5 +125,7 @@ class ActionType:
OVERLAY_OPENED = "overlay_opened" OVERLAY_OPENED = "overlay_opened"
OVERLAY_CLOSED = "overlay_closed" OVERLAY_CLOSED = "overlay_closed"
CHAPTER_SELECTED = "chapter_selected" CHAPTER_SELECTED = "chapter_selected"
BOOKMARK_SELECTED = "bookmark_selected"
TAB_SWITCHED = "tab_switched"
SETTING_CHANGED = "setting_changed" SETTING_CHANGED = "setting_changed"
BACK_TO_LIBRARY = "back_to_library" BACK_TO_LIBRARY = "back_to_library"

View File

@ -502,3 +502,136 @@ def generate_bookmarks_overlay(bookmarks: List[Dict]) -> str:
</html> </html>
''' '''
return html return html
def generate_navigation_overlay(
chapters: List[Dict],
bookmarks: List[Dict],
active_tab: str = "contents",
page_size: tuple = (800, 1200)
) -> str:
"""
Generate HTML for the unified navigation overlay with Contents and Bookmarks tabs.
This combines TOC and Bookmarks into a single overlay with tab switching.
Tabs are clickable links that switch between contents (tab:contents) and bookmarks (tab:bookmarks).
Args:
chapters: List of chapter dictionaries with keys:
- index: Chapter index
- title: Chapter title
bookmarks: List of bookmark dictionaries with keys:
- name: Bookmark name
- position: Position info (optional)
active_tab: Which tab to show ("contents" or "bookmarks")
page_size: Page dimensions (width, height) for sizing the overlay
Returns:
HTML string for navigation overlay with tab switching
"""
# Build chapter list items with clickable links
chapter_items = []
for i, chapter in enumerate(chapters):
title = chapter["title"]
link_text = f'{i+1}. {title}'
if len(title) <= 2:
link_text = f'{i+1}. {title} ' # Extra spaces for padding
chapter_items.append(
f'<p style="padding: 12px; margin: 5px 0; background-color: #f0f0f0; '
f'border-left: 3px solid #000;">'
f'<a href="chapter:{chapter["index"]}" style="text-decoration: none; color: #000;">'
f'{link_text}</a></p>'
)
# Build bookmark list items with clickable links
bookmark_items = []
for bookmark in bookmarks:
name = bookmark['name']
position_text = bookmark.get('position', 'Saved position')
bookmark_items.append(
f'<p style="padding: 12px; margin: 5px 0; background-color: #f0f0f0; '
f'border-left: 3px solid #000;">'
f'<a href="bookmark:{name}" style="text-decoration: none; color: #000; display: block;">'
f'<span style="font-weight: bold; display: block;">{name}</span>'
f'<span style="font-size: 11px; color: #666;">{position_text}</span>'
f'</a></p>'
)
# Determine which content to show
contents_display = "block" if active_tab == "contents" else "none"
bookmarks_display = "block" if active_tab == "bookmarks" else "none"
# Style active tab
contents_tab_style = "background-color: #000; color: #fff;" if active_tab == "contents" else "background-color: #f0f0f0; color: #000;"
bookmarks_tab_style = "background-color: #000; color: #fff;" if active_tab == "bookmarks" else "background-color: #f0f0f0; color: #000;"
chapters_html = ''.join(chapter_items) if chapter_items else '<p style="padding: 20px; text-align: center; color: #999;">No chapters available</p>'
bookmarks_html = ''.join(bookmark_items) if bookmark_items else '<p style="padding: 20px; text-align: center; color: #999;">No bookmarks yet</p>'
html = f'''
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Navigation</title>
</head>
<body style="background-color: white; margin: 0; padding: 0; font-family: Arial, sans-serif;">
<!-- Tab Bar -->
<div style="display: flex; border-bottom: 2px solid #ccc; background-color: #f8f8f8;">
<a href="tab:contents"
style="flex: 1; padding: 15px; text-align: center; font-weight: bold;
text-decoration: none; border-right: 1px solid #ccc; {contents_tab_style}">
Contents
</a>
<a href="tab:bookmarks"
style="flex: 1; padding: 15px; text-align: center; font-weight: bold;
text-decoration: none; {bookmarks_tab_style}">
Bookmarks
</a>
</div>
<!-- Contents Tab Content -->
<div id="contents-tab" style="padding: 25px; display: {contents_display};">
<h2 style="color: #000; margin: 0 0 15px 0; font-size: 20px; text-align: center;">
Table of Contents
</h2>
<p style="text-align: center; color: #666; margin: 0 0 15px 0; padding-bottom: 12px;
border-bottom: 2px solid #ccc; font-size: 13px;">
{len(chapters)} chapters
</p>
<div style="overflow-y: auto; max-height: calc(100vh - 200px);">
{chapters_html}
</div>
</div>
<!-- Bookmarks Tab Content -->
<div id="bookmarks-tab" style="padding: 25px; display: {bookmarks_display};">
<h2 style="color: #000; margin: 0 0 15px 0; font-size: 20px; text-align: center;">
Bookmarks
</h2>
<p style="text-align: center; color: #666; margin: 0 0 15px 0; padding-bottom: 12px;
border-bottom: 2px solid #ccc; font-size: 13px;">
{len(bookmarks)} saved
</p>
<div style="overflow-y: auto; max-height: calc(100vh - 200px);">
{bookmarks_html}
</div>
</div>
<!-- Close Button (bottom right) -->
<div style="position: fixed; bottom: 20px; right: 20px;">
<a href="action:close"
style="display: inline-block; padding: 12px 24px; background-color: #dc3545;
color: white; text-decoration: none; border-radius: 4px; font-weight: bold;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);">
Close
</a>
</div>
</body>
</html>
'''
return html

View File

@ -14,7 +14,8 @@ from .state import OverlayState
from .html_generator import ( from .html_generator import (
generate_toc_overlay, generate_toc_overlay,
generate_settings_overlay, generate_settings_overlay,
generate_bookmarks_overlay generate_bookmarks_overlay,
generate_navigation_overlay
) )
@ -370,6 +371,115 @@ class OverlayManager:
# Composite and return # Composite and return
return self.composite_overlay(base_page, overlay_image) 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]: def close_overlay(self) -> Optional[Image.Image]:
""" """
Close the current overlay and return to base page. Close the current overlay and return to base page.

View File

@ -27,9 +27,10 @@ class EreaderMode(Enum):
class OverlayState(Enum): class OverlayState(Enum):
"""Overlay states within READING mode""" """Overlay states within READING mode"""
NONE = "none" NONE = "none"
TOC = "toc" TOC = "toc" # Deprecated: use NAVIGATION instead
SETTINGS = "settings" SETTINGS = "settings"
BOOKMARKS = "bookmarks" BOOKMARKS = "bookmarks" # Deprecated: use NAVIGATION instead
NAVIGATION = "navigation" # Unified overlay for TOC and Bookmarks
@dataclass @dataclass

View File

@ -0,0 +1,196 @@
"""
Example demonstrating the unified navigation overlay feature.
This example shows how to:
1. Open the navigation overlay with Contents and Bookmarks tabs
2. Switch between tabs
3. Navigate to chapters and bookmarks
4. Handle user interactions with the overlay
The navigation overlay replaces the separate TOC and Bookmarks overlays
with a single, unified interface that provides both features in a tabbed view.
"""
from pathlib import Path
from dreader.application import EbookReader
from dreader.state import OverlayState
def main():
# Create reader instance
reader = EbookReader(page_size=(800, 1200), margin=20)
# Load a sample book (adjust path as needed)
book_path = Path(__file__).parent / "books" / "hamlet.epub"
if not book_path.exists():
print(f"Book not found at {book_path}")
print("Creating a simple HTML book for demo...")
# Create a simple multi-chapter book
html = """
<html>
<head><title>Demo Book</title></head>
<body>
<h1>Chapter 1: Introduction</h1>
<p>This is the first chapter with some introductory content.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
<h1>Chapter 2: Main Content</h1>
<p>This is the second chapter with main content.</p>
<p>Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
<h1>Chapter 3: Conclusion</h1>
<p>This is the final chapter with concluding remarks.</p>
<p>Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
</body>
</html>
"""
reader.load_html(
html_string=html,
title="Demo Book",
author="Example Author",
document_id="demo_navigation"
)
else:
print(f"Loading book: {book_path}")
reader.load_epub(str(book_path))
print("\n=== Navigation Overlay Demo ===\n")
# Display current page
position_info = reader.get_position_info()
print(f"Current position: {position_info}")
print(f"Reading progress: {reader.get_reading_progress():.1%}")
# Get chapters
chapters = reader.get_chapters()
print(f"\nAvailable chapters: {len(chapters)}")
for i, (title, idx) in enumerate(chapters[:5]): # Show first 5
print(f" {i+1}. {title}")
# Save some bookmarks for demonstration
print("\n--- Saving bookmarks ---")
reader.save_position("Start of Book")
print("Saved bookmark: 'Start of Book'")
reader.next_page()
reader.next_page()
reader.save_position("Chapter 1 Progress")
print("Saved bookmark: 'Chapter 1 Progress'")
# List saved bookmarks
bookmarks = reader.list_saved_positions()
print(f"\nTotal bookmarks: {len(bookmarks)}")
for name in bookmarks:
print(f" - {name}")
# === Demo 1: Open navigation overlay with Contents tab ===
print("\n\n--- Demo 1: Opening Navigation Overlay (Contents Tab) ---")
image = reader.open_navigation_overlay(active_tab="contents")
if image:
print(f"✓ Navigation overlay opened successfully")
print(f" Overlay state: {reader.get_overlay_state()}")
print(f" Is overlay open: {reader.is_overlay_open()}")
print(f" Image size: {image.size}")
# Save the rendered overlay for inspection
output_path = Path("/tmp/navigation_overlay_contents.png")
image.save(output_path)
print(f" Saved to: {output_path}")
# === Demo 2: Switch to Bookmarks tab ===
print("\n\n--- Demo 2: Switching to Bookmarks Tab ---")
image = reader.switch_navigation_tab("bookmarks")
if image:
print(f"✓ Switched to Bookmarks tab")
print(f" Overlay state: {reader.get_overlay_state()}")
# Save the rendered overlay for inspection
output_path = Path("/tmp/navigation_overlay_bookmarks.png")
image.save(output_path)
print(f" Saved to: {output_path}")
# === Demo 3: Switch back to Contents tab ===
print("\n\n--- Demo 3: Switching back to Contents Tab ---")
image = reader.switch_navigation_tab("contents")
if image:
print(f"✓ Switched back to Contents tab")
# Save the rendered overlay for inspection
output_path = Path("/tmp/navigation_overlay_contents_2.png")
image.save(output_path)
print(f" Saved to: {output_path}")
# === Demo 4: Close overlay ===
print("\n\n--- Demo 4: Closing Navigation Overlay ---")
image = reader.close_overlay()
if image:
print(f"✓ Overlay closed successfully")
print(f" Overlay state: {reader.get_overlay_state()}")
print(f" Is overlay open: {reader.is_overlay_open()}")
# === Demo 5: Open with Bookmarks tab directly ===
print("\n\n--- Demo 5: Opening directly to Bookmarks Tab ---")
image = reader.open_navigation_overlay(active_tab="bookmarks")
if image:
print(f"✓ Navigation overlay opened with Bookmarks tab")
# Save the rendered overlay for inspection
output_path = Path("/tmp/navigation_overlay_bookmarks_direct.png")
image.save(output_path)
print(f" Saved to: {output_path}")
# Close overlay
reader.close_overlay()
# === Demo 6: Simulate user interaction flow ===
print("\n\n--- Demo 6: Simulated User Interaction Flow ---")
print("Simulating: User opens overlay, switches tabs, selects bookmark")
# 1. User opens navigation overlay
print("\n 1. User taps navigation button -> Opens overlay with Contents tab")
reader.open_navigation_overlay(active_tab="contents")
print(f" State: {reader.get_overlay_state()}")
# 2. User switches to Bookmarks tab
print("\n 2. User taps 'Bookmarks' tab")
reader.switch_navigation_tab("bookmarks")
print(f" State: {reader.get_overlay_state()}")
# 3. User selects a bookmark
print("\n 3. User taps on bookmark 'Start of Book'")
page = reader.load_position("Start of Book")
if page:
print(f" ✓ Loaded bookmark successfully")
print(f" Position: {reader.get_position_info()}")
# 4. Close overlay
print("\n 4. System closes overlay after selection")
reader.close_overlay()
print(f" State: {reader.get_overlay_state()}")
# === Summary ===
print("\n\n=== Demo Complete ===")
print(f"\nGenerated overlay images in /tmp:")
print(f" - navigation_overlay_contents.png")
print(f" - navigation_overlay_bookmarks.png")
print(f" - navigation_overlay_contents_2.png")
print(f" - navigation_overlay_bookmarks_direct.png")
print("\n✓ Navigation overlay provides unified interface for:")
print(" • Table of Contents (chapter navigation)")
print(" • Bookmarks (saved positions)")
print(" • Tab switching between Contents and Bookmarks")
print(" • Consistent interaction patterns")
# Cleanup
reader.close()
print("\nReader closed.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,196 @@
"""
Tests for the unified navigation overlay (TOC + Bookmarks tabs)
"""
import pytest
from pathlib import Path
from PIL import Image
from dreader.application import EbookReader
from dreader.state import OverlayState
from dreader.gesture import TouchEvent, GestureType, ActionType
@pytest.fixture
def reader_with_book():
"""Create a reader with a test book loaded"""
reader = EbookReader(page_size=(400, 600), margin=10)
# Load a simple test book
test_book = Path(__file__).parent.parent / "examples" / "books" / "hamlet.epub"
if test_book.exists():
reader.load_epub(str(test_book))
else:
# Fallback: create simple HTML for testing
html = """
<html>
<body>
<h1>Chapter 1</h1>
<p>This is chapter 1 content</p>
<h1>Chapter 2</h1>
<p>This is chapter 2 content</p>
</body>
</html>
"""
reader.load_html(html, title="Test Book", author="Test Author", document_id="test")
yield reader
reader.close()
def test_open_navigation_overlay_contents_tab(reader_with_book):
"""Test opening navigation overlay with Contents tab active"""
reader = reader_with_book
# Open navigation overlay with contents tab
image = reader.open_navigation_overlay(active_tab="contents")
assert image is not None
assert isinstance(image, Image.Image)
assert reader.get_overlay_state() == OverlayState.NAVIGATION
assert reader.is_overlay_open()
def test_open_navigation_overlay_bookmarks_tab(reader_with_book):
"""Test opening navigation overlay with Bookmarks tab active"""
reader = reader_with_book
# Save a bookmark first
reader.save_position("Test Bookmark")
# Open navigation overlay with bookmarks tab
image = reader.open_navigation_overlay(active_tab="bookmarks")
assert image is not None
assert isinstance(image, Image.Image)
assert reader.get_overlay_state() == OverlayState.NAVIGATION
def test_switch_navigation_tabs(reader_with_book):
"""Test switching between Contents and Bookmarks tabs"""
reader = reader_with_book
# Open with contents tab
reader.open_navigation_overlay(active_tab="contents")
# Switch to bookmarks
image = reader.switch_navigation_tab("bookmarks")
assert image is not None
assert reader.get_overlay_state() == OverlayState.NAVIGATION
# Switch back to contents
image = reader.switch_navigation_tab("contents")
assert image is not None
assert reader.get_overlay_state() == OverlayState.NAVIGATION
def test_close_navigation_overlay(reader_with_book):
"""Test closing navigation overlay"""
reader = reader_with_book
# Open overlay
reader.open_navigation_overlay()
assert reader.is_overlay_open()
# Close overlay
image = reader.close_overlay()
assert image is not None
assert not reader.is_overlay_open()
assert reader.get_overlay_state() == OverlayState.NONE
def test_navigation_overlay_tab_switching_gesture(reader_with_book):
"""Test tab switching via gesture/touch handling"""
reader = reader_with_book
# Open navigation overlay
reader.open_navigation_overlay(active_tab="contents")
# Query the overlay to find the bookmarks tab button
# This would normally be done by finding the coordinates of the "Bookmarks" tab
# For now, we test that the switch method works
result = reader.switch_navigation_tab("bookmarks")
assert result is not None
assert reader.get_overlay_state() == OverlayState.NAVIGATION
def test_navigation_overlay_with_no_bookmarks(reader_with_book):
"""Test navigation overlay when there are no bookmarks"""
reader = reader_with_book
# Open bookmarks tab (should show "No bookmarks yet")
image = reader.open_navigation_overlay(active_tab="bookmarks")
assert image is not None
# The overlay should still open successfully
assert reader.get_overlay_state() == OverlayState.NAVIGATION
def test_navigation_overlay_preserves_page_position(reader_with_book):
"""Test that opening/closing navigation overlay preserves reading position"""
reader = reader_with_book
# Go to page 2
reader.next_page()
initial_position = reader.get_position_info()
# Open and close navigation overlay
reader.open_navigation_overlay()
reader.close_overlay()
# Verify position hasn't changed
final_position = reader.get_position_info()
assert initial_position == final_position
def test_navigation_overlay_chapter_selection(reader_with_book):
"""Test selecting a chapter from the navigation overlay"""
reader = reader_with_book
# Get chapters
chapters = reader.get_chapters()
if len(chapters) < 2:
pytest.skip("Test book doesn't have enough chapters")
# Open navigation overlay
reader.open_navigation_overlay(active_tab="contents")
# Get initial position
initial_position = reader.get_position_info()
# Jump to chapter via the reader method (simulating a tap on chapter)
reader.jump_to_chapter(chapters[1][1]) # chapters[1] = (title, index)
reader.close_overlay()
# Verify position changed
new_position = reader.get_position_info()
assert new_position != initial_position
def test_navigation_overlay_bookmark_selection(reader_with_book):
"""Test selecting a bookmark from the navigation overlay"""
reader = reader_with_book
# Save a bookmark at page 1
reader.save_position("Bookmark 1")
# Move to a different page
reader.next_page()
position_before = reader.get_position_info()
# Open navigation overlay with bookmarks tab
reader.open_navigation_overlay(active_tab="bookmarks")
# Load the bookmark (simulating a tap on bookmark)
page = reader.load_position("Bookmark 1")
assert page is not None
reader.close_overlay()
# Verify position changed back to bookmark
position_after = reader.get_position_info()
assert position_after != position_before
if __name__ == "__main__":
pytest.main([__file__, "-v"])