parent
638ae4da6e
commit
bda30e2a72
18 changed files with 481 additions and 119 deletions
@ -1,12 +1,14 @@ |
|||||||
target/ |
target/ |
||||||
**/*.rs.bk |
|
||||||
.idea/ |
.idea/ |
||||||
data/ |
data/ |
||||||
|
certs/ |
||||||
|
static/ |
||||||
|
.vscode/ |
||||||
|
release/ |
||||||
|
|
||||||
|
**/*.rs.bk |
||||||
**/*.pyc |
**/*.pyc |
||||||
certs/* |
|
||||||
*.log |
*.log |
||||||
echoer |
echoer |
||||||
.env.private |
.env.private |
||||||
*.lock |
*.lock |
||||||
static/ |
|
||||||
.vscode/ |
|
@ -0,0 +1,42 @@ |
|||||||
|
use once_cell::sync::Lazy; |
||||||
|
use std::any::{type_name, Any}; |
||||||
|
use std::future::Future; |
||||||
|
use std::pin::Pin; |
||||||
|
use std::task::{Context, Poll}; |
||||||
|
use tokio::sync::Mutex; |
||||||
|
|
||||||
|
type RV = Box<dyn Any + Send>; |
||||||
|
static LAST_RETVAL: Lazy<Mutex<Option<RV>>> = Lazy::new(|| Mutex::new(None)); |
||||||
|
|
||||||
|
pub struct ReturnValue; |
||||||
|
|
||||||
|
impl ReturnValue { |
||||||
|
pub async fn set(t: RV) { |
||||||
|
*LAST_RETVAL.lock().await = Some(t); |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn get<T: 'static>() -> Box<T> { |
||||||
|
ReturnValue |
||||||
|
.await |
||||||
|
.downcast::<T>() |
||||||
|
.expect(&format!("wrong type {}", type_name::<T>())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Future for ReturnValue { |
||||||
|
type Output = Box<dyn Any>; |
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
||||||
|
if let Poll::Ready(mut rv) = unsafe { Pin::new_unchecked(&mut LAST_RETVAL.lock()) }.poll(cx) |
||||||
|
{ |
||||||
|
if let Some(rv) = rv.take() { |
||||||
|
return Poll::Ready(rv); |
||||||
|
} |
||||||
|
} |
||||||
|
Poll::Pending |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub trait RetVal { |
||||||
|
fn retval(&self) -> RV; |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
use crossbeam::channel::{unbounded, Receiver, Sender}; |
||||||
|
|
||||||
|
pub struct Channel<T> { |
||||||
|
pub tx: Sender<T>, |
||||||
|
pub rx: Receiver<T>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> Channel<T> { |
||||||
|
pub fn new() -> Self { |
||||||
|
let (tx, rx) = unbounded::<T>(); |
||||||
|
Self { tx, rx } |
||||||
|
} |
||||||
|
|
||||||
|
pub fn send(&self, msg: T) { |
||||||
|
self.tx.send(msg).unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn recv(&self) -> T { |
||||||
|
self.rx.recv().unwrap() |
||||||
|
} |
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool { |
||||||
|
self.rx.is_empty() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<T> Default for Channel<T> { |
||||||
|
fn default() -> Self { |
||||||
|
Channel::new() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
use super::{Window, WndId}; |
||||||
|
use crate::tui::{Frame, GEvent, RetVal, ReturnValue, GENERAL_EVENT_CHANNEL}; |
||||||
|
use anyhow::Result as AResult; |
||||||
|
use crossterm::event::KeyCode; |
||||||
|
use std::any::Any; |
||||||
|
use tui::layout::{Alignment, Constraint, Direction, Layout, Rect}; |
||||||
|
use tui::style::{Modifier, Style}; |
||||||
|
use tui::widgets::{Block, Clear, List, ListItem, ListState, Paragraph}; |
||||||
|
|
||||||
|
#[derive(Default)] |
||||||
|
pub struct ConfirmWnd { |
||||||
|
pub id: WndId, |
||||||
|
msg: String, |
||||||
|
variants: Vec<String>, |
||||||
|
state: ListState, |
||||||
|
} |
||||||
|
|
||||||
|
impl ConfirmWnd { |
||||||
|
pub fn new_yn(msg: impl Into<String>, variants: Option<Vec<String>>) -> Self { |
||||||
|
let default = vec!["Yes".to_string(), "No".to_string()]; |
||||||
|
let variants = match variants { |
||||||
|
Some(v) if !v.is_empty() => v, |
||||||
|
_ => default, |
||||||
|
}; |
||||||
|
let mut this = Self { |
||||||
|
msg: msg.into(), |
||||||
|
variants, |
||||||
|
..Default::default() |
||||||
|
}; |
||||||
|
this.state.select(Some(0)); |
||||||
|
this |
||||||
|
} |
||||||
|
|
||||||
|
pub fn on_right(&mut self) { |
||||||
|
let selected = self.state.selected().unwrap_or(0); |
||||||
|
self.state |
||||||
|
.select(Some((selected + 1).rem_euclid(self.variants.len()))); |
||||||
|
} |
||||||
|
|
||||||
|
pub fn on_left(&mut self) { |
||||||
|
let selected = self.state.selected().unwrap_or(0); |
||||||
|
let vars_len = self.variants.len(); |
||||||
|
self.state |
||||||
|
.select(Some((selected + vars_len - 1).rem_euclid(vars_len))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl RetVal for ConfirmWnd { |
||||||
|
fn retval(&self) -> Box<dyn Any + Send> { |
||||||
|
let value = self |
||||||
|
.variants |
||||||
|
.get(self.state.selected().unwrap()) |
||||||
|
.unwrap() |
||||||
|
.to_owned(); |
||||||
|
Box::new(value) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[async_trait] |
||||||
|
impl Window for ConfirmWnd { |
||||||
|
async fn handle_kbd(&mut self, k: KeyCode) -> AResult<()> { |
||||||
|
match k { |
||||||
|
KeyCode::Right => self.on_right(), |
||||||
|
KeyCode::Left => self.on_left(), |
||||||
|
KeyCode::Enter | KeyCode::Esc => self.close(false).await, |
||||||
|
_ => (), |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
async fn handle_update(&mut self) -> AResult<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
async fn handle_close(&mut self) -> bool { |
||||||
|
true |
||||||
|
} |
||||||
|
|
||||||
|
fn draw(&mut self, f: &mut Frame) { |
||||||
|
let size = f.size(); |
||||||
|
let rect = centered_rect(60, 40, size); |
||||||
|
f.render_widget(Clear, rect); |
||||||
|
|
||||||
|
let chunks = Layout::default() |
||||||
|
.direction(Direction::Vertical) |
||||||
|
.constraints(vec![Constraint::Percentage(70), Constraint::Percentage(30)]) |
||||||
|
.split(rect); |
||||||
|
let msg = Paragraph::new(self.msg.as_ref()); |
||||||
|
f.render_widget(msg, chunks[0]); |
||||||
|
|
||||||
|
let options = self |
||||||
|
.variants |
||||||
|
.iter() |
||||||
|
.map(AsRef::as_ref) |
||||||
|
.map(ListItem::new) |
||||||
|
.collect::<Vec<ListItem>>(); |
||||||
|
let list = |
||||||
|
List::new(options).highlight_style(Style::default().add_modifier(Modifier::BOLD)); |
||||||
|
f.render_stateful_widget(list, chunks[1], &mut self.state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub async fn confirm_wnd(msg: impl Into<String>) -> bool { |
||||||
|
let wnd = ConfirmWnd::new_yn(msg.into(), None); |
||||||
|
GENERAL_EVENT_CHANNEL.send(GEvent::CreateWnd(wnd.into_shared())); |
||||||
|
*ReturnValue::get().await |
||||||
|
} |
||||||
|
|
||||||
|
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { |
||||||
|
let popup_layout = Layout::default() |
||||||
|
.direction(Direction::Vertical) |
||||||
|
.constraints( |
||||||
|
[ |
||||||
|
Constraint::Percentage((100 - percent_y) / 2), |
||||||
|
Constraint::Percentage(percent_y), |
||||||
|
Constraint::Percentage((100 - percent_y) / 2), |
||||||
|
] |
||||||
|
.as_ref(), |
||||||
|
) |
||||||
|
.split(r); |
||||||
|
|
||||||
|
Layout::default() |
||||||
|
.direction(Direction::Horizontal) |
||||||
|
.constraints( |
||||||
|
[ |
||||||
|
Constraint::Percentage((100 - percent_x) / 2), |
||||||
|
Constraint::Percentage(percent_x), |
||||||
|
Constraint::Percentage((100 - percent_x) / 2), |
||||||
|
] |
||||||
|
.as_ref(), |
||||||
|
) |
||||||
|
.split(popup_layout[1])[1] |
||||||
|
} |
Loading…
Reference in new issue