From a64b10e20cfee118d8942b6cae3b76ee35c33181 Mon Sep 17 00:00:00 2001 From: talwat <83217276+talwat@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:30:06 +0200 Subject: [PATCH] fix: fix flickering on windows & abstract window drawing --- src/player/ui.rs | 81 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 18 deletions(-) diff --git a/src/player/ui.rs b/src/player/ui.rs index a2de40c..8e211d2 100644 --- a/src/player/ui.rs +++ b/src/player/ui.rs @@ -1,7 +1,7 @@ //! The module which manages all user interface, including inputs. use std::{ - io::stdout, + io::{stdout, Stdout}, sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -50,11 +50,71 @@ lazy_static! { static ref VOLUME_TIMER: AtomicUsize = AtomicUsize::new(0); } +/// Represents an abstraction for drawing the actual lowfi window itself. +/// +/// The main purpose of this struct is just to add the fancy border, +/// as well as clear the screen before drawing. +pub struct Window { + borders: [String; 2], + out: Stdout, +} + +impl Window { + /// Initializes a new [Window]. + pub fn new() -> Self { + Self { + borders: [ + format!("┌{}┐\r\n", "─".repeat(WIDTH + 2)), + // This one doesn't have a leading \r\n to avoid extra space under the window. + format!("└{}┘", "─".repeat(WIDTH + 2)), + ], + out: stdout(), + } + } + + /// Actually draws the window, with each element in `content` being on a new line. + pub fn draw(&mut self, content: Vec) -> eyre::Result<()> { + let len = content.len() as u16; + + let menu: String = content + .into_iter() + .map(|x| format!("│ {} │\r\n", x.reset()).to_string()) + .collect(); + + // We're doing this because Windows is stupid and can't stand + // writing to the last line repeatedly. Again, it's stupid. + #[cfg(windows)] + let (rendered, height) = ( + format!("{}{}{}\r\n", self.borders[0], menu, self.borders[1]), + len + 2, + ); + + // Unix has no such ridiculous limitations, so we calculate + // the height of the window accurately. + #[cfg(not(windows))] + let (rendered, height) = ( + format!("{}{}{}", self.borders[0], menu, self.borders[1]), + len + 1, + ); + + crossterm::execute!( + self.out, + Clear(ClearType::FromCursorDown), + MoveToColumn(0), + Print(rendered), + MoveToColumn(0), + MoveUp(height), + )?; + + Ok(()) + } +} + /// The code for the terminal interface itself. /// /// * `minimalist` - All this does is hide the bottom control bar. async fn interface(player: Arc, minimalist: bool) -> eyre::Result<()> { - let mut stdout = std::io::stdout(); + let mut window = Window::new(); loop { // Load `current` once so that it doesn't have to be loaded over and over @@ -89,22 +149,7 @@ async fn interface(player: Arc, minimalist: bool) -> eyre::Result<()> { vec![action, middle, controls] }; - // Formats the menu properly. - let menu: Vec = menu - .into_iter() - .map(|x| format!("│ {} │\r\n", x.reset()).to_string()) - .collect(); - - crossterm::execute!( - stdout, - Clear(ClearType::FromCursorDown), - MoveToColumn(0), - Print(format!("┌{}┐\r\n", "─".repeat(WIDTH + 2))), - Print(menu.join("")), - Print(format!("└{}┘", "─".repeat(WIDTH + 2))), - MoveToColumn(0), - MoveUp(menu.len() as u16 + 1) - )?; + window.draw(menu)?; sleep(Duration::from_secs_f32(FRAME_DELTA)).await; }