now displays messages in the message list \o/
This commit is contained in:
parent
1bc6776615
commit
41c05ec38a
9 changed files with 115 additions and 143 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -66,7 +66,8 @@ jobs:
|
||||||
path: |
|
path: |
|
||||||
~/.cargo/registry
|
~/.cargo/registry
|
||||||
~/.cargo/git
|
~/.cargo/git
|
||||||
|
~/.cargo/bin
|
||||||
target
|
target
|
||||||
key: ${{ runner.os }}-cargo
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
|
||||||
# vim: set sw=2 et :
|
# vim: set sw=2 et :
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2020,6 +2020,7 @@ dependencies = [
|
||||||
"futures 0.3.13",
|
"futures 0.3.13",
|
||||||
"gluon",
|
"gluon",
|
||||||
"hex 0.4.3",
|
"hex 0.4.3",
|
||||||
|
"indexmap",
|
||||||
"inotify",
|
"inotify",
|
||||||
"log",
|
"log",
|
||||||
"mailparse",
|
"mailparse",
|
||||||
|
@ -3224,6 +3225,7 @@ version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -44,9 +44,10 @@ tokio = { version = "1.3.0", features = ["full"] }
|
||||||
tokio-rustls = "0.22.0"
|
tokio-rustls = "0.22.0"
|
||||||
tokio-stream = { version = "0.1.4", features = ["sync"] }
|
tokio-stream = { version = "0.1.4", features = ["sync"] }
|
||||||
tokio-util = { version = "0.6.4", features = ["full"] }
|
tokio-util = { version = "0.6.4", features = ["full"] }
|
||||||
toml = "0.5.8"
|
toml = { version = "0.5.8", features = ["preserve_order"] }
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
|
indexmap = "1.6.2"
|
||||||
|
|
||||||
[dependencies.panorama-imap]
|
[dependencies.panorama-imap]
|
||||||
path = "imap"
|
path = "imap"
|
||||||
|
|
|
@ -79,6 +79,7 @@ pub async fn sync_main(
|
||||||
debug!("select response: {:?}", select);
|
debug!("select response: {:?}", select);
|
||||||
|
|
||||||
if let (Some(exists), Some(uidvalidity)) = (select.exists, select.uid_validity) {
|
if let (Some(exists), Some(uidvalidity)) = (select.exists, select.uid_validity) {
|
||||||
|
// figure out which uids don't exist locally yet
|
||||||
let new_uids = stream::iter(1..exists).map(Ok).try_filter_map(|uid| {
|
let new_uids = stream::iter(1..exists).map(Ok).try_filter_map(|uid| {
|
||||||
mail_store.try_identify_email(&acct_name, &folder, uid, uidvalidity, None)
|
mail_store.try_identify_email(&acct_name, &folder, uid, uidvalidity, None)
|
||||||
// invert the option to only select uids that haven't been downloaded
|
// invert the option to only select uids that haven't been downloaded
|
||||||
|
|
|
@ -6,10 +6,12 @@ use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{self, FutureExt, TryFutureExt},
|
future::{self, FutureExt, TryFutureExt},
|
||||||
stream::{StreamExt, TryStreamExt},
|
stream::{StreamExt, TryStreamExt},
|
||||||
};
|
};
|
||||||
|
use indexmap::IndexMap;
|
||||||
use panorama_imap::response::AttributeValue;
|
use panorama_imap::response::AttributeValue;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
|
@ -19,7 +21,7 @@ use sqlx::{
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs,
|
fs,
|
||||||
sync::{broadcast, RwLock},
|
sync::{broadcast, watch, RwLock},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +39,10 @@ pub struct MailStore {
|
||||||
config: Arc<RwLock<Option<Config>>>,
|
config: Arc<RwLock<Option<Config>>>,
|
||||||
inner: Arc<RwLock<Option<MailStoreInner>>>,
|
inner: Arc<RwLock<Option<MailStoreInner>>>,
|
||||||
handle: Arc<JoinHandle<()>>,
|
handle: Arc<JoinHandle<()>>,
|
||||||
|
store_out_tx: Arc<watch::Sender<Option<MailStoreUpdate>>>,
|
||||||
|
|
||||||
|
/// A receiver for listening to updates to the mail store
|
||||||
|
pub store_out_rx: watch::Receiver<Option<MailStoreUpdate>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -44,12 +50,16 @@ pub struct MailStore {
|
||||||
struct MailStoreInner {
|
struct MailStoreInner {
|
||||||
pool: SqlitePool,
|
pool: SqlitePool,
|
||||||
mail_dir: PathBuf,
|
mail_dir: PathBuf,
|
||||||
accounts: HashMap<String, Arc<AccountRef>>,
|
accounts: IndexMap<String, Arc<AccountRef>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
/// Probably an event about new emails? i forgot
|
/// Probably an event about new emails? i forgot
|
||||||
pub struct EmailUpdateInfo {}
|
pub enum MailStoreUpdate {
|
||||||
|
/// The list of accounts has been updated (probably as a result of a config update)
|
||||||
|
AccountListUpdate(()),
|
||||||
|
}
|
||||||
|
|
||||||
impl MailStore {
|
impl MailStore {
|
||||||
/// Creates a new MailStore
|
/// Creates a new MailStore
|
||||||
|
@ -60,9 +70,14 @@ impl MailStore {
|
||||||
let inner = Arc::new(RwLock::new(None));
|
let inner = Arc::new(RwLock::new(None));
|
||||||
let inner2 = inner.clone();
|
let inner2 = inner.clone();
|
||||||
|
|
||||||
|
let (store_out_tx, store_out_rx) = watch::channel(None);
|
||||||
|
let store_out_tx = Arc::new(store_out_tx);
|
||||||
|
let store_out_tx2 = store_out_tx.clone();
|
||||||
|
|
||||||
let listener = async move {
|
let listener = async move {
|
||||||
while let Ok(()) = config_watcher.changed().await {
|
while let Ok(()) = config_watcher.changed().await {
|
||||||
let new_config = config_watcher.borrow().clone();
|
let new_config = config_watcher.borrow().clone();
|
||||||
|
|
||||||
let fut = future::try_join(
|
let fut = future::try_join(
|
||||||
async {
|
async {
|
||||||
let mut write = config2.write().await;
|
let mut write = config2.write().await;
|
||||||
|
@ -77,13 +92,14 @@ impl MailStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
match fut.await {
|
match fut.await {
|
||||||
Ok(_) => {}
|
Ok(_) => store_out_tx2.send(Some(MailStoreUpdate::AccountListUpdate(()))),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("during mail loop: {}", e);
|
error!("during mail loop: {}", e);
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let handle = tokio::spawn(listener);
|
let handle = tokio::spawn(listener);
|
||||||
|
@ -92,9 +108,14 @@ impl MailStore {
|
||||||
config,
|
config,
|
||||||
inner,
|
inner,
|
||||||
handle: Arc::new(handle),
|
handle: Arc::new(handle),
|
||||||
|
store_out_tx,
|
||||||
|
store_out_rx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Nuke all messages with an invalid UIDVALIDITY
|
||||||
|
pub async fn nuke_old_uidvalidity(&self, current: usize) {}
|
||||||
|
|
||||||
/// Given a UID and optional message-id try to identify a particular message
|
/// Given a UID and optional message-id try to identify a particular message
|
||||||
pub async fn try_identify_email(
|
pub async fn try_identify_email(
|
||||||
&self,
|
&self,
|
||||||
|
@ -265,11 +286,11 @@ impl MailStore {
|
||||||
|
|
||||||
/// Return a map of the accounts that are currently being tracked as well as a reference to the
|
/// Return a map of the accounts that are currently being tracked as well as a reference to the
|
||||||
/// account handles themselves
|
/// account handles themselves
|
||||||
pub async fn list_accounts(&self) -> HashMap<String, Arc<AccountRef>> {
|
pub async fn list_accounts(&self) -> IndexMap<String, Arc<AccountRef>> {
|
||||||
let read = self.inner.read().await;
|
let read = self.inner.read().await;
|
||||||
let inner = match &*read {
|
let inner = match read.as_ref() {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => return HashMap::new(),
|
None => return IndexMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
inner.accounts.clone()
|
inner.accounts.clone()
|
||||||
|
@ -365,6 +386,11 @@ impl AccountRef {
|
||||||
.bind(folder)
|
.bind(folder)
|
||||||
.fetch(&self.pool)
|
.fetch(&self.pool)
|
||||||
.map_ok(|(date, subject): (String, String)| EmailMetadata {
|
.map_ok(|(date, subject): (String, String)| EmailMetadata {
|
||||||
|
date: Some(
|
||||||
|
DateTime::parse_from_rfc3339(&date)
|
||||||
|
.unwrap()
|
||||||
|
.with_timezone(&Local),
|
||||||
|
),
|
||||||
subject,
|
subject,
|
||||||
..EmailMetadata::default()
|
..EmailMetadata::default()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,90 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
|
||||||
|
|
||||||
use crate::mail::{EmailMetadata, MailEvent};
|
|
||||||
|
|
||||||
/// UI's view of the currently-known mail-related state of all accounts.
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct MailStore {
|
|
||||||
accounts: Arc<RwLock<HashMap<String, Arc<RwLock<MailAccountState>>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailStore {
|
|
||||||
pub fn handle_mail_event(&self, evt: MailEvent) {
|
|
||||||
let acct_name = evt.acct_name().to_owned();
|
|
||||||
|
|
||||||
{
|
|
||||||
let accounts = self.accounts.read();
|
|
||||||
let contains_key = accounts.contains_key(&acct_name);
|
|
||||||
std::mem::drop(accounts);
|
|
||||||
|
|
||||||
if !contains_key {
|
|
||||||
let mut accounts = self.accounts.write();
|
|
||||||
accounts.insert(
|
|
||||||
acct_name.clone(),
|
|
||||||
Arc::new(RwLock::new(MailAccountState::default())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let accounts = self.accounts.read();
|
|
||||||
if let Some(lock) = accounts.get(&acct_name) {
|
|
||||||
let mut state = lock.write();
|
|
||||||
state.update(evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_accts(&self) -> Vec<String> {
|
|
||||||
self.accounts.read().keys().cloned().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn folders_of(&self, acct_name: impl AsRef<str>) -> Option<Vec<String>> {
|
|
||||||
let accounts = self.accounts.read();
|
|
||||||
let lock = accounts.get(acct_name.as_ref())?;
|
|
||||||
let state = lock.read();
|
|
||||||
Some(state.folders.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn messages_of(&self, acct_name: impl AsRef<str>) -> Option<Vec<EmailMetadata>> {
|
|
||||||
let accounts = self.accounts.read();
|
|
||||||
let lock = accounts.get(acct_name.as_ref())?;
|
|
||||||
let state = lock.read();
|
|
||||||
let mut msgs = Vec::new();
|
|
||||||
for uid in state.message_uids.iter() {
|
|
||||||
if let Some(meta) = state.message_map.get(uid) {
|
|
||||||
msgs.push(meta.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(msgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct MailAccountState {
|
|
||||||
pub folders: Vec<String>,
|
|
||||||
pub message_uids: Vec<u32>,
|
|
||||||
pub message_map: HashMap<u32, EmailMetadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailAccountState {
|
|
||||||
pub fn update(&mut self, evt: MailEvent) {
|
|
||||||
match evt {
|
|
||||||
MailEvent::FolderList(_, new_folders) => self.folders = new_folders,
|
|
||||||
MailEvent::MessageUids(_, new_uids) => self.message_uids = new_uids,
|
|
||||||
|
|
||||||
MailEvent::UpdateUid(_, uid, attrs) => {
|
|
||||||
let meta = EmailMetadata::from_attrs(attrs);
|
|
||||||
let uid = meta.uid.unwrap_or(uid);
|
|
||||||
self.message_map.insert(uid, meta);
|
|
||||||
}
|
|
||||||
MailEvent::NewUid(_, uid) => {
|
|
||||||
debug!("new msg!");
|
|
||||||
self.message_uids.push(uid);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// debug!("mail store updated! {:?}", self);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,9 +19,12 @@ use panorama_tui::{
|
||||||
widgets::*,
|
widgets::*,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::{sync::RwLock, task::JoinHandle};
|
||||||
|
|
||||||
use crate::mail::{store::AccountRef, EmailMetadata};
|
use crate::mail::{
|
||||||
|
store::{AccountRef, MailStoreUpdate},
|
||||||
|
EmailMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{FrameType, HandlesInput, InputResult, MailStore, TermType, Window, UI};
|
use super::{FrameType, HandlesInput, InputResult, MailStore, TermType, Window, UI};
|
||||||
|
|
||||||
|
@ -29,11 +32,17 @@ use super::{FrameType, HandlesInput, InputResult, MailStore, TermType, Window, U
|
||||||
/// A singular UI view of a list of mail
|
/// A singular UI view of a list of mail
|
||||||
pub struct MailView {
|
pub struct MailView {
|
||||||
pub mail_store: MailStore,
|
pub mail_store: MailStore,
|
||||||
pub current_account: Option<Arc<AccountRef>>,
|
|
||||||
pub current_folder: Option<String>,
|
|
||||||
pub message_list: TableState,
|
pub message_list: TableState,
|
||||||
pub selected: Arc<AtomicU32>,
|
pub selected: Arc<AtomicU32>,
|
||||||
pub change: Arc<AtomicI8>,
|
pub change: Arc<AtomicI8>,
|
||||||
|
current: Arc<RwLock<Option<Current>>>,
|
||||||
|
mail_store_listener: JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Current {
|
||||||
|
account: Arc<AccountRef>,
|
||||||
|
folder: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HandlesInput for MailView {
|
impl HandlesInput for MailView {
|
||||||
|
@ -60,7 +69,7 @@ impl Window for MailView {
|
||||||
String::from("email")
|
String::from("email")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI) {
|
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI) -> Result<()> {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
|
@ -89,30 +98,27 @@ impl Window for MailView {
|
||||||
.highlight_symbol(">>");
|
.highlight_symbol(">>");
|
||||||
|
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
if let Some(acct_ref) = self.current_account.as_ref() {
|
if let Some(current) = self.current.read().await.as_ref() {
|
||||||
let messages = acct_ref.get_newest_n_messages("INBOX", chunks[1].height as usize);
|
let messages = current
|
||||||
}
|
.account
|
||||||
|
.get_newest_n_messages("INBOX", chunks[1].height as usize)
|
||||||
for (acct_name, acct_ref) in accts.iter() {
|
.await?;
|
||||||
let result: Option<Vec<EmailMetadata>> = None; // self.mail_store.messages_of(acct);
|
for meta in messages.iter() {
|
||||||
if let Some(messages) = result {
|
let mut row = Row::new(vec![
|
||||||
for meta in messages {
|
String::from(if meta.unread { "\u{2b24}" } else { "" }),
|
||||||
let mut row = Row::new(vec![
|
meta.uid.map(|u| u.to_string()).unwrap_or_default(),
|
||||||
String::from(if meta.unread { "\u{2b24}" } else { "" }),
|
meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(),
|
||||||
meta.uid.map(|u| u.to_string()).unwrap_or_default(),
|
meta.from.clone(),
|
||||||
meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(),
|
meta.subject.clone(),
|
||||||
meta.from.clone(),
|
]);
|
||||||
meta.subject.clone(),
|
if meta.unread {
|
||||||
]);
|
row = row.style(
|
||||||
if meta.unread {
|
Style::default()
|
||||||
row = row.style(
|
.fg(Color::LightCyan)
|
||||||
Style::default()
|
.add_modifier(Modifier::BOLD),
|
||||||
.fg(Color::LightCyan)
|
);
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
rows.push(row);
|
|
||||||
}
|
}
|
||||||
|
rows.push(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,6 +139,8 @@ impl Window for MailView {
|
||||||
|
|
||||||
f.render_widget(dirlist, chunks[0]);
|
f.render_widget(dirlist, chunks[0]);
|
||||||
f.render_widget(table, chunks[1]);
|
f.render_widget(table, chunks[1]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&mut self) {
|
async fn update(&mut self) {
|
||||||
|
@ -174,21 +182,42 @@ fn humanize_timestamp(date: DateTime<Local>) -> String {
|
||||||
|
|
||||||
impl MailView {
|
impl MailView {
|
||||||
pub fn new(mail_store: MailStore) -> Self {
|
pub fn new(mail_store: MailStore) -> Self {
|
||||||
|
let current = Arc::new(RwLock::new(None));
|
||||||
|
let current2 = current.clone();
|
||||||
|
|
||||||
|
let mut listener = mail_store.store_out_rx.clone();
|
||||||
|
let mail_store2 = mail_store.clone();
|
||||||
|
let mail_store_listener = tokio::spawn(async move {
|
||||||
|
while let Ok(()) = listener.changed().await {
|
||||||
|
let updated = listener.borrow().clone();
|
||||||
|
debug!("new update from mail store: {:?}", updated);
|
||||||
|
|
||||||
|
// TODO: maybe do the processing of updates somewhere else?
|
||||||
|
// in case events get missed
|
||||||
|
match updated {
|
||||||
|
Some(MailStoreUpdate::AccountListUpdate(_)) => {
|
||||||
|
// TODO: maybe have a default account?
|
||||||
|
let accounts = mail_store2.list_accounts().await;
|
||||||
|
if let Some((acct_name, acct_ref)) = accounts.iter().next() {
|
||||||
|
let mut write = current2.write().await;
|
||||||
|
*write = Some(Current {
|
||||||
|
account: acct_ref.clone(),
|
||||||
|
folder: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
MailView {
|
MailView {
|
||||||
mail_store,
|
mail_store,
|
||||||
current_account: None,
|
current,
|
||||||
current_folder: None,
|
|
||||||
message_list: TableState::default(),
|
message_list: TableState::default(),
|
||||||
selected: Arc::new(AtomicU32::default()),
|
selected: Arc::new(AtomicU32::default()),
|
||||||
change: Arc::new(AtomicI8::default()),
|
change: Arc::new(AtomicI8::default()),
|
||||||
}
|
mail_store_listener,
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_current_account(&mut self, name: impl AsRef<str>) {
|
|
||||||
let name = name.as_ref();
|
|
||||||
let accounts = self.mail_store.list_accounts().await;
|
|
||||||
if let Some(acct_ref) = accounts.get(name) {
|
|
||||||
self.current_account = Some(acct_ref.clone());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,5 +248,4 @@ impl MailView {
|
||||||
// self.message_list.select(Some(len - 1));
|
// self.message_list.select(Some(len - 1));
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ pub async fn run_ui2(params: UiParams) -> Result<()> {
|
||||||
term.pre_draw()?;
|
term.pre_draw()?;
|
||||||
{
|
{
|
||||||
let mut frame = term.get_frame();
|
let mut frame = term.get_frame();
|
||||||
ui.draw(&mut frame).await;
|
ui.draw(&mut frame).await?;
|
||||||
}
|
}
|
||||||
term.post_draw()?;
|
term.post_draw()?;
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ pub struct UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UI {
|
impl UI {
|
||||||
async fn draw(&mut self, f: &mut FrameType<'_, '_>) {
|
async fn draw(&mut self, f: &mut FrameType<'_, '_>) -> Result<()> {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
|
@ -179,10 +179,12 @@ impl UI {
|
||||||
let visible = self.window_layout.visible_windows(chunks[0]);
|
let visible = self.window_layout.visible_windows(chunks[0]);
|
||||||
for (layout_id, area) in visible.into_iter() {
|
for (layout_id, area) in visible.into_iter() {
|
||||||
if let Some(window) = self.windows.get(&layout_id) {
|
if let Some(window) = self.windows.get(&layout_id) {
|
||||||
window.draw(f, area, self).await;
|
window.draw(f, area, self).await?;
|
||||||
debug!("drew {:?} {:?}", layout_id, area);
|
debug!("drew {:?} {:?}", layout_id, area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_window(&mut self, window: impl Window) {
|
fn open_window(&mut self, window: impl Window) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use panorama_tui::tui::layout::Rect;
|
use panorama_tui::tui::layout::Rect;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ pub trait Window: HandlesInput {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
|
||||||
/// Main draw function
|
/// Main draw function
|
||||||
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI);
|
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI) -> Result<()>;
|
||||||
// async fn draw(&self, f: FrameType, area: Rect, ui: Rc<UI>);
|
// async fn draw(&self, f: FrameType, area: Rect, ui: Rc<UI>);
|
||||||
|
|
||||||
/// Update function
|
/// Update function
|
||||||
|
|
Loading…
Reference in a new issue