feat: improve the UX of the input component (#2935)

This commit is contained in:
三咲雅 misaki masa 2025-06-30 16:27:25 +08:00 committed by GitHub
parent 8657e6b6f5
commit 5a66559d1c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 76 additions and 61 deletions

View file

@ -2,8 +2,7 @@ use std::{collections::HashMap, path::PathBuf};
use yazi_proxy::options::CmpItem;
use yazi_shared::Id;
use crate::Scrollable;
use yazi_widgets::Scrollable;
#[derive(Default)]
pub struct Cmp {

View file

@ -1,8 +1,8 @@
use yazi_fs::Step;
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use yazi_widgets::{Scrollable, Step};
use crate::{Scrollable, cmp::Cmp};
use crate::cmp::Cmp;
struct Opt {
step: Step,
@ -23,7 +23,7 @@ impl Cmp {
impl Scrollable for Cmp {
#[inline]
fn len(&self) -> usize { self.cands.len() }
fn total(&self) -> usize { self.cands.len() }
#[inline]
fn limit(&self) -> usize { self.cands.len().min(10) }

View file

@ -1,6 +1,6 @@
use yazi_fs::Step;
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use yazi_widgets::Step;
use crate::{confirm::Confirm, mgr::Mgr};

View file

@ -1,9 +1,9 @@
use yazi_adapter::Dimension;
use yazi_fs::Step;
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use yazi_widgets::{Scrollable, Step};
use crate::{Scrollable, help::{HELP_MARGIN, Help}};
use crate::help::{HELP_MARGIN, Help};
struct Opt {
step: Step,
@ -28,7 +28,7 @@ impl Help {
impl Scrollable for Help {
#[inline]
fn len(&self) -> usize { self.bindings.len() }
fn total(&self) -> usize { self.bindings.len() }
#[inline]
fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }

View file

@ -4,8 +4,7 @@ use yazi_adapter::Dimension;
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
use yazi_macro::{render, render_and};
use yazi_shared::Layer;
use crate::Scrollable;
use yazi_widgets::Scrollable;
#[derive(Default)]
pub struct Help {

View file

@ -6,8 +6,6 @@
clippy::unit_arg
)]
yazi_macro::mod_flat!(scrollable);
yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which);
pub fn init() {

View file

@ -1,9 +1,9 @@
use yazi_config::YAZI;
use yazi_fs::Step;
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use yazi_widgets::{Scrollable, Step};
use crate::{Scrollable, pick::Pick};
use crate::pick::Pick;
struct Opt {
step: Step,
@ -24,7 +24,7 @@ impl Pick {
impl Scrollable for Pick {
#[inline]
fn len(&self) -> usize { self.items.len() }
fn total(&self) -> usize { self.items.len() }
#[inline]
fn limit(&self) -> usize {

View file

@ -1,8 +1,7 @@
use anyhow::Result;
use tokio::sync::oneshot::Sender;
use yazi_config::popup::Position;
use crate::Scrollable;
use yazi_widgets::Scrollable;
#[derive(Default)]
pub struct Pick {

View file

@ -1,7 +1,7 @@
use yazi_fs::Step;
use yazi_macro::render;
use yazi_proxy::MgrProxy;
use yazi_shared::event::CmdCow;
use yazi_widgets::Step;
use crate::spot::Spot;

View file

@ -1,7 +1,7 @@
use yazi_fs::Step;
use yazi_macro::render;
use yazi_proxy::MgrProxy;
use yazi_shared::event::CmdCow;
use yazi_widgets::Step;
use crate::tab::Tab;

View file

@ -2,12 +2,11 @@ use std::mem;
use yazi_config::{LAYOUT, YAZI};
use yazi_dds::Pubsub;
use yazi_fs::{File, Files, FilesOp, FolderStage, Step, cha::Cha};
use yazi_fs::{File, Files, FilesOp, FolderStage, cha::Cha};
use yazi_macro::err;
use yazi_proxy::MgrProxy;
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
use crate::Scrollable;
use yazi_widgets::{Scrollable, Step};
pub struct Folder {
pub url: Url,
@ -100,7 +99,7 @@ impl Folder {
let mut b = if self.files.is_empty() {
(mem::take(&mut self.cursor), mem::take(&mut self.offset)) != (0, 0)
} else {
self.scroll(step.into())
self.scroll(step)
};
self.trace = self.hovered().filter(|_| b).map(|h| h.urn_owned()).or(self.trace.take());
@ -169,7 +168,7 @@ impl Folder {
impl Scrollable for Folder {
#[inline]
fn len(&self) -> usize { self.files.len() }
fn total(&self) -> usize { self.files.len() }
#[inline]
fn limit(&self) -> usize { LAYOUT.get().limit() }

View file

@ -1,6 +1,6 @@
use yazi_fs::Step;
use yazi_macro::render;
use yazi_shared::event::CmdCow;
use yazi_widgets::Step;
use crate::tasks::Tasks;

View file

@ -1,6 +1,6 @@
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{ListItem, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget}};
use yazi_config::THEME;
use yazi_core::Scrollable;
use yazi_widgets::Scrollable;
use crate::Ctx;
@ -17,11 +17,11 @@ impl Widget for List<'_> {
let pick = &self.cx.pick;
// Vertical scrollbar
if pick.len() > pick.limit() {
if pick.total() > pick.limit() {
Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
area,
buf,
&mut ScrollbarState::new(pick.len()).position(pick.cursor),
&mut ScrollbarState::new(pick.total()).position(pick.cursor),
);
}

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(cha mounts);
yazi_macro::mod_flat!(calculator cwd file files filter fns op path sorter sorting stage step xdg);
yazi_macro::mod_flat!(calculator cwd file files filter fns op path sorter sorting stage xdg);
pub fn init() {
CWD.init(<_>::default());

View file

@ -30,21 +30,35 @@ impl Input {
return;
}
render!(self.handle_op(opt.step.cursor(snap), false));
let (o_cur, n_cur) = (snap.cursor, opt.step.cursor(snap));
render!(self.handle_op(n_cur, false));
let (limit, snap) = (self.limit, self.snap_mut());
if snap.offset > snap.cursor {
snap.offset = snap.cursor;
} else if snap.value.is_empty() {
snap.offset = 0;
} else {
let delta = snap.mode.delta();
let range = snap.offset..snap.cursor + delta;
if snap.width(range.clone()) >= limit as u16 {
let it = snap.slice(range).chars().rev().map(|c| if snap.obscure { '•' } else { c });
snap.offset = snap.cursor - InputSnap::find_window(it, 0, limit).end.saturating_sub(delta);
}
if snap.value.is_empty() {
return snap.offset = 0;
}
let (o_off, scrolloff) = (snap.offset, 5.min(limit / 2));
snap.offset = if n_cur <= o_cur {
let it = snap.slice(0..n_cur).chars().rev().map(|c| if snap.obscure { '•' } else { c });
let pad = InputSnap::find_window(it, 0, scrolloff).end;
if n_cur >= o_off { snap.offset.min(n_cur - pad) } else { n_cur - pad }
} else {
let count = snap.count();
let it = snap.slice(n_cur..count).chars().map(|c| if snap.obscure { '•' } else { c });
let pad = InputSnap::find_window(it, 0, scrolloff + snap.mode.delta()).end;
let it = snap.slice(0..n_cur + pad).chars().rev().map(|c| if snap.obscure { '•' } else { c });
let max = InputSnap::find_window(it, 0, limit).end;
if snap.width(o_off..n_cur) < limit as u16 {
snap.offset.max(n_cur + pad - max)
} else {
n_cur + pad - max
}
};
}
}

View file

@ -6,5 +6,7 @@ use crate::input::Input;
impl Input {
pub fn redo(&mut self, _: CmdCow) {
render!(self.snaps.redo());
self.r#move(0);
}
}

View file

@ -8,9 +8,12 @@ impl Input {
if !self.snaps.undo() {
return;
}
self.r#move(0);
if self.snap().mode == InputMode::Insert {
self.escape(());
}
render!();
}
}

View file

@ -1 +1,3 @@
yazi_macro::mod_pub!(input);
yazi_macro::mod_flat!(scrollable step);

View file

@ -1,25 +1,25 @@
use yazi_fs::Step;
use crate::Step;
pub trait Scrollable {
fn len(&self) -> usize;
fn total(&self) -> usize;
fn limit(&self) -> usize;
fn scrolloff(&self) -> usize { self.limit() / 2 }
fn cursor_mut(&mut self) -> &mut usize;
fn offset_mut(&mut self) -> &mut usize;
fn scroll(&mut self, step: Step) -> bool {
let new = step.add(*self.cursor_mut(), self.len(), self.limit());
fn scroll(&mut self, step: impl Into<Step>) -> bool {
let new = step.into().add(*self.cursor_mut(), self.total(), self.limit());
if new > *self.cursor_mut() { self.next(new) } else { self.prev(new) }
}
fn next(&mut self, n_cur: usize) -> bool {
let (o_cur, o_off) = (*self.cursor_mut(), *self.offset_mut());
let (len, limit, scrolloff) = (self.len(), self.limit(), self.scrolloff());
let (total, limit, scrolloff) = (self.total(), self.limit(), self.scrolloff());
let n_off = if n_cur < len.min(o_off + limit).saturating_sub(scrolloff) {
o_off.min(len.saturating_sub(1))
let n_off = if n_cur < total.min(o_off + limit).saturating_sub(scrolloff) {
o_off.min(total.saturating_sub(1))
} else {
len.saturating_sub(limit).min(o_off + n_cur - o_cur)
total.saturating_sub(limit).min(o_off + n_cur - o_cur)
};
*self.cursor_mut() = n_cur;
@ -33,7 +33,7 @@ pub trait Scrollable {
let n_off = if n_cur < o_off + self.scrolloff() {
o_off.saturating_sub(o_cur - n_cur)
} else {
self.len().saturating_sub(1).min(o_off)
self.total().saturating_sub(1).min(o_off)
};
*self.cursor_mut() = n_cur;

View file

@ -8,16 +8,16 @@ pub enum Step {
Bot,
Prev,
Next,
Fixed(isize),
Offset(isize),
Percent(i8),
}
impl Default for Step {
fn default() -> Self { Self::Fixed(0) }
fn default() -> Self { Self::Offset(0) }
}
impl From<isize> for Step {
fn from(n: isize) -> Self { Self::Fixed(n) }
fn from(n: isize) -> Self { Self::Offset(n) }
}
impl FromStr for Step {
@ -30,7 +30,7 @@ impl FromStr for Step {
"prev" => Self::Prev,
"next" => Self::Next,
s if s.ends_with('%') => Self::Percent(s[..s.len() - 1].parse()?),
s => Self::Fixed(s.parse()?),
s => Self::Offset(s.parse()?),
})
}
}
@ -53,22 +53,22 @@ impl Step {
return 0;
}
let fixed = match self {
let off = match self {
Self::Top => return 0,
Self::Bot => return len - 1,
Self::Prev => -1,
Self::Next => 1,
Self::Fixed(n) => n,
Self::Offset(n) => n,
Self::Percent(0) => 0,
Self::Percent(n) => n as isize * limit as isize / 100,
};
if matches!(self, Self::Prev | Self::Next) {
fixed.saturating_add_unsigned(pos).rem_euclid(len as _) as _
} else if fixed >= 0 {
pos.saturating_add_signed(fixed)
off.saturating_add_unsigned(pos).rem_euclid(len as _) as _
} else if off >= 0 {
pos.saturating_add_signed(off)
} else {
pos.saturating_sub(fixed.unsigned_abs())
pos.saturating_sub(off.unsigned_abs())
}
.min(len - 1)
}