Implement Welcome screen + App skeleton with state management, file opening, and settings persistence
This commit is contained in:
145
src/app.rs
145
src/app.rs
@@ -1,11 +1,148 @@
|
||||
pub struct App;
|
||||
use crate::book::Book;
|
||||
use crate::persistence;
|
||||
use crate::theme::{self, Settings};
|
||||
use eframe::egui;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub struct App {
|
||||
pub state: AppState,
|
||||
settings: Settings,
|
||||
settings_dir: std::path::PathBuf,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub book: Option<Book>,
|
||||
pub current_section: usize,
|
||||
pub current_page: usize,
|
||||
pub sidebar_open: bool,
|
||||
pub file_path: Option<PathBuf>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(_cc: &eframe::CreationContext) -> Self {
|
||||
Self
|
||||
pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
|
||||
let settings_dir = persistence::settings_dir();
|
||||
let settings = persistence::load_settings(&settings_dir).unwrap_or_default();
|
||||
cc.egui_ctx.set_style(theme::create_style(&settings.theme));
|
||||
Self {
|
||||
state: AppState {
|
||||
book: None,
|
||||
current_section: 0,
|
||||
current_page: 0,
|
||||
sidebar_open: false,
|
||||
file_path: None,
|
||||
error_message: None,
|
||||
},
|
||||
settings,
|
||||
settings_dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_file(&mut self, path: PathBuf) {
|
||||
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 mut recent = Vec::new();
|
||||
recent.push(path_str.clone());
|
||||
for f in &self.settings.recent_files {
|
||||
if *f != path_str {
|
||||
recent.push(f.clone());
|
||||
if recent.len() >= 10 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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.sidebar_open = false;
|
||||
self.state.file_path = Some(path);
|
||||
self.state.error_message = None;
|
||||
self.save_settings();
|
||||
}
|
||||
Err(e) => {
|
||||
self.state.error_message = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_settings(&self) {
|
||||
let _ = persistence::save_settings(&self.settings_dir, &self.settings);
|
||||
}
|
||||
|
||||
fn save_reading_position(&mut self) {
|
||||
if let Some(ref path) = self.state.file_path {
|
||||
let path_str = path.to_string_lossy().to_string();
|
||||
self.settings.reading_positions.insert(
|
||||
path_str,
|
||||
theme::ReadingPosition {
|
||||
section: self.state.current_section,
|
||||
page: self.state.current_page,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn welcome_view(&mut self, ctx: &egui::Context) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add_space(150.0);
|
||||
ui.heading("ePub Reader");
|
||||
ui.add_space(20.0);
|
||||
if ui
|
||||
.add(egui::Button::new("📂 打开 ePub 文件").min_size(egui::vec2(200.0, 40.0)))
|
||||
.clicked()
|
||||
{
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.add_filter("ePub", &["epub"])
|
||||
.pick_file()
|
||||
{
|
||||
self.open_file(path);
|
||||
}
|
||||
}
|
||||
ui.add_space(30.0);
|
||||
if !self.settings.recent_files.is_empty() {
|
||||
ui.label("最近阅读:");
|
||||
ui.separator();
|
||||
let mut to_open: Option<PathBuf> = None;
|
||||
let mut to_remove: Option<usize> = None;
|
||||
for (i, path) in self.settings.recent_files.iter().enumerate() {
|
||||
let name = std::path::Path::new(path)
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| path.clone());
|
||||
if ui.button(&name).clicked() {
|
||||
let p = std::path::PathBuf::from(path);
|
||||
if p.exists() {
|
||||
to_open = Some(p);
|
||||
} else {
|
||||
to_remove = Some(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(path) = to_open {
|
||||
self.open_file(path);
|
||||
}
|
||||
if let Some(i) = to_remove {
|
||||
self.settings.recent_files.remove(i);
|
||||
self.save_settings();
|
||||
}
|
||||
}
|
||||
if let Some(ref msg) = self.state.error_message {
|
||||
ui.add_space(20.0);
|
||||
ui.colored_label(egui::Color32::RED, msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
fn update(&mut self, _ctx: &eframe::egui::Context, _frame: &mut eframe::Frame) {}
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if self.state.book.is_none() {
|
||||
self.welcome_view(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user