A fork of pulp-os for the xteink4 adding custom apps
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

consolidate magic nums, or atleast attempt to tame

hansmrtn 0f756d92 3b0fae9e

+342 -106
+9 -13
kernel/src/drivers/input.rs
··· 1 1 // debounced input from ADC ladders and power button 2 2 // one button at a time (ladder hw limitation) 3 - // 15 ms debounce, 1 s long press, 150 ms repeat 4 - // ADC reads oversampled 4x to reject noise (~40 us per channel) 3 + // ADC reads oversampled to reject noise (~40 us per channel) 5 4 6 5 use esp_hal::time::{Duration, Instant}; 7 6 7 + use crate::board::button::{decode_ladder, Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS}; 8 8 use crate::board::InputHw; 9 - use crate::board::button::{Button, ROW1_THRESHOLDS, ROW2_THRESHOLDS, decode_ladder}; 9 + use crate::kernel::timing; 10 10 11 11 macro_rules! read_averaged { 12 12 ($adc:expr, $pin:expr) => {{ 13 13 let mut sum: u32 = 0; 14 - for _ in 0..ADC_OVERSAMPLE { 14 + for _ in 0..timing::ADC_OVERSAMPLE { 15 15 sum += nb::block!($adc.read_oneshot($pin)).unwrap() as u32; 16 16 } 17 - (sum / ADC_OVERSAMPLE) as u16 17 + (sum / timing::ADC_OVERSAMPLE) as u16 18 18 }}; 19 19 } 20 - 21 - const DEBOUNCE_MS: u64 = 15; 22 - const LONG_PRESS_MS: u64 = 1000; 23 - const REPEAT_MS: u64 = 150; 24 - const ADC_OVERSAMPLE: u32 = 4; 25 20 26 21 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 27 22 pub enum Event { ··· 115 110 self.candidate_since = now; 116 111 } 117 112 118 - let debounced = if now - self.candidate_since >= Duration::from_millis(DEBOUNCE_MS) { 113 + let debounced = if now - self.candidate_since >= Duration::from_millis(timing::DEBOUNCE_MS) 114 + { 119 115 self.candidate 120 116 } else { 121 117 self.stable ··· 140 136 if !self.hold_consumed { 141 137 let held = now - self.press_since; 142 138 143 - if !self.long_press_fired && held >= Duration::from_millis(LONG_PRESS_MS) { 139 + if !self.long_press_fired && held >= Duration::from_millis(timing::LONG_PRESS_MS) { 144 140 self.long_press_fired = true; 145 141 self.last_repeat = now; 146 142 return Some(Event::LongPress(btn)); 147 143 } 148 144 149 145 if self.long_press_fired 150 - && (now - self.last_repeat) >= Duration::from_millis(REPEAT_MS) 146 + && (now - self.last_repeat) >= Duration::from_millis(timing::REPEAT_MS) 151 147 { 152 148 self.last_repeat = now; 153 149 return Some(Event::Repeat(btn));
+4 -2
kernel/src/kernel/app.rs
··· 186 186 self.coalesce_until = None; 187 187 } 188 188 189 - // mark dirty with 50ms coalescing window; use only for background 189 + // mark dirty with coalescing window; use only for background 190 190 // batch updates (title scanner) where many rapid dirty marks 191 191 // should coalesce into a single refresh 192 192 #[inline] 193 193 pub fn mark_dirty_coalesced(&mut self, region: Region) { 194 + use super::timing; 194 195 self.request_partial_redraw(region); 195 196 if !self.immediate && self.coalesce_until.is_none() { 196 - self.coalesce_until = Some(Instant::now() + embassy_time::Duration::from_millis(50)); 197 + self.coalesce_until = 198 + Some(Instant::now() + embassy_time::Duration::from_millis(timing::COALESCE_WINDOW_MS)); 197 199 } 198 200 } 199 201
+33 -7
kernel/src/kernel/config.rs
··· 5 5 6 6 pub const SETTINGS_FILE: &str = "SETTINGS.TXT"; 7 7 8 + // default sleep timeout in minutes 9 + pub const DEFAULT_SLEEP_TIMEOUT: u16 = 10; 10 + 11 + // maximum sleep timeout in minutes 12 + pub const MAX_SLEEP_TIMEOUT: u16 = 120; 13 + 14 + // increment step for sleep timeout adjustment 15 + pub const SLEEP_TIMEOUT_STEP: u16 = 5; 16 + 17 + // default ghost clear interval 18 + pub const DEFAULT_GHOST_CLEAR: u8 = 10; 19 + 20 + // minimum ghost clear interval 21 + pub const MIN_GHOST_CLEAR: u8 = 5; 22 + 23 + // maximum ghost clear interval 24 + pub const MAX_GHOST_CLEAR: u8 = 100; 25 + 26 + // increment step for ghost clear adjustment 27 + pub const GHOST_CLEAR_STEP: u8 = 5; 28 + 29 + // default font size index (0=XSmall, 1=Small, 2=Medium, 3=Large, 4=XLarge) 30 + pub const DEFAULT_FONT_SIZE_IDX: u8 = 2; 31 + 8 32 #[derive(Clone, Copy)] 9 33 pub struct SystemSettings { 10 34 pub sleep_timeout: u16, // minutes idle before sleep; 0 = never ··· 22 46 impl SystemSettings { 23 47 pub const fn defaults() -> Self { 24 48 Self { 25 - sleep_timeout: 10, 26 - ghost_clear_every: 10, 27 - book_font_size_idx: 2, 28 - ui_font_size_idx: 2, 49 + sleep_timeout: DEFAULT_SLEEP_TIMEOUT, 50 + ghost_clear_every: DEFAULT_GHOST_CLEAR, 51 + book_font_size_idx: DEFAULT_FONT_SIZE_IDX, 52 + ui_font_size_idx: DEFAULT_FONT_SIZE_IDX, 29 53 } 30 54 } 31 55 ··· 34 58 } 35 59 36 60 pub fn sanitize_with_max_font(&mut self, max_font: u8) { 37 - self.sleep_timeout = self.sleep_timeout.min(120); 38 - self.ghost_clear_every = self.ghost_clear_every.clamp(5, 100); 61 + self.sleep_timeout = self.sleep_timeout.min(MAX_SLEEP_TIMEOUT); 62 + self.ghost_clear_every = self 63 + .ghost_clear_every 64 + .clamp(MIN_GHOST_CLEAR, MAX_GHOST_CLEAR); 39 65 self.book_font_size_idx = self.book_font_size_idx.min(max_font); 40 66 self.ui_font_size_idx = self.ui_font_size_idx.min(max_font); 41 67 } 42 68 43 - // reasonable default; distros override via sanitize_with_max_font 69 + // reasonable default - override via sanitize_with_max_font 44 70 const DEFAULT_MAX_FONT_IDX: u8 = 4; 45 71 } 46 72
+1
kernel/src/kernel/mod.rs
··· 14 14 pub mod handle; 15 15 pub mod scheduler; 16 16 pub mod tasks; 17 + pub mod timing; 17 18 pub mod wake; 18 19 pub mod work_queue; 19 20
+3 -3
kernel/src/kernel/scheduler.rs
··· 27 27 28 28 use crate::ui::{free_stack_bytes, stack_high_water_mark}; 29 29 30 - const TICK_MS: u64 = 10; 30 + use super::timing; 31 31 32 32 impl super::Kernel { 33 33 // render boot console to EPD; call before boot() to show ··· 71 71 // 2. EPD busy pin wait inside render() 72 72 // everything between them is synchronous function calls 73 73 pub async fn run<A: AppLayer>(&mut self, app_mgr: &mut A) -> ! { 74 - let mut work_ticker = Ticker::every(Duration::from_millis(TICK_MS)); 74 + let mut work_ticker = Ticker::every(Duration::from_millis(timing::TICK_MS)); 75 75 76 76 loop { 77 77 if app_mgr.needs_special_mode() { ··· 349 349 match select( 350 350 app_mgr.run_background(&mut handle), 351 351 with_timeout( 352 - Duration::from_millis(TICK_MS), 352 + Duration::from_millis(timing::TICK_MS), 353 353 tasks::INPUT_EVENTS.receive(), 354 354 ), 355 355 )
+9 -9
kernel/src/kernel/tasks.rs
··· 8 8 use crate::drivers::battery; 9 9 use crate::drivers::input::{Event, InputDriver}; 10 10 11 + use super::timing; 12 + 11 13 pub const INPUT_CHANNEL_CAP: usize = 8; 12 14 pub static INPUT_EVENTS: Channel<CriticalSectionRawMutex, Event, INPUT_CHANNEL_CAP> = 13 15 Channel::new(); ··· 22 24 23 25 pub static BATTERY_MV: Signal<CriticalSectionRawMutex, u16> = Signal::new(); 24 26 25 - const BATTERY_INTERVAL_TICKS: u32 = 3000; // 3000 x 10 ms = 30 s 26 - 27 27 #[embassy_executor::task] 28 28 pub async fn input_task(mut input: InputDriver) -> ! { 29 - let mut ticker = Ticker::every(Duration::from_millis(10)); 29 + let mut ticker = Ticker::every(Duration::from_millis(timing::INPUT_TICK_MS)); 30 30 let mut battery_counter: u32 = 0; 31 31 32 32 let raw = input.read_battery_mv(); ··· 45 45 } 46 46 47 47 battery_counter += 1; 48 - if battery_counter >= BATTERY_INTERVAL_TICKS { 48 + if battery_counter >= timing::BATTERY_INTERVAL_TICKS { 49 49 battery_counter = 0; 50 50 let raw = input.read_battery_mv(); 51 51 BATTERY_MV.signal(battery::adc_to_battery_mv(raw)); ··· 59 59 60 60 #[embassy_executor::task] 61 61 pub async fn housekeeping_task() -> ! { 62 - Timer::after(Duration::from_secs(5)).await; 62 + Timer::after(Duration::from_secs(timing::HOUSEKEEPING_INITIAL_DELAY_SECS)).await; 63 63 64 - let mut status_ticker = Ticker::every(Duration::from_secs(5)); 65 - let mut sd_ticker = Ticker::every(Duration::from_secs(30)); 64 + let mut status_ticker = Ticker::every(Duration::from_secs(timing::STATUS_INTERVAL_SECS)); 65 + let mut sd_ticker = Ticker::every(Duration::from_secs(timing::SD_CHECK_INTERVAL_SECS)); 66 66 67 - Timer::after(Duration::from_secs(2)).await; // stagger behind SD 68 - let mut bm_ticker = Ticker::every(Duration::from_secs(30)); 67 + Timer::after(Duration::from_secs(timing::BOOKMARK_FLUSH_STAGGER_SECS)).await; 68 + let mut bm_ticker = Ticker::every(Duration::from_secs(timing::BOOKMARK_FLUSH_INTERVAL_SECS)); 69 69 70 70 loop { 71 71 use embassy_futures::select::{Either3, select3};
+53
kernel/src/kernel/timing.rs
··· 1 + // timing constants for the kernel scheduler and tasks 2 + // 3 + // timing values that control polling intervals, debouncing, 4 + // coalescing, and housekeeping. some of these may 5 + // become runtime-configurable settings in the future, idk 6 + 7 + // main scheduler tick interval (ms) 8 + // controls how often the event loop wakes to check for work 9 + pub const TICK_MS: u64 = 10; 10 + 11 + // input task poll interval (ms) 12 + // ADC button sampling rate; should probably(?) match or equal TICK_MS 13 + pub const INPUT_TICK_MS: u64 = 10; 14 + 15 + // button debounce window (ms) 16 + // raw input must be stable for this duration before registering 17 + pub const DEBOUNCE_MS: u64 = 15; 18 + 19 + // long-press detection threshold (ms) 20 + // holding a button for this duration generates a LongPress event 21 + pub const LONG_PRESS_MS: u64 = 1000; 22 + 23 + // key repeat interval (ms) 24 + // after long-press, generates Repeat events at this rate 25 + pub const REPEAT_MS: u64 = 150; 26 + 27 + // ADC oversampling count 28 + pub const ADC_OVERSAMPLE: u32 = 4; 29 + 30 + // status log interval (s) 31 + pub const STATUS_INTERVAL_SECS: u64 = 5; 32 + 33 + // SD card presence check interval (s) 34 + pub const SD_CHECK_INTERVAL_SECS: u64 = 30; 35 + 36 + // bookmark flush interval (seconds) 37 + pub const BOOKMARK_FLUSH_INTERVAL_SECS: u64 = 30; 38 + 39 + // dookmark flush stagger delay (seconds) 40 + // dffset from SD check to avoid simultaneous SD operations 41 + pub const BOOKMARK_FLUSH_STAGGER_SECS: u64 = 2; 42 + 43 + // initial housekeeping delay (seconds) 44 + // delay before first housekeeping cycle to let boot settle 45 + pub const HOUSEKEEPING_INITIAL_DELAY_SECS: u64 = 5; 46 + 47 + // coalesce window for batch dirty marks (ms) 48 + // batch multiple rapid dirty marks into a single refresh 49 + pub const COALESCE_WINDOW_MS: u64 = 50; 50 + 51 + // battery read interval in input task ticks 52 + // ticks * 10 ms = seconds between battery reads 53 + pub const BATTERY_INTERVAL_TICKS: u32 = 3000;
+93
kernel/src/ui/layout.rs
··· 1 + // common layout constants for UI rendering 2 + // 3 + // centralizes magic layout values used across apps and widgets. 4 + // some of these may become runtime-configurable settings in the 5 + // future (e.g., margin sizes based on font size preferences). 6 + 7 + use super::statusbar::BAR_HEIGHT; 8 + use crate::board::{SCREEN_H, SCREEN_W}; 9 + 10 + // ── Content area ──────────────────────────────────────────────── 11 + 12 + /// Top of the content area (below status bar). 13 + pub const CONTENT_TOP: u16 = BAR_HEIGHT; 14 + 15 + /// Height of the content area (screen minus status bar). 16 + pub const CONTENT_H: u16 = SCREEN_H - BAR_HEIGHT; 17 + 18 + // ── Standard spacing ──────────────────────────────────────────── 19 + 20 + /// Standard margin for content edges (left/right). 21 + pub const STANDARD_MARGIN: u16 = 8; 22 + 23 + /// Large margin for content edges (used in some UIs). 24 + pub const LARGE_MARGIN: u16 = 16; 25 + 26 + /// Standard gap between items. 27 + pub const STANDARD_GAP: u16 = 4; 28 + 29 + /// Larger gap between sections or after headers. 30 + pub const SECTION_GAP: u16 = 8; 31 + 32 + // ── Title/header layout ───────────────────────────────────────── 33 + 34 + /// Y offset for titles below CONTENT_TOP. 35 + pub const TITLE_Y_OFFSET: u16 = 4; 36 + 37 + /// Standard title Y position. 38 + pub const TITLE_Y: u16 = CONTENT_TOP + TITLE_Y_OFFSET; 39 + 40 + /// Full-width for content spanning most of the screen. 41 + /// Used for headers and wide content areas. 42 + pub const FULL_CONTENT_W: u16 = SCREEN_W - 2 * LARGE_MARGIN; // 448 43 + 44 + /// Width for header/title regions (leaves room for status on right). 45 + pub const HEADER_W: u16 = 300; 46 + 47 + /// Width for status regions (battery, page number, etc.). 48 + pub const STATUS_W: u16 = 144; 49 + 50 + /// X position for right-aligned status in header. 51 + pub const STATUS_X: u16 = SCREEN_W - LARGE_MARGIN - STATUS_W; // 320 52 + 53 + // ── List/menu layout ──────────────────────────────────────────── 54 + 55 + /// Standard row height for list items. 56 + pub const LIST_ROW_H: u16 = 52; 57 + 58 + /// Gap between list rows. 59 + pub const LIST_ROW_GAP: u16 = 4; 60 + 61 + /// Combined row stride (row + gap). 62 + pub const LIST_ROW_STRIDE: u16 = LIST_ROW_H + LIST_ROW_GAP; 63 + 64 + /// Menu/settings row height (slightly smaller than list). 65 + pub const MENU_ROW_H: u16 = 40; 66 + 67 + /// Gap between menu rows. 68 + pub const MENU_ROW_GAP: u16 = 6; 69 + 70 + /// Combined menu row stride. 71 + pub const MENU_ROW_STRIDE: u16 = MENU_ROW_H + MENU_ROW_GAP; 72 + 73 + // ── Progress and overlays ─────────────────────────────────────── 74 + 75 + /// Height of progress bars. 76 + pub const PROGRESS_H: u16 = 2; 77 + 78 + /// Position overlay dimensions (for page/chapter display). 79 + pub const POSITION_OVERLAY_W: u16 = 280; 80 + pub const POSITION_OVERLAY_H: u16 = 40; 81 + 82 + // ── Loading indicator ─────────────────────────────────────────── 83 + 84 + /// Default height for loading indicator region. 85 + pub const LOADING_H: u16 = 24; 86 + 87 + // ── Footer layout ─────────────────────────────────────────────── 88 + 89 + /// Standard footer height from bottom. 90 + pub const FOOTER_MARGIN: u16 = 60; 91 + 92 + /// Footer Y position. 93 + pub const FOOTER_Y: u16 = SCREEN_H - FOOTER_MARGIN;
+9 -4
kernel/src/ui/mod.rs
··· 4 4 // font-dependent widgets (BitmapLabel, QuickMenu, ButtonFeedback) 5 5 // live in the distro's apps::widgets module 6 6 7 + pub mod layout; 7 8 pub mod stack_fmt; 8 9 pub mod statusbar; 9 10 mod widget; 10 11 11 - pub use stack_fmt::{StackFmt, stack_fmt}; 12 - pub use statusbar::{ 13 - BAR_HEIGHT, CONTENT_TOP, free_stack_bytes, paint_stack, stack_high_water_mark, 12 + pub use layout::{ 13 + CONTENT_H, CONTENT_TOP, FOOTER_MARGIN, FOOTER_Y, FULL_CONTENT_W, HEADER_W, LARGE_MARGIN, 14 + LIST_ROW_GAP, LIST_ROW_H, LIST_ROW_STRIDE, LOADING_H, MENU_ROW_GAP, MENU_ROW_H, 15 + MENU_ROW_STRIDE, POSITION_OVERLAY_H, POSITION_OVERLAY_W, PROGRESS_H, SECTION_GAP, STANDARD_GAP, 16 + STANDARD_MARGIN, STATUS_W, STATUS_X, TITLE_Y, TITLE_Y_OFFSET, 14 17 }; 18 + pub use stack_fmt::{stack_fmt, StackFmt}; 19 + pub use statusbar::{free_stack_bytes, paint_stack, stack_high_water_mark, BAR_HEIGHT}; 15 20 pub use widget::{ 16 - Alignment, Region, draw_loading_indicator, draw_progress_bar, wrap_next, wrap_prev, 21 + draw_loading_indicator, draw_progress_bar, wrap_next, wrap_prev, Alignment, Region, 17 22 }; 18 23 19 24 pub use crate::board::{SCREEN_H, SCREEN_W};
+5 -9
kernel/src/ui/statusbar.rs
··· 2 2 // system stats are emitted via log::info! in the scheduler 3 3 4 4 pub const BAR_HEIGHT: u16 = 4; 5 - pub const CONTENT_TOP: u16 = BAR_HEIGHT; 6 5 7 6 const STACK_PAINT_WORD: u32 = 0xDEAD_BEEF; 8 7 9 - // paint the unused stack with a sentinel word so stack_high_water_mark 10 - // can later measure peak usage; call very early in boot 8 + const STACK_GUARD_SKIP: usize = 256; 9 + 11 10 pub fn paint_stack() { 12 11 #[cfg(target_arch = "riscv32")] 13 12 { ··· 21 20 } 22 21 let bottom = (&raw const _stack_end_cpu0) as usize; 23 22 24 - let guard_skip = 256; 25 - let paint_bottom = bottom + guard_skip; 26 - 27 - let paint_top = sp.saturating_sub(256); 23 + let paint_bottom = bottom + STACK_GUARD_SKIP; 24 + let paint_top = sp.saturating_sub(STACK_GUARD_SKIP); 28 25 29 26 if paint_top <= paint_bottom { 30 27 return; ··· 73 70 let bottom = (&raw const _stack_end_cpu0) as usize; 74 71 let top = (&raw const _stack_start_cpu0) as usize; 75 72 76 - let guard_skip = 256; 77 - let scan_bottom = bottom + guard_skip; 73 + let scan_bottom = bottom + STACK_GUARD_SKIP; 78 74 79 75 let start = (scan_bottom + 3) & !3; 80 76
+11 -3
kernel/src/ui/widget.rs
··· 1 1 // region geometry, alignment helpers, progress bar, loading indicator 2 2 3 3 use embedded_graphics::{ 4 - mono_font::MonoTextStyle, mono_font::ascii::FONT_9X18, pixelcolor::BinaryColor, prelude::*, 4 + mono_font::ascii::FONT_9X18, mono_font::MonoTextStyle, pixelcolor::BinaryColor, prelude::*, 5 5 primitives::PrimitiveStyle, primitives::Rectangle, text::Text, 6 6 }; 7 7 ··· 106 106 if count == 0 { 107 107 return 0; 108 108 } 109 - if current + 1 >= count { 0 } else { current + 1 } 109 + if current + 1 >= count { 110 + 0 111 + } else { 112 + current + 1 113 + } 110 114 } 111 115 112 116 #[inline] ··· 114 118 if count == 0 { 115 119 return 0; 116 120 } 117 - if current == 0 { count - 1 } else { current - 1 } 121 + if current == 0 { 122 + count - 1 123 + } else { 124 + current - 1 125 + } 118 126 } 119 127 120 128 // horizontal progress bar for 1-bit e-paper
+20 -11
src/apps/files.rs
··· 18 18 use crate::error::{Error, ErrorKind}; 19 19 use crate::fonts; 20 20 use crate::kernel::KernelHandle; 21 - use crate::ui::{Alignment, BitmapDynLabel, BitmapLabel, CONTENT_TOP, Region}; 21 + use crate::ui::{ 22 + Alignment, BitmapDynLabel, BitmapLabel, CONTENT_TOP, FULL_CONTENT_W, HEADER_W, 23 + LARGE_MARGIN, LIST_ROW_GAP, LIST_ROW_H, Region, SECTION_GAP, STATUS_W, STATUS_X, 24 + TITLE_Y_OFFSET, 25 + }; 22 26 use smol_epub::epub::{self, EpubMeta, EpubSpine}; 23 27 use smol_epub::zip::ZipIndex; 24 28 25 29 const PAGE_SIZE: usize = 7; 26 30 27 - const LIST_X: u16 = 16; 28 - const LIST_W: u16 = 448; 31 + const LIST_X: u16 = LARGE_MARGIN; 32 + const LIST_W: u16 = FULL_CONTENT_W; 33 + 34 + const TITLE_Y: u16 = CONTENT_TOP + TITLE_Y_OFFSET; 35 + 36 + const FILES_STATUS_Y: u16 = TITLE_Y; 37 + const FILES_STATUS_H: u16 = 28; 38 + const STATUS_REGION: Region = Region::new(STATUS_X, FILES_STATUS_Y, STATUS_W, FILES_STATUS_H); 29 39 30 - const STATUS_REGION: Region = Region::new(320, CONTENT_TOP + 8, 144, 28); 40 + const ROW_H: u16 = LIST_ROW_H; 41 + const ROW_GAP: u16 = LIST_ROW_GAP; 31 42 32 - const ROW_H: u16 = 52; 33 - const ROW_GAP: u16 = 4; // gap between rows (border-to-border) 34 - const HEADER_LIST_GAP: u16 = 8; // gap between heading bottom and first row 43 + const HEADER_LIST_GAP: u16 = SECTION_GAP; 35 44 36 45 impl Default for FilesApp { 37 46 fn default() -> Self { ··· 69 78 stale_cache: false, 70 79 error: None, 71 80 ui_fonts: uf, 72 - list_y: CONTENT_TOP + 8 + uf.heading.line_height + HEADER_LIST_GAP, 81 + list_y: TITLE_Y + uf.heading.line_height + HEADER_LIST_GAP, 73 82 title_scan_idx: 0, 74 83 title_scanning: false, 75 84 title_reload: false, ··· 78 87 79 88 pub fn set_ui_font_size(&mut self, idx: u8) { 80 89 self.ui_fonts = fonts::UiFonts::for_size(idx); 81 - self.list_y = CONTENT_TOP + 8 + self.ui_fonts.heading.line_height + HEADER_LIST_GAP; 90 + self.list_y = TITLE_Y + self.ui_fonts.heading.line_height + HEADER_LIST_GAP; 82 91 } 83 92 84 93 fn selected_entry(&self) -> Option<&DirEntry> { ··· 308 317 fn draw(&self, strip: &mut StripBuffer) { 309 318 let header_region = Region::new( 310 319 LIST_X, 311 - CONTENT_TOP + 8, 312 - 300, 320 + TITLE_Y, 321 + HEADER_W, 313 322 self.ui_fonts.heading.line_height, 314 323 ); 315 324 BitmapLabel::new(header_region, "Files", self.ui_fonts.heading)
+30 -13
src/apps/reader/mod.rs
··· 29 29 use crate::kernel::QuickAction; 30 30 use crate::kernel::bookmarks; 31 31 use crate::kernel::work_queue; 32 - use crate::ui::{Alignment, BUTTON_BAR_H, CONTENT_TOP, Region, StackFmt}; 32 + use crate::ui::{ 33 + Alignment, CONTENT_TOP, HEADER_W, LOADING_H, POSITION_OVERLAY_H, POSITION_OVERLAY_W, 34 + PROGRESS_H, Region, STANDARD_MARGIN, StackFmt, TITLE_Y_OFFSET, 35 + }; 33 36 use smol_epub::DecodedImage; 34 37 use smol_epub::cache; 35 38 use smol_epub::epub::{self, EpubMeta, EpubSpine, EpubToc, TocSource}; ··· 38 41 }; 39 42 use smol_epub::zip::{self, ZipIndex}; 40 43 41 - pub(super) const MARGIN: u16 = 8; 42 - pub(super) const HEADER_Y: u16 = CONTENT_TOP + 2; 44 + pub(super) const MARGIN: u16 = STANDARD_MARGIN; 45 + 46 + pub(super) const HEADER_Y: u16 = CONTENT_TOP + TITLE_Y_OFFSET - 2; // slightly tighter 43 47 pub(super) const HEADER_H: u16 = 16; 48 + 44 49 pub(super) const TEXT_Y: u16 = HEADER_Y + HEADER_H + 2; 50 + 45 51 pub(super) const LINE_H: u16 = 20; 52 + 46 53 pub(super) const CHARS_PER_LINE: usize = 51; 54 + 47 55 pub(super) const LINES_PER_PAGE: usize = 37; 56 + 48 57 pub(super) const PAGE_BUF: usize = 8192; 58 + 49 59 pub(super) const MAX_PAGES: usize = 1024; 50 60 51 - pub(super) const HEADER_REGION: Region = Region::new(MARGIN, HEADER_Y, 300, HEADER_H); 52 - pub(super) const STATUS_REGION: Region = Region::new(308, HEADER_Y, 164, HEADER_H); 61 + pub(super) const HEADER_REGION: Region = Region::new(MARGIN, HEADER_Y, HEADER_W, HEADER_H); 62 + 63 + const STATUS_X: u16 = MARGIN + HEADER_W + 8; 64 + const STATUS_W: u16 = SCREEN_W - STATUS_X - MARGIN; 65 + pub(super) const STATUS_REGION: Region = Region::new(STATUS_X, HEADER_Y, STATUS_W, HEADER_H); 53 66 54 67 pub(super) const PAGE_REGION: Region = Region::new(0, HEADER_Y, SCREEN_W, SCREEN_H - HEADER_Y); 55 68 56 69 pub(super) const NO_PREFETCH: usize = usize::MAX; 57 70 58 71 pub(super) const TEXT_W: u32 = (SCREEN_W - 2 * MARGIN) as u32; 59 - pub(super) const TEXT_AREA_H: u16 = SCREEN_H - TEXT_Y - BUTTON_BAR_H; 72 + 73 + pub(super) const TEXT_AREA_H: u16 = SCREEN_H - TEXT_Y - 4; 74 + 60 75 pub(super) const EOCD_TAIL: usize = 512; 76 + 61 77 pub(super) const INDENT_PX: u32 = 24; 78 + 62 79 pub(super) const IMAGE_DISPLAY_H: u16 = 200; 80 + 63 81 pub(super) const CHAPTER_CACHE_MAX: usize = 98304; 64 82 65 - // images <= this size are dispatched to the async worker for decoding; 66 - // images > this size are decoded on the main loop via streaming SD reads 83 + // images <= this size are dispatched to async worker for decoding; 84 + // images > this size are decoded on main loop via streaming SD reads 67 85 pub(super) const PRECACHE_IMG_MAX: u32 = 30 * 1024; 68 86 69 - pub(super) const PROGRESS_H: u16 = 2; 70 - pub(super) const PROGRESS_Y: u16 = SCREEN_H - PROGRESS_H - 1; 87 + pub(super) const READER_PROGRESS_H: u16 = PROGRESS_H; 88 + pub(super) const PROGRESS_Y: u16 = SCREEN_H - READER_PROGRESS_H - 1; 71 89 pub(super) const PROGRESS_W: u16 = SCREEN_W - 2 * MARGIN; 72 90 73 - pub(super) const POSITION_OVERLAY_W: u16 = 280; 74 - pub(super) const POSITION_OVERLAY_H: u16 = 40; 75 91 pub(super) const POSITION_OVERLAY: Region = Region::new( 76 92 (SCREEN_W - POSITION_OVERLAY_W) / 2, 77 93 (SCREEN_H - POSITION_OVERLAY_H) / 2, ··· 79 95 POSITION_OVERLAY_H, 80 96 ); 81 97 82 - pub(super) const LOADING_REGION: Region = Region::new(MARGIN, TEXT_Y, 464, 24); 98 + const LOADING_W: u16 = SCREEN_W - 2 * MARGIN - 16; 99 + pub(super) const LOADING_REGION: Region = Region::new(MARGIN, TEXT_Y, LOADING_W, LOADING_H); 83 100 84 101 pub const QA_FONT_SIZE: u8 = 1; 85 102 pub(super) const QA_PREV_CHAPTER: u8 = 3;
+32 -19
src/apps/settings.rs
··· 9 9 use crate::fonts::max_size_idx; 10 10 use crate::kernel::KernelHandle; 11 11 use crate::kernel::config::{ 12 - self, SystemSettings, WifiConfig, parse_settings_txt, write_settings_txt, 12 + self, GHOST_CLEAR_STEP, MAX_GHOST_CLEAR, MAX_SLEEP_TIMEOUT, MIN_GHOST_CLEAR, 13 + SLEEP_TIMEOUT_STEP, SystemSettings, WifiConfig, parse_settings_txt, write_settings_txt, 13 14 }; 14 - use crate::ui::{Alignment, BitmapLabel, CONTENT_TOP, Region, StackFmt, wrap_next, wrap_prev}; 15 + use crate::ui::{ 16 + Alignment, BitmapLabel, CONTENT_TOP, FULL_CONTENT_W, LARGE_MARGIN, MENU_ROW_GAP, MENU_ROW_H, 17 + Region, SECTION_GAP, StackFmt, TITLE_Y, wrap_next, wrap_prev, 18 + }; 15 19 16 - const ROW_H: u16 = 40; 17 - const ROW_GAP: u16 = 6; 20 + // ── Settings layout constants ─────────────────────────────────── 21 + 22 + const ROW_H: u16 = MENU_ROW_H; 23 + const ROW_GAP: u16 = MENU_ROW_GAP; 18 24 const ROW_STRIDE: u16 = ROW_H + ROW_GAP; 19 25 20 - const LABEL_X: u16 = 16; 26 + const LABEL_X: u16 = LARGE_MARGIN; 21 27 const LABEL_W: u16 = 160; 22 28 const COL_GAP: u16 = 8; 23 29 const VALUE_X: u16 = LABEL_X + LABEL_W + COL_GAP; 24 - const VALUE_W: u16 = 296; 30 + const VALUE_W: u16 = FULL_CONTENT_W - LABEL_W - COL_GAP; // fills remaining width 25 31 26 32 const NUM_ITEMS: usize = 4; 27 - const HEADING_ITEMS_GAP: u16 = 8; 33 + const HEADING_ITEMS_GAP: u16 = SECTION_GAP; 28 34 29 35 impl Default for SettingsApp { 30 36 fn default() -> Self { ··· 52 58 loaded: false, 53 59 save_needed: false, 54 60 ui_fonts: uf, 55 - items_top: CONTENT_TOP + 4 + uf.heading.line_height + HEADING_ITEMS_GAP, 61 + items_top: TITLE_Y + uf.heading.line_height + HEADING_ITEMS_GAP, 56 62 } 57 63 } 58 64 59 65 pub fn set_ui_font_size(&mut self, idx: u8) { 60 66 self.ui_fonts = fonts::UiFonts::for_size(idx); 61 - self.items_top = CONTENT_TOP + 4 + self.ui_fonts.heading.line_height + HEADING_ITEMS_GAP; 67 + self.items_top = TITLE_Y + self.ui_fonts.heading.line_height + HEADING_ITEMS_GAP; 62 68 } 63 69 64 70 pub fn system_settings(&self) -> &SystemSettings { ··· 166 172 match self.selected { 167 173 0 => { 168 174 self.settings.sleep_timeout = match self.settings.sleep_timeout { 169 - 0 => 5, 170 - t if t >= 120 => 120, 171 - t => t + 5, 175 + 0 => SLEEP_TIMEOUT_STEP, 176 + t if t >= MAX_SLEEP_TIMEOUT => MAX_SLEEP_TIMEOUT, 177 + t => t + SLEEP_TIMEOUT_STEP, 172 178 }; 173 179 } 174 180 1 => { 175 - self.settings.ghost_clear_every = 176 - self.settings.ghost_clear_every.saturating_add(5).min(100); 181 + self.settings.ghost_clear_every = self 182 + .settings 183 + .ghost_clear_every 184 + .saturating_add(GHOST_CLEAR_STEP) 185 + .min(MAX_GHOST_CLEAR); 177 186 } 178 187 2 => { 179 188 if self.settings.book_font_size_idx < max_size_idx() { ··· 194 203 match self.selected { 195 204 0 => { 196 205 self.settings.sleep_timeout = match self.settings.sleep_timeout { 197 - 0..=5 => 0, 198 - t => t - 5, 206 + t if t <= SLEEP_TIMEOUT_STEP => 0, 207 + t => t - SLEEP_TIMEOUT_STEP, 199 208 }; 200 209 } 201 210 1 => { 202 - self.settings.ghost_clear_every = 203 - self.settings.ghost_clear_every.saturating_sub(5).max(5); 211 + self.settings.ghost_clear_every = self 212 + .settings 213 + .ghost_clear_every 214 + .saturating_sub(GHOST_CLEAR_STEP) 215 + .max(MIN_GHOST_CLEAR); 204 216 } 205 217 2 => { 206 218 if self.settings.book_font_size_idx > 0 { ··· 316 328 } 317 329 318 330 fn draw(&self, strip: &mut StripBuffer) { 319 - let title_region = Region::new(16, CONTENT_TOP + 4, 448, self.ui_fonts.heading.line_height); 331 + let title_region = 332 + Region::new(LARGE_MARGIN, TITLE_Y, FULL_CONTENT_W, self.ui_fonts.heading.line_height); 320 333 BitmapLabel::new(title_region, "Settings", self.ui_fonts.heading) 321 334 .alignment(Alignment::CenterLeft) 322 335 .draw(strip)
+30 -13
src/apps/upload.rs
··· 22 22 use crate::fonts::bitmap::BitmapFont; 23 23 use crate::kernel::config::WifiConfig; 24 24 use crate::kernel::tasks; 25 - use crate::ui::{Alignment, BitmapLabel, ButtonFeedback, CONTENT_TOP, Region, stack_fmt}; 25 + use crate::ui::{ 26 + Alignment, BitmapLabel, ButtonFeedback, CONTENT_TOP, FOOTER_Y, LARGE_MARGIN, Region, stack_fmt, 27 + }; 26 28 27 - const HEADING_X: u16 = 16; 29 + const HEADING_X: u16 = LARGE_MARGIN; 28 30 const HEADING_W: u16 = SCREEN_W - HEADING_X * 2; 29 31 30 32 const BODY_X: u16 = 24; 31 33 const BODY_W: u16 = SCREEN_W - BODY_X * 2; 32 34 const BODY_LINE_GAP: u16 = 10; 33 - 34 - const FOOTER_Y: u16 = SCREEN_H - 60; 35 35 36 36 const HTTP_200_HTML: &[u8] = 37 37 b"HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n"; ··· 59 59 const MAX_BOUNDARY_LEN: usize = 120; 60 60 const WORK_BUF_SIZE: usize = 2048; 61 61 62 + // TCP buffer sizes 63 + 64 + const TCP_RX_BUF_SIZE: usize = 2048; 65 + const TCP_TX_BUF_SIZE: usize = 1536; 66 + 67 + const HTTP_HEADER_BUF_SIZE: usize = 1024; 68 + 69 + const DIR_LIST_MAX: usize = 64; 70 + 71 + // HTTP timing 72 + const HTTP_TIMEOUT_SECS: u64 = 30; 73 + const ACCEPT_RETRY_MS: u64 = 200; 74 + 75 + const SOCKET_CLOSE_DELAY_MS: u64 = 50; 76 + 77 + const MDNS_BIND_RETRY_MS: u64 = 100; 78 + 62 79 enum ServerEvent { 63 80 Nothing, 64 81 Uploaded { name: [u8; 13], name_len: u8 }, ··· 276 293 ) 277 294 .await; 278 295 279 - let mut rx_buf = [0u8; 2048]; 280 - let mut tx_buf = [0u8; 1536]; 296 + let mut rx_buf = [0u8; TCP_RX_BUF_SIZE]; 297 + let mut tx_buf = [0u8; TCP_TX_BUF_SIZE]; 281 298 282 299 loop { 283 300 let inner_result = match select( ··· 333 350 where 334 351 { 335 352 let mut socket = TcpSocket::new(stack, rx_buf, tx_buf); 336 - socket.set_timeout(Some(Duration::from_secs(30))); 353 + socket.set_timeout(Some(Duration::from_secs(HTTP_TIMEOUT_SECS))); 337 354 338 355 if socket 339 356 .accept(IpListenEndpoint { ··· 343 360 .await 344 361 .is_err() 345 362 { 346 - Timer::after(Duration::from_millis(200)).await; 363 + Timer::after(Duration::from_millis(ACCEPT_RETRY_MS)).await; 347 364 return ServerEvent::Nothing; 348 365 } 349 366 350 - let mut hdr = [0u8; 1024]; 367 + let mut hdr = [0u8; HTTP_HEADER_BUF_SIZE]; 351 368 let mut hdr_len = 0usize; 352 369 353 370 loop { ··· 409 426 if is_get && path == b"/files" { 410 427 let _ = socket.write_all(HTTP_200_JSON).await; 411 428 412 - let mut entries = [storage::DirEntry::EMPTY; 64]; 429 + let mut entries = [storage::DirEntry::EMPTY; DIR_LIST_MAX]; 413 430 let count = match storage::list_root_files(sd, &mut entries) { 414 431 Ok(n) => n, 415 432 Err(_) => { ··· 821 838 } 822 839 823 840 async fn close_socket(socket: &mut TcpSocket<'_>) { 824 - Timer::after(Duration::from_millis(50)).await; 841 + Timer::after(Duration::from_millis(SOCKET_CLOSE_DELAY_MS)).await; 825 842 socket.close(); 826 - Timer::after(Duration::from_millis(50)).await; 843 + Timer::after(Duration::from_millis(SOCKET_CLOSE_DELAY_MS)).await; 827 844 socket.abort(); 828 845 } 829 846 ··· 845 862 let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buf, &mut tx_meta, &mut tx_buf); 846 863 847 864 if socket.bind(MDNS_PORT).is_err() { 848 - Timer::after(Duration::from_millis(100)).await; 865 + Timer::after(Duration::from_millis(MDNS_BIND_RETRY_MS)).await; 849 866 return; 850 867 } 851 868