mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: context-aware icons for inputs (#4080)
This commit is contained in:
parent
2a106aa231
commit
ef654f4420
7 changed files with 59 additions and 11 deletions
|
|
@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
- Bulk create ([#3793])
|
- Bulk create ([#3793])
|
||||||
- Make help menu a command palette ([#4074])
|
- Make help menu a command palette ([#4074])
|
||||||
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
||||||
|
- Context-aware icons for inputs ([#4080])
|
||||||
- Dynamic keymap Lua API ([#4031])
|
- Dynamic keymap Lua API ([#4031])
|
||||||
- New `ui.Input` element ([#4040])
|
- New `ui.Input` element ([#4040])
|
||||||
- Image preview with Überzug++ on Niri ([#3990])
|
- Image preview with Überzug++ on Niri ([#3990])
|
||||||
|
|
@ -1763,3 +1764,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
[#4067]: https://github.com/sxyazi/yazi/pull/4067
|
[#4067]: https://github.com/sxyazi/yazi/pull/4067
|
||||||
[#4068]: https://github.com/sxyazi/yazi/pull/4068
|
[#4068]: https://github.com/sxyazi/yazi/pull/4068
|
||||||
[#4074]: https://github.com/sxyazi/yazi/pull/4074
|
[#4074]: https://github.com/sxyazi/yazi/pull/4074
|
||||||
|
[#4080]: https://github.com/sxyazi/yazi/pull/4080
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,10 @@ impl Actor for Show {
|
||||||
|
|
||||||
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
let area = cx.mgr.area(opt.position).padding(cx.input.padding());
|
||||||
let input = &mut cx.input;
|
let input = &mut cx.input;
|
||||||
input.main.visible = true;
|
input.main.id = opt.id.clone();
|
||||||
input.main.title = opt.title.clone();
|
input.main.title = opt.title.clone();
|
||||||
input.main.position = opt.position;
|
input.main.position = opt.position;
|
||||||
|
input.main.visible = true;
|
||||||
|
|
||||||
opt.styles = (&THEME.input).into();
|
opt.styles = (&THEME.input).into();
|
||||||
opt.blinking = YAZI.input.cursor_blink;
|
opt.blinking = YAZI.input.cursor_blink;
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,8 @@ impl Actor for Rename {
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tab, old) = (cx.tab().id, hovered.url_owned());
|
let (tab, old) = (cx.tab().id, hovered.url_owned());
|
||||||
let mut input = input!(cx, YAZI.input.rename().with_value(name).with_cursor(cursor))?;
|
let mut input =
|
||||||
|
input!(cx, YAZI.input.rename(hovered.is_dir()).with_value(name).with_cursor(cursor))?;
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
let Some(InputEvent::Submit(name)) = input.recv().await else { return };
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ pub struct Input {
|
||||||
impl Input {
|
impl Input {
|
||||||
pub fn cd(&self, cwd: Url) -> InputOpt {
|
pub fn cd(&self, cwd: Url) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "cd".to_owned(),
|
||||||
title: self.cd_title.clone(),
|
title: self.cd_title.clone(),
|
||||||
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
||||||
position: Position::new(self.cd_origin, self.cd_offset),
|
position: Position::new(self.cd_origin, self.cd_offset),
|
||||||
|
|
@ -57,14 +58,16 @@ impl Input {
|
||||||
|
|
||||||
pub fn create(&self, dir: bool) -> InputOpt {
|
pub fn create(&self, dir: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "create".to_owned(),
|
||||||
title: self.create_title[dir as usize].clone(),
|
title: self.create_title[dir as usize].clone(),
|
||||||
position: Position::new(self.create_origin, self.create_offset),
|
position: Position::new(self.create_origin, self.create_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rename(&self) -> InputOpt {
|
pub fn rename(&self, is_dir: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: format!("rename-{}", if is_dir { "dir" } else { "file" }),
|
||||||
title: self.rename_title.clone(),
|
title: self.rename_title.clone(),
|
||||||
position: Position::new(self.rename_origin, self.rename_offset),
|
position: Position::new(self.rename_origin, self.rename_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -73,6 +76,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn filter(&self) -> InputOpt {
|
pub fn filter(&self) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "filter".to_owned(),
|
||||||
title: self.filter_title.clone(),
|
title: self.filter_title.clone(),
|
||||||
position: Position::new(self.filter_origin, self.filter_offset),
|
position: Position::new(self.filter_origin, self.filter_offset),
|
||||||
realtime: true,
|
realtime: true,
|
||||||
|
|
@ -82,6 +86,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn find(&self, prev: bool) -> InputOpt {
|
pub fn find(&self, prev: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "find".to_owned(),
|
||||||
title: self.find_title[prev as usize].clone(),
|
title: self.find_title[prev as usize].clone(),
|
||||||
position: Position::new(self.find_origin, self.find_offset),
|
position: Position::new(self.find_origin, self.find_offset),
|
||||||
realtime: true,
|
realtime: true,
|
||||||
|
|
@ -91,6 +96,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn search(&self, name: &str) -> InputOpt {
|
pub fn search(&self, name: &str) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "search".to_owned(),
|
||||||
title: self.search_title.replace("{n}", name),
|
title: self.search_title.replace("{n}", name),
|
||||||
position: Position::new(self.search_origin, self.search_offset),
|
position: Position::new(self.search_origin, self.search_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -99,6 +105,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn shell(&self, block: bool) -> InputOpt {
|
pub fn shell(&self, block: bool) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "shell".to_owned(),
|
||||||
title: self.shell_title[block as usize].clone(),
|
title: self.shell_title[block as usize].clone(),
|
||||||
position: Position::new(self.shell_origin, self.shell_offset),
|
position: Position::new(self.shell_origin, self.shell_offset),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -107,6 +114,7 @@ impl Input {
|
||||||
|
|
||||||
pub fn tab_rename(&self) -> InputOpt {
|
pub fn tab_rename(&self) -> InputOpt {
|
||||||
InputOpt {
|
InputOpt {
|
||||||
|
id: "tab-rename".to_owned(),
|
||||||
title: "Rename tab:".to_owned(),
|
title: "Rename tab:".to_owned(),
|
||||||
position: Position::new(Origin::TopCenter, Offset {
|
position: Position::new(Origin::TopCenter, Offset {
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,10 @@ impl Input {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct InputMain {
|
pub struct InputMain {
|
||||||
inner: yazi_widgets::input::Input,
|
inner: yazi_widgets::input::Input,
|
||||||
pub visible: bool,
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub position: Position,
|
pub position: Position,
|
||||||
|
pub visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for InputMain {
|
impl Deref for InputMain {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use ratatui_core::{buffer::Buffer, layout::{Margin, Rect}, text::Line, widgets::Widget};
|
use ratatui_core::{buffer::Buffer, layout::{Margin, Rect}, text::Line, widgets::Widget};
|
||||||
use ratatui_widgets::{block::Block, borders::BorderType};
|
use ratatui_widgets::{block::Block, borders::BorderType};
|
||||||
use yazi_config::THEME;
|
use yazi_config::{Icon, THEME};
|
||||||
use yazi_core::Core;
|
use yazi_core::Core;
|
||||||
|
use yazi_fs::{cha::{Cha, ChaKind}, file::File};
|
||||||
|
use yazi_shim::path::CROSS_SEPARATOR;
|
||||||
|
|
||||||
pub(crate) struct Input<'a> {
|
pub(crate) struct Input<'a> {
|
||||||
core: &'a Core,
|
core: &'a Core,
|
||||||
|
|
@ -9,21 +13,50 @@ pub(crate) struct Input<'a> {
|
||||||
|
|
||||||
impl<'a> Input<'a> {
|
impl<'a> Input<'a> {
|
||||||
pub(crate) fn new(core: &'a Core) -> Self { Self { core } }
|
pub(crate) fn new(core: &'a Core) -> Self { Self { core } }
|
||||||
|
|
||||||
|
fn icon(&self) -> Option<Icon> {
|
||||||
|
let input = &self.core.input.main;
|
||||||
|
|
||||||
|
let is_dir = input.value().ends_with(CROSS_SEPARATOR);
|
||||||
|
let is_hovered = input.id.starts_with("rename-");
|
||||||
|
|
||||||
|
let (path, mode): (&str, u16) = match input.id.as_str() {
|
||||||
|
"cd" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
||||||
|
"create" => (input.value(), if is_dir { 0o40755 } else { 0o100644 }),
|
||||||
|
"rename-file" => (input.value(), 0o100644),
|
||||||
|
"rename-dir" => (input.value(), 0o40755),
|
||||||
|
"shell" => ("icon.sh", 0o100644),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
THEME.icon.matches(
|
||||||
|
&File {
|
||||||
|
url: Path::new(path).into(),
|
||||||
|
cha: Cha { kind: ChaKind::empty(), mode: mode.try_into().ok()?, ..Default::default() },
|
||||||
|
link_to: None,
|
||||||
|
},
|
||||||
|
is_hovered,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Input<'_> {
|
impl Widget for Input<'_> {
|
||||||
fn render(self, _: Rect, buf: &mut Buffer) {
|
fn render(self, _: Rect, buf: &mut Buffer) {
|
||||||
let input = &self.core.input;
|
let input = &self.core.input.main;
|
||||||
|
|
||||||
let outer = self.core.mgr.area(input.main.position);
|
let outer = self.core.mgr.area(input.position);
|
||||||
yazi_widgets::clear::Clear::default().render(outer, buf);
|
yazi_widgets::clear::Clear::default().render(outer, buf);
|
||||||
|
|
||||||
Block::bordered()
|
let mut block = Block::bordered()
|
||||||
.border_type(BorderType::Rounded)
|
.border_type(BorderType::Rounded)
|
||||||
.border_style(THEME.input.border.get())
|
.border_style(THEME.input.border.get())
|
||||||
.title(Line::styled(&input.main.title, THEME.input.title.get()))
|
.title(Line::styled(&input.title, THEME.input.title.get()));
|
||||||
.render(outer, buf);
|
|
||||||
|
|
||||||
input.main.render(outer.inner(Margin::new(1, 1)), buf);
|
if let Some(i) = self.icon() {
|
||||||
|
block = block.title_bottom(Line::raw(format!("{} ", i.text)).style(i.style).right_aligned());
|
||||||
|
}
|
||||||
|
|
||||||
|
block.render(outer, buf);
|
||||||
|
input.render(outer.inner(Margin::new(1, 1)), buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::input::{InputCallback, InputStyles};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct InputOpt {
|
pub struct InputOpt {
|
||||||
|
pub id: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub styles: InputStyles,
|
pub styles: InputStyles,
|
||||||
|
|
@ -42,6 +43,7 @@ impl TryFrom<&Table> for InputOpt {
|
||||||
|
|
||||||
fn try_from(t: &Table) -> Result<Self, Self::Error> {
|
fn try_from(t: &Table) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
id: t.raw_get("id").unwrap_or_default(),
|
||||||
title: t.raw_get("title").unwrap_or_default(),
|
title: t.raw_get("title").unwrap_or_default(),
|
||||||
value: t.raw_get("value").unwrap_or_default(),
|
value: t.raw_get("value").unwrap_or_default(),
|
||||||
styles: t.raw_get("styles")?,
|
styles: t.raw_get("styles")?,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue