功能更新: 目录点击跳转、翻页按钮移至底部右侧、所有按钮添加悬停提示
This commit is contained in:
120
src/reader.rs
120
src/reader.rs
@@ -1,6 +1,6 @@
|
||||
use eframe::egui;
|
||||
use crate::book::Book;
|
||||
use crate::theme::Theme;
|
||||
use crate::theme::{self, Theme};
|
||||
|
||||
pub fn recalculate_pages(book: &mut Book, font_size: f32, panel_width: f32, panel_height: f32) {
|
||||
let char_width = font_size * 0.6;
|
||||
@@ -25,6 +25,8 @@ pub struct ReaderAction {
|
||||
pub go_back: bool,
|
||||
pub toggle_theme: bool,
|
||||
pub toggle_bookmark: bool,
|
||||
pub page_next: bool,
|
||||
pub page_prev: bool,
|
||||
}
|
||||
|
||||
pub fn reading_view(
|
||||
@@ -41,11 +43,15 @@ pub fn reading_view(
|
||||
go_back: false,
|
||||
toggle_theme: false,
|
||||
toggle_bookmark: false,
|
||||
page_next: false,
|
||||
page_prev: false,
|
||||
};
|
||||
|
||||
let panel_size = ui.available_size();
|
||||
recalculate_pages(book, *font_size, panel_size.x, panel_size.y);
|
||||
|
||||
let colors = theme::reader_colors(theme);
|
||||
|
||||
// --- Sidebar (TOC) ---
|
||||
if *sidebar_open {
|
||||
egui::SidePanel::left("toc_sidebar")
|
||||
@@ -54,7 +60,7 @@ pub fn reading_view(
|
||||
.show_inside(ui, |ui| {
|
||||
ui.heading("目录");
|
||||
ui.separator();
|
||||
render_toc(ui, &book.toc, &book.sections, current_section);
|
||||
render_toc(ui, &book.toc, current_section, current_page);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,29 +68,30 @@ pub fn reading_view(
|
||||
egui::TopBottomPanel::top("reader_toolbar")
|
||||
.show_inside(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("← 返回").clicked() {
|
||||
if ui.button("← 返回").on_hover_text("返回书架").clicked() {
|
||||
action.go_back = true;
|
||||
}
|
||||
ui.separator();
|
||||
ui.label(&book.title);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
let theme_icon = match theme {
|
||||
Theme::Dark => "☀️",
|
||||
Theme::Light => "🌙",
|
||||
let (theme_icon, theme_hint) = match theme {
|
||||
Theme::Dark => ("🌞", "切换到浅色主题"),
|
||||
Theme::Light => ("🌙", "切换到夜间主题"),
|
||||
Theme::Sepia => ("📜", "切换到棕褐色主题"),
|
||||
};
|
||||
if ui.button(theme_icon).clicked() {
|
||||
if ui.button(theme_icon).on_hover_text(theme_hint).clicked() {
|
||||
action.toggle_theme = true;
|
||||
}
|
||||
if ui.button("🔖").clicked() {
|
||||
if ui.button("🔖").on_hover_text("添加/移除书签").clicked() {
|
||||
action.toggle_bookmark = true;
|
||||
}
|
||||
if ui.button("A⁻").clicked() {
|
||||
if ui.button("A⁻").on_hover_text("缩小字体").clicked() {
|
||||
*font_size = (*font_size - 2.0).max(10.0);
|
||||
}
|
||||
if ui.button("A⁺").clicked() {
|
||||
if ui.button("A⁺").on_hover_text("放大字体").clicked() {
|
||||
*font_size = (*font_size + 2.0).min(48.0);
|
||||
}
|
||||
if ui.button("☰").clicked() {
|
||||
if ui.button("☰").on_hover_text("打开/关闭目录").clicked() {
|
||||
*sidebar_open = !*sidebar_open;
|
||||
}
|
||||
});
|
||||
@@ -105,6 +112,36 @@ pub fn reading_view(
|
||||
.unwrap_or("");
|
||||
ui.label(section_title);
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
// Page turn buttons (right side of bottom bar)
|
||||
let prev_enabled = *current_page > 0 || *current_section > 0;
|
||||
let next_enabled = (*current_page + 1 < book.sections[*current_section]
|
||||
.pages.len().saturating_sub(1))
|
||||
|| (*current_section + 1 < book.sections.len());
|
||||
|
||||
let next_btn = egui::Button::new(
|
||||
egui::RichText::new("下一页 ▶").size(13.0)
|
||||
).min_size(egui::vec2(100.0, 28.0));
|
||||
|
||||
let prev_btn = egui::Button::new(
|
||||
egui::RichText::new("◀ 上一页").size(13.0)
|
||||
).min_size(egui::vec2(100.0, 28.0));
|
||||
|
||||
if !next_enabled {
|
||||
ui.add_enabled(false, next_btn);
|
||||
} else if ui.add(next_btn).on_hover_text("下一页 (→键)").clicked() {
|
||||
action.page_next = true;
|
||||
}
|
||||
|
||||
ui.add_space(8.0);
|
||||
|
||||
if !prev_enabled {
|
||||
ui.add_enabled(false, prev_btn);
|
||||
} else if ui.add(prev_btn).on_hover_text("上一页 (←键)").clicked() {
|
||||
action.page_prev = true;
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
let label = if total_pages > 0 {
|
||||
format!("{}/{}", *current_page + 1, total_pages)
|
||||
} else {
|
||||
@@ -131,13 +168,11 @@ pub fn reading_view(
|
||||
let end = section.pages[*current_page + 1];
|
||||
let text: String = section.content.chars().skip(start).take(end - start).collect();
|
||||
ui.put(rect, |ui: &mut egui::Ui| {
|
||||
let color = match theme {
|
||||
Theme::Dark => egui::Color32::WHITE,
|
||||
Theme::Light => egui::Color32::BLACK,
|
||||
};
|
||||
ui.add(
|
||||
egui::Label::new(
|
||||
egui::RichText::new(&text).size(*font_size).color(color)
|
||||
egui::RichText::new(&text)
|
||||
.size(*font_size)
|
||||
.color(colors.text)
|
||||
).wrap()
|
||||
)
|
||||
});
|
||||
@@ -149,45 +184,19 @@ pub fn reading_view(
|
||||
if let Some(click_pos) = response.interact_pointer_pos() {
|
||||
let x_ratio = (click_pos.x - rect.min.x) / rect.width();
|
||||
if x_ratio < 0.3 {
|
||||
if *current_page > 0 {
|
||||
*current_page -= 1;
|
||||
} else if *current_section > 0 {
|
||||
*current_section -= 1;
|
||||
*current_page = book.sections[*current_section]
|
||||
.pages.len().saturating_sub(2);
|
||||
}
|
||||
action.page_prev = true;
|
||||
} else if x_ratio > 0.7 {
|
||||
if *current_page + 1 < book.sections[*current_section]
|
||||
.pages.len().saturating_sub(1)
|
||||
{
|
||||
*current_page += 1;
|
||||
} else if *current_section + 1 < book.sections.len() {
|
||||
*current_section += 1;
|
||||
*current_page = 0;
|
||||
}
|
||||
action.page_next = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard navigation
|
||||
if ui.input(|i| i.key_pressed(egui::Key::ArrowRight)) {
|
||||
if *current_page + 1 < book.sections[*current_section]
|
||||
.pages.len().saturating_sub(1)
|
||||
{
|
||||
*current_page += 1;
|
||||
} else if *current_section + 1 < book.sections.len() {
|
||||
*current_section += 1;
|
||||
*current_page = 0;
|
||||
}
|
||||
action.page_next = true;
|
||||
}
|
||||
if ui.input(|i| i.key_pressed(egui::Key::ArrowLeft)) {
|
||||
if *current_page > 0 {
|
||||
*current_page -= 1;
|
||||
} else if *current_section > 0 {
|
||||
*current_section -= 1;
|
||||
*current_page = book.sections[*current_section]
|
||||
.pages.len().saturating_sub(2);
|
||||
}
|
||||
action.page_prev = true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,22 +206,29 @@ pub fn reading_view(
|
||||
fn render_toc(
|
||||
ui: &mut egui::Ui,
|
||||
entries: &[crate::book::TocEntry],
|
||||
_sections: &[crate::book::Section],
|
||||
current_section: &mut usize,
|
||||
current_page: &mut usize,
|
||||
) {
|
||||
for entry in entries {
|
||||
let is_current = entry.section == *current_section;
|
||||
let response = if is_current {
|
||||
ui.colored_label(egui::Color32::YELLOW, &entry.label)
|
||||
let label_text = if is_current {
|
||||
egui::RichText::new(&entry.label).color(egui::Color32::YELLOW).strong()
|
||||
} else {
|
||||
ui.label(&entry.label)
|
||||
egui::RichText::new(&entry.label)
|
||||
};
|
||||
let response = ui.add(
|
||||
egui::Button::new(label_text)
|
||||
.frame(false)
|
||||
.wrap()
|
||||
);
|
||||
if response.clicked() {
|
||||
*current_section = entry.section;
|
||||
*current_page = 0;
|
||||
}
|
||||
response.on_hover_text(format!("跳转到: {}", entry.label));
|
||||
if !entry.children.is_empty() {
|
||||
ui.indent(&entry.label, |ui| {
|
||||
render_toc(ui, &entry.children, _sections, current_section);
|
||||
render_toc(ui, &entry.children, current_section, current_page);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -281,4 +297,4 @@ mod tests {
|
||||
let pages = calculate_pages("test", 0);
|
||||
assert_eq!(pages, vec![0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user