listing: scroll account sidebar menu

Closes #85 Accounts sidebar doesn't scroll
jmap-eventsource
Manos Pitsidianakis 2020-11-27 23:05:35 +02:00
parent b053aaa145
commit b659749880
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
11 changed files with 403 additions and 356 deletions

View File

@ -136,25 +136,18 @@ impl ContactList {
min_width.2 = cmp::max(min_width.2, c.url().split_graphemes().len());
}
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
ret
};
/* name column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, self.length, default_cell, context);
CellBuffer::new_with_context(min_width.0, self.length, None, context);
/* email column */
self.data_columns.columns[1] =
CellBuffer::new_with_context(min_width.1, self.length, default_cell, context);
CellBuffer::new_with_context(min_width.1, self.length, None, context);
/* url column */
self.data_columns.columns[2] =
CellBuffer::new_with_context(min_width.2, self.length, default_cell, context);
CellBuffer::new_with_context(min_width.2, self.length, None, context);
/* source column */
self.data_columns.columns[3] =
CellBuffer::new_with_context("external".len(), self.length, default_cell, context);
CellBuffer::new_with_context("external".len(), self.length, None, context);
let account = &context.accounts[self.account_pos];
let book = &account.address_book;
@ -209,16 +202,9 @@ impl ContactList {
}
if self.length == 0 {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
ret
};
let message = "Address book is empty.".to_string();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length, default_cell, context);
CellBuffer::new_with_context(message.len(), self.length, None, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],

View File

@ -468,6 +468,12 @@ enum ListingFocus {
Mailbox,
}
#[derive(PartialEq, Copy, Clone, Debug)]
enum MenuEntryCursor {
Status,
Mailbox(usize),
}
#[derive(Debug)]
pub struct Listing {
component: ListingComponent,
@ -475,8 +481,9 @@ pub struct Listing {
status: Option<AccountStatus>,
dirty: bool,
visible: bool,
cursor_pos: (usize, usize),
menu_cursor_pos: (usize, usize),
cursor_pos: (usize, MenuEntryCursor),
menu_cursor_pos: (usize, MenuEntryCursor),
menu_content: CellBuffer,
startup_checks_rate: RateLimit,
id: ComponentId,
theme_default: ThemeAttribute,
@ -632,6 +639,7 @@ impl Component for Listing {
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
self.set_dirty(true);
self.menu_content.empty();
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
@ -640,12 +648,13 @@ impl Component for Listing {
}
return true;
}
UIEvent::MailboxDelete((account_hash, _mailbox_hash))
| UIEvent::MailboxCreate((account_hash, _mailbox_hash)) => {
UIEvent::MailboxDelete((account_hash, mailbox_hash))
| UIEvent::MailboxCreate((account_hash, mailbox_hash)) => {
let account_index = context
.accounts
.get_index_of(account_hash)
.expect("Invalid account_hash in UIEventMailbox{Delete,Create}");
self.menu_content.empty();
self.accounts[account_index].entries = context.accounts[&*account_hash]
.list_mailboxes()
.into_iter()
@ -656,14 +665,21 @@ impl Component for Listing {
})
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
if self.cursor_pos.0 == account_index {
self.cursor_pos.1 = std::cmp::min(
self.accounts[self.cursor_pos.0].entries.len() - 1,
self.cursor_pos.1,
let mut fallback = 0;
if let MenuEntryCursor::Mailbox(ref mut cur) = self.cursor_pos.1 {
*cur = std::cmp::min(
self.accounts[self.cursor_pos.0]
.entries
.len()
.saturating_sub(1),
*cur,
);
fallback = *cur;
}
if self.component.coordinates() == (*account_hash, *mailbox_hash) {
self.component.set_coordinates((
self.accounts[self.cursor_pos.0].hash,
self.accounts[self.cursor_pos.0].entries[self.cursor_pos.1].3,
self.accounts[self.cursor_pos.0].entries[fallback].3,
));
self.component.refresh_mailbox(context, true);
}
@ -675,6 +691,26 @@ impl Component for Listing {
self.set_dirty(true);
return true;
}
UIEvent::ChangeMode(UIMode::Normal) => {
self.dirty = true;
}
UIEvent::Resize => {
self.set_dirty(true);
}
UIEvent::Action(Action::ViewMailbox(ref idx)) => {
if let Some((_, _, _, mailbox_hash)) =
self.accounts[self.cursor_pos.0].entries.get(*idx)
{
let account_hash = self.accounts[self.cursor_pos.0].hash;
self.cursor_pos.1 = MenuEntryCursor::Mailbox(*idx);
self.status = None;
self.component
.set_coordinates((account_hash, *mailbox_hash));
self.menu_content.empty();
self.set_dirty(true);
}
return true;
}
_ => {}
}
@ -759,34 +795,37 @@ impl Component for Listing {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
};
match k {
let target = match k {
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"]) => {
if self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1 + amount)
.is_some()
{
self.cursor_pos.1 += amount;
} else {
return true;
match self.cursor_pos.1 {
MenuEntryCursor::Status => amount.saturating_sub(1),
MenuEntryCursor::Mailbox(idx) => idx + amount,
}
}
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) => {
if self.cursor_pos.1 >= amount {
if self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1 - amount)
.is_some()
{
self.cursor_pos.1 -= amount;
} else {
match self.cursor_pos.1 {
MenuEntryCursor::Status => {
return true;
}
} else {
return true;
MenuEntryCursor::Mailbox(idx) => {
if idx >= amount {
idx - amount
} else {
return true;
}
}
}
}
_ => {}
_ => return true,
};
if self.accounts[self.cursor_pos.0]
.entries
.get(target)
.is_some()
{
self.cursor_pos.1 = MenuEntryCursor::Mailbox(target)
} else {
return true;
}
self.change_account(context);
return true;
@ -815,14 +854,16 @@ impl Component for Listing {
match k {
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_account"]) => {
if self.cursor_pos.0 + amount < self.accounts.len() {
self.cursor_pos = (self.cursor_pos.0 + amount, 0);
self.cursor_pos =
(self.cursor_pos.0 + amount, MenuEntryCursor::Mailbox(0));
} else {
return true;
}
}
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_account"]) => {
if self.cursor_pos.0 >= amount {
self.cursor_pos = (self.cursor_pos.0 - amount, 0);
self.cursor_pos =
(self.cursor_pos.0 - amount, MenuEntryCursor::Mailbox(0));
} else {
return true;
}
@ -841,25 +882,6 @@ impl Component for Listing {
self.menu_visibility = !self.menu_visibility;
self.set_dirty(true);
}
UIEvent::ChangeMode(UIMode::Normal) => {
self.dirty = true;
}
UIEvent::Resize => {
self.set_dirty(true);
}
UIEvent::Action(Action::ViewMailbox(ref idx)) => {
if let Some((_, _, _, mailbox_hash)) =
self.accounts[self.cursor_pos.0].entries.get(*idx)
{
let account_hash = self.accounts[self.cursor_pos.0].hash;
self.cursor_pos.1 = *idx;
self.status = None;
self.component
.set_coordinates((account_hash, *mailbox_hash));
self.set_dirty(true);
}
return true;
}
_ => {}
}
@ -1047,14 +1069,15 @@ impl Component for Listing {
if shortcut!(key == shortcuts[Listing::DESCRIPTION]["refresh"]) =>
{
let account = &mut context.accounts[self.cursor_pos.0];
if let Some(&mailbox_hash) = account.mailboxes_order.get(self.cursor_pos.1)
{
if let Err(err) = account.refresh(mailbox_hash) {
context.replies.push_back(UIEvent::Notification(
Some("Could not refresh.".to_string()),
err.to_string(),
Some(NotificationType::Error(err.kind)),
));
if let MenuEntryCursor::Mailbox(idx) = self.cursor_pos.1 {
if let Some(&mailbox_hash) = account.mailboxes_order.get(idx) {
if let Err(err) = account.refresh(mailbox_hash) {
context.replies.push_back(UIEvent::Notification(
Some("Could not refresh.".to_string()),
err.to_string(),
Some(NotificationType::Error(err.kind)),
));
}
}
}
return true;
@ -1101,12 +1124,11 @@ impl Component for Listing {
}
UIEvent::Input(ref k)
if shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_mailbox"])
&& self.menu_cursor_pos.1 == 0 =>
&& self.menu_cursor_pos.1 == MenuEntryCursor::Status =>
{
self.status = Some(AccountStatus::new(
self.menu_cursor_pos.0,
self.theme_default,
));
self.cursor_pos = self.menu_cursor_pos;
self.open_status(self.menu_cursor_pos.0, context);
self.set_dirty(true);
self.focus = ListingFocus::Mailbox;
self.ratio = 90;
return true;
@ -1115,8 +1137,6 @@ impl Component for Listing {
if shortcut!(k == shortcuts[Listing::DESCRIPTION]["open_mailbox"]) =>
{
self.cursor_pos = self.menu_cursor_pos;
self.cursor_pos.1 = self.cursor_pos.1.saturating_sub(1);
self.status = None;
self.change_account(context);
self.focus = ListingFocus::Mailbox;
self.ratio = 90;
@ -1151,32 +1171,77 @@ impl Component for Listing {
};
if shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_up"]) {
while amount > 0 {
if self.menu_cursor_pos.1 > 0 {
self.menu_cursor_pos.1 -= 1;
} else if self.menu_cursor_pos.0 > 0 {
self.menu_cursor_pos.0 -= 1;
self.menu_cursor_pos.1 =
self.accounts[self.menu_cursor_pos.0].entries.len();
} else {
return true;
match self.menu_cursor_pos {
(
ref mut account_cursor,
ref mut entry_cursor @ MenuEntryCursor::Status,
) => {
if *account_cursor > 0 {
*account_cursor -= 1;
*entry_cursor = MenuEntryCursor::Mailbox(
self.accounts[*account_cursor]
.entries
.len()
.saturating_sub(1),
);
} else {
return true;
}
}
(_, MenuEntryCursor::Mailbox(ref mut mailbox_idx)) => {
if *mailbox_idx > 0 {
*mailbox_idx -= 1;
} else {
self.menu_cursor_pos.1 = MenuEntryCursor::Status;
}
}
}
amount -= 1;
}
} else if shortcut!(k == shortcuts[Listing::DESCRIPTION]["scroll_down"]) {
while amount > 0 {
if self.menu_cursor_pos.1
< self.accounts[self.menu_cursor_pos.0].entries.len()
{
self.menu_cursor_pos.1 += 1;
} else if self.menu_cursor_pos.0 + 1 < self.accounts.len() {
self.menu_cursor_pos.0 += 1;
self.menu_cursor_pos.1 = 0;
} else {
return true;
match self.menu_cursor_pos {
/* If current account has mailboxes, go to first mailbox */
(
ref account_cursor,
ref mut entry_cursor @ MenuEntryCursor::Status,
) if !self.accounts[*account_cursor].entries.is_empty() => {
*entry_cursor = MenuEntryCursor::Mailbox(0);
}
/* If current account has no mailboxes, go to next account */
(
ref mut account_cursor,
ref mut entry_cursor @ MenuEntryCursor::Status,
) if *account_cursor + 1 < self.accounts.len() => {
*account_cursor += 1;
*entry_cursor = MenuEntryCursor::Status;
}
/* If current account has no mailboxes and there is no next account, return true */
(_, MenuEntryCursor::Status) => {
return true;
}
(
ref mut account_cursor,
MenuEntryCursor::Mailbox(ref mut mailbox_idx),
) => {
if (*mailbox_idx + 1)
< self.accounts[*account_cursor].entries.len()
{
*mailbox_idx += 1;
} else if *account_cursor + 1 < self.accounts.len() {
*account_cursor += 1;
self.menu_cursor_pos.1 = MenuEntryCursor::Status;
} else {
return true;
}
}
}
amount -= 1;
}
}
self.menu_content.empty();
self.set_dirty(true);
return true;
}
@ -1201,37 +1266,39 @@ impl Component for Listing {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
return true;
};
match k {
let target = match k {
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_mailbox"]) => {
if self.accounts[self.menu_cursor_pos.0]
.entries
.get(self.menu_cursor_pos.1.saturating_sub(1) + amount)
.is_some()
{
self.menu_cursor_pos.1 += amount;
self.set_dirty(true);
} else {
return true;
match self.menu_cursor_pos.1 {
MenuEntryCursor::Status => amount.saturating_sub(1),
MenuEntryCursor::Mailbox(idx) => idx + amount,
}
}
k if shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_mailbox"]) => {
if self.cursor_pos.1 >= amount + 1 {
if self.accounts[self.menu_cursor_pos.0]
.entries
.get(self.menu_cursor_pos.1.saturating_sub(1) - amount)
.is_some()
{
self.menu_cursor_pos.1 -= amount;
self.set_dirty(true);
} else {
match self.menu_cursor_pos.1 {
MenuEntryCursor::Status => {
return true;
}
} else {
return true;
MenuEntryCursor::Mailbox(idx) => {
if idx >= amount {
idx - amount
} else {
return true;
}
}
}
}
_ => {}
_ => return true,
};
if self.accounts[self.menu_cursor_pos.0]
.entries
.get(target)
.is_some()
{
self.menu_cursor_pos.1 = MenuEntryCursor::Mailbox(target)
} else {
return true;
}
self.menu_content.empty();
return true;
}
UIEvent::Input(ref k)
@ -1262,7 +1329,8 @@ impl Component for Listing {
|| shortcut!(k == shortcuts[Listing::DESCRIPTION]["next_page"]) =>
{
if self.menu_cursor_pos.0 + amount < self.accounts.len() {
self.menu_cursor_pos = (self.menu_cursor_pos.0 + amount, 0);
self.menu_cursor_pos =
(self.menu_cursor_pos.0 + amount, MenuEntryCursor::Mailbox(0));
} else {
return true;
}
@ -1271,13 +1339,15 @@ impl Component for Listing {
|| shortcut!(k == shortcuts[Listing::DESCRIPTION]["prev_page"]) =>
{
if self.menu_cursor_pos.0 >= amount {
self.menu_cursor_pos = (self.menu_cursor_pos.0 - amount, 0);
self.menu_cursor_pos =
(self.menu_cursor_pos.0 - amount, MenuEntryCursor::Mailbox(0));
} else {
return true;
}
}
_ => return false,
}
self.menu_content.empty();
self.set_dirty(true);
return true;
@ -1379,13 +1449,19 @@ impl Component for Listing {
}
fn get_status(&self, context: &Context) -> String {
let mailbox_hash = if let Some((_, _, _, mailbox_hash)) = self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1)
{
*mailbox_hash
} else {
return String::new();
let mailbox_hash = match self.cursor_pos.1 {
MenuEntryCursor::Mailbox(idx) => {
if let Some((_, _, _, mailbox_hash)) =
self.accounts[self.cursor_pos.0].entries.get(idx)
{
*mailbox_hash
} else {
return String::new();
}
}
MenuEntryCursor::Status => {
return format!("{} status", &self.accounts[self.cursor_pos.0].name)
}
};
let account = &context.accounts[self.cursor_pos.0];
@ -1439,8 +1515,9 @@ impl Listing {
status: None,
visible: true,
dirty: true,
cursor_pos: (0, 0),
menu_cursor_pos: (0, 0),
cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_cursor_pos: (0, MenuEntryCursor::Mailbox(0)),
menu_content: CellBuffer::new_with_context(0, 0, None, context),
startup_checks_rate: RateLimit::new(2, 1000, context.job_executor.clone()),
theme_default: conf::value(context, "theme_default"),
id: ComponentId::new_v4(),
@ -1459,37 +1536,80 @@ impl Listing {
}
fn draw_menu(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if !self.is_dirty() {
return;
}
clear_area(grid, area, self.theme_default);
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
self.dirty = false;
let mut y = get_y(upper_left);
for a in &self.accounts {
if y > get_y(bottom_right) {
break;
let total_height: usize = 3 * (self.accounts.len())
+ self
.accounts
.iter()
.map(|entry| entry.entries.len() + 1)
.sum::<usize>();
let min_width: usize = 2 * width!(area);
let (width, height) = self.menu_content.size();
let cursor = match self.focus {
ListingFocus::Mailbox => self.cursor_pos,
ListingFocus::Menu => self.menu_cursor_pos,
};
if min_width > width || height < total_height || self.dirty {
let _ = self.menu_content.resize(
min_width * 2,
total_height,
self.menu_content.default_cell,
);
let bottom_right = pos_dec(self.menu_content.size(), (1, 1));
let mut y = 0;
for a in 0..self.accounts.len() {
if y > get_y(bottom_right) {
break;
}
y += self.print_account(((0, y), bottom_right), a, context);
y += 3;
}
y += self.print_account(grid, (set_y(upper_left, y), bottom_right), &a, context);
y += 3;
}
let rows = height!(area);
let (width, height) = self.menu_content.size();
const SCROLLING_CONTEXT: usize = 3;
let y_offset = (cursor.0)
+ self
.accounts
.iter()
.take(cursor.0)
.map(|entry| entry.entries.len() + 1)
.sum::<usize>()
+ match cursor.1 {
MenuEntryCursor::Status => 0,
MenuEntryCursor::Mailbox(idx) => idx + 1,
}
+ SCROLLING_CONTEXT;
let skip_offset = if y_offset <= rows {
0
} else {
rows * y_offset.wrapping_div(rows).saturating_sub(1) + y_offset.wrapping_rem(rows)
};
copy_area(
grid,
&self.menu_content,
area,
(
(
0,
std::cmp::min((height - 1).saturating_sub(rows), skip_offset),
),
(width - 1, std::cmp::min(skip_offset + rows, height - 1)),
),
);
context.dirty_areas.push_back(area);
}
/*
* Print a single account in the menu area.
*/
fn print_account(
&self,
grid: &mut CellBuffer,
area: Area,
a: &AccountMenuEntry,
context: &mut Context,
) -> usize {
fn print_account(&mut self, area: Area, aidx: usize, context: &mut Context) -> usize {
debug_assert!(is_valid_area!(area));
// Each entry and its index in the account
let mailboxes: HashMap<MailboxHash, Mailbox> = context.accounts[a.index]
let mailboxes: HashMap<MailboxHash, Mailbox> = context.accounts[self.accounts[aidx].index]
.mailbox_entries
.iter()
.map(|(&hash, entry)| (hash, entry.ref_mailbox.clone()))
@ -1498,15 +1618,20 @@ impl Listing {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
let must_highlight_account: bool = (self.focus == ListingFocus::Mailbox
&& self.cursor_pos.0 == a.index)
|| (self.focus == ListingFocus::Menu && self.menu_cursor_pos.0 == a.index);
let cursor = match self.focus {
ListingFocus::Mailbox => self.cursor_pos,
ListingFocus::Menu => self.menu_cursor_pos,
};
let must_highlight_account: bool = cursor.0 == self.accounts[aidx].index;
let mut lines: Vec<(usize, usize, u32, bool, MailboxHash, Option<usize>)> = Vec::new();
for (i, &(depth, indentation, has_sibling, mailbox_hash)) in a.entries.iter().enumerate() {
for (i, &(depth, indentation, has_sibling, mailbox_hash)) in
self.accounts[aidx].entries.iter().enumerate()
{
if mailboxes[&mailbox_hash].is_subscribed() {
match context.accounts[a.index][&mailbox_hash].status {
match context.accounts[self.accounts[aidx].index][&mailbox_hash].status {
crate::conf::accounts::MailboxStatus::Failed(_) => {
lines.push((depth, i, indentation, has_sibling, mailbox_hash, None));
}
@ -1525,7 +1650,7 @@ impl Listing {
}
let account_attrs = if must_highlight_account {
if self.focus == ListingFocus::Menu && self.menu_cursor_pos.1 == 0 {
if cursor.1 == MenuEntryCursor::Status {
let mut v = crate::conf::value(context, "mail.sidebar_highlighted");
if !context.settings.terminal.use_color() {
v.attrs |= Attr::REVERSE;
@ -1540,8 +1665,8 @@ impl Listing {
/* Print account name first */
write_string_to_grid(
&a.name,
grid,
&self.accounts[aidx].name,
&mut self.menu_content,
account_attrs.fg,
account_attrs.bg,
account_attrs.attrs,
@ -1552,7 +1677,7 @@ impl Listing {
if lines.is_empty() {
write_string_to_grid(
"offline",
grid,
&mut self.menu_content,
Color::Byte(243),
account_attrs.bg,
account_attrs.attrs,
@ -1571,9 +1696,10 @@ impl Listing {
break;
}
let (att, index_att, unread_count_att) = if must_highlight_account {
if (self.focus == ListingFocus::Mailbox && self.cursor_pos.1 == idx)
|| (self.focus == ListingFocus::Menu && self.menu_cursor_pos.1 == idx + 1)
{
if match cursor.1 {
MenuEntryCursor::Mailbox(c) => c == idx,
_ => false,
} {
let mut ret = (
crate::conf::value(context, "mail.sidebar_highlighted"),
crate::conf::value(context, "mail.sidebar_highlighted_index"),
@ -1623,19 +1749,25 @@ impl Listing {
ctr
};
let has_sibling_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_has_sibling)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_no_sibling)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let has_sibling_str: &str = account_settings!(
context[self.accounts[aidx].hash]
.listing
.sidebar_mailbox_tree_has_sibling
)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_str: &str = account_settings!(
context[self.accounts[aidx].hash]
.listing
.sidebar_mailbox_tree_no_sibling
)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let has_sibling_leaf_str: &str = account_settings!(
context[a.hash]
context[self.accounts[aidx].hash]
.listing
.sidebar_mailbox_tree_has_sibling_leaf
)
@ -1643,15 +1775,18 @@ impl Listing {
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_leaf_str: &str =
account_settings!(context[a.hash].listing.sidebar_mailbox_tree_no_sibling_leaf)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let no_sibling_leaf_str: &str = account_settings!(
context[self.accounts[aidx].hash]
.listing
.sidebar_mailbox_tree_no_sibling_leaf
)
.as_ref()
.map(|s| s.as_str())
.unwrap_or(" ");
let (x, _) = write_string_to_grid(
&format!("{:>width$}", inc, width = total_mailbox_no_digits),
grid,
&mut self.menu_content,
index_att.fg,
index_att.bg,
index_att.attrs,
@ -1681,7 +1816,7 @@ impl Listing {
}
let (x, _) = write_string_to_grid(
&branches,
grid,
&mut self.menu_content,
att.fg,
att.bg,
att.attrs,
@ -1689,8 +1824,8 @@ impl Listing {
None,
);
let (x, _) = write_string_to_grid(
context.accounts[a.index].mailbox_entries[&mailbox_idx].name(),
grid,
context.accounts[self.accounts[aidx].index].mailbox_entries[&mailbox_idx].name(),
&mut self.menu_content,
att.fg,
att.bg,
att.attrs,
@ -1711,7 +1846,7 @@ impl Listing {
let (x, _) = write_string_to_grid(
&count_string,
grid,
&mut self.menu_content,
unread_count_att.fg,
unread_count_att.bg,
unread_count_att.attrs
@ -1730,8 +1865,11 @@ impl Listing {
),
None,
);
for c in grid.row_iter(x..(get_x(bottom_right) + 1), y) {
grid[c].set_fg(att.fg).set_bg(att.bg).set_attrs(att.attrs);
for c in self.menu_content.row_iter(x..(get_x(bottom_right) + 1), y) {
self.menu_content[c]
.set_fg(att.fg)
.set_bg(att.bg)
.set_attrs(att.attrs);
}
idx += 1;
}
@ -1754,31 +1892,48 @@ impl Listing {
})
.map(|f| (f.depth, f.indentation, f.has_sibling, f.hash))
.collect::<_>();
/* Account might have no mailboxes yet if it's offline */
if let Some((_, _, _, mailbox_hash)) = self.accounts[self.cursor_pos.0]
.entries
.get(self.cursor_pos.1)
{
self.component
.set_coordinates((account_hash, *mailbox_hash));
/* Check if per-mailbox configuration overrides general configuration */
match self.cursor_pos.1 {
MenuEntryCursor::Mailbox(idx) => {
/* Account might have no mailboxes yet if it's offline */
if let Some((_, _, _, mailbox_hash)) =
self.accounts[self.cursor_pos.0].entries.get(idx)
{
self.component
.set_coordinates((account_hash, *mailbox_hash));
/* Check if per-mailbox configuration overrides general configuration */
let index_style =
mailbox_settings!(context[account_hash][mailbox_hash].listing.index_style);
self.component.set_style(*index_style);
} else {
/* Set to dummy */
self.component = Offline(OfflineListing::new((account_hash, 0)));
let index_style =
mailbox_settings!(context[account_hash][mailbox_hash].listing.index_style);
self.component.set_style(*index_style);
} else {
/* Set to dummy */
self.component = Offline(OfflineListing::new((account_hash, 0)));
}
self.status = None;
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
self.get_status(context),
)));
}
MenuEntryCursor::Status => {
self.open_status(self.cursor_pos.0, context);
}
}
self.sidebar_divider = *account_settings!(context[account_hash].listing.sidebar_divider);
self.status = None;
self.set_dirty(true);
self.menu_cursor_pos = self.cursor_pos;
/* clear menu to force redraw */
self.menu_content.empty();
}
fn open_status(&mut self, account_idx: usize, context: &mut Context) {
self.status = Some(AccountStatus::new(account_idx, self.theme_default));
self.menu_content.empty();
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::UpdateStatus(
self.get_status(context),
)));
self.menu_cursor_pos = self.cursor_pos;
self.menu_cursor_pos.1 += 1;
}
}

View File

@ -227,17 +227,10 @@ impl MailListingTrait for CompactListing {
match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) {
Ok(()) => {}
Err(_) => {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0;
write_string_to_grid(
message.as_str(),
@ -392,30 +385,23 @@ impl MailListingTrait for CompactListing {
min_width.0 = self.length.saturating_sub(1).to_string().len();
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
/* date column */
self.data_columns.columns[1] =
CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
/* from column */
self.data_columns.columns[2] =
CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
self.data_columns.segment_tree[2] = row_widths.2.into();
/* flags column */
self.data_columns.columns[3] =
CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.3, rows.len(), None, context);
/* subject column */
self.data_columns.columns[4] =
CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.4, rows.len(), None, context);
self.data_columns.segment_tree[4] = row_widths.4.into();
self.rows = rows;
@ -433,7 +419,7 @@ impl MailListingTrait for CompactListing {
if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
CellBuffer::new_with_context(message.len(), self.length + 1, None, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
@ -804,15 +790,8 @@ impl ListingTrait for CompactListing {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(0, 0, default_cell, context);
CellBuffer::new_with_context(0, 0, None, context);
}
self.redraw_threads_list(
context,
@ -831,15 +810,8 @@ impl ListingTrait for CompactListing {
format!("Failed to search for term {}: {}", &self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],

View File

@ -195,17 +195,9 @@ impl MailListingTrait for ConversationsListing {
match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) {
Ok(()) => {}
Err(_) => {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.content = CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0;
write_string_to_grid(
message.as_str(),
@ -353,8 +345,7 @@ impl MailListingTrait for ConversationsListing {
}
let width = max_entry_columns;
self.content =
CellBuffer::new_with_context(width, 4 * rows.len(), Cell::with_char(' '), context);
self.content = CellBuffer::new_with_context(width, 4 * rows.len(), None, context);
let padding_fg = self.color_cache.padding.fg;
@ -484,15 +475,8 @@ impl MailListingTrait for ConversationsListing {
}
}
if self.length == 0 && self.filter_term.is_empty() {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let message: String = account[&self.cursor_pos.1].status();
self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.content = CellBuffer::new_with_context(message.len(), 1, None, context);
write_string_to_grid(
&message,
&mut self.content,
@ -820,14 +804,7 @@ impl ListingTrait for ConversationsListing {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.content = CellBuffer::new_with_context(0, 0, default_cell, context);
self.content = CellBuffer::new_with_context(0, 0, None, context);
}
self.redraw_threads_list(
context,
@ -846,15 +823,7 @@ impl ListingTrait for ConversationsListing {
format!("Failed to search for term {}: {}", self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.content = CellBuffer::new_with_context(message.len(), 1, None, context);
write_string_to_grid(
&message,
&mut self.content,

View File

@ -223,17 +223,10 @@ impl MailListingTrait for PlainListing {
match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) {
Ok(()) => {}
Err(_) => {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0;
write_string_to_grid(
message.as_str(),
@ -650,15 +643,8 @@ impl ListingTrait for PlainListing {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(0, 0, default_cell, context);
CellBuffer::new_with_context(0, 0, None, context);
}
self.redraw_list(
context,
@ -677,15 +663,8 @@ impl ListingTrait for PlainListing {
format!("Failed to search for term {}: {}", &self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
@ -851,28 +830,21 @@ impl PlainListing {
min_width.0 = self.length.saturating_sub(1).to_string().len();
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
/* date column */
self.data_columns.columns[1] =
CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
/* from column */
self.data_columns.columns[2] =
CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
/* flags column */
self.data_columns.columns[3] =
CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.3, rows.len(), None, context);
/* subject column */
self.data_columns.columns[4] =
CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.4, rows.len(), None, context);
let iter = if self.filter_term.is_empty() {
Box::new(self.local_collection.iter().cloned())
@ -1005,7 +977,7 @@ impl PlainListing {
if self.length == 0 && self.filter_term.is_empty() {
let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
CellBuffer::new_with_context(message.len(), self.length + 1, None, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],

View File

@ -182,17 +182,10 @@ impl MailListingTrait for ThreadListing {
match context.accounts[&self.cursor_pos.0].load(self.cursor_pos.1) {
Ok(_) => {}
Err(_) => {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let message: String =
context.accounts[&self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
self.length = 0;
write_string_to_grid(
message.as_str(),
@ -231,17 +224,10 @@ impl MailListingTrait for ThreadListing {
let threads = account.collection.get_threads(self.cursor_pos.1);
self.length = 0;
self.order.clear();
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
if threads.len() == 0 {
let message: String = account[&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, None, context);
write_string_to_grid(
message.as_str(),
&mut self.data_columns.columns[0],
@ -380,21 +366,21 @@ impl MailListingTrait for ThreadListing {
min_width.0 = idx.saturating_sub(1).to_string().len();
/* index column */
self.data_columns.columns[0] =
CellBuffer::new_with_context(min_width.0, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.0, rows.len(), None, context);
/* date column */
self.data_columns.columns[1] =
CellBuffer::new_with_context(min_width.1, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.1, rows.len(), None, context);
/* from column */
self.data_columns.columns[2] =
CellBuffer::new_with_context(min_width.2, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.2, rows.len(), None, context);
self.data_columns.segment_tree[2] = row_widths.2.into();
/* flags column */
self.data_columns.columns[3] =
CellBuffer::new_with_context(min_width.3, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.3, rows.len(), None, context);
/* subject column */
self.data_columns.columns[4] =
CellBuffer::new_with_context(min_width.4, rows.len(), default_cell, context);
CellBuffer::new_with_context(min_width.4, rows.len(), None, context);
self.data_columns.segment_tree[4] = row_widths.4.into();
self.rows = rows;

View File

@ -233,7 +233,7 @@ impl ThreadView {
e.heading = string;
width = cmp::max(width, e.index.0 * 4 + e.heading.grapheme_width() + 2);
}
let mut content = CellBuffer::new_with_context(width, height, Cell::default(), context);
let mut content = CellBuffer::new_with_context(width, height, None, context);
if self.reversed {
for (y, e) in self.entries.iter().rev().enumerate() {
/* Box character drawing stuff */

View File

@ -953,13 +953,8 @@ impl Component for Tabbed {
),
);
}
let mut empty_cell = Cell::default();
empty_cell
.set_fg(self.theme_default.fg)
.set_bg(self.theme_default.bg)
.set_attrs(self.theme_default.attrs);
self.help_content =
CellBuffer::new_with_context(max_width, max_length + 2, empty_cell, context);
CellBuffer::new_with_context(max_width, max_length + 2, None, context);
self.help_content.set_growable(true);
let (width, height) = self.help_content.size();
let (cols, rows) = (width!(area), height!(area));

View File

@ -740,11 +740,6 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> Selec
context: &Context,
) -> Selector<T, F> {
let theme_default = crate::conf::value(context, "theme_default");
let mut empty_cell = Cell::with_char(' ');
empty_cell
.set_fg(theme_default.fg)
.set_bg(theme_default.bg)
.set_attrs(theme_default.attrs);
let width = std::cmp::max(
OK_CANCEL.len(),
std::cmp::max(
@ -763,7 +758,7 @@ impl<T: PartialEq + Debug + Clone + Sync + Send, F: 'static + Sync + Send> Selec
/* Extra room for buttons Okay/Cancel */
2
};
let mut content = CellBuffer::new_with_context(width, height, empty_cell, context);
let mut content = CellBuffer::new_with_context(width, height, None, context);
if single_only {
for (i, e) in entries.iter().enumerate() {
write_string_to_grid(

View File

@ -179,12 +179,12 @@ impl Pager {
return Pager::from_buf(content, cursor_pos);
}
let content = {
let mut empty_cell = Cell::with_char(' ');
empty_cell.set_fg(colors.fg);
empty_cell.set_bg(colors.bg);
if let Some(context) = context {
CellBuffer::new_with_context(1, 1, empty_cell, context)
CellBuffer::new_with_context(1, 1, None, context)
} else {
let mut empty_cell = Cell::with_char(' ');
empty_cell.set_fg(colors.fg);
empty_cell.set_bg(colors.bg);
CellBuffer::new(1, 1, empty_cell)
}
};

View File

@ -61,6 +61,7 @@ pub struct CellBuffer {
cols: usize,
rows: usize,
buf: Vec<Cell>,
pub default_cell: Cell,
/// ASCII-only flag.
pub ascii_drawing: bool,
/// If printing to this buffer and we run out of space, expand it.
@ -99,6 +100,7 @@ impl CellBuffer {
cols,
rows,
buf: vec![cell; cols * rows],
default_cell: cell,
growable: false,
ascii_drawing: false,
tag_table: Default::default(),
@ -106,11 +108,25 @@ impl CellBuffer {
}
}
pub fn new_with_context(cols: usize, rows: usize, cell: Cell, context: &Context) -> CellBuffer {
pub fn new_with_context(
cols: usize,
rows: usize,
default_cell: Option<Cell>,
context: &Context,
) -> CellBuffer {
let default_cell = default_cell.unwrap_or_else(|| {
let mut ret = Cell::default();
let theme_default = crate::conf::value(context, "theme_default");
ret.set_fg(theme_default.fg)
.set_bg(theme_default.bg)
.set_attrs(theme_default.attrs);
ret
});
CellBuffer {
cols,
rows,
buf: vec![cell; cols * rows],
buf: vec![default_cell; cols * rows],
default_cell,
growable: false,
ascii_drawing: context.settings.terminal.ascii_drawing,
tag_table: Default::default(),
@ -2301,6 +2317,7 @@ pub mod ansi {
buf,
rows,
cols: max_cols,
default_cell: Cell::default(),
growable: false,
ascii_drawing: false,
tag_table: Default::default(),