parent
638ae4da6e
commit
bda30e2a72
18 changed files with 481 additions and 119 deletions
@ -1,12 +1,14 @@ |
||||
target/ |
||||
**/*.rs.bk |
||||
.idea/ |
||||
data/ |
||||
certs/ |
||||
static/ |
||||
.vscode/ |
||||
release/ |
||||
|
||||
**/*.rs.bk |
||||
**/*.pyc |
||||
certs/* |
||||
*.log |
||||
echoer |
||||
.env.private |
||||
*.lock |
||||
static/ |
||||
.vscode/ |
||||
*.lock |
@ -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