fix: popup components (Input, Select, etc.) being covered by previewed images (#360)

This commit is contained in:
三咲雅 · Misaki Masa 2023-11-23 21:36:35 +08:00 committed by GitHub
parent c643afd6e6
commit 63b7491ad7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 377 additions and 257 deletions

View file

@ -7,7 +7,7 @@ use tracing::warn;
use yazi_config::PREVIEW;
use yazi_shared::{env_exists, RoCell};
use super::{Iterm2, Kitty};
use super::{Iterm2, Kitty, KittyOld};
use crate::{ueberzug::Ueberzug, Sixel, TMUX};
static IMAGE_SHOWN: AtomicBool = AtomicBool::new(false);
@ -18,6 +18,7 @@ static UEBERZUG: RoCell<Option<UnboundedSender<Option<(PathBuf, Rect)>>>> = RoCe
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Adaptor {
Kitty,
KittyOld,
Iterm2,
Sixel,
@ -83,7 +84,7 @@ impl Adaptor {
let mut protocols = match Self::emulator() {
Emulator::Unknown => vec![],
Emulator::Kitty => vec![Self::Kitty],
Emulator::Konsole => vec![Self::Kitty, Self::Iterm2, Self::Sixel],
Emulator::Konsole => vec![Self::KittyOld, Self::Iterm2, Self::Sixel],
Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel],
Emulator::WezTerm => vec![Self::Iterm2, Self::Sixel],
Emulator::Foot => vec![Self::Sixel],
@ -99,6 +100,9 @@ impl Adaptor {
if env_exists("ZELLIJ_SESSION_NAME") {
protocols.retain(|p| *p == Self::Sixel);
}
if *TMUX && protocols.len() > 1 {
protocols.retain(|p| *p != Self::KittyOld);
}
if let Some(p) = protocols.first() {
return *p;
}
@ -148,6 +152,7 @@ impl ToString for Adaptor {
fn to_string(&self) -> String {
match self {
Self::Kitty => "kitty",
Self::KittyOld => "kitty",
Self::Iterm2 => "iterm2",
Self::Sixel => "sixel",
Self::X11 => "x11",
@ -174,6 +179,7 @@ impl Adaptor {
match self {
Self::Kitty => Kitty::image_show(path, rect).await,
Self::KittyOld => KittyOld::image_show(path, rect).await,
Self::Iterm2 => Iterm2::image_show(path, rect).await,
Self::Sixel => Sixel::image_show(path, rect).await,
_ => Ok(if let Some(tx) = &*UEBERZUG {
@ -190,6 +196,7 @@ impl Adaptor {
match self {
Self::Kitty => Kitty::image_hide(rect),
Self::Iterm2 => Iterm2::image_hide(rect),
Self::KittyOld => KittyOld::image_hide(),
Self::Sixel => Sixel::image_hide(rect),
_ => Ok(if let Some(tx) = &*UEBERZUG {
tx.send(None)?;
@ -199,6 +206,6 @@ impl Adaptor {
#[inline]
pub(super) fn needs_ueberzug(self) -> bool {
!matches!(self, Self::Kitty | Self::Iterm2 | Self::Sixel)
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)
}
}

View file

@ -0,0 +1,76 @@
use std::{io::{stdout, Write}, path::Path};
use anyhow::Result;
use base64::{engine::general_purpose, Engine};
use image::DynamicImage;
use ratatui::prelude::Rect;
use yazi_shared::Term;
use super::image::Image;
use crate::{CLOSE, ESCAPE, START};
pub(super) struct KittyOld;
impl KittyOld {
pub(super) async fn image_show(path: &Path, rect: Rect) -> Result<()> {
let img = Image::downscale(path, (rect.width, rect.height)).await?;
let b = Self::encode(img).await?;
Self::image_hide()?;
Term::move_lock(stdout().lock(), (rect.x, rect.y), |stdout| Ok(stdout.write_all(&b)?))
}
#[inline]
pub(super) fn image_hide() -> Result<()> {
let mut stdout = stdout().lock();
stdout.write_all(format!("{}_Gq=1,a=d,d=A{}\\{}", START, ESCAPE, CLOSE).as_bytes())?;
stdout.flush()?;
Ok(())
}
async fn encode(img: DynamicImage) -> Result<Vec<u8>> {
fn output(raw: &[u8], format: u8, size: (u32, u32)) -> Result<Vec<u8>> {
let b64 = general_purpose::STANDARD.encode(raw).chars().collect::<Vec<_>>();
let mut it = b64.chunks(4096).peekable();
let mut buf = Vec::with_capacity(b64.len() + it.len() * 50);
if let Some(first) = it.next() {
write!(
buf,
"{}_Gq=1,a=T,z=-1,C=1,f={},s={},v={},m={};{}{}\\{}",
START,
format,
size.0,
size.1,
it.peek().is_some() as u8,
first.iter().collect::<String>(),
ESCAPE,
CLOSE
)?;
}
while let Some(chunk) = it.next() {
write!(
buf,
"{}_Gm={};{}{}\\{}",
START,
it.peek().is_some() as u8,
chunk.iter().collect::<String>(),
ESCAPE,
CLOSE
)?;
}
buf.write_all(CLOSE.as_bytes())?;
Ok(buf)
}
let size = (img.width(), img.height());
tokio::task::spawn_blocking(move || match img {
DynamicImage::ImageRgb8(v) => output(v.as_raw(), 24, size),
DynamicImage::ImageRgba8(v) => output(v.as_raw(), 32, size),
v => output(v.to_rgb8().as_raw(), 24, size),
})
.await?
}
}

View file

@ -4,12 +4,14 @@ mod adaptor;
mod image;
mod iterm2;
mod kitty;
mod kitty_old;
mod sixel;
mod ueberzug;
use adaptor::*;
use iterm2::*;
use kitty::*;
use kitty_old::*;
use sixel::*;
use yazi_shared::{env_exists, RoCell};

View file

@ -41,9 +41,9 @@ impl<'de> Deserialize<'de> for Opener {
#[serde(rename = "for")]
for_: Option<String>,
// TODO: remove this when v1.0.5 is released --
// TODO: remove this when v0.1.6 is released --
display_name: Option<String>,
// TODO: -- remove this when v1.0.5 is released
// TODO: -- remove this when v0.1.6 is released
}
let mut shadow = Shadow::deserialize(deserializer)?;

View file

@ -51,9 +51,4 @@ impl Ctx {
}
None
}
#[inline]
pub fn image_layer(&self) -> bool {
!self.which.visible && !self.help.visible && !self.tasks.visible
}
}

View file

@ -24,7 +24,6 @@ pub enum Event {
Files(FilesOp),
Pages(usize),
Mimetype(BTreeMap<Url, String>),
Peek(Option<(usize, Url)>),
Preview(PreviewLock),
// Input
@ -80,12 +79,6 @@ macro_rules! emit {
(Mimetype($mimes:expr)) => {
$crate::Event::Mimetype($mimes).emit();
};
(Peek) => {
$crate::Event::Peek(None).emit();
};
(Peek($skip:expr, $url:expr)) => {
$crate::Event::Peek(Some(($skip, $url))).emit();
};
(Preview($lock:expr)) => {
$crate::Event::Preview($lock).emit();
};

View file

@ -4,7 +4,7 @@ use yazi_config::{keymap::{Control, Key, KeymapLayer}, KEYMAP};
use yazi_shared::Term;
use super::HELP_MARGIN;
use crate::{emit, input::Input};
use crate::input::Input;
#[derive(Default)]
pub struct Help {
@ -34,8 +34,6 @@ impl Help {
self.offset = 0;
self.cursor = 0;
emit!(Peek); // Show/hide preview for images
true
}

View file

@ -44,7 +44,6 @@ impl Manager {
if let Ok(f) = File::from(child.clone()).await {
emit!(Files(FilesOp::Creating(cwd, f.into_map())));
Manager::_hover(Some(child));
Manager::_refresh();
}
Ok::<(), anyhow::Error>(())
});

View file

@ -23,25 +23,26 @@ impl Manager {
}
pub fn hover(&mut self, opt: impl Into<Opt>) -> bool {
// Hover on the file
let opt = opt.into() as Opt;
let b = self.current_mut().repos(opt.url);
// Re-peek
self.peek(0);
// Refresh watcher
let mut to_watch = BTreeSet::new();
for tab in self.tabs.iter() {
to_watch.insert(&tab.current.cwd);
match tab.current.hovered() {
Some(h) if h.is_dir() => _ = to_watch.insert(&h.url),
_ => {}
}
if let Some(ref p) = tab.parent {
to_watch.insert(&p.cwd);
}
if let Some(h) = tab.current.hovered().filter(|&h| h.is_dir()) {
to_watch.insert(&h.url);
}
}
self.watcher.watch(to_watch);
// Trigger peek
emit!(Peek);
// Hover
let opt = opt.into() as Opt;
self.current_mut().repos(opt.url)
b
}
}

View file

@ -1,31 +1,86 @@
use crate::manager::Manager;
use yazi_config::{keymap::{Exec, KeymapLayer}, MANAGER};
use yazi_shared::{Url, MIME_DIR};
use crate::{emit, manager::Manager};
#[derive(Debug)]
pub struct Opt {
step: isize,
only_if: Option<Url>,
upper_bound: bool,
}
impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self {
Self {
step: e.args.first().and_then(|s| s.parse().ok()).unwrap_or(0),
only_if: e.named.get("only-if").map(Url::from),
upper_bound: e.named.contains_key("upper-bound"),
}
}
}
impl From<isize> for Opt {
fn from(step: isize) -> Self { Self { step, only_if: None, upper_bound: false } }
}
impl Manager {
pub fn peek(&mut self, sequent: bool, show_image: bool) -> bool {
let Some(hovered) = self.hovered().cloned() else {
return self.active_mut().preview.reset(|_| true);
#[inline]
pub fn _peek_upper_bound(bound: usize, only_if: &Url) {
emit!(Call(
Exec::call("peek", vec![bound.to_string()])
.with("only-if", only_if.to_string())
.with_bool("upper-bound", true)
.vec(),
KeymapLayer::Manager
));
}
pub fn peek(&mut self, opt: impl Into<Opt>) -> bool {
let Some(hovered) = self.hovered() else {
return self.active_mut().preview.reset();
};
let url = &hovered.url;
if !show_image {
self.active_mut().preview.reset(|l| l.is_image());
}
if hovered.is_dir() {
let position = self.active().history(url).map(|f| (f.offset, f.files.len()));
self.active_mut().preview.folder(url, position, sequent);
let opt = opt.into() as Opt;
if matches!(opt.only_if, Some(ref u) if *u != hovered.url) {
return false;
}
let Some(mime) = self.mimetype.get(url).cloned() else {
return self.active_mut().preview.reset(|_| true);
if hovered.is_dir() {
return self.peek_folder(opt, hovered.url.clone());
}
let Some(mime) = self.mimetype.get(&hovered.url).cloned() else {
return self.active_mut().preview.reset();
};
if sequent {
self.active_mut().preview.sequent(url, &mime, show_image);
let (url, cha) = (hovered.url.clone(), hovered.cha);
if self.active().preview.same_url(&url) {
self.active_mut().preview.arrow(opt.step, &mime);
} else if opt.upper_bound {
self.active_mut().preview.apply_bound(opt.step as usize);
} else {
self.active_mut().preview.go(url, &mime, show_image);
self.active_mut().preview.set_skip(0);
}
self.active_mut().preview.go(&url, cha, &mime);
false
}
fn peek_folder(&mut self, opt: Opt, url: Url) -> bool {
let folder = self.active().history.get(&url);
let (skip, bound) = folder
.map(|f| (f.offset, f.files.len().saturating_sub(MANAGER.layout.folder_height())))
.unwrap_or_default();
if self.active().preview.same_url(&url) {
self.active_mut().preview.arrow(opt.step, MIME_DIR);
self.active_mut().preview.apply_bound(bound);
return false;
}
let in_chunks = folder.is_none();
self.active_mut().preview.set_skip(skip);
self.active_mut().preview.go_folder(url, in_chunks);
false
}
}

View file

@ -31,7 +31,7 @@ impl Tabs {
#[inline]
pub(super) fn set_idx(&mut self, idx: usize) {
self.idx = idx;
self.active_mut().preview.reset(|l| l.is_image());
self.active_mut().preview.reset_image();
Manager::_refresh();
}
}

View file

@ -3,3 +3,5 @@ mod provider;
pub use preview::*;
use provider::*;
pub static COLLISION: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);

View file

@ -1,13 +1,13 @@
use std::time::Duration;
use std::{mem, time::Duration};
use tokio::{pin, task::JoinHandle};
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use yazi_adaptor::ADAPTOR;
use yazi_config::MANAGER;
use yazi_shared::{MimeKind, PeekError, Url, MIME_DIR};
use yazi_shared::{Cha, MimeKind, PeekError, Url};
use super::Provider;
use crate::{emit, files::{Files, FilesOp}, Highlighter};
use crate::{emit, files::{Files, FilesOp}, manager::Manager, Highlighter};
#[derive(Default)]
pub struct Preview {
@ -19,7 +19,7 @@ pub struct Preview {
pub struct PreviewLock {
pub url: Url,
pub mime: String,
pub cha: Option<Cha>,
pub skip: usize,
pub data: PreviewData,
}
@ -32,125 +32,59 @@ pub enum PreviewData {
}
impl Preview {
pub fn go(&mut self, url: &Url, mime: &str, show_image: bool) {
let kind = MimeKind::new(mime);
if !show_image && kind.show_as_image() {
return;
} else if self.same(url, mime) {
pub fn go(&mut self, url: &Url, cha: Cha, mime: &str) {
if self.content_unchanged(url, &cha) {
return;
}
self.reset(|_| true);
if !self.same_mime(url, mime) {
self.skip = 0;
}
self.reset();
let (url, kind, skip) = (url.clone(), MimeKind::new(mime), self.skip);
let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip);
self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &url, skip).await {
Ok(data) => {
emit!(Preview(PreviewLock { url, mime, skip, data }));
emit!(Preview(PreviewLock { url, cha: Some(cha), skip, data }));
}
Err(PeekError::Exceed(max)) => {
emit!(Peek(max, url));
Manager::_peek_upper_bound(max, &url);
}
_ => {}
}
}));
}
pub fn folder(&mut self, url: &Url, position: Option<(usize, usize)>, sequent: bool) {
if let Some((_, len)) = position {
self.skip = self.skip.min(len.saturating_sub(MANAGER.layout.preview_height()));
}
if self.same(url, MIME_DIR) {
return;
} else if !self.same_mime(url, MIME_DIR) {
self.skip = position.map(|(offset, _)| offset).unwrap_or(0);
}
self.reset(|_| true);
emit!(Preview(PreviewLock {
pub fn go_folder(&mut self, url: Url, in_chunks: bool) {
self.reset();
self.lock = Some(PreviewLock {
url: url.clone(),
mime: MIME_DIR.to_owned(),
cha: None,
skip: self.skip,
data: PreviewData::Folder,
}));
});
if sequent {
return;
}
let url = url.clone();
self.handle = Some(tokio::spawn(async move {
let Ok(rx) = Files::from_dir(&url).await else {
emit!(Files(FilesOp::IOErr(url)));
emit!(Files(FilesOp::IOErr(url.clone())));
return;
};
if position.is_some() {
emit!(Files(FilesOp::Full(url, UnboundedReceiverStream::new(rx).collect().await)));
if !in_chunks {
emit!(Files(FilesOp::Full(url.clone(), UnboundedReceiverStream::new(rx).collect().await)));
return;
}
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(500));
pin!(rx);
let stream =
UnboundedReceiverStream::new(rx).chunks_timeout(10000, Duration::from_millis(500));
pin!(stream);
let ticket = FilesOp::prepare(&url);
while let Some(chunk) = rx.next().await {
while let Some(chunk) = stream.next().await {
emit!(Files(FilesOp::Part(url.clone(), ticket, chunk)));
}
}));
}
pub fn sequent(&mut self, url: &Url, mime: &str, show_image: bool) {
let kind = MimeKind::new(mime);
if !show_image && kind.show_as_image() {
return;
} else if self.same(url, mime) {
return;
}
self.handle.take().map(|h| h.abort());
Highlighter::abort();
let (url, mime, skip) = (url.clone(), mime.to_owned(), self.skip);
self.handle = Some(tokio::spawn(async move {
match Provider::auto(kind, &url, skip).await {
Ok(data) => {
emit!(Preview(PreviewLock { url, mime, skip, data }));
}
Err(PeekError::Exceed(max)) => {
emit!(Peek(max, url));
}
_ => {}
}
}));
}
pub fn arrow(&mut self, step: isize) -> bool {
let Some(kind) = self.lock.as_ref().map(|l| MimeKind::new(&l.mime)) else {
return false;
};
let old = self.skip;
let size = Provider::step_size(kind, step.unsigned_abs());
self.skip = if step < 0 { old.saturating_sub(size) } else { old + size };
self.skip != old
}
pub fn arrow_max(&mut self, max: usize) -> bool {
if self.skip > max {
self.skip = max;
return true;
}
false
}
pub fn reset<F: FnOnce(&PreviewLock) -> bool>(&mut self, f: F) -> bool {
pub fn reset(&mut self) -> bool {
self.handle.take().map(|h| h.abort());
Highlighter::abort();
ADAPTOR.image_hide(MANAGER.layout.image_rect()).ok();
@ -159,39 +93,71 @@ impl Preview {
return false;
};
if !f(lock) {
return false;
}
let b = !lock.is_image();
self.lock = None;
b
}
pub fn reset_image(&mut self) -> bool {
if !matches!(self.lock, Some(ref lock) if lock.is_image()) {
return false;
}
self.reset();
true
}
#[inline]
pub fn same_url(&self, url: &Url) -> bool {
matches!(self.lock, Some(ref lock) if lock.url == *url)
}
fn content_unchanged(&self, url: &Url, cha: &Cha) -> bool {
let Some(lock) = &self.lock else {
return false;
};
let Some(cha_) = &lock.cha else {
return false;
};
*url == lock.url
&& self.skip == lock.skip
&& cha.len == cha_.len
&& cha.modified == cha_.modified
&& cha.meta == cha_.meta
&& {
#[cfg(unix)]
{
cha.permissions == cha_.permissions
}
#[cfg(windows)]
{
true
}
}
}
}
impl Preview {
// --- skip
#[inline]
pub fn same(&self, url: &Url, mime: &str) -> bool {
if let Some(ref lock) = self.lock {
return &lock.url == url && lock.mime == mime && lock.skip == self.skip;
}
false
pub fn arrow(&mut self, step: isize, mime: &str) -> bool {
let size = Provider::step_size(MimeKind::new(mime), step.unsigned_abs());
let skip = if step < 0 { self.skip.saturating_sub(size) } else { self.skip + size };
mem::replace(&mut self.skip, skip) != skip
}
#[inline]
pub fn same_mime(&self, url: &Url, mime: &str) -> bool {
if let Some(ref lock) = self.lock {
return &lock.url == url && lock.mime == mime;
}
false
}
pub fn set_skip(&mut self, skip: usize) -> bool { mem::replace(&mut self.skip, skip) != skip }
#[inline]
pub fn same_path(&self, url: &Url) -> bool {
if let Some(ref lock) = self.lock {
return &lock.url == url;
pub fn apply_bound(&mut self, upper: usize) -> bool {
if self.skip <= upper {
return false;
}
false
self.skip = upper;
true
}
}

View file

@ -1,6 +1,6 @@
use yazi_config::keymap::Exec;
use crate::{emit, tab::Tab};
use crate::{manager::Manager, tab::Tab};
impl Tab {
pub fn hidden(&mut self, e: &Exec) -> bool {
@ -10,7 +10,7 @@ impl Tab {
_ => !self.conf.show_hidden,
};
if self.apply_files_attrs(false) {
emit!(Peek);
Manager::_hover(None);
return true;
}
false

View file

@ -76,7 +76,7 @@ impl Tab {
handle.abort();
}
if self.current.cwd.is_search() {
self.preview.reset(|l| l.is_image());
self.preview.reset_image();
let rep = self.history_new(&self.current.cwd.to_regular());
drop(mem::replace(&mut self.current, rep));

View file

@ -47,21 +47,9 @@ impl From<&Url> for Tab {
}
impl Tab {
pub fn update_peek(&mut self, max: usize, url: Url) -> bool {
let Some(hovered) = self.current.hovered() else {
return false;
};
if url != hovered.url {
return false;
}
self.preview.arrow_max(max)
}
pub fn update_preview(&mut self, lock: PreviewLock) -> bool {
let Some(hovered) = self.current.hovered().map(|h| &h.url) else {
return self.preview.reset(|_| true);
return self.preview.reset();
};
if lock.url != *hovered {

View file

@ -1,6 +1,6 @@
use yazi_config::keymap::Exec;
use crate::{emit, tasks::Tasks};
use crate::tasks::Tasks;
pub struct Opt;
@ -14,7 +14,6 @@ impl From<()> for Opt {
impl Tasks {
pub fn toggle(&mut self, _: impl Into<Opt>) -> bool {
self.visible = !self.visible;
emit!(Peek); // Show/hide preview for images
true
}
}

View file

@ -24,7 +24,7 @@ impl Which {
self.times = 1;
self.cands =
KEYMAP.get(layer).iter().filter(|s| s.on.len() > 1 && &s.on[0] == key).cloned().collect();
self.switch(true);
self.visible = true;
true
}
@ -35,22 +35,16 @@ impl Which {
.collect();
if self.cands.is_empty() {
self.switch(false);
self.visible = false;
} else if self.cands.len() == 1 {
self.switch(false);
self.visible = false;
emit!(Call(self.cands[0].to_call(), self.layer));
} else if let Some(i) = self.cands.iter().position(|c| c.on.len() == self.times + 1) {
self.switch(false);
self.visible = false;
emit!(Call(self.cands[i].to_call(), self.layer));
}
self.times += 1;
true
}
#[inline]
fn switch(&mut self, state: bool) {
self.visible = state;
emit!(Peek); // Show/hide preview for images
}
}

View file

@ -1,11 +1,11 @@
use std::ffi::OsString;
use std::{ffi::OsString, sync::atomic::Ordering};
use anyhow::{Ok, Result};
use crossterm::event::KeyEvent;
use ratatui::prelude::Rect;
use ratatui::{backend::Backend, prelude::Rect};
use tokio::sync::oneshot;
use yazi_config::{keymap::{Exec, Key, KeymapLayer}, BOOT};
use yazi_core::{emit, files::FilesOp, input::InputMode, manager::Manager, Ctx, Event};
use yazi_core::{emit, files::FilesOp, input::InputMode, manager::Manager, preview::COLLISION, Ctx, Event};
use yazi_shared::Term;
use crate::{Executor, Logs, Panic, Root, Signals};
@ -33,7 +33,7 @@ impl App {
}
Event::Key(key) => app.dispatch_key(key),
Event::Paste(str) => app.dispatch_paste(str),
Event::Render(_) => app.dispatch_render(),
Event::Render(_) => app.dispatch_render()?,
Event::Resize(cols, rows) => app.dispatch_resize(cols, rows),
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
Event::Call(exec, layer) => app.dispatch_call(exec, layer),
@ -76,18 +76,47 @@ impl App {
}
}
fn dispatch_render(&mut self) {
if let Some(term) = &mut self.term {
_ = term.draw(|f| {
yazi_plugin::scope(&self.cx, |_| {
f.render_widget(Root::new(&self.cx), f.size());
});
fn dispatch_render(&mut self) -> Result<()> {
let Some(term) = &mut self.term else {
return Ok(());
};
if let Some((x, y)) = self.cx.cursor() {
f.set_cursor(x, y);
}
let collision = COLLISION.swap(false, Ordering::Relaxed);
let frame = term.draw(|f| {
yazi_plugin::scope(&self.cx, |_| {
f.render_widget(Root::new(&self.cx), f.size());
});
if let Some((x, y)) = self.cx.cursor() {
f.set_cursor(x, y);
}
})?;
if !COLLISION.load(Ordering::Relaxed) {
if collision {
// Reload preview if collision is resolved
self.cx.manager.active_mut().preview.reset();
self.cx.manager.peek(0);
}
return Ok(());
}
let mut patches = Vec::new();
for x in frame.area.left()..frame.area.right() {
for y in frame.area.top()..frame.area.bottom() {
let cell = frame.buffer.get(x, y);
if cell.skip {
patches.push((x, y, cell.clone()));
}
}
}
term.backend_mut().draw(patches.iter().map(|(x, y, cell)| (*x, *y, cell)))?;
if let Some((x, y)) = self.cx.cursor() {
term.show_cursor()?;
term.set_cursor(x, y)?;
}
term.backend_mut().flush()?;
Ok(())
}
fn dispatch_resize(&mut self, cols: u16, rows: u16) {
@ -96,13 +125,13 @@ impl App {
}
self.cx.manager.current_mut().set_page(true);
self.cx.manager.active_mut().preview.reset(|_| true);
self.cx.manager.peek(true, self.cx.image_layer());
self.cx.manager.active_mut().preview.reset();
self.cx.manager.peek(0);
emit!(Render);
}
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
self.cx.manager.active_mut().preview.reset(|l| l.is_image());
self.cx.manager.active_mut().preview.reset_image();
if state {
self.signals.stop_term(true);
self.term = None;
@ -148,15 +177,7 @@ impl App {
Event::Mimetype(mimes) => {
if manager.update_mimetype(mimes, tasks) {
emit!(Render);
emit!(Peek);
}
}
Event::Peek(sequent) => {
if let Some((max, url)) = sequent {
manager.active_mut().update_peek(max, url);
self.cx.manager.peek(true, self.cx.image_layer());
} else {
self.cx.manager.peek(false, self.cx.image_layer());
manager.peek(0);
}
}
Event::Preview(lock) => {

View file

@ -1,9 +1,11 @@
use std::path::MAIN_SEPARATOR;
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
use yazi_config::{popup::{Offset, Position}, THEME};
use yazi_core::Ctx;
use crate::widgets;
pub(crate) struct Completion<'a> {
cx: &'a Ctx,
}
@ -53,7 +55,7 @@ impl<'a> Widget for Completion<'a> {
area.height = rect.height.saturating_sub(area.y).min(area.height);
}
Clear.render(area, buf);
widgets::Clear.render(area, buf);
List::new(items)
.block(
Block::new()

View file

@ -88,6 +88,7 @@ impl<'a> Executor<'a> {
};
}
on!(MANAGER, peek);
on!(MANAGER, hover);
on!(MANAGER, refresh);
on!(MANAGER, quit, &self.cx.tasks);
@ -139,11 +140,6 @@ impl<'a> Executor<'a> {
on!(TABS, swap);
match exec.cmd.as_bytes() {
b"peek" => {
let step = exec.args.first().and_then(|s| s.parse().ok()).unwrap_or(0);
self.cx.manager.active_mut().preview.arrow(step);
self.cx.manager.peek(true, self.cx.image_layer())
}
// Tasks
b"tasks_show" => self.cx.tasks.toggle(()),
// Help

View file

@ -1,8 +1,9 @@
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, widgets::{Clear, Paragraph, Widget}};
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, widgets::{Paragraph, Widget}};
use yazi_config::THEME;
use yazi_core::Ctx;
use super::Bindings;
use crate::widgets;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,
@ -19,7 +20,7 @@ impl<'a> Widget for Layout<'a> {
.constraints([Constraint::Min(0), Constraint::Length(1)])
.split(area);
Clear.render(area, buf);
widgets::Clear.render(area, buf);
let help = &self.cx.help;
Paragraph::new(help.keyword().unwrap_or_else(|| format!("{}.help", help.layer)))

View file

@ -1,11 +1,13 @@
use std::ops::Range;
use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}};
use ratatui::{buffer::Buffer, layout::Rect, text::{Line, Text}, widgets::{Block, BorderType, Borders, Paragraph, Widget}};
use yazi_config::THEME;
use yazi_core::{input::InputMode, Ctx};
use yazi_shared::Term;
use crate::widgets;
pub(crate) struct Input<'a> {
cx: &'a Ctx,
}
@ -25,7 +27,7 @@ impl<'a> Widget for Input<'a> {
Text::from(input.value())
};
Clear.render(area, buf);
widgets::Clear.render(area, buf);
Paragraph::new(value)
.block(
Block::new()

View file

@ -12,6 +12,7 @@ mod select;
mod signals;
mod tasks;
mod which;
mod widgets;
use app::*;
use executor::*;

View file

@ -1,7 +1,9 @@
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}};
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, List, ListItem, Widget}};
use yazi_config::THEME;
use yazi_core::Ctx;
use crate::widgets;
pub(crate) struct Select<'a> {
cx: &'a Ctx,
}
@ -28,7 +30,7 @@ impl<'a> Widget for Select<'a> {
})
.collect::<Vec<_>>();
Clear.render(area, buf);
widgets::Clear.render(area, buf);
List::new(items)
.block(
Block::new()

View file

@ -1,19 +0,0 @@
use ratatui::{buffer::Buffer, layout::Rect, widgets::{self, Widget}};
pub(super) struct Clear;
impl Widget for Clear {
fn render(self, mut area: Rect, buf: &mut Buffer) {
if area.x > 0 {
area.x -= 1;
area.width += 2;
}
if area.y > 0 {
area.y -= 1;
area.height += 2;
}
widgets::Clear.render(area, buf)
}
}

View file

@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Direction, R
use yazi_config::THEME;
use yazi_core::{tasks::TASKS_PERCENT, Ctx};
use super::Clear;
use crate::widgets;
pub(crate) struct Layout<'a> {
cx: &'a Ctx,
@ -36,7 +36,7 @@ impl<'a> Widget for Layout<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let area = Self::area(area);
Clear.render(area, buf);
widgets::Clear.render(area, buf);
let block = Block::new()
.title({
let mut line = Line::from("Tasks");

View file

@ -1,5 +1,3 @@
mod clear;
mod layout;
use clear::*;
pub(super) use layout::*;

View file

@ -1,8 +1,9 @@
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, widgets::{Block, Clear, Widget}};
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, widgets::{Block, Widget}};
use yazi_config::THEME;
use yazi_core::Ctx;
use super::Side;
use crate::widgets;
pub(crate) struct Which<'a> {
cx: &'a Ctx,
@ -38,7 +39,7 @@ impl Widget for Which<'_> {
.constraints([Constraint::Ratio(1, 3), Constraint::Ratio(1, 3), Constraint::Ratio(1, 3)])
.split(area);
Clear.render(area, buf);
widgets::Clear.render(area, buf);
Block::new().style(THEME.which.mask.into()).render(area, buf);
Side::new(which.times, cands.0).render(chunks[0], buf);
Side::new(which.times, cands.1).render(chunks[1], buf);

View file

@ -0,0 +1,43 @@
use std::sync::atomic::Ordering;
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
use yazi_adaptor::ADAPTOR;
use yazi_config::MANAGER;
use yazi_core::preview::COLLISION;
pub(crate) struct Clear;
#[inline]
const fn is_overlapping(a: &Rect, b: &Rect) -> bool {
a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y
}
fn overlap(a: &Rect, b: &Rect) -> Option<Rect> {
if !is_overlapping(a, b) {
return None;
}
let x = a.x.max(b.x);
let y = a.y.max(b.y);
let width = (a.x + a.width).min(b.x + b.width) - x;
let height = (a.y + a.height).min(b.y + b.height) - y;
Some(Rect { x, y, width, height })
}
impl Widget for Clear {
fn render(self, area: Rect, buf: &mut Buffer) {
ratatui::widgets::Clear.render(area, buf);
let Some(r) = overlap(&area, &MANAGER.layout.image_rect()) else {
return;
};
ADAPTOR.image_hide(r).ok();
COLLISION.store(true, Ordering::Relaxed);
for x in r.left()..r.right() {
for y in r.top()..r.bottom() {
buf.get_mut(x, y).set_skip(true);
}
}
}
}

View file

@ -0,0 +1,3 @@
mod clear;
pub(super) use clear::*;

View file

@ -14,17 +14,11 @@ impl<'a> Preview<'a> {
impl<'a> Widget for Preview<'a> {
fn render(self, area: Rect, buf: &mut Buffer) {
let manager = &self.cx.manager;
let Some(hovered) = manager.hovered().map(|h| &h.url) else {
let Some(ref lock) = self.cx.manager.active().preview.lock else {
return;
};
let preview = &manager.active().preview;
if !preview.same_path(hovered) {
return;
}
match &preview.lock.as_ref().unwrap().data {
match &lock.data {
PreviewData::Folder => {
Folder::preview(self.cx).render(area, buf);
}

View file

@ -3,7 +3,7 @@ use std::{fs::Metadata, time::SystemTime};
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChaMeta: u8 {
const DIR = 0b00000001;