feat: add kraft paper tiled background texture for comfortable reading
This commit is contained in:
@@ -8,6 +8,7 @@ pub struct App {
|
|||||||
pub state: AppState,
|
pub state: AppState,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
settings_dir: std::path::PathBuf,
|
settings_dir: std::path::PathBuf,
|
||||||
|
kraft_texture: Option<egui::TextureHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
@@ -51,6 +52,7 @@ impl App {
|
|||||||
let settings_dir = persistence::settings_dir();
|
let settings_dir = persistence::settings_dir();
|
||||||
let settings = persistence::load_settings(&settings_dir).unwrap_or_default();
|
let settings = persistence::load_settings(&settings_dir).unwrap_or_default();
|
||||||
cc.egui_ctx.set_style(theme::create_style(&settings.theme));
|
cc.egui_ctx.set_style(theme::create_style(&settings.theme));
|
||||||
|
let kraft_texture = Some(crate::texture::generate_kraft_paper(&cc.egui_ctx));
|
||||||
Self {
|
Self {
|
||||||
state: AppState {
|
state: AppState {
|
||||||
book: None,
|
book: None,
|
||||||
@@ -62,6 +64,7 @@ impl App {
|
|||||||
},
|
},
|
||||||
settings,
|
settings,
|
||||||
settings_dir,
|
settings_dir,
|
||||||
|
kraft_texture,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +192,8 @@ impl eframe::App for App {
|
|||||||
&mut self.settings.font_size,
|
&mut self.settings.font_size,
|
||||||
&self.settings.theme,
|
&self.settings.theme,
|
||||||
&file_path,
|
&file_path,
|
||||||
|
self.kraft_texture.as_ref(),
|
||||||
|
self.settings.use_kraft_bg,
|
||||||
);
|
);
|
||||||
|
|
||||||
if action.go_back {
|
if action.go_back {
|
||||||
@@ -198,6 +203,10 @@ impl eframe::App for App {
|
|||||||
self.state.current_page = 0;
|
self.state.current_page = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action.toggle_kraft_bg {
|
||||||
|
self.settings.use_kraft_bg = !self.settings.use_kraft_bg;
|
||||||
|
}
|
||||||
|
|
||||||
if action.toggle_theme {
|
if action.toggle_theme {
|
||||||
self.settings.theme = match self.settings.theme {
|
self.settings.theme = match self.settings.theme {
|
||||||
theme::Theme::Light => theme::Theme::Dark,
|
theme::Theme::Light => theme::Theme::Dark,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ mod book;
|
|||||||
mod font;
|
mod font;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
mod reader;
|
mod reader;
|
||||||
|
mod texture;
|
||||||
mod theme;
|
mod theme;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub struct ReaderAction {
|
|||||||
pub go_back: bool,
|
pub go_back: bool,
|
||||||
pub toggle_theme: bool,
|
pub toggle_theme: bool,
|
||||||
pub toggle_bookmark: bool,
|
pub toggle_bookmark: bool,
|
||||||
|
pub toggle_kraft_bg: bool,
|
||||||
pub page_next: bool,
|
pub page_next: bool,
|
||||||
pub page_prev: bool,
|
pub page_prev: bool,
|
||||||
}
|
}
|
||||||
@@ -38,11 +39,14 @@ pub fn reading_view(
|
|||||||
font_size: &mut f32,
|
font_size: &mut f32,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
_file_path: &str,
|
_file_path: &str,
|
||||||
|
kraft_texture: Option<&egui::TextureHandle>,
|
||||||
|
use_kraft_bg: bool,
|
||||||
) -> ReaderAction {
|
) -> ReaderAction {
|
||||||
let mut action = ReaderAction {
|
let mut action = ReaderAction {
|
||||||
go_back: false,
|
go_back: false,
|
||||||
toggle_theme: false,
|
toggle_theme: false,
|
||||||
toggle_bookmark: false,
|
toggle_bookmark: false,
|
||||||
|
toggle_kraft_bg: false,
|
||||||
page_next: false,
|
page_next: false,
|
||||||
page_prev: false,
|
page_prev: false,
|
||||||
};
|
};
|
||||||
@@ -91,6 +95,10 @@ pub fn reading_view(
|
|||||||
if ui.button("A⁺").on_hover_text("放大字体").clicked() {
|
if ui.button("A⁺").on_hover_text("放大字体").clicked() {
|
||||||
*font_size = (*font_size + 2.0).min(48.0);
|
*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() {
|
if ui.button("☰").on_hover_text("打开/关闭目录").clicked() {
|
||||||
*sidebar_open = !*sidebar_open;
|
*sidebar_open = !*sidebar_open;
|
||||||
}
|
}
|
||||||
@@ -163,6 +171,13 @@ pub fn reading_view(
|
|||||||
let available = ui.available_size();
|
let available = ui.available_size();
|
||||||
let (rect, response) = ui.allocate_at_least(available, egui::Sense::click());
|
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)
|
// Add reading margins (inset)
|
||||||
let inset = 24.0;
|
let inset = 24.0;
|
||||||
let text_rect = egui::Rect::from_min_size(
|
let text_rect = egui::Rect::from_min_size(
|
||||||
|
|||||||
66
src/texture.rs
Normal file
66
src/texture.rs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -170,6 +170,7 @@ pub fn create_style(theme: &Theme) -> Style {
|
|||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub font_size: f32,
|
pub font_size: f32,
|
||||||
pub theme: Theme,
|
pub theme: Theme,
|
||||||
|
pub use_kraft_bg: bool,
|
||||||
pub recent_files: Vec<String>,
|
pub recent_files: Vec<String>,
|
||||||
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
|
pub reading_positions: std::collections::HashMap<String, ReadingPosition>,
|
||||||
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
|
pub bookmarks: std::collections::HashMap<String, Vec<Bookmark>>,
|
||||||
@@ -181,6 +182,7 @@ impl Default for Settings {
|
|||||||
Self {
|
Self {
|
||||||
font_size: 20.0,
|
font_size: 20.0,
|
||||||
theme: Theme::Light,
|
theme: Theme::Light,
|
||||||
|
use_kraft_bg: false,
|
||||||
recent_files: Vec::new(),
|
recent_files: Vec::new(),
|
||||||
reading_positions: std::collections::HashMap::new(),
|
reading_positions: std::collections::HashMap::new(),
|
||||||
bookmarks: std::collections::HashMap::new(),
|
bookmarks: std::collections::HashMap::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user