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, - } - } }