Skip to content

Commit

Permalink
fix: skip unnecessary album art work and fix some overdraws when chan…
Browse files Browse the repository at this point in the history
…ging screens (#65)
  • Loading branch information
mierak authored Sep 12, 2024
1 parent d7d5b5c commit 3e4951b
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 64 deletions.
4 changes: 3 additions & 1 deletion src/config/tabs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{bail, Result};
use anyhow::{bail, ensure, Result};
use derive_more::{Deref, Display, Into};
use itertools::Itertools;
use std::collections::HashMap;
Expand Down Expand Up @@ -102,6 +102,8 @@ impl TryFrom<TabsFile> for Tabs {
},
)?;

ensure!(!tabs.is_empty(), "At least one tab is required");

let active_panes = tabs
.iter()
.flat_map(|(_, tab)| tab.panes.panes_iter().map(|pane| pane.pane))
Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ fn main_task<B: Backend + std::io::Write>(
Ok(ui::KeyHandleResult::RenderRequested) => {
render_wanted = true;
}
Ok(ui::KeyHandleResult::FullRenderRequested) => {
render_wanted = true;
full_rerender_wanted = true;
}
Err(err) => {
status_error!(err:?; "Error: {}", err.to_status());
render_wanted = true;
Expand Down Expand Up @@ -387,7 +391,8 @@ fn main_task<B: Backend + std::io::Write>(
continue;
}
if full_rerender_wanted {
terminal.clear().expect("Terminal clear to succeed");
terminal.swap_buffers();
terminal.swap_buffers();
full_rerender_wanted = false;
}
terminal
Expand Down
7 changes: 7 additions & 0 deletions src/tests/fixtures/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{collections::HashSet, sync::mpsc::channel};

use ratatui::{backend::TestBackend, Terminal};
use rstest::fixture;

use crate::{config::Config, context::AppContext, mpd::commands::Status};
Expand Down Expand Up @@ -27,3 +28,9 @@ pub fn app_context() -> AppContext {
pub fn config() -> Config {
Config::default()
}

#[fixture]
#[allow(clippy::unwrap_used)]
pub fn terminal() -> Terminal<TestBackend> {
Terminal::new(TestBackend::new(100, 100)).unwrap()
}
15 changes: 9 additions & 6 deletions src/ui/image/facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,12 @@ impl AlbumArtFacade {
Ok(())
}

pub fn render(&mut self, frame: &mut Frame, area: Rect, _config: &Config) -> anyhow::Result<()> {
self.last_size = area;
pub fn render(&mut self, frame: &mut Frame, _config: &Config) -> anyhow::Result<()> {
match &mut self.image_state {
ImageState::Kitty(state) => state.render(frame.buffer_mut(), area)?,
ImageState::Ueberzug(state) => state.render(frame.buffer_mut(), area)?,
ImageState::Iterm2(iterm2) => iterm2.render(frame.buffer_mut(), area)?,
ImageState::Sixel(s) => s.render(frame.buffer_mut(), area)?,
ImageState::Kitty(state) => state.render(frame.buffer_mut(), self.last_size)?,
ImageState::Ueberzug(state) => state.render(frame.buffer_mut(), self.last_size)?,
ImageState::Iterm2(iterm2) => iterm2.render(frame.buffer_mut(), self.last_size)?,
ImageState::Sixel(s) => s.render(frame.buffer_mut(), self.last_size)?,
ImageState::None => {}
};
Ok(())
Expand Down Expand Up @@ -150,6 +149,10 @@ impl AlbumArtFacade {
ImageState::None => Ok(()),
}
}

pub fn set_size(&mut self, area: Rect) {
self.last_size = area;
}
}

impl From<ImageMethod> for ImageProtocol {
Expand Down
79 changes: 62 additions & 17 deletions src/ui/image/iterm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use crossterm::{
};
use std::{
io::Write,
sync::{mpsc::channel, Arc},
sync::{
mpsc::{channel, Receiver, Sender},
Arc,
},
};

use ratatui::{buffer::Buffer, layout::Rect, style::Color};
Expand All @@ -17,6 +20,7 @@ use crate::{
utils::{
image_proto::{get_image_area_size_px, jpg_encode, resize_image},
macros::try_cont,
mpsc::RecvLast,
tmux,
},
};
Expand All @@ -29,16 +33,27 @@ struct EncodedData {
size: usize,
width: u32,
height: u32,
id: u64,
}

#[derive(Debug)]
struct DataToEncode {
width: u16,
height: u16,
wants_full_render: bool,
data: Arc<Vec<u8>>,
request_id: u64,
}

#[derive(Debug)]
pub struct Iterm2 {
image_data_to_encode: Arc<Vec<u8>>,
encoded_data: Option<EncodedData>,
default_art: Arc<Vec<u8>>,
sender: std::sync::mpsc::Sender<(u16, u16, bool, Arc<Vec<u8>>)>,
encoded_data_receiver: std::sync::mpsc::Receiver<EncodedData>,
sender: Sender<DataToEncode>,
encoded_data_receiver: Receiver<EncodedData>,
state: State,
last_id: u64,
}

#[derive(Debug)]
Expand All @@ -64,17 +79,27 @@ impl ImageProto for Iterm2 {
) -> Result<()> {
match self.state {
State::Initial => {
self.sender
.send((width, height, false, Arc::clone(&self.image_data_to_encode)))?;
self.sender.send(DataToEncode {
width,
height,
wants_full_render: false,
data: Arc::clone(&self.image_data_to_encode),
request_id: self.last_id,
})?;
self.state = State::Encoding;
}
State::Resize => {
self.sender
.send((width, height, true, Arc::clone(&self.image_data_to_encode)))?;
self.sender.send(DataToEncode {
width,
height,
wants_full_render: true,
data: Arc::clone(&self.image_data_to_encode),
request_id: self.last_id,
})?;
self.state = State::Encoding;
}
_ => {
if let Ok(data) = self.encoded_data_receiver.try_recv() {
if let Ok(data) = self.encoded_data_receiver.try_recv_last() {
self.encoded_data = Some(data);
self.state = State::Encoded;
}
Expand All @@ -96,15 +121,22 @@ impl ImageProto for Iterm2 {
if let Some(data) = &self.encoded_data {
self.clear_area(bg_color, Rect { x, y, width, height })?;

let mut stdout = std::io::stdout();
queue!(stdout, SavePosition)?;
queue!(stdout, MoveTo(x, y))?;
let EncodedData {
content,
size,
width,
height,
id,
} = data;

if *id != self.last_id {
return Ok(());
}

let mut stdout = std::io::stdout();
queue!(stdout, SavePosition)?;
queue!(stdout, MoveTo(x, y))?;

if tmux::is_inside_tmux() {
write!(stdout, "{}", &format!("\x1bPtmux;\x1b\x1b]1337;File=inline=1;size={size};width={width}px;height={height}px;preserveAspectRatio=1;doNotMoveCursor=1:{content}\x07\x1b\\"))?;
} else {
Expand Down Expand Up @@ -135,6 +167,7 @@ impl ImageProto for Iterm2 {
}

fn set_data(&mut self, data: Option<Vec<u8>>) -> Result<()> {
self.last_id += 1;
if let Some(data) = data {
self.image_data_to_encode = Arc::new(data);
} else {
Expand All @@ -149,15 +182,25 @@ impl ImageProto for Iterm2 {

impl Iterm2 {
pub fn new(default_art: &[u8], max_size: Size, request_render: impl Fn(bool) + Send + 'static) -> Self {
let (sender, receiver) = channel::<(u16, u16, bool, Arc<Vec<u8>>)>();
let (sender, receiver) = channel::<DataToEncode>();
let (encoded_tx, encoded_rx) = channel::<EncodedData>();

std::thread::spawn(move || loop {
if let Ok((w, h, full_render, data)) = receiver.recv() {
let encoded = try_cont!(Iterm2::encode(w, h, &data, max_size), "Failed to encode data");
if let Ok(DataToEncode {
width,
height,
wants_full_render,
data,
request_id,
}) = receiver.recv_last()
{
let encoded = try_cont!(
Iterm2::encode(width, height, &data, max_size, request_id),
"Failed to encode data"
);
try_cont!(encoded_tx.send(encoded), "Failed to send encoded data");

request_render(full_render);
request_render(wants_full_render);
}
});
let default_art = Arc::new(default_art.to_vec());
Expand All @@ -169,10 +212,11 @@ impl Iterm2 {
sender,
encoded_data_receiver: encoded_rx,
state: State::Initial,
last_id: 0,
}
}

fn encode(width: u16, height: u16, data: &[u8], max_size_px: Size) -> Result<EncodedData> {
fn encode(width: u16, height: u16, data: &[u8], max_size_px: Size, id: u64) -> Result<EncodedData> {
let start = std::time::Instant::now();
let (iwidth, iheight) = match get_image_area_size_px(width, height, max_size_px) {
Ok(v) => v,
Expand All @@ -192,12 +236,13 @@ impl Iterm2 {

let content = base64::engine::general_purpose::STANDARD.encode(&jpg);

log::debug!(compressed_bytes = content.len(), image_bytes = jpg.len(), elapsed:? = start.elapsed(); "encoded data");
log::debug!(id, compressed_bytes = content.len(), image_bytes = jpg.len(), elapsed:? = start.elapsed(); "encoded data");
Ok(EncodedData {
content,
size: jpg.len(),
width: image.width(),
height: image.height(),
id,
})
}

Expand Down
3 changes: 2 additions & 1 deletion src/ui/image/kitty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
utils::{
image_proto::{get_image_area_size_px, resize_image},
macros::status_error,
mpsc::RecvLast,
tmux,
},
};
Expand Down Expand Up @@ -104,7 +105,7 @@ impl KittyImageState {
let data_sender = image_data_to_transfer_channel.0;

std::thread::spawn(move || {
while let Ok((vec, width, height)) = rx.recv() {
while let Ok((vec, width, height)) = rx.recv_last() {
let data = match create_data_to_transfer(&vec, width, height, Compression::new(6), max_size) {
Ok(data) => data,
Err(err) => {
Expand Down
Loading

0 comments on commit 3e4951b

Please # to comment.