feat: 响应式分页引擎 - 基于egui Galley精确测量替代字符计数

This commit is contained in:
2026-05-16 12:11:26 +08:00
parent 4add295bfa
commit af03d18470
4 changed files with 296 additions and 190 deletions

View File

@@ -22,6 +22,7 @@ pub struct AppState {
pub sidebar_open: bool,
pub file_path: Option<PathBuf>,
pub error_message: Option<String>,
pub pending_anchor: Option<theme::ReadingPosition>,
}
impl AppState {
@@ -32,7 +33,7 @@ impl AppState {
} else if self.current_section > 0 {
self.current_section -= 1;
self.current_page = book.sections[self.current_section]
.pages.len().saturating_sub(2);
.page_block_ranges.len().saturating_sub(1);
}
}
}
@@ -40,7 +41,7 @@ impl AppState {
pub fn next_page(&mut self) {
if let Some(ref book) = self.book {
if self.current_page + 1 < book.sections[self.current_section]
.pages.len().saturating_sub(1)
.page_block_ranges.len()
{
self.current_page += 1;
} else if self.current_section + 1 < book.sections.len() {
@@ -64,6 +65,7 @@ impl App {
sidebar_open: false,
file_path: None,
error_message: None,
pending_anchor: None,
},
settings,
settings_dir,
@@ -78,7 +80,7 @@ impl App {
match crate::book::load_epub(&path) {
Ok(book) => {
let path_str = path.to_string_lossy().to_string();
let pos = self.settings.reading_positions.get(&path_str).copied();
let pos = self.settings.reading_positions.get(&path_str).cloned();
let mut recent = Vec::new();
recent.push(path_str.clone());
for f in &self.settings.recent_files {
@@ -91,8 +93,9 @@ impl App {
}
self.settings.recent_files = recent;
self.state.book = Some(book);
self.state.current_section = pos.map(|p| p.section).unwrap_or(0);
self.state.current_page = pos.map(|p| p.page).unwrap_or(0);
self.state.current_section = pos.as_ref().map(|p| p.section).unwrap_or(0);
self.state.current_page = pos.as_ref().map(|p| p.page).unwrap_or(0);
self.state.pending_anchor = pos;
self.state.sidebar_open = false;
self.state.file_path = Some(path);
self.state.error_message = None;
@@ -117,11 +120,27 @@ impl App {
fn save_reading_position(&mut self) {
if let Some(ref path) = self.state.file_path {
let path_str = path.to_string_lossy().to_string();
let (block_index, text_snippet) = if let Some(ref book) = self.state.book {
let section = &book.sections[self.state.current_section];
if let Some(&(start, _end)) = section.page_block_ranges.get(self.state.current_page) {
let first_block = start;
let snippet = section.blocks.get(first_block)
.map(|b| b.text.chars().take(30).collect())
.unwrap_or_default();
(Some(first_block), Some(snippet))
} else {
(None, None)
}
} else {
(None, None)
};
self.settings.reading_positions.insert(
path_str,
theme::ReadingPosition {
section: self.state.current_section,
page: self.state.current_page,
block_index,
text_snippet,
},
);
}
@@ -395,6 +414,24 @@ BgType::Custom(ref path) if !path.is_empty() => {
}
}
if let Some(anchor) = self.state.pending_anchor.take() {
if let Some(ref book) = self.state.book {
if anchor.section < book.sections.len() {
let section = &book.sections[anchor.section];
let restored = if let Some(block_idx) = anchor.block_index {
crate::reader::find_page_for_block(section, block_idx)
} else if let Some(ref snippet) = anchor.text_snippet {
crate::reader::find_page_by_snippet(section, snippet)
} else {
None
};
if let Some(page) = restored {
self.state.current_page = page;
}
}
}
}
// Sync style changes back to active profile (outside closure)
if let Some(p) = self.settings.profiles.iter_mut()
.find(|p| p.name == self.settings.active_profile)