mirror of
https://github.com/sxyazi/yazi.git
synced 2026-05-13 08:16:40 +00:00
feat: improve the UX of the input component (#2935)
This commit is contained in:
parent
8657e6b6f5
commit
5a66559d1c
20 changed files with 76 additions and 61 deletions
|
|
@ -2,8 +2,7 @@ use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use yazi_proxy::options::CmpItem;
|
use yazi_proxy::options::CmpItem;
|
||||||
use yazi_shared::Id;
|
use yazi_shared::Id;
|
||||||
|
use yazi_widgets::Scrollable;
|
||||||
use crate::Scrollable;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Cmp {
|
pub struct Cmp {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::{Scrollable, Step};
|
||||||
|
|
||||||
use crate::{Scrollable, cmp::Cmp};
|
use crate::cmp::Cmp;
|
||||||
|
|
||||||
struct Opt {
|
struct Opt {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
|
@ -23,7 +23,7 @@ impl Cmp {
|
||||||
|
|
||||||
impl Scrollable for Cmp {
|
impl Scrollable for Cmp {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize { self.cands.len() }
|
fn total(&self) -> usize { self.cands.len() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn limit(&self) -> usize { self.cands.len().min(10) }
|
fn limit(&self) -> usize { self.cands.len().min(10) }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::Step;
|
||||||
|
|
||||||
use crate::{confirm::Confirm, mgr::Mgr};
|
use crate::{confirm::Confirm, mgr::Mgr};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use yazi_adapter::Dimension;
|
use yazi_adapter::Dimension;
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::CmdCow;
|
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 {
|
struct Opt {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
|
@ -28,7 +28,7 @@ impl Help {
|
||||||
|
|
||||||
impl Scrollable for Help {
|
impl Scrollable for Help {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize { self.bindings.len() }
|
fn total(&self) -> usize { self.bindings.len() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }
|
fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ use yazi_adapter::Dimension;
|
||||||
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
||||||
use yazi_macro::{render, render_and};
|
use yazi_macro::{render, render_and};
|
||||||
use yazi_shared::Layer;
|
use yazi_shared::Layer;
|
||||||
|
use yazi_widgets::Scrollable;
|
||||||
use crate::Scrollable;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@
|
||||||
clippy::unit_arg
|
clippy::unit_arg
|
||||||
)]
|
)]
|
||||||
|
|
||||||
yazi_macro::mod_flat!(scrollable);
|
|
||||||
|
|
||||||
yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which);
|
yazi_macro::mod_pub!(cmp confirm help input mgr notify pick spot tab tasks which);
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::{Scrollable, Step};
|
||||||
|
|
||||||
use crate::{Scrollable, pick::Pick};
|
use crate::pick::Pick;
|
||||||
|
|
||||||
struct Opt {
|
struct Opt {
|
||||||
step: Step,
|
step: Step,
|
||||||
|
|
@ -24,7 +24,7 @@ impl Pick {
|
||||||
|
|
||||||
impl Scrollable for Pick {
|
impl Scrollable for Pick {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize { self.items.len() }
|
fn total(&self) -> usize { self.items.len() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn limit(&self) -> usize {
|
fn limit(&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::sync::oneshot::Sender;
|
use tokio::sync::oneshot::Sender;
|
||||||
use yazi_config::popup::Position;
|
use yazi_config::popup::Position;
|
||||||
|
use yazi_widgets::Scrollable;
|
||||||
use crate::Scrollable;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Pick {
|
pub struct Pick {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_proxy::MgrProxy;
|
use yazi_proxy::MgrProxy;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::Step;
|
||||||
|
|
||||||
use crate::spot::Spot;
|
use crate::spot::Spot;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_proxy::MgrProxy;
|
use yazi_proxy::MgrProxy;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::Step;
|
||||||
|
|
||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ use std::mem;
|
||||||
|
|
||||||
use yazi_config::{LAYOUT, YAZI};
|
use yazi_config::{LAYOUT, YAZI};
|
||||||
use yazi_dds::Pubsub;
|
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_macro::err;
|
||||||
use yazi_proxy::MgrProxy;
|
use yazi_proxy::MgrProxy;
|
||||||
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
|
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
|
||||||
|
use yazi_widgets::{Scrollable, Step};
|
||||||
use crate::Scrollable;
|
|
||||||
|
|
||||||
pub struct Folder {
|
pub struct Folder {
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
|
|
@ -100,7 +99,7 @@ impl Folder {
|
||||||
let mut b = if self.files.is_empty() {
|
let mut b = if self.files.is_empty() {
|
||||||
(mem::take(&mut self.cursor), mem::take(&mut self.offset)) != (0, 0)
|
(mem::take(&mut self.cursor), mem::take(&mut self.offset)) != (0, 0)
|
||||||
} else {
|
} else {
|
||||||
self.scroll(step.into())
|
self.scroll(step)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.trace = self.hovered().filter(|_| b).map(|h| h.urn_owned()).or(self.trace.take());
|
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 {
|
impl Scrollable for Folder {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn len(&self) -> usize { self.files.len() }
|
fn total(&self) -> usize { self.files.len() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn limit(&self) -> usize { LAYOUT.get().limit() }
|
fn limit(&self) -> usize { LAYOUT.get().limit() }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use yazi_fs::Step;
|
|
||||||
use yazi_macro::render;
|
use yazi_macro::render;
|
||||||
use yazi_shared::event::CmdCow;
|
use yazi_shared::event::CmdCow;
|
||||||
|
use yazi_widgets::Step;
|
||||||
|
|
||||||
use crate::tasks::Tasks;
|
use crate::tasks::Tasks;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{ListItem, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget}};
|
use ratatui::{buffer::Buffer, layout::{Margin, Rect}, widgets::{ListItem, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget, Widget}};
|
||||||
use yazi_config::THEME;
|
use yazi_config::THEME;
|
||||||
use yazi_core::Scrollable;
|
use yazi_widgets::Scrollable;
|
||||||
|
|
||||||
use crate::Ctx;
|
use crate::Ctx;
|
||||||
|
|
||||||
|
|
@ -17,11 +17,11 @@ impl Widget for List<'_> {
|
||||||
let pick = &self.cx.pick;
|
let pick = &self.cx.pick;
|
||||||
|
|
||||||
// Vertical scrollbar
|
// Vertical scrollbar
|
||||||
if pick.len() > pick.limit() {
|
if pick.total() > pick.limit() {
|
||||||
Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
|
Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
|
||||||
area,
|
area,
|
||||||
buf,
|
buf,
|
||||||
&mut ScrollbarState::new(pick.len()).position(pick.cursor),
|
&mut ScrollbarState::new(pick.total()).position(pick.cursor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
yazi_macro::mod_pub!(cha mounts);
|
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() {
|
pub fn init() {
|
||||||
CWD.init(<_>::default());
|
CWD.init(<_>::default());
|
||||||
|
|
|
||||||
|
|
@ -30,21 +30,35 @@ impl Input {
|
||||||
return;
|
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());
|
let (limit, snap) = (self.limit, self.snap_mut());
|
||||||
if snap.offset > snap.cursor {
|
if snap.value.is_empty() {
|
||||||
snap.offset = snap.cursor;
|
return snap.offset = 0;
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ use crate::input::Input;
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn redo(&mut self, _: CmdCow) {
|
pub fn redo(&mut self, _: CmdCow) {
|
||||||
render!(self.snaps.redo());
|
render!(self.snaps.redo());
|
||||||
|
|
||||||
|
self.r#move(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,12 @@ impl Input {
|
||||||
if !self.snaps.undo() {
|
if !self.snaps.undo() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.r#move(0);
|
||||||
if self.snap().mode == InputMode::Insert {
|
if self.snap().mode == InputMode::Insert {
|
||||||
self.escape(());
|
self.escape(());
|
||||||
}
|
}
|
||||||
|
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
yazi_macro::mod_pub!(input);
|
yazi_macro::mod_pub!(input);
|
||||||
|
|
||||||
|
yazi_macro::mod_flat!(scrollable step);
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
use yazi_fs::Step;
|
use crate::Step;
|
||||||
|
|
||||||
pub trait Scrollable {
|
pub trait Scrollable {
|
||||||
fn len(&self) -> usize;
|
fn total(&self) -> usize;
|
||||||
fn limit(&self) -> usize;
|
fn limit(&self) -> usize;
|
||||||
fn scrolloff(&self) -> usize { self.limit() / 2 }
|
fn scrolloff(&self) -> usize { self.limit() / 2 }
|
||||||
fn cursor_mut(&mut self) -> &mut usize;
|
fn cursor_mut(&mut self) -> &mut usize;
|
||||||
fn offset_mut(&mut self) -> &mut usize;
|
fn offset_mut(&mut self) -> &mut usize;
|
||||||
|
|
||||||
fn scroll(&mut self, step: Step) -> bool {
|
fn scroll(&mut self, step: impl Into<Step>) -> bool {
|
||||||
let new = step.add(*self.cursor_mut(), self.len(), self.limit());
|
let new = step.into().add(*self.cursor_mut(), self.total(), self.limit());
|
||||||
if new > *self.cursor_mut() { self.next(new) } else { self.prev(new) }
|
if new > *self.cursor_mut() { self.next(new) } else { self.prev(new) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self, n_cur: usize) -> bool {
|
fn next(&mut self, n_cur: usize) -> bool {
|
||||||
let (o_cur, o_off) = (*self.cursor_mut(), *self.offset_mut());
|
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) {
|
let n_off = if n_cur < total.min(o_off + limit).saturating_sub(scrolloff) {
|
||||||
o_off.min(len.saturating_sub(1))
|
o_off.min(total.saturating_sub(1))
|
||||||
} else {
|
} 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;
|
*self.cursor_mut() = n_cur;
|
||||||
|
|
@ -33,7 +33,7 @@ pub trait Scrollable {
|
||||||
let n_off = if n_cur < o_off + self.scrolloff() {
|
let n_off = if n_cur < o_off + self.scrolloff() {
|
||||||
o_off.saturating_sub(o_cur - n_cur)
|
o_off.saturating_sub(o_cur - n_cur)
|
||||||
} else {
|
} else {
|
||||||
self.len().saturating_sub(1).min(o_off)
|
self.total().saturating_sub(1).min(o_off)
|
||||||
};
|
};
|
||||||
|
|
||||||
*self.cursor_mut() = n_cur;
|
*self.cursor_mut() = n_cur;
|
||||||
|
|
@ -8,16 +8,16 @@ pub enum Step {
|
||||||
Bot,
|
Bot,
|
||||||
Prev,
|
Prev,
|
||||||
Next,
|
Next,
|
||||||
Fixed(isize),
|
Offset(isize),
|
||||||
Percent(i8),
|
Percent(i8),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Step {
|
impl Default for Step {
|
||||||
fn default() -> Self { Self::Fixed(0) }
|
fn default() -> Self { Self::Offset(0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<isize> for Step {
|
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 {
|
impl FromStr for Step {
|
||||||
|
|
@ -30,7 +30,7 @@ impl FromStr for Step {
|
||||||
"prev" => Self::Prev,
|
"prev" => Self::Prev,
|
||||||
"next" => Self::Next,
|
"next" => Self::Next,
|
||||||
s if s.ends_with('%') => Self::Percent(s[..s.len() - 1].parse()?),
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fixed = match self {
|
let off = match self {
|
||||||
Self::Top => return 0,
|
Self::Top => return 0,
|
||||||
Self::Bot => return len - 1,
|
Self::Bot => return len - 1,
|
||||||
Self::Prev => -1,
|
Self::Prev => -1,
|
||||||
Self::Next => 1,
|
Self::Next => 1,
|
||||||
Self::Fixed(n) => n,
|
Self::Offset(n) => n,
|
||||||
Self::Percent(0) => 0,
|
Self::Percent(0) => 0,
|
||||||
Self::Percent(n) => n as isize * limit as isize / 100,
|
Self::Percent(n) => n as isize * limit as isize / 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches!(self, Self::Prev | Self::Next) {
|
if matches!(self, Self::Prev | Self::Next) {
|
||||||
fixed.saturating_add_unsigned(pos).rem_euclid(len as _) as _
|
off.saturating_add_unsigned(pos).rem_euclid(len as _) as _
|
||||||
} else if fixed >= 0 {
|
} else if off >= 0 {
|
||||||
pos.saturating_add_signed(fixed)
|
pos.saturating_add_signed(off)
|
||||||
} else {
|
} else {
|
||||||
pos.saturating_sub(fixed.unsigned_abs())
|
pos.saturating_sub(off.unsigned_abs())
|
||||||
}
|
}
|
||||||
.min(len - 1)
|
.min(len - 1)
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue