fix: crashes caused by pop-up components (#52)

This commit is contained in:
三咲雅 · Misaki Masa 2023-08-14 07:27:54 +08:00 committed by GitHub
parent 4480db86b7
commit 938c4ec865
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 125 additions and 85 deletions

View File

@ -155,13 +155,11 @@ impl App {
emit!(Render); emit!(Render);
} }
Event::Select(mut opt, tx) => { Event::Select(opt, tx) => {
opt.position = self.cx.position(opt.position);
self.cx.select.show(opt, tx); self.cx.select.show(opt, tx);
emit!(Render); emit!(Render);
} }
Event::Input(mut opt, tx) => { Event::Input(opt, tx) => {
opt.position = self.cx.position(opt.position);
self.cx.input.show(opt, tx); self.cx.input.show(opt, tx);
emit!(Render); emit!(Render);
} }

View File

@ -1,6 +1,8 @@
use core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}; use core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
use config::keymap::KeymapLayer; use config::keymap::KeymapLayer;
use libc::winsize;
use ratatui::prelude::Rect;
use shared::tty_size; use shared::tty_size;
pub struct Ctx { 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] #[inline]
pub(super) fn cursor(&self) -> Option<(u16, u16)> { pub(super) fn cursor(&self) -> Option<(u16, u16)> {
if self.input.visible { 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 None
} }
@ -49,17 +82,4 @@ impl Ctx {
pub(super) fn image_layer(&self) -> bool { pub(super) fn image_layer(&self) -> bool {
!matches!(self.layer(), KeymapLayer::Which | KeymapLayer::Tasks) !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,
}
}
} }

View File

@ -1,4 +1,5 @@
use core::input::InputMode; use core::input::InputMode;
use std::ops::Range;
use ansi_to_tui::IntoText; use ansi_to_tui::IntoText;
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Style}, text::{Line, Text}, widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget}}; 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> { impl<'a> Widget for Input<'a> {
fn render(self, _: Rect, buf: &mut Buffer) { fn render(self, _: Rect, buf: &mut Buffer) {
let input = &self.cx.input; 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() { let value = if let Ok(v) = input.value_pretty() {
v.into_text().unwrap() v.into_text().unwrap()
@ -41,8 +42,11 @@ impl<'a> Widget for Input<'a> {
.style(Style::new().fg(Color::White)) .style(Style::new().fg(Color::White))
.render(area, buf); .render(area, buf);
if let Some(selected) = input.selected() { if let Some(Range { start, end }) = input.selected() {
buf.set_style(selected, Style::new().bg(Color::Rgb(72, 77, 102))) 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() { let _ = match input.mode() {

View File

@ -13,7 +13,7 @@ impl<'a> Select<'a> {
impl<'a> Widget for Select<'a> { impl<'a> Widget for Select<'a> {
fn render(self, _: Rect, buf: &mut Buffer) { fn render(self, _: Rect, buf: &mut Buffer) {
let select = &self.cx.select; let select = &self.cx.select;
let area = select.area(); let area = self.cx.area(&select.position);
let items = select let items = select
.window() .window()

View File

@ -31,7 +31,7 @@ impl Preset {
fn merge_str(user: &str, base: &str) -> String { fn merge_str(user: &str, base: &str) -> String {
let path = BaseDirectories::new().unwrap().get_config_file(user); let path = BaseDirectories::new().unwrap().get_config_file(user);
let mut user = fs::read_to_string(path).unwrap_or("".to_string()).parse::<Table>().unwrap(); let mut user = fs::read_to_string(path).unwrap_or_default().parse::<Table>().unwrap();
let base = base.parse::<Table>().unwrap(); let base = base.parse::<Table>().unwrap();
Self::merge(&mut user, &base, 2); Self::merge(&mut user, &base, 2);

View File

@ -1,7 +1,6 @@
use std::ops::Range; use std::ops::Range;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use ratatui::layout::Rect;
use shared::CharKind; use shared::CharKind;
use tokio::sync::oneshot::Sender; use tokio::sync::oneshot::Sender;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
@ -15,7 +14,7 @@ pub struct Input {
pub visible: bool, pub visible: bool,
title: String, title: String,
position: (u16, u16), pub position: Position,
callback: Option<Sender<Result<String>>>, callback: Option<Sender<Result<String>>>,
// Shell // Shell
@ -29,10 +28,7 @@ impl Input {
self.visible = true; self.visible = true;
self.title = opt.title; self.title = opt.title;
self.position = match opt.position { self.position = opt.position;
Position::Coords(x, y) => (x, y),
_ => unreachable!(),
};
self.callback = Some(tx); self.callback = Some(tx);
// Shell // Shell
@ -317,21 +313,12 @@ impl Input {
pub fn mode(&self) -> InputMode { self.snap().mode } pub fn mode(&self) -> InputMode { self.snap().mode }
#[inline] #[inline]
pub fn area(&self) -> Rect { pub fn cursor(&self) -> u16 {
// TODO: hardcode
Rect { x: self.position.0, y: self.position.1 + 2, width: 50, height: 3 }
}
#[inline]
pub fn cursor(&self) -> (u16, u16) {
let snap = self.snap(); let snap = self.snap();
let width = snap.slice(snap.offset..snap.cursor).width() as u16; snap.slice(snap.offset..snap.cursor).width() as u16
let area = self.area();
(area.x + width + 1, area.y + 1)
} }
pub fn selected(&self) -> Option<Rect> { pub fn selected(&self) -> Option<Range<u16>> {
let snap = self.snap(); let snap = self.snap();
let start = snap.op.start()?; let start = snap.op.start()?;
@ -341,12 +328,8 @@ impl Input {
let win = snap.window(); let win = snap.window();
let Range { start, end } = start.max(win.start)..end.min(win.end); let Range { start, end } = start.max(win.start)..end.min(win.end);
Some(Rect { let s = snap.slice(snap.offset..start).width() as u16;
x: self.position.0 + 1 + snap.slice(snap.offset..start).width() as u16, Some(s..s + snap.slice(start..end).width() as u16)
y: self.position.1 + 3,
width: snap.slice(start..end).width() as u16,
height: 1,
})
} }
#[inline] #[inline]

View File

@ -1,3 +1,5 @@
use ratatui::prelude::Rect;
use crate::Position; use crate::Position;
pub struct InputOpt { pub struct InputOpt {
@ -12,7 +14,7 @@ impl InputOpt {
Self { Self {
title: title.as_ref().to_owned(), title: title.as_ref().to_owned(),
value: String::new(), value: String::new(),
position: Position::Top, position: Position::Top(/* TODO: hardcode */ Rect { x: 0, y: 2, width: 50, height: 3 }),
highlight: false, highlight: false,
} }
} }
@ -21,7 +23,10 @@ impl InputOpt {
Self { Self {
title: title.as_ref().to_owned(), title: title.as_ref().to_owned(),
value: String::new(), value: String::new(),
position: Position::Hovered, position: Position::Hovered(
// TODO: hardcode
Rect { x: 0, y: 1, width: 50, height: 3 },
),
highlight: false, highlight: false,
} }
} }

View File

@ -6,7 +6,7 @@ use shared::MIME_DIR;
use tokio::fs; use tokio::fs;
use super::{PreviewData, Tab, Tabs, Watcher}; 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 { pub struct Manager {
tabs: Tabs, tabs: Tabs,
@ -161,11 +161,10 @@ impl Manager {
return; return;
} }
let result = emit!(Select(SelectOpt { let result = emit!(Select(SelectOpt::hovered(
title: "Open with:".to_string(), "Open with:",
items: openers.iter().map(|o| o.display_name.clone()).collect(), openers.iter().map(|o| o.display_name.clone()).collect()
position: Position::Hovered, )));
}));
if let Ok(choice) = result.await { if let Ok(choice) = result.await {
emit!(Open(files, Some(openers[choice].clone()))); emit!(Open(files, Some(openers[choice].clone())));
} }

View File

@ -1,5 +1,23 @@
use ratatui::prelude::Rect;
#[derive(Default)]
pub enum Position { pub enum Position {
Top, #[default]
Hovered, None,
Coords(u16, u16), Top(Rect),
Hovered(Rect),
}
impl Position {
#[inline]
pub fn rect(&self) -> Option<Rect> {
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)) }
} }

View File

@ -1,5 +1,7 @@
mod option;
mod select; mod select;
pub use option::*;
pub use select::*; pub use select::*;
pub const SELECT_PADDING: u16 = 2; pub const SELECT_PADDING: u16 = 2;

30
core/src/select/option.rs Normal file
View File

@ -0,0 +1,30 @@
use ratatui::prelude::Rect;
use crate::Position;
pub struct SelectOpt {
pub title: String,
pub items: Vec<String>,
pub position: Position,
}
impl SelectOpt {
pub fn top(title: &str, items: Vec<String>) -> 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<String>) -> Self {
Self {
title: title.to_owned(),
items,
position: Position::Hovered(
// TODO: hardcode
Rect { x: 0, y: 1, width: 50, height: 3 },
),
}
}
}

View File

@ -1,16 +1,15 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use ratatui::prelude::Rect;
use shared::tty_size; use shared::tty_size;
use tokio::sync::oneshot::Sender; use tokio::sync::oneshot::Sender;
use super::SELECT_PADDING; use super::{SelectOpt, SELECT_PADDING};
use crate::Position; use crate::Position;
#[derive(Default)] #[derive(Default)]
pub struct Select { pub struct Select {
title: String, title: String,
items: Vec<String>, items: Vec<String>,
position: (u16, u16), pub position: Position,
offset: usize, offset: usize,
cursor: usize, cursor: usize,
@ -19,22 +18,14 @@ pub struct Select {
pub visible: bool, pub visible: bool,
} }
pub struct SelectOpt {
pub title: String,
pub items: Vec<String>,
pub position: Position,
}
impl Select { impl Select {
pub fn show(&mut self, opt: SelectOpt, tx: Sender<Result<usize>>) { pub fn show(&mut self, opt: SelectOpt, tx: Sender<Result<usize>>) {
self.close(false); self.close(false);
self.title = opt.title; self.title = opt.title;
self.items = opt.items; self.items = opt.items;
self.position = match opt.position { self.position = opt.position;
Position::Coords(x, y) => (x, y),
_ => unreachable!(),
};
self.callback = Some(tx); self.callback = Some(tx);
self.visible = true; self.visible = true;
} }
@ -96,14 +87,4 @@ impl Select {
#[inline] #[inline]
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset } 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,
}
}
} }