From fec25cfec8c4f104f96e15b85aefd97292c1063f Mon Sep 17 00:00:00 2001 From: Stefan Arentz Date: Mon, 6 Jul 2026 21:04:38 -0400 Subject: [PATCH] Add 4 window pixels of padding around the screen contents (#210) * Add 4 window pixels of padding around the screen contents Move the canvases from emulated-pixel to window-pixel coordinates and draw the screen texture at an explicit 3x rect inset by PAD, so the contents no longer touch the window border. The dst rect stays an exact integer 3x of the texture, keeping the nearest-neighbor crispness. In the ][+ this also stops the status-bar mode from stretching the screen over the bar strip before overdrawing it: the screen rect is now mode-independent and the bar sits below it inside the side padding. Co-Authored-By: Claude Fable 5 * Make the window padding configurable via EWM_WINDOW_PADDING Defaults to 4; missing or unparsable values fall back to the default. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- ewm/src/boo.rs | 23 +++++++++++++++++++---- ewm/src/one.rs | 19 +++++++++++++++---- ewm/src/sdl.rs | 9 +++++++++ ewm/src/two.rs | 41 ++++++++++++++++++++++------------------- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/ewm/src/boo.rs b/ewm/src/boo.rs index 551414e..eaa528f 100644 --- a/ewm/src/boo.rs +++ b/ewm/src/boo.rs @@ -4,6 +4,7 @@ use sdl3::event::Event; use sdl3::keyboard::Keycode; use sdl3::pixels::{Color, PixelFormat}; +use sdl3::rect::Rect; use sdl3::render::ScaleMode; use sdl3::sys::render::SDL_RendererLogicalPresentation; @@ -48,6 +49,8 @@ static MENU: [&str; TTY_ROWS] = [ ]; pub fn main(_args: &[String]) -> BooChoice { + let pad = sdl::window_padding(); + // Setup SDL let context = match sdl3::init() { @@ -60,7 +63,11 @@ pub fn main(_args: &[String]) -> BooChoice { let video = context.video().expect("Failed to initialize SDL video"); let window = video - .window("EWM v0.1 - Bootloader", 280 * 3, 192 * 3) + .window( + "EWM v0.1 - Bootloader", + 280 * 3 + 2 * pad, + 192 * 3 + 2 * pad, + ) .position_centered() .build() .expect("Failed create window"); @@ -72,10 +79,12 @@ pub fn main(_args: &[String]) -> BooChoice { return BooChoice::Quit; } + // Logical units are window pixels: the tty texture is drawn at 3x into + // an explicit rect, leaving pad window pixels around it. canvas .set_logical_size( - TTY_PIXEL_WIDTH as u32, - TTY_PIXEL_HEIGHT as u32, + TTY_PIXEL_WIDTH as u32 * 3 + 2 * pad, + TTY_PIXEL_HEIGHT as u32 * 3 + 2 * pad, SDL_RendererLogicalPresentation::LETTERBOX, ) .expect("Failed to set logical size"); @@ -140,8 +149,14 @@ pub fn main(_args: &[String]) -> BooChoice { texture .update(None, &bytes, TTY_PIXEL_WIDTH * 4) .expect("Failed to update texture"); + let dst = Rect::new( + pad as i32, + pad as i32, + TTY_PIXEL_WIDTH as u32 * 3, + TTY_PIXEL_HEIGHT as u32 * 3, + ); canvas - .copy(&texture, None, None) + .copy(&texture, None, dst) .expect("Failed to copy texture"); canvas.present(); diff --git a/ewm/src/one.rs b/ewm/src/one.rs index 0736e6f..ed7e5e1 100644 --- a/ewm/src/one.rs +++ b/ewm/src/one.rs @@ -12,6 +12,7 @@ use ewm_core::mem::{DeviceHandle, Memory}; use sdl3::event::Event; use sdl3::keyboard::{Keycode, Mod}; use sdl3::pixels::PixelFormat; +use sdl3::rect::Rect; use sdl3::render::ScaleMode; use sdl3::sys::render::SDL_RendererLogicalPresentation; use sdl3::video::FullscreenType; @@ -197,6 +198,8 @@ fn step_cpu(one: &mut One, cycles: u32) { } pub fn main(args: &[String]) -> i32 { + let pad = sdl::window_padding(); + // Parse Apple 1 specific options. The C default model is the Replica 1. let mut model = OneModel::Replica1; let mut memory: Vec = Vec::new(); @@ -252,7 +255,7 @@ pub fn main(args: &[String]) -> i32 { let video = context.video().expect("Failed to initialize SDL video"); let window = video - .window("EWM v0.1 - Apple 1", 280 * 3, 192 * 3) + .window("EWM v0.1 - Apple 1", 280 * 3 + 2 * pad, 192 * 3 + 2 * pad) .position_centered() .build(); let window = match window { @@ -270,10 +273,12 @@ pub fn main(args: &[String]) -> i32 { return 1; } + // Logical units are window pixels: the tty texture is drawn at 3x into + // an explicit rect, leaving pad window pixels around it. canvas .set_logical_size( - TTY_PIXEL_WIDTH as u32, - TTY_PIXEL_HEIGHT as u32, + TTY_PIXEL_WIDTH as u32 * 3 + 2 * pad, + TTY_PIXEL_HEIGHT as u32 * 3 + 2 * pad, SDL_RendererLogicalPresentation::LETTERBOX, ) .expect("Failed to set logical size"); @@ -375,8 +380,14 @@ pub fn main(args: &[String]) -> i32 { texture .update(None, &bytes, TTY_PIXEL_WIDTH * 4) .expect("Failed to update texture"); + let dst = Rect::new( + pad as i32, + pad as i32, + TTY_PIXEL_WIDTH as u32 * 3, + TTY_PIXEL_HEIGHT as u32 * 3, + ); canvas - .copy(&texture, None, None) + .copy(&texture, None, dst) .expect("Failed to copy texture"); canvas.present(); diff --git a/ewm/src/sdl.rs b/ewm/src/sdl.rs index 6829ae4..fde35cf 100644 --- a/ewm/src/sdl.rs +++ b/ewm/src/sdl.rs @@ -53,6 +53,15 @@ pub fn check_renderer(canvas: &WindowCanvas) -> Result<(), String> { Ok(()) } +/// Window pixels between the screen contents and the window border, +/// from `EWM_WINDOW_PADDING` (default 4). +pub fn window_padding() -> u32 { + std::env::var("EWM_WINDOW_PADDING") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(4) +} + /// Port of `ewm_sdl_green`: full green packed for the renderer's format. pub fn green(canvas: &WindowCanvas) -> u32 { match pixel_format(canvas) { diff --git a/ewm/src/two.rs b/ewm/src/two.rs index 390008c..4b71618 100644 --- a/ewm/src/two.rs +++ b/ewm/src/two.rs @@ -620,6 +620,7 @@ pub fn main(args: &[String]) -> i32 { Err(code) => return code, }; let fps = options.fps; + let pad = sdl::window_padding(); // Initialize SDL @@ -635,7 +636,7 @@ pub fn main(args: &[String]) -> i32 { let controller_subsystem = context.gamepad().ok(); let window = video - .window("EWM v0.1 / Apple ][+", 280 * 3, 192 * 3) + .window("EWM v0.1 / Apple ][+", 280 * 3 + 2 * pad, 192 * 3 + 2 * pad) .position(400, 60) .build(); let window = match window { @@ -653,10 +654,12 @@ pub fn main(args: &[String]) -> i32 { return 1; } + // Logical units are window pixels: the screen texture is drawn at 3x + // into an explicit rect, leaving pad window pixels around it. canvas .set_logical_size( - SCR_WIDTH as u32, - SCR_HEIGHT as u32, + SCR_WIDTH as u32 * 3 + 2 * pad, + SCR_HEIGHT as u32 * 3 + 2 * pad, SDL_RendererLogicalPresentation::LETTERBOX, ) .expect("Failed to set logical size"); @@ -855,21 +858,15 @@ pub fn main(args: &[String]) -> i32 { } else { 0 }; - let _ = canvas - .window_mut() - .set_size(SCR_WIDTH as u32 * 3, SCR_HEIGHT as u32 * 3 + extra); + let _ = canvas.window_mut().set_size( + SCR_WIDTH as u32 * 3 + 2 * pad, + SCR_HEIGHT as u32 * 3 + 2 * pad + extra, + ); let _ = canvas.set_logical_size( - SCR_WIDTH as u32 * 3, - SCR_HEIGHT as u32 * 3 + extra, + SCR_WIDTH as u32 * 3 + 2 * pad, + SCR_HEIGHT as u32 * 3 + 2 * pad + extra, SDL_RendererLogicalPresentation::LETTERBOX, ); - if !status_bar_visible { - let _ = canvas.set_logical_size( - SCR_WIDTH as u32, - SCR_HEIGHT as u32, - SDL_RendererLogicalPresentation::LETTERBOX, - ); - } } Keycode::P => paused = !paused, _ => {} @@ -952,8 +949,14 @@ pub fn main(args: &[String]) -> i32 { texture .update(None, &pixels_to_bytes(&scr.pixels), SCR_WIDTH * 4) .expect("Failed to update texture"); + let screen_dst = Rect::new( + pad as i32, + pad as i32, + SCR_WIDTH as u32 * 3, + SCR_HEIGHT as u32 * 3, + ); canvas - .copy(&texture, None, None) + .copy(&texture, None, screen_dst) .expect("Failed to copy texture"); if status_bar_visible { @@ -962,8 +965,8 @@ pub fn main(args: &[String]) -> i32 { .update(None, &pixels_to_bytes(&bar), TTY_PIXEL_WIDTH * 4) .expect("Failed to update bar texture"); let dst = Rect::new( - 0, - SCR_HEIGHT as i32 * 3, + pad as i32, + pad as i32 + SCR_HEIGHT as i32 * 3, SCR_WIDTH as u32 * 3, STATUS_BAR_HEIGHT * 3, ); @@ -989,7 +992,7 @@ pub fn main(args: &[String]) -> i32 { TTY_PIXEL_WIDTH * 4, ) .expect("Failed to update tty texture"); - let _ = canvas.copy(&tty_texture, None, None); + let _ = canvas.copy(&tty_texture, None, screen_dst); } canvas.present();