diff --git a/yazi-config/preset/yazi.toml b/yazi-config/preset/yazi.toml index 794a9130..90d7d942 100644 --- a/yazi-config/preset/yazi.toml +++ b/yazi-config/preset/yazi.toml @@ -76,56 +76,56 @@ preload = [] [input] # cd cd_title = "Change directory:" -cd_position = "top-center" +cd_origin = "top-center" cd_offset = [ 0, 2, 50, 3 ] cd_completion = 1 # create -create_title = "Create:" -create_position = "top-center" -create_offset = [ 0, 2, 50, 3 ] +create_title = "Create:" +create_origin = "top-center" +create_offset = [ 0, 2, 50, 3 ] # rename -rename_title = "Rename:" -rename_position = "hovered" -rename_offset = [ 0, 1, 50, 3 ] +rename_title = "Rename:" +rename_origin = "hovered" +rename_offset = [ 0, 1, 50, 3 ] # trash -trash_title = "Move {n} selected file{s} to trash? (y/N)" -trash_position = "top-center" -trash_offset = [ 0, 2, 50, 3 ] +trash_title = "Move {n} selected file{s} to trash? (y/N)" +trash_origin = "top-center" +trash_offset = [ 0, 2, 50, 3 ] # delete -delete_title = "Delete {n} selected file{s} permanently? (y/N)" -delete_position = "top-center" -delete_offset = [ 0, 2, 50, 3 ] +delete_title = "Delete {n} selected file{s} permanently? (y/N)" +delete_origin = "top-center" +delete_offset = [ 0, 2, 50, 3 ] # find find_title = [ "Find previous:", "Find next:" ] -find_position = "top-center" +find_origin = "top-center" find_offset = [ 0, 2, 50, 3 ] find_realtime = true # search -search_title = "Search:" -search_position = "top-center" -search_offset = [ 0, 2, 50, 3 ] +search_title = "Search:" +search_origin = "top-center" +search_offset = [ 0, 2, 50, 3 ] # shell shell_title = [ "Shell:", "Shell (block):" ] -shell_position = "top-center" +shell_origin = "top-center" shell_offset = [ 0, 2, 50, 3 ] shell_highlight = true # overwrite -overwrite_title = "Overwrite an existing file? (y/N)" -overwrite_position = "top-center" -overwrite_offset = [ 0, 2, 50, 3 ] +overwrite_title = "Overwrite an existing file? (y/N)" +overwrite_origin = "top-center" +overwrite_offset = [ 0, 2, 50, 3 ] [select] -open_title = "Open with:" -open_position = "hovered" -open_offset = [ 0, 1, 50, 3 ] +open_title = "Open with:" +open_origin = "hovered" +open_offset = [ 0, 1, 50, 3 ] [log] enabled = false diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 90aeb312..4cbee28f 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -1,49 +1,49 @@ use serde::Deserialize; -use super::position::{Offset, Position}; +use super::{Offset, Origin}; use crate::MERGED_YAZI; -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] pub struct Input { // cd - pub cd_title: String, - pub cd_position: Position, - pub cd_offset: Offset, + pub cd_title: String, + pub cd_origin: Origin, + pub cd_offset: Offset, // create - pub create_title: String, - pub create_position: Position, - pub create_offset: Offset, + pub create_title: String, + pub create_origin: Origin, + pub create_offset: Offset, // rename - pub rename_title: String, - pub rename_position: Position, - pub rename_offset: Offset, + pub rename_title: String, + pub rename_origin: Origin, + pub rename_offset: Offset, // trash - pub trash_title: String, - pub trash_position: Position, - pub trash_offset: Offset, + pub trash_title: String, + pub trash_origin: Origin, + pub trash_offset: Offset, // delete - pub delete_title: String, - pub delete_position: Position, - pub delete_offset: Offset, + pub delete_title: String, + pub delete_origin: Origin, + pub delete_offset: Offset, // find - pub find_title: [String; 2], - pub find_position: Position, - pub find_offset: Offset, + pub find_title: [String; 2], + pub find_origin: Origin, + pub find_offset: Offset, // search - pub search_title: String, - pub search_position: Position, - pub search_offset: Offset, + pub search_title: String, + pub search_origin: Origin, + pub search_offset: Offset, // shell - pub shell_title: [String; 2], - pub shell_position: Position, - pub shell_offset: Offset, + pub shell_title: [String; 2], + pub shell_origin: Origin, + pub shell_offset: Offset, } impl Default for Input { diff --git a/yazi-config/src/popup/mod.rs b/yazi-config/src/popup/mod.rs index 4d08ce5e..8308b911 100644 --- a/yazi-config/src/popup/mod.rs +++ b/yazi-config/src/popup/mod.rs @@ -1,9 +1,13 @@ mod input; +mod offset; mod options; +mod origin; mod position; mod select; pub use input::*; +pub use offset::*; pub use options::*; +pub use origin::*; pub use position::*; pub use select::*; diff --git a/yazi-config/src/popup/offset.rs b/yazi-config/src/popup/offset.rs new file mode 100644 index 00000000..43a2c29c --- /dev/null +++ b/yazi-config/src/popup/offset.rs @@ -0,0 +1,31 @@ +use anyhow::bail; +use serde::Deserialize; + +#[derive(Clone, Copy, Default, Deserialize)] +#[serde(try_from = "Vec")] +pub struct Offset { + pub x: i16, + pub y: i16, + pub width: u16, + pub height: u16, +} + +impl TryFrom> for Offset { + type Error = anyhow::Error; + + fn try_from(values: Vec) -> Result { + if values.len() != 4 { + bail!("invalid offset: {:?}", values); + } + if values[2] < 0 || values[3] < 0 { + bail!("invalid offset: {:?}", values); + } + + Ok(Self { + x: values[0], + y: values[1], + width: values[2] as u16, + height: values[3] as u16, + }) + } +} diff --git a/yazi-config/src/popup/origin.rs b/yazi-config/src/popup/origin.rs new file mode 100644 index 00000000..72cb3fde --- /dev/null +++ b/yazi-config/src/popup/origin.rs @@ -0,0 +1,24 @@ +use serde::Deserialize; + +#[derive(Clone, Copy, Default, Deserialize)] +pub enum Origin { + #[default] + #[serde(rename = "top-left")] + TopLeft, + #[serde(rename = "top-center")] + TopCenter, + #[serde(rename = "top-right")] + TopRight, + + #[serde(rename = "bottom-left")] + BottomLeft, + #[serde(rename = "bottom-center")] + BottomCenter, + #[serde(rename = "bottom-right")] + BottomRight, + + #[serde(rename = "center")] + Center, + #[serde(rename = "hovered")] + Hovered, +} diff --git a/yazi-config/src/popup/position.rs b/yazi-config/src/popup/position.rs index a901f120..d8a3e3a9 100644 --- a/yazi-config/src/popup/position.rs +++ b/yazi-config/src/popup/position.rs @@ -1,54 +1,91 @@ -use anyhow::bail; -use serde::{Deserialize, Serialize}; +use crossterm::terminal::WindowSize; +use ratatui::layout::Rect; +use yazi_shared::Term; -#[derive(Debug, Default, Deserialize)] -pub enum Position { - #[serde(rename = "top-left")] - TopLeft, - #[serde(rename = "top-center")] - TopCenter, - #[serde(rename = "top-right")] - TopRight, +use super::{Offset, Origin}; - #[serde(rename = "bottom-left")] - BottomLeft, - #[serde(rename = "bottom-center")] - BottomCenter, - #[serde(rename = "bottom-right")] - BottomRight, - - #[serde(rename = "center")] - #[default] - Center, - #[serde(rename = "hovered")] - Hovered, +#[derive(Clone, Copy, Default)] +pub struct Position { + pub origin: Origin, + pub offset: Offset, } -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] -#[serde(try_from = "Vec")] -pub struct Offset { - pub x: i16, - pub y: i16, - pub width: u16, - pub height: u16, -} +impl Position { + pub fn rect(&self) -> Rect { + let Offset { x, y, width, height } = self.offset; + let WindowSize { columns, rows, .. } = Term::size(); -impl TryFrom> for Offset { - type Error = anyhow::Error; + let max_x = columns.saturating_sub(width); + let max_y = rows.saturating_sub(height); - fn try_from(values: Vec) -> Result { - if values.len() != 4 { - bail!("invalid offset: {:?}", values); - } - if values[2] < 0 || values[3] < 0 { - bail!("invalid offset: {:?}", values); + let (x, y) = match self.origin { + // Top + Origin::TopLeft => (x.clamp(0, max_x as i16) as u16, y.clamp(0, max_y as i16) as u16), + Origin::TopCenter => ( + (columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x), + y.clamp(0, max_y as i16) as u16, + ), + Origin::TopRight => { + (max_x.saturating_add_signed(x).clamp(0, max_x), y.clamp(0, max_y as i16) as u16) + } + + // Bottom + Origin::BottomLeft => { + (x.clamp(0, max_x as i16) as u16, max_y.saturating_add_signed(y).clamp(0, max_y)) + } + Origin::BottomCenter => ( + (columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x), + max_y.saturating_add_signed(y).clamp(0, max_y), + ), + Origin::BottomRight => ( + max_x.saturating_add_signed(x).clamp(0, max_x), + max_y.saturating_add_signed(y).clamp(0, max_y), + ), + + // Special + // Origin::Hovered => { + // return Origin::rect(&if let Some(r) = + // self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url)) + // { + // Origin::Sticky(r) + // } else { + // Origin::TopCenter(rect_shim) + // }); + // } + Origin::Center => ( + (columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x), + (max_y / 2).saturating_sub(height / 2).saturating_add_signed(y).clamp(0, max_y), + ), + Origin::Hovered => unreachable!(), + }; + + Rect { + x, + y, + width: width.min(columns.saturating_sub(x)), + height: height.min(rows.saturating_sub(y)), } + } - Ok(Self { - x: values[0], - y: values[1], - width: values[2] as u16, - height: values[3] as u16, - }) + pub fn sticky(base: Rect, offset: Offset) -> Rect { + let Offset { x, y, width, height } = offset; + let WindowSize { columns, rows, .. } = Term::size(); + + let above = + base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows; + + let x = base.x.saturating_add_signed(x).clamp(0, columns.saturating_sub(width)); + let y = if above { + base.y.saturating_sub(height.saturating_sub(y.unsigned_abs())) + } else { + base.y.saturating_add(base.height).saturating_add_signed(y) + }; + + Rect { + x, + y, + width: width.min(columns.saturating_sub(x)), + height: height.min(rows.saturating_sub(y)), + } } } diff --git a/yazi-config/src/popup/select.rs b/yazi-config/src/popup/select.rs index ad5dd86a..076c8303 100644 --- a/yazi-config/src/popup/select.rs +++ b/yazi-config/src/popup/select.rs @@ -1,14 +1,14 @@ use serde::Deserialize; -use super::position::{Offset, Position}; +use super::{Offset, Origin}; use crate::MERGED_YAZI; -#[derive(Debug, Deserialize)] +#[derive(Deserialize)] pub struct Select { // open - pub open_title: String, - pub open_position: Position, - pub open_offset: Offset, + pub open_title: String, + pub open_origin: Origin, + pub open_offset: Offset, } impl Default for Select { diff --git a/yazi-core/src/context.rs b/yazi-core/src/context.rs index cd41965e..34ee2461 100644 --- a/yazi-core/src/context.rs +++ b/yazi-core/src/context.rs @@ -1,7 +1,5 @@ -use crossterm::terminal::WindowSize; use ratatui::prelude::Rect; -use yazi_config::popup::Position; -use yazi_shared::Term; +use yazi_config::popup::{Origin, Position}; use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which}; @@ -29,123 +27,12 @@ impl Ctx { } pub fn area(&self, pos: &Position) -> Rect { - let WindowSize { columns, rows, .. } = Term::size(); + // TODO: hovered + if let Origin::Hovered = pos.origin { + return Rect::default(); + } - let (x, y) = match *pos { - Position::TopLeft(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = 0_u16; - let base_y = 0_u16; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::TopRight(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = right_max; - let base_y = 0_u16; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::TopCenter(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = (columns / 2).saturating_sub(width / 2); - let base_y = 0_u16; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::Center(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = (columns / 2).saturating_sub(width / 2); - let base_y = (bottom_max / 2).saturating_sub(height / 2); - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::BottomCenter(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = (columns / 2).saturating_sub(width / 2); - let base_y = bottom_max; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::BottomLeft(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = 0_u16; - let base_y = bottom_max; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::BottomRight(Offset { x_offset, y_offset, width, height }) => { - let right_max = columns.saturating_sub(width); - let bottom_max = rows.saturating_sub(height); - - let base_x = right_max; - let base_y = bottom_max; - - let x = base_x.saturating_add_signed(x_offset).max(0).min(right_max); - let y = base_y.saturating_add_signed(y_offset).max(0).min(bottom_max); - - (x, y) - } - Position::Hovered(rect_shim) => { - return self.area(&if let Some(r) = - self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.url)) - { - Position::Sticky(rect_shim, r) - } else { - Position::TopCenter(rect_shim) - }); - } - Position::Sticky(Offset { x_offset, y_offset, width, height }, r) => { - let base_x = r.x; - let base_y = r.y; - if base_y.saturating_add(height).saturating_add(r.height).saturating_add_signed(y_offset) - > rows - { - ( - base_x.saturating_add_signed(x_offset).max(0).min(columns.saturating_sub(width)), - base_y.saturating_sub(height.saturating_sub(y_offset.unsigned_abs())), - ) - } else { - ( - base_x.saturating_add_signed(x_offset), - base_y.saturating_add(r.height).saturating_add_signed(y_offset), - ) - } - } - }; - - let (w, h) = pos.dimension(); - Rect { x, y, width: w.min(columns.saturating_sub(x)), height: h.min(rows.saturating_sub(y)) } + pos.rect() } #[inline] diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index 6c3329f4..dd221da6 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -1,6 +1,6 @@ use std::ffi::OsString; -use yazi_config::{keymap::Exec, popup::SelectOpt, OPEN, SELECT}; +use yazi_config::{keymap::Exec, popup::SelectOpt, OPEN}; use yazi_shared::MIME_DIR; use crate::{emit, external, manager::Manager}; diff --git a/yazi-core/src/manager/commands/quit.rs b/yazi-core/src/manager/commands/quit.rs index 5d6c4466..65f02070 100644 --- a/yazi-core/src/manager/commands/quit.rs +++ b/yazi-core/src/manager/commands/quit.rs @@ -24,16 +24,17 @@ impl Manager { } tokio::spawn(async move { - let mut result = emit!(Input(InputOpt::top_center( - format!("{tasks} tasks running, sure to quit? (y/N)",), - Default::default() - ))); + // TODO + // let mut result = emit!(Input(InputOpt::top_center( + // format!("{tasks} tasks running, sure to quit? (y/N)",), + // Default::default() + // ))); - if let Some(Ok(choice)) = result.recv().await { - if choice == "y" || choice == "Y" { - emit!(Quit(opt.no_cwd_file)); - } - } + // if let Some(Ok(choice)) = result.recv().await { + // if choice == "y" || choice == "Y" { + // emit!(Quit(opt.no_cwd_file)); + // } + // } }); false } diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index cb07f2f2..bae6ca67 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeSet, ffi::OsStr, io::{stdout, BufWriter, Write}, pat use anyhow::{anyhow, bail, Result}; use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; -use yazi_config::{keymap::Exec, popup::InputOpt, INPUT, OPEN, PREVIEW}; +use yazi_config::{keymap::Exec, popup::InputOpt, OPEN, PREVIEW}; use yazi_shared::{max_common_root, Defer, Term, Url}; use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, manager::Manager, Event, BLOCKER}; diff --git a/yazi-core/src/tab/commands/find.rs b/yazi-core/src/tab/commands/find.rs index a20f0d59..53dd0ef4 100644 --- a/yazi-core/src/tab/commands/find.rs +++ b/yazi-core/src/tab/commands/find.rs @@ -2,7 +2,7 @@ use std::time::Duration; use tokio::pin; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; -use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt, INPUT}; +use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt}; use yazi_shared::{Debounce, InputError}; use crate::{emit, tab::{Finder, FinderCase, Tab}}; @@ -39,7 +39,6 @@ impl Tab { pub fn find<'a>(&mut self, opt: impl Into>) -> bool { let opt = opt.into() as Opt; tokio::spawn(async move { - let title = if opt.prev { "Find previous:" } else { "Find next:" }; let rx = emit!(Input(InputOpt::find(opt.prev))); let rx = Debounce::new(UnboundedReceiverStream::new(rx), Duration::from_millis(50)); diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index f2efa9d1..5e271900 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -3,7 +3,7 @@ use std::{mem, time::Duration}; use anyhow::bail; use tokio::pin; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; -use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt, INPUT}; +use yazi_config::{keymap::{Exec, KeymapLayer}, popup::InputOpt}; use crate::{emit, external, files::FilesOp, tab::Tab}; diff --git a/yazi-core/src/tab/commands/shell.rs b/yazi-core/src/tab/commands/shell.rs index 19ada902..fb309e72 100644 --- a/yazi-core/src/tab/commands/shell.rs +++ b/yazi-core/src/tab/commands/shell.rs @@ -1,4 +1,4 @@ -use yazi_config::{keymap::Exec, open::Opener, popup::InputOpt, INPUT}; +use yazi_config::{keymap::Exec, open::Opener, popup::InputOpt}; use crate::{emit, tab::Tab}; diff --git a/yazi-fm/src/completion/completion.rs b/yazi-fm/src/completion/completion.rs index 9b4459a0..f748ef77 100644 --- a/yazi-fm/src/completion/completion.rs +++ b/yazi-fm/src/completion/completion.rs @@ -1,8 +1,8 @@ use std::path::MAIN_SEPARATOR; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, Borders, Clear, List, ListItem, Widget}}; -use yazi_config::THEME; -use yazi_core::{Ctx, Offset, Position}; +use yazi_config::{popup::{Offset, Position}, THEME}; +use yazi_core::Ctx; pub(crate) struct Completion<'a> { cx: &'a Ctx, @@ -39,15 +39,12 @@ impl<'a> Widget for Completion<'a> { .collect::>(); let input_area = self.cx.area(&self.cx.input.position); - let mut area = self.cx.area(&Position::Sticky( - Offset { - x_offset: 1, - y_offset: 0, - width: input_area.width.saturating_sub(2), - height: items.len() as u16 + 2, - }, - input_area, - )); + let mut area = Position::sticky(input_area, Offset { + x: 1, + y: 0, + width: input_area.width.saturating_sub(2), + height: items.len() as u16 + 2, + }); if area.y > input_area.y { area.y = area.y.saturating_sub(1);