From e4369cfb4aad6e40df00009943c54baab029d5a5 Mon Sep 17 00:00:00 2001 From: Linus Date: Fri, 26 Jan 2024 18:43:40 +0100 Subject: [PATCH 1/2] Add fullscreen mode --- src/layout/confirmfullscreen.rs | 61 ++++++++ src/layout/mod.rs | 9 ++ src/layout/settings.rs | 11 ++ src/main.rs | 260 +++++++++++++++++++++----------- 4 files changed, 250 insertions(+), 91 deletions(-) create mode 100644 src/layout/confirmfullscreen.rs diff --git a/src/layout/confirmfullscreen.rs b/src/layout/confirmfullscreen.rs new file mode 100644 index 0000000..edcfdc9 --- /dev/null +++ b/src/layout/confirmfullscreen.rs @@ -0,0 +1,61 @@ +use super::{ButtonAction, Element, Layout, LayoutId}; +use libremarkable::framebuffer::common; + +pub fn create() -> Layout { + let buttons = vec![ + Element::Text { + rect: common::mxcfb_rect { + left: 0, + top: 1400 - 300 - 10 - 10, + width: common::DISPLAYWIDTH as u32, + height: 100, + }, + text: "Go Fullscreen?", + size: 100.0, + }, + Element::Text { + rect: common::mxcfb_rect { + left: 0, + top: 1400 - 300 - 10 - 10 + 100, + width: common::DISPLAYWIDTH as u32, + height: 100, + }, + text: "You'll need to attach a physical keyboard to play!", + size: 50.0, + }, + Element::Text { + rect: common::mxcfb_rect { + left: 0, + top: 1400 - 300 - 10 - 10 + 100 + 75, + width: common::DISPLAYWIDTH as u32, + height: 100, + }, + text: "To exit fullscreen, just touch anywhere.", + size: 50.0, + }, + Element::Button { + rect: common::mxcfb_rect { + left: (common::DISPLAYWIDTH as u32 - (300 + 50 + 300)) / 2, + top: 1400 - 300 - 10 - 10 + 100 + 75 + 75 + 50, + width: 300, + height: 150, + }, + label: "OK", + label_size: 75.0, + action: ButtonAction::EnterFullscreen, + }, + Element::Button { + rect: common::mxcfb_rect { + left: (common::DISPLAYWIDTH as u32 - (300 + 50 + 300)) / 2 + 300 + 50, + top: 1400 - 300 - 10 - 10 + 100 + 75 + 75 + 50, + width: 300, + height: 150, + }, + label: "Back", + label_size: 75.0, + action: ButtonAction::SwitchLayout(LayoutId::Settings), + }, + ]; + + Layout::new(buttons) +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f0b6370..e92bec1 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -8,6 +8,7 @@ use libremarkable::framebuffer::{FramebufferDraw, FramebufferIO, FramebufferRefr use libremarkable::input::{Finger, InputEvent, MultitouchEvent}; mod confirmexit; +mod confirmfullscreen; mod controls; mod keyboard; mod settings; @@ -15,6 +16,7 @@ mod settings; pub enum InputOutcome { KeyData(KeyData), SwitchLayout(LayoutId), + EnterFullscreen, } pub struct LayoutManager { @@ -57,6 +59,7 @@ impl LayoutManager { layouts.insert(LayoutId::Settings, settings::create()); layouts.insert(LayoutId::ConfirmExit, confirmexit::create()); layouts.insert(LayoutId::Keyboard, keyboard::create()); + layouts.insert(LayoutId::ConfirmFullscreen, confirmfullscreen::create()); let instance = Self { layouts, @@ -108,6 +111,7 @@ pub enum LayoutId { Settings, ConfirmExit, Keyboard, + ConfirmFullscreen, } impl Default for LayoutId { @@ -282,6 +286,9 @@ impl Layout { ButtonAction::SwitchLayout(layout_id) => { outcomes.push(InputOutcome::SwitchLayout(*layout_id)); } + ButtonAction::EnterFullscreen => { + outcomes.push(InputOutcome::EnterFullscreen); + } } } } @@ -298,6 +305,7 @@ impl Layout { ButtonAction::Function(_) => {} ButtonAction::SwitchLayout(_) => {} + ButtonAction::EnterFullscreen => {} } } } @@ -333,4 +341,5 @@ enum ButtonAction { DoomKey(u8), Function(Box), SwitchLayout(LayoutId), + EnterFullscreen, } diff --git a/src/layout/settings.rs b/src/layout/settings.rs index 6f08d59..722079b 100644 --- a/src/layout/settings.rs +++ b/src/layout/settings.rs @@ -51,6 +51,17 @@ pub fn create() -> Layout { width: 400, height: 100, }, + label: "Fullscreen", + label_size: 50.0, + action: ButtonAction::SwitchLayout(LayoutId::ConfirmFullscreen), + }, + Element::Button { + rect: common::mxcfb_rect { + left: 62, + top: 1400 - 300 - 10 + 100 + 10 + (100 + 10) * 2, + width: 400, + height: 100, + }, label: "Exit", label_size: 50.0, action: ButtonAction::SwitchLayout(LayoutId::ConfirmExit), diff --git a/src/main.rs b/src/main.rs index f5389e6..23f5946 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use libremarkable::image::{DynamicImage, RgbImage}; use libremarkable::input::{ev::EvDevContext, InputDevice, InputEvent}; use once_cell::sync::Lazy; use std::io::Cursor; +use std::sync::atomic::AtomicBool; use std::sync::Mutex; use std::time::{Duration, Instant}; @@ -57,6 +58,62 @@ impl DoomGeneric for Game { } } +fn clear() { + let _ = FB.lock().unwrap().clear(); +} + +fn draw_title() { + let title_text = concat!("DOOMarkable v", env!("CARGO_PKG_VERSION")); + let subtitle_text = "https://github.com/LinusCDE/doomarkable"; + let title_size = 80; + let subtitle_size = 30; + let title_rect = FB.lock().unwrap().draw_text( + Point2 { x: 0f32, y: 0f32 }, + title_text, + title_size as f32, + common::color::BLACK, + true, + ); + let subtitle_rect = FB.lock().unwrap().draw_text( + Point2 { x: 0f32, y: 0f32 }, + subtitle_text, + subtitle_size as f32, + common::color::BLACK, + true, + ); + + FB.lock().unwrap().draw_text( + Point2 { + x: (common::DISPLAYWIDTH as u32 - title_rect.width) as f32 / 2.0, + y: (62 - 20 + title_size) as f32, + }, + title_text, + title_size as f32, + common::color::BLACK, + false, + ); + FB.lock().unwrap().draw_text( + Point2 { + x: (common::DISPLAYWIDTH as u32 - subtitle_rect.width) as f32 / 2.0, + y: (62 - 20 + title_size + subtitle_size) as f32, + }, + subtitle_text, + subtitle_size as f32, + common::color::BLACK, + false, + ); +} + +fn full_refresh() { + FB.lock().unwrap().full_refresh( + common::waveform_mode::WAVEFORM_MODE_GC16, + common::display_temp::TEMP_USE_MAX, + common::dither_mode::EPDC_FLAG_USE_REMARKABLE_DITHER, + 0, + true, + ); +} + fn main() { if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "INFO"); @@ -89,7 +146,7 @@ fn main() { 0, true, ); - FB.lock().unwrap().clear(); + clear(); // The dither_cache was calculated in build/main.rs and // this env is set to the file path containing this cache. @@ -107,66 +164,23 @@ fn main() { info!("Loaded dither cache in {:?}", start.elapsed()); // Title - let title_text = concat!("DOOMarkable v", env!("CARGO_PKG_VERSION")); - let subtitle_text = "https://github.com/LinusCDE/doomarkable"; - let title_size = 80; - let subtitle_size = 30; - let title_rect = FB.lock().unwrap().draw_text( - Point2 { x: 0f32, y: 0f32 }, - title_text, - title_size as f32, - common::color::BLACK, - true, - ); - let subtitle_rect = FB.lock().unwrap().draw_text( - Point2 { x: 0f32, y: 0f32 }, - subtitle_text, - subtitle_size as f32, - common::color::BLACK, - true, - ); - - FB.lock().unwrap().draw_text( - Point2 { - x: (common::DISPLAYWIDTH as u32 - title_rect.width) as f32 / 2.0, - y: (62 - 20 + title_size) as f32, - }, - title_text, - title_size as f32, - common::color::BLACK, - false, - ); - FB.lock().unwrap().draw_text( - Point2 { - x: (common::DISPLAYWIDTH as u32 - subtitle_rect.width) as f32 / 2.0, - y: (62 - 20 + title_size + subtitle_size) as f32, - }, - subtitle_text, - subtitle_size as f32, - common::color::BLACK, - false, - ); + draw_title(); + full_refresh(); // Keys - FB.lock().unwrap().full_refresh( - common::waveform_mode::WAVEFORM_MODE_GC16, - common::display_temp::TEMP_USE_MAX, - common::dither_mode::EPDC_FLAG_USE_REMARKABLE_DITHER, - 0, - true, - ); - let default_image = libremarkable::image::load_from_memory(include_bytes!("../res/default_screen.png")) .unwrap() .to_rgb8(); let image = std::sync::Arc::new(std::sync::Mutex::new(default_image)); let image_clone = image.clone(); + let fullscreen = std::sync::Arc::new(AtomicBool::new(false)); + let fullscreen_clone = fullscreen.clone(); std::thread::spawn(move || { let mut last_frame_drawn = Instant::now() - Duration::from_millis(1000); let width = game::DOOMGENERIC_RESX as u32 * SCALE_FACTOR as u32; - let height = game::DOOMGENERIC_RESY as u32 * SCALE_FACTOR as u32; + //let height = game::DOOMGENERIC_RESY as u32 * SCALE_FACTOR as u32; let pos = Point2 { x: (common::DISPLAYWIDTH as i32 - width as i32) / 2, //y: (common::DISPLAYHEIGHT as i32 - height as i32) / 2, @@ -200,32 +214,38 @@ fn main() { // Battery indicator in corner if last_battery_indicator_update.elapsed() > battery_indicator_update_interval { + let is_fullscreen = fullscreen_clone.load(std::sync::atomic::Ordering::Relaxed); last_battery_indicator_update = Instant::now(); - let percentage = libremarkable::battery::percentage().unwrap_or(-1); + let percentage = if is_fullscreen { + -1 + } else { + libremarkable::battery::percentage().unwrap_or(-1) + }; if percentage != last_battery_percentage { last_battery_percentage = percentage; - - let text = format!("{}% ", percentage); // Spaces to prevent residual text when text gets narrower - let rect = FB.lock().unwrap().draw_text( - Point2 { - x: 10.0, - y: (common::DISPLAYHEIGHT - 10) as f32, - }, - &text, - 30f32, - common::color::BLACK, - false, - ); - FB.lock().unwrap().partial_refresh( - &rect, - PartialRefreshMode::Async, - common::waveform_mode::WAVEFORM_MODE_GC16_FAST, - common::display_temp::TEMP_USE_MAX, - common::dither_mode::EPDC_FLAG_USE_REMARKABLE_DITHER, - 0, - false, - ); - debug!("Updated battery indicator"); + if !is_fullscreen { + let text = format!("{}% ", percentage); // Spaces to prevent residual text when text gets narrower + let rect = FB.lock().unwrap().draw_text( + Point2 { + x: 10.0, + y: (common::DISPLAYHEIGHT - 10) as f32, + }, + &text, + 30f32, + common::color::BLACK, + false, + ); + FB.lock().unwrap().partial_refresh( + &rect, + PartialRefreshMode::Async, + common::waveform_mode::WAVEFORM_MODE_GC16_FAST, + common::display_temp::TEMP_USE_MAX, + common::dither_mode::EPDC_FLAG_USE_REMARKABLE_DITHER, + 0, + false, + ); + debug!("Updated battery indicator"); + } } } @@ -242,20 +262,19 @@ fn main() { debug!("Dithering took {:?}", start.elapsed()); let start = Instant::now(); - //fb.draw_image(&dithered_img, pos); - draw_image_mono(&mut FB.lock().unwrap(), pos, &dithered_img); + let game_rect = if fullscreen_clone.load(std::sync::atomic::Ordering::Relaxed) { + draw_image_mono_fullscreen(&mut FB.lock().unwrap(), &dithered_img) + } else { + //fb.draw_image(&dithered_img, pos); + draw_image_mono(&mut FB.lock().unwrap(), pos, &dithered_img) + }; let waveform = match libremarkable::device::CURRENT_DEVICE.model { libremarkable::device::Model::Gen1 => common::waveform_mode::WAVEFORM_MODE_GLR16, libremarkable::device::Model::Gen2 => common::waveform_mode::WAVEFORM_MODE_DU, }; FB.lock().unwrap().partial_refresh( - &common::mxcfb_rect { - left: pos.x as u32, - top: pos.y as u32, - width, - height, - }, + &game_rect, PartialRefreshMode::Async, //common::waveform_mode::WAVEFORM_MODE_DU, //common::waveform_mode::WAVEFORM_MODE_GLR16, @@ -279,8 +298,25 @@ fn main() { let (input_tx, input_rx) = std::sync::mpsc::channel::(); EvDevContext::new(InputDevice::Multitouch, input_tx).start(); + let mut fullscreen_since: Option = None; for event in input_rx { + if let Some(fullscreen_since_time) = fullscreen_since { + // Any touch 500ms after fullscreen entered => exit fullscreen + if fullscreen_since_time.elapsed() > Duration::from_millis(500) { + // Exit out of fullscreen (portrait game, bring back layout) + info!("Exiting fullscreen mode..."); + fullscreen.store(false, std::sync::atomic::Ordering::Relaxed); + fullscreen_since = None; + clear(); + draw_title(); + layout_manager + .switch_layout(layout::LayoutId::Controls, &mut FB.lock().unwrap()); + full_refresh(); + } + continue; // No layout handling while in fullscreen + } + for outcome in layout_manager.current_layout_mut().handle_input(event) { match outcome { layout::InputOutcome::KeyData(keydata) => { @@ -289,6 +325,14 @@ fn main() { layout::InputOutcome::SwitchLayout(new_layout_id) => { layout_manager.switch_layout(new_layout_id, &mut FB.lock().unwrap()) } + layout::InputOutcome::EnterFullscreen => { + // Switch to fullscreen (landscape game, no layout rendering) + info!("Entering fullscreen mode..."); + fullscreen_since = Some(Instant::now()); + fullscreen.store(true, std::sync::atomic::Ordering::Relaxed); + clear(); + full_refresh(); + } } } } @@ -303,7 +347,11 @@ fn main() { warn!("Game loop quit!"); } -fn draw_image_mono(fb: &mut Framebuffer, pos: Point2, img: &libremarkable::image::GrayImage) { +fn draw_image_mono( + fb: &mut Framebuffer, + pos: Point2, + img: &libremarkable::image::GrayImage, +) -> common::mxcfb_rect { let width = img.width(); let height = img.height(); let mut fb_raw_data: Vec = @@ -314,14 +362,44 @@ fn draw_image_mono(fb: &mut Framebuffer, pos: Point2, img: &libremarkable:: fb_raw_data.push(pixel_value); } - fb.restore_region( - common::mxcfb_rect { - top: pos.y as u32, - left: pos.x as u32, - width, - height, - }, - &fb_raw_data, - ) - .unwrap(); + let rect = common::mxcfb_rect { + top: pos.y as u32, + left: pos.x as u32, + width, + height, + }; + fb.restore_region(rect, &fb_raw_data).unwrap(); + rect +} + +fn draw_image_mono_fullscreen( + fb: &mut Framebuffer, + img: &libremarkable::image::GrayImage, +) -> common::mxcfb_rect { + let (portrait_width, portrait_height) = (img.width() as usize, img.height() as usize); + let (landscape_width, landscape_height) = (portrait_height, portrait_width); + + let mut fb_raw_data: Vec = vec![0u8; portrait_width * 2 * portrait_height]; + for (x, y, pixel_value) in img.enumerate_pixels() { + // Rotate by 90 degrees + let new_x = portrait_height - (y as usize + 1); + let new_y = x as usize; + + // Put new x and y values into linear fb_raw_data + let index = (new_y * landscape_width + new_x) * 2; + fb_raw_data[index] = pixel_value.0[0]; + fb_raw_data[index + 1] = pixel_value.0[0]; + } + let pos = Point2 { + x: (common::DISPLAYWIDTH as i32 - landscape_width as i32) / 2, + y: (common::DISPLAYHEIGHT as i32 - landscape_height as i32) / 2, + }; + let rect = common::mxcfb_rect { + top: pos.y as u32, + left: pos.x as u32, + width: landscape_width as u32, + height: landscape_height as u32, + }; + fb.restore_region(rect, &fb_raw_data).unwrap(); + rect } From c6761f9276be3fb1ac818720f404280b2ef34bd0 Mon Sep 17 00:00:00 2001 From: Linus Date: Fri, 26 Jan 2024 19:01:33 +0100 Subject: [PATCH 2/2] Fix wrong pixel values The conversion was faulty and resulted in wrong values. Not sure why I never noticed it. --- src/main.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 23f5946..4f22d7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -163,6 +163,13 @@ fn main() { let mut ditherer = blue_noise_dither::CachedDither4X::new(dither_cache_raw); info!("Loaded dither cache in {:?}", start.elapsed()); + // Create grayscale to native pixel color map + let mut gray_to_native = [(0u8, 0u8); 256]; + for gray_pixel_value in 0..256 { + let native_pixel = common::color::GRAY(255 - gray_pixel_value as u8).as_native(); + gray_to_native[gray_pixel_value] = (native_pixel[0], native_pixel[1]); + } + // Title draw_title(); full_refresh(); @@ -263,10 +270,10 @@ fn main() { let start = Instant::now(); let game_rect = if fullscreen_clone.load(std::sync::atomic::Ordering::Relaxed) { - draw_image_mono_fullscreen(&mut FB.lock().unwrap(), &dithered_img) + draw_image_mono_fullscreen(&mut FB.lock().unwrap(), &dithered_img, &gray_to_native) } else { //fb.draw_image(&dithered_img, pos); - draw_image_mono(&mut FB.lock().unwrap(), pos, &dithered_img) + draw_image_mono(&mut FB.lock().unwrap(), pos, &dithered_img, &gray_to_native) }; let waveform = match libremarkable::device::CURRENT_DEVICE.model { @@ -351,6 +358,7 @@ fn draw_image_mono( fb: &mut Framebuffer, pos: Point2, img: &libremarkable::image::GrayImage, + gray_to_native: &[(u8, u8); 256], ) -> common::mxcfb_rect { let width = img.width(); let height = img.height(); @@ -358,8 +366,8 @@ fn draw_image_mono( Vec::with_capacity(img.width() as usize * 2 * img.height() as usize); let img_vec = img.to_vec(); for pixel_value in img_vec { - fb_raw_data.push(pixel_value); - fb_raw_data.push(pixel_value); + fb_raw_data.push(gray_to_native[pixel_value as usize].0); + fb_raw_data.push(gray_to_native[pixel_value as usize].1); } let rect = common::mxcfb_rect { @@ -375,6 +383,7 @@ fn draw_image_mono( fn draw_image_mono_fullscreen( fb: &mut Framebuffer, img: &libremarkable::image::GrayImage, + gray_to_native: &[(u8, u8); 256], ) -> common::mxcfb_rect { let (portrait_width, portrait_height) = (img.width() as usize, img.height() as usize); let (landscape_width, landscape_height) = (portrait_height, portrait_width); @@ -387,8 +396,8 @@ fn draw_image_mono_fullscreen( // Put new x and y values into linear fb_raw_data let index = (new_y * landscape_width + new_x) * 2; - fb_raw_data[index] = pixel_value.0[0]; - fb_raw_data[index + 1] = pixel_value.0[0]; + fb_raw_data[index] = gray_to_native[pixel_value.0[0] as usize].0; + fb_raw_data[index + 1] = gray_to_native[pixel_value.0[0] as usize].1; } let pos = Point2 { x: (common::DISPLAYWIDTH as i32 - landscape_width as i32) / 2,