diff --git a/src/app.rs b/src/app.rs index 72f705c..21a8ca4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ pub struct App { pub state: AppState, settings: Settings, settings_dir: std::path::PathBuf, + kraft_texture: Option, } pub struct AppState { @@ -51,6 +52,7 @@ impl App { 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)); + let kraft_texture = Some(crate::texture::generate_kraft_paper(&cc.egui_ctx)); Self { state: AppState { book: None, @@ -62,6 +64,7 @@ impl App { }, settings, settings_dir, + kraft_texture, } } @@ -189,6 +192,8 @@ impl eframe::App for App { &mut self.settings.font_size, &self.settings.theme, &file_path, + self.kraft_texture.as_ref(), + self.settings.use_kraft_bg, ); if action.go_back { @@ -198,6 +203,10 @@ impl eframe::App for App { self.state.current_page = 0; } + if action.toggle_kraft_bg { + self.settings.use_kraft_bg = !self.settings.use_kraft_bg; + } + if action.toggle_theme { self.settings.theme = match self.settings.theme { theme::Theme::Light => theme::Theme::Dark, diff --git a/src/main.rs b/src/main.rs index bc4179a..3bc425c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod book; mod font; mod persistence; mod reader; +mod texture; mod theme; fn main() -> eframe::Result { diff --git a/src/reader.rs b/src/reader.rs index 64bd57d..2e9de30 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -25,6 +25,7 @@ pub struct ReaderAction { pub go_back: bool, pub toggle_theme: bool, pub toggle_bookmark: bool, + pub toggle_kraft_bg: bool, pub page_next: bool, pub page_prev: bool, } @@ -38,11 +39,14 @@ pub fn reading_view( font_size: &mut f32, theme: &Theme, _file_path: &str, + kraft_texture: Option<&egui::TextureHandle>, + use_kraft_bg: bool, ) -> ReaderAction { let mut action = ReaderAction { go_back: false, toggle_theme: false, toggle_bookmark: false, + toggle_kraft_bg: false, page_next: false, page_prev: false, }; @@ -91,6 +95,10 @@ pub fn reading_view( if ui.button("A⁺").on_hover_text("放大字体").clicked() { *font_size = (*font_size + 2.0).min(48.0); } + let kraft_icon = if use_kraft_bg { "📄" } else { "📋" }; + if ui.button(kraft_icon).on_hover_text("牛皮纸背景").clicked() { + action.toggle_kraft_bg = true; + } if ui.button("☰").on_hover_text("打开/关闭目录").clicked() { *sidebar_open = !*sidebar_open; } @@ -163,6 +171,13 @@ pub fn reading_view( let available = ui.available_size(); let (rect, response) = ui.allocate_at_least(available, egui::Sense::click()); + // Draw kraft paper background if enabled + if use_kraft_bg { + if let Some(tex) = kraft_texture { + crate::texture::draw_tiled_bg(ui.painter(), rect, tex); + } + } + // Add reading margins (inset) let inset = 24.0; let text_rect = egui::Rect::from_min_size( diff --git a/src/texture.rs b/src/texture.rs new file mode 100644 index 0000000..59f0ab8 --- /dev/null +++ b/src/texture.rs @@ -0,0 +1,66 @@ +use eframe::egui; + +fn hash(x: u32, y: u32) -> u32 { + let mut h = x.wrapping_mul(374761393) ^ y.wrapping_mul(668265263); + h = h.wrapping_mul(h ^ (h >> 13)); + h ^ (h >> 15) +} + +fn paper_noise(x: u32, y: u32) -> u8 { + // Combine multiple hash values for more natural-looking noise + let n1 = hash(x, y); + let n2 = hash(x.wrapping_add(137), y.wrapping_add(251)); + let n3 = hash(x.wrapping_mul(7), y.wrapping_mul(13)); + ((n1 ^ n2.wrapping_add(n3)) % 18) as u8 +} + +pub fn generate_kraft_paper(ctx: &egui::Context) -> egui::TextureHandle { + let size = 128usize; + let mut pixels = Vec::with_capacity(size * size); + + for y in 0..size { + for x in 0..size { + let n = paper_noise(x as u32, y as u32); + // Warm brown-beige base with slight variation + let r = 212u8.wrapping_add(n); + let g = 194u8.wrapping_add(n); + let b = 168u8.wrapping_add(n); + pixels.push(egui::Color32::from_rgb(r, g, b)); + } + } + + let image = egui::ColorImage { + size: [size, size], + pixels, + }; + ctx.load_texture("kraft_paper", image, Default::default()) +} + +pub fn draw_tiled_bg( + painter: &egui::Painter, + rect: egui::Rect, + texture: &egui::TextureHandle, +) { + let tile_size = 128.0f32; + let mut ty = rect.min.y; + while ty < rect.max.y { + let mut tx = rect.min.x; + while tx < rect.max.x { + let tile_rect = egui::Rect::from_min_max( + egui::pos2(tx, ty), + egui::pos2( + (tx + tile_size).min(rect.max.x), + (ty + tile_size).min(rect.max.y), + ), + ); + painter.image( + texture.id(), + tile_rect, + egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), + egui::Color32::WHITE, + ); + tx += tile_size; + } + ty += tile_size; + } +} diff --git a/src/theme.rs b/src/theme.rs index 28241b0..4c44a7f 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -170,6 +170,7 @@ pub fn create_style(theme: &Theme) -> Style { pub struct Settings { pub font_size: f32, pub theme: Theme, + pub use_kraft_bg: bool, pub recent_files: Vec, pub reading_positions: std::collections::HashMap, pub bookmarks: std::collections::HashMap>, @@ -181,6 +182,7 @@ impl Default for Settings { Self { font_size: 20.0, theme: Theme::Light, + use_kraft_bg: false, recent_files: Vec::new(), reading_positions: std::collections::HashMap::new(), bookmarks: std::collections::HashMap::new(),