diff --git a/app/src/app.rs b/app/src/app.rs
index 633c7b37..472e06b2 100644
--- a/app/src/app.rs
+++ b/app/src/app.rs
@@ -155,13 +155,11 @@ impl App {
emit!(Render);
}
- Event::Select(mut opt, tx) => {
- opt.position = self.cx.position(opt.position);
+ Event::Select(opt, tx) => {
self.cx.select.show(opt, tx);
emit!(Render);
}
- Event::Input(mut opt, tx) => {
- opt.position = self.cx.position(opt.position);
+ Event::Input(opt, tx) => {
self.cx.input.show(opt, tx);
emit!(Render);
}
diff --git a/app/src/context.rs b/app/src/context.rs
index 18dc162f..8bbd8694 100644
--- a/app/src/context.rs
+++ b/app/src/context.rs
@@ -1,6 +1,8 @@
use core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
use config::keymap::KeymapLayer;
+use libc::winsize;
+use ratatui::prelude::Rect;
use shared::tty_size;
pub struct Ctx {
@@ -22,10 +24,41 @@ impl Ctx {
}
}
+ pub(super) fn area(&self, pos: &Position) -> Rect {
+ let winsize { ws_row, ws_col, .. } = tty_size();
+
+ let (x, y) = match pos {
+ Position::None => return Rect::default(),
+ Position::Top(Rect { mut x, mut y, width, height }) => {
+ x = x.min(ws_col.saturating_sub(*width));
+ y = y.min(ws_row.saturating_sub(*height));
+ ((tty_size().ws_col / 2).saturating_sub(width / 2) + x, y)
+ }
+ Position::Hovered(rect @ Rect { mut x, y, width, height }) => {
+ let Some(r) =
+ self.manager.hovered().and_then(|h| self.manager.current().rect_current(&h.path))
+ else {
+ return self.area(&Position::Top(*rect));
+ };
+
+ x = x.min(ws_col.saturating_sub(*width));
+ if y + height + r.y + r.height > ws_row {
+ (x + r.x, r.y.saturating_sub(height.saturating_sub(1)))
+ } else {
+ (x + r.x, y + r.y + r.height)
+ }
+ }
+ };
+
+ let (w, h) = pos.dimension().unwrap();
+ Rect { x, y, width: w.min(ws_col.saturating_sub(x)), height: h.min(ws_row.saturating_sub(y)) }
+ }
+
#[inline]
pub(super) fn cursor(&self) -> Option<(u16, u16)> {
if self.input.visible {
- return Some(self.input.cursor());
+ let Rect { x, y, .. } = self.area(&self.input.position);
+ return Some((x + 1 + self.input.cursor(), y + 1));
}
None
}
@@ -49,17 +82,4 @@ impl Ctx {
pub(super) fn image_layer(&self) -> bool {
!matches!(self.layer(), KeymapLayer::Which | KeymapLayer::Tasks)
}
-
- pub(super) fn position(&self, pos: Position) -> Position {
- match pos {
- Position::Top => Position::Coords((tty_size().ws_col / 2).saturating_sub(25), 2),
- Position::Hovered => self
- .manager
- .hovered()
- .and_then(|h| self.manager.current().rect_current(&h.path))
- .map(|r| Position::Coords(r.x, r.y))
- .unwrap_or_else(|| self.position(Position::Top)),
- p @ Position::Coords(..) => p,
- }
- }
}
diff --git a/app/src/input/input.rs b/app/src/input/input.rs
index e5508469..aab7fc88 100644
--- a/app/src/input/input.rs
+++ b/app/src/input/input.rs
@@ -1,4 +1,5 @@
use core::input::InputMode;
+use std::ops::Range;
use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::{Line, Text}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}};
@@ -17,7 +18,7 @@ impl<'a> Input<'a> {
impl<'a> Widget for Input<'a> {
fn render(self, _: Rect, buf: &mut Buffer) {
let input = &self.cx.input;
- let area = input.area();
+ let area = self.cx.area(&input.position);
let value = if let Ok(v) = input.value_pretty() {
v.into_text().unwrap()
@@ -41,8 +42,11 @@ impl<'a> Widget for Input<'a> {
.style(Style::new().fg(Color::White))
.render(area, buf);
- if let Some(selected) = input.selected() {
- buf.set_style(selected, Style::new().bg(Color::Rgb(72, 77, 102)))
+ if let Some(Range { start, end }) = input.selected() {
+ buf.set_style(
+ Rect { x: area.x + 1 + start, y: area.y + 1, width: end - start, height: 1 },
+ Style::new().bg(Color::Rgb(72, 77, 102)),
+ )
}
let _ = match input.mode() {
diff --git a/app/src/select/select.rs b/app/src/select/select.rs
index 367f8596..7846385a 100644
--- a/app/src/select/select.rs
+++ b/app/src/select/select.rs
@@ -13,7 +13,7 @@ impl<'a> Select<'a> {
impl<'a> Widget for Select<'a> {
fn render(self, _: Rect, buf: &mut Buffer) {
let select = &self.cx.select;
- let area = select.area();
+ let area = self.cx.area(&select.position);
let items = select
.window()
diff --git a/config/src/preset.rs b/config/src/preset.rs
index cd745c7d..164ed31a 100644
--- a/config/src/preset.rs
+++ b/config/src/preset.rs
@@ -31,7 +31,7 @@ impl Preset {
fn merge_str(user: &str, base: &str) -> String {
let path = BaseDirectories::new().unwrap().get_config_file(user);
- let mut user = fs::read_to_string(path).unwrap_or("".to_string()).parse::
().unwrap();
+ let mut user = fs::read_to_string(path).unwrap_or_default().parse::().unwrap();
let base = base.parse::().unwrap();
Self::merge(&mut user, &base, 2);
diff --git a/core/src/input/input.rs b/core/src/input/input.rs
index 67b7f2c1..59f917ee 100644
--- a/core/src/input/input.rs
+++ b/core/src/input/input.rs
@@ -1,7 +1,6 @@
use std::ops::Range;
use anyhow::{anyhow, Result};
-use ratatui::layout::Rect;
use shared::CharKind;
use tokio::sync::oneshot::Sender;
use unicode_width::UnicodeWidthStr;
@@ -14,9 +13,9 @@ pub struct Input {
snaps: InputSnaps,
pub visible: bool,
- title: String,
- position: (u16, u16),
- callback: Option>>,
+ title: String,
+ pub position: Position,
+ callback: Option>>,
// Shell
pub(super) highlight: bool,
@@ -29,10 +28,7 @@ impl Input {
self.visible = true;
self.title = opt.title;
- self.position = match opt.position {
- Position::Coords(x, y) => (x, y),
- _ => unreachable!(),
- };
+ self.position = opt.position;
self.callback = Some(tx);
// Shell
@@ -317,21 +313,12 @@ impl Input {
pub fn mode(&self) -> InputMode { self.snap().mode }
#[inline]
- pub fn area(&self) -> Rect {
- // TODO: hardcode
- Rect { x: self.position.0, y: self.position.1 + 2, width: 50, height: 3 }
- }
-
- #[inline]
- pub fn cursor(&self) -> (u16, u16) {
+ pub fn cursor(&self) -> u16 {
let snap = self.snap();
- let width = snap.slice(snap.offset..snap.cursor).width() as u16;
-
- let area = self.area();
- (area.x + width + 1, area.y + 1)
+ snap.slice(snap.offset..snap.cursor).width() as u16
}
- pub fn selected(&self) -> Option {
+ pub fn selected(&self) -> Option> {
let snap = self.snap();
let start = snap.op.start()?;
@@ -341,12 +328,8 @@ impl Input {
let win = snap.window();
let Range { start, end } = start.max(win.start)..end.min(win.end);
- Some(Rect {
- x: self.position.0 + 1 + snap.slice(snap.offset..start).width() as u16,
- y: self.position.1 + 3,
- width: snap.slice(start..end).width() as u16,
- height: 1,
- })
+ let s = snap.slice(snap.offset..start).width() as u16;
+ Some(s..s + snap.slice(start..end).width() as u16)
}
#[inline]
diff --git a/core/src/input/option.rs b/core/src/input/option.rs
index fef77620..ae970858 100644
--- a/core/src/input/option.rs
+++ b/core/src/input/option.rs
@@ -1,3 +1,5 @@
+use ratatui::prelude::Rect;
+
use crate::Position;
pub struct InputOpt {
@@ -12,7 +14,7 @@ impl InputOpt {
Self {
title: title.as_ref().to_owned(),
value: String::new(),
- position: Position::Top,
+ position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }),
highlight: false,
}
}
@@ -21,7 +23,10 @@ impl InputOpt {
Self {
title: title.as_ref().to_owned(),
value: String::new(),
- position: Position::Hovered,
+ position: Position::Hovered(
+ // TODO: hardcode
+ Rect { x: 0, y: 1, width: 50, height: 3 },
+ ),
highlight: false,
}
}
diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs
index 9a55ef1a..a073b461 100644
--- a/core/src/manager/manager.rs
+++ b/core/src/manager/manager.rs
@@ -6,7 +6,7 @@ use shared::MIME_DIR;
use tokio::fs;
use super::{PreviewData, Tab, Tabs, Watcher};
-use crate::{emit, external, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks, Position};
+use crate::{emit, external, files::{File, FilesOp}, input::InputOpt, manager::Folder, select::SelectOpt, tasks::Tasks};
pub struct Manager {
tabs: Tabs,
@@ -161,11 +161,10 @@ impl Manager {
return;
}
- let result = emit!(Select(SelectOpt {
- title: "Open with:".to_string(),
- items: openers.iter().map(|o| o.display_name.clone()).collect(),
- position: Position::Hovered,
- }));
+ let result = emit!(Select(SelectOpt::hovered(
+ "Open with:",
+ openers.iter().map(|o| o.display_name.clone()).collect()
+ )));
if let Ok(choice) = result.await {
emit!(Open(files, Some(openers[choice].clone())));
}
diff --git a/core/src/position.rs b/core/src/position.rs
index 813597be..68c92a62 100644
--- a/core/src/position.rs
+++ b/core/src/position.rs
@@ -1,5 +1,23 @@
+use ratatui::prelude::Rect;
+
+#[derive(Default)]
pub enum Position {
- Top,
- Hovered,
- Coords(u16, u16),
+ #[default]
+ None,
+ Top(Rect),
+ Hovered(Rect),
+}
+
+impl Position {
+ #[inline]
+ pub fn rect(&self) -> Option {
+ match self {
+ Position::None => None,
+ Position::Top(rect) => Some(*rect),
+ Position::Hovered(rect) => Some(*rect),
+ }
+ }
+
+ #[inline]
+ pub fn dimension(&self) -> Option<(u16, u16)> { self.rect().map(|r| (r.width, r.height)) }
}
diff --git a/core/src/select/mod.rs b/core/src/select/mod.rs
index 93af1071..fa446e89 100644
--- a/core/src/select/mod.rs
+++ b/core/src/select/mod.rs
@@ -1,5 +1,7 @@
+mod option;
mod select;
+pub use option::*;
pub use select::*;
pub const SELECT_PADDING: u16 = 2;
diff --git a/core/src/select/option.rs b/core/src/select/option.rs
new file mode 100644
index 00000000..da1c8a25
--- /dev/null
+++ b/core/src/select/option.rs
@@ -0,0 +1,30 @@
+use ratatui::prelude::Rect;
+
+use crate::Position;
+
+pub struct SelectOpt {
+ pub title: String,
+ pub items: Vec,
+ pub position: Position,
+}
+
+impl SelectOpt {
+ pub fn top(title: &str, items: Vec) -> Self {
+ Self {
+ title: title.to_owned(),
+ items,
+ position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }),
+ }
+ }
+
+ pub fn hovered(title: &str, items: Vec) -> Self {
+ Self {
+ title: title.to_owned(),
+ items,
+ position: Position::Hovered(
+ // TODO: hardcode
+ Rect { x: 0, y: 1, width: 50, height: 3 },
+ ),
+ }
+ }
+}
diff --git a/core/src/select/select.rs b/core/src/select/select.rs
index 2205c6a0..92dadbcf 100644
--- a/core/src/select/select.rs
+++ b/core/src/select/select.rs
@@ -1,16 +1,15 @@
use anyhow::{anyhow, Result};
-use ratatui::prelude::Rect;
use shared::tty_size;
use tokio::sync::oneshot::Sender;
-use super::SELECT_PADDING;
+use super::{SelectOpt, SELECT_PADDING};
use crate::Position;
#[derive(Default)]
pub struct Select {
- title: String,
- items: Vec,
- position: (u16, u16),
+ title: String,
+ items: Vec,
+ pub position: Position,
offset: usize,
cursor: usize,
@@ -19,22 +18,14 @@ pub struct Select {
pub visible: bool,
}
-pub struct SelectOpt {
- pub title: String,
- pub items: Vec,
- pub position: Position,
-}
-
impl Select {
pub fn show(&mut self, opt: SelectOpt, tx: Sender>) {
self.close(false);
self.title = opt.title;
self.items = opt.items;
- self.position = match opt.position {
- Position::Coords(x, y) => (x, y),
- _ => unreachable!(),
- };
+ self.position = opt.position;
+
self.callback = Some(tx);
self.visible = true;
}
@@ -96,14 +87,4 @@ impl Select {
#[inline]
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
-
- #[inline]
- pub fn area(&self) -> Rect {
- Rect {
- x: self.position.0,
- y: self.position.1 + 2,
- width: 50,
- height: self.limit() as u16 + SELECT_PADDING,
- }
- }
}